diff --git a/.pql/changelog/ticket_deps/0000-schema.sql b/.pql/changelog/ticket_deps/0000-schema.sql index 3a49ee0f..f13a0107 100644 --- a/.pql/changelog/ticket_deps/0000-schema.sql +++ b/.pql/changelog/ticket_deps/0000-schema.sql @@ -1,15 +1,8 @@ --- Auto-generated by pql init. CREATE TABLE statements +-- Auto-generated by migrate-ids (D-26). CREATE TABLE statements -- for the planning schema; per-table dir keeps the changelog --- self-describing per D-15. CREATE TABLE IF NOT EXISTS is --- idempotent so running schema files from each directory in --- replay order is harmless. --- --- Importer parses the markers below to detect schema drift --- between the producing pql version and the local one — a --- bumped canonical_version means projection rules changed --- and replay must refuse rather than silently corrupt state. --- pql:created_by: 1.4.26 --- pql:canonical_version: 1 +-- self-describing per D-15. +-- pql:created_by: migrate-ids +-- pql:canonical_version: 2 CREATE TABLE IF NOT EXISTS decisions ( @@ -43,14 +36,23 @@ CREATE TABLE IF NOT EXISTS decision_refs ( PRIMARY KEY (source_id, target_id, ref_type) ); +-- Identity split (D-26): a ticket's stable, collision-proof identity is its +-- record_id (a locally-generated ULID, planning.NewRecordID); the friendly +-- T-NNN label lives in ticket_idmap and may be reconciled. Every structural +-- reference (parent, deps, history, labels) targets record_id, so a label +-- clash never corrupts the graph — only ticket_idmap needs a relabel. CREATE TABLE IF NOT EXISTS tickets ( - id TEXT PRIMARY KEY, + record_id TEXT PRIMARY KEY, type TEXT NOT NULL CHECK(type IN ('initiative','epic','story','task','bug')), - parent_id TEXT REFERENCES tickets(id), + parent_record_id TEXT REFERENCES tickets(record_id), title TEXT NOT NULL, description TEXT, - status TEXT NOT NULL DEFAULT 'backlog' - CHECK(status IN ('backlog','ready','in_progress','review','done','cancelled')), + -- No CHECK enumeration: the ticket status vocabulary is per-vault + -- configurable (ticket_statuses in .pql/config.yaml). Validation lives + -- in Go (planning.StatusSet), so adding/renaming statuses needs no + -- schema change. The DEFAULT is a harmless fallback — CreateTicket + -- always inserts the configured default explicitly. + status TEXT NOT NULL DEFAULT 'backlog', priority TEXT DEFAULT 'medium' CHECK(priority IN ('critical','high','medium','low')), assigned_to TEXT, @@ -63,19 +65,33 @@ CREATE TABLE IF NOT EXISTS tickets ( canonical_version INTEGER ); +-- ticket_idmap maps a record_id to its current friendly label (T-NNN). +-- ticket_id is intentionally NOT globally unique: two uncoordinated clones +-- can mint the same label, which surfaces as a duplicate-label collision +-- (detected at replay) and is fixed with "pql ticket relabel". +CREATE TABLE IF NOT EXISTS ticket_idmap ( + record_id TEXT PRIMARY KEY REFERENCES tickets(record_id), + ticket_id TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER +); + CREATE TABLE IF NOT EXISTS ticket_deps ( - blocker_id TEXT NOT NULL REFERENCES tickets(id), - blocked_id TEXT NOT NULL REFERENCES tickets(id), + blocker_record_id TEXT NOT NULL REFERENCES tickets(record_id), + blocked_record_id TEXT NOT NULL REFERENCES tickets(record_id), created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), deleted_at TEXT, hash TEXT, canonical_version INTEGER, - PRIMARY KEY (blocker_id, blocked_id) + PRIMARY KEY (blocker_record_id, blocked_record_id) ); CREATE TABLE IF NOT EXISTS ticket_history ( - ticket_id TEXT NOT NULL REFERENCES tickets(id), + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), field TEXT NOT NULL, old_value TEXT, new_value TEXT, @@ -89,14 +105,14 @@ CREATE TABLE IF NOT EXISTS ticket_history ( ); CREATE TABLE IF NOT EXISTS ticket_labels ( - ticket_id TEXT NOT NULL REFERENCES tickets(id), + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), label TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), deleted_at TEXT, hash TEXT, canonical_version INTEGER, - PRIMARY KEY (ticket_id, label) + PRIMARY KEY (ticket_record_id, label) ); CREATE TABLE IF NOT EXISTS meta ( @@ -109,6 +125,8 @@ CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status); CREATE INDEX IF NOT EXISTS idx_tickets_team ON tickets(team); CREATE INDEX IF NOT EXISTS idx_tickets_decision_ref ON tickets(decision_ref); CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON tickets(assigned_to); +CREATE INDEX IF NOT EXISTS idx_tickets_parent ON tickets(parent_record_id); +CREATE INDEX IF NOT EXISTS idx_ticket_idmap_label ON ticket_idmap(ticket_id); CREATE INDEX IF NOT EXISTS idx_decisions_domain ON decisions(domain); CREATE INDEX IF NOT EXISTS idx_decisions_type ON decisions(type); CREATE INDEX IF NOT EXISTS idx_decision_refs_target ON decision_refs(target_id); diff --git a/.pql/changelog/ticket_deps/2026-05.sql b/.pql/changelog/ticket_deps/2026-05.sql index 5f642ddc..47c53b68 100644 --- a/.pql/changelog/ticket_deps/2026-05.sql +++ b/.pql/changelog/ticket_deps/2026-05.sql @@ -1,37 +1,37 @@ -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'T-125', '2026-05-18 11:59:28', '2026-05-18 11:59:28', NULL, 'fbe1dfb1615023fa202e252cb739bd7a', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'T-126', '2026-05-18 11:59:31', '2026-05-18 11:59:31', NULL, '7b10042aaffdce1e47ab70a994383bf8', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-125', 'T-126', '2026-05-18 11:59:34', '2026-05-18 11:59:34', NULL, 'a0b6f14621e099766b5114019aa19387', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'T-127', '2026-05-18 11:59:37', '2026-05-18 11:59:37', NULL, 'ea019bca081078c5ffa8a8b7cac25c06', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'T-128', '2026-05-18 11:59:41', '2026-05-18 11:59:41', NULL, 'f427a991e08749b0af3f687e1a79662b', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'T-129', '2026-05-18 11:59:44', '2026-05-18 11:59:44', NULL, '817d449ac21409e538b265a149b2cbbf', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-126', 'T-129', '2026-05-18 11:59:47', '2026-05-18 11:59:47', NULL, '0c981e27934388b8318f93864ce2ed5a', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'T-130', '2026-05-18 11:59:51', '2026-05-18 11:59:51', NULL, 'bbae054d4114c63da5e9879645735731', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'T-131', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, '41a5ab9997a7fea9d18203b5b3a6dabe', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'T-131', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, '734748d235d679f688e6b2d698498c83', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-128', 'T-131', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, 'bd0ef38128e33691ec48eed74894a496', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-125', 'T-131', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, 'c2540c385f7868140669a6692d5c84ee', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-126', 'T-131', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, 'f73653ab142777d6070365c96b160fa4', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-137', 'T-138', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '006ad7aafbd995638eb556d4d2dad269', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-134', 'T-138', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '4b3b69867b7ffc2875284c92ccf826e3', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-139', 'T-140', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '4c13d1852912bb226d41db62f89a915d', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-134', 'T-139', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '7d6ecc13b0edada978e05e19186a70a3', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-137', 'T-140', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '8fdddd396b50b61c9a1802b7250b4a1c', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-135', 'T-137', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, 'ae447ef31c40b8003cb8e372ec0b23b0', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-136', 'T-137', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, 'cb44e9c64d3051a320f314e2d81ee552', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-139', 'T-141', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, 'e2c7e7ea34fb4b007bd3d384ab788ae7', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-145', 'T-141', '2026-05-23 06:08:24', '2026-05-23 06:08:24', NULL, '1426109288c2a7bbf5a3190940e09fbd', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-151', 'T-152', '2026-05-23 09:52:40', '2026-05-23 09:52:40', NULL, 'c1958579d0319728ff264fff515fa5f9', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-151', 'T-153', '2026-05-23 09:52:43', '2026-05-23 09:52:43', NULL, '697ba3998bb654d0e490413d32426c1a', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-151', 'T-154', '2026-05-23 09:52:45', '2026-05-23 09:52:45', NULL, 'b66731a8e02d061e324c707e499f3768', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-165', 'T-166', '2026-05-24 16:27:29', '2026-05-24 16:27:29', NULL, 'aa2cff48755c11f6b81679d48c5f2c03', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-165', 'T-167', '2026-05-24 16:27:29', '2026-05-24 16:27:29', NULL, 'b123c2f27de04ba814d71b5f521e761d', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-165', 'T-168', '2026-05-24 16:27:29', '2026-05-24 16:27:29', NULL, 'f1069ecd7d3725941b5bdad1db49db28', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-165', 'T-169', '2026-05-24 16:27:37', '2026-05-24 16:27:37', NULL, '7faf9e3d99a7f5cbee4ca7fbd5b2c2e8', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'T-170', '2026-05-24 16:27:43', '2026-05-24 16:27:43', NULL, 'e7dd5c5e9a4683aadc8276821dd925b4', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'T-171', '2026-05-24 16:27:46', '2026-05-24 16:27:46', NULL, '6f283faa601b8cbccc50d9a4c9a49ee2', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-170', 'T-171', '2026-05-24 16:27:52', '2026-05-24 16:27:52', NULL, '1815c2248aebfb6e4806d1dffb9e3ea2', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'T-172', '2026-05-24 16:27:56', '2026-05-24 16:27:56', NULL, '9bb8d0b4d690668248a8e1b063103bcd', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-170', 'T-180', '2026-05-25 09:16:24', '2026-05-25 09:16:24', NULL, '532f10087fe5c703f64b9d7b69abd3ac', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'T-181', '2026-05-25 09:16:24', '2026-05-25 09:16:24', NULL, '9db789b59940b908f0f57750820b3a35', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'T-183', '2026-05-25 09:26:48', '2026-05-25 09:26:48', NULL, '5f87cfd814f3f69900a306714a6b5816', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'T-53', '2026-05-31 18:01:19', '2026-05-31 18:01:19', NULL, 'a099cda147ee9f0f524b7c4dd7290d7a', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', '06FB0TNQM6KY9JVRKF22GGFMXR', '2026-05-18 11:59:28', '2026-05-18 11:59:28', NULL, 'ade2722e88d76b4ea6264545ff9b5398', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', '06FB0TNQM78E2NAM4YK3A2AV6R', '2026-05-18 11:59:31', '2026-05-18 11:59:31', NULL, 'd996c0868b309fd7719ab4a8c2e0dd2b', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KY9JVRKF22GGFMXR', '06FB0TNQM78E2NAM4YK3A2AV6R', '2026-05-18 11:59:34', '2026-05-18 11:59:34', NULL, '05db5396eae627dbfae19415bf12802b', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', '06FB0TNQM7J40S0A8Q8PC0PDY8', '2026-05-18 11:59:37', '2026-05-18 11:59:37', NULL, '44a606dc211dc172a0b7e45dca353555', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7J40S0A8Q8PC0PDY8', '06FB0TNQM6KEY6593FMW8Y40VC', '2026-05-18 11:59:41', '2026-05-18 11:59:41', NULL, 'a5958fcc4bc52bcba6c5d22119c1152a', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', '06FB0TNQM40XWGSN99DBDQDHJR', '2026-05-18 11:59:44', '2026-05-18 11:59:44', NULL, 'fd9d60cea050f1b2a8657664150e977c', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78E2NAM4YK3A2AV6R', '06FB0TNQM40XWGSN99DBDQDHJR', '2026-05-18 11:59:47', '2026-05-18 11:59:47', NULL, '32e0aec7ec29068519dbd31c8a8cdc80', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', '06FB0TNQM5MW2V3QCMWKMY4VQM', '2026-05-18 11:59:51', '2026-05-18 11:59:51', NULL, 'cd33e0846477b82b5cf7e00781783b8c', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78E2NAM4YK3A2AV6R', '06FB0TNQM5ZSRZARG7SXCC71NM', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, '4ce716dceb22197381bbf6f7f5cb339c', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KEY6593FMW8Y40VC', '06FB0TNQM5ZSRZARG7SXCC71NM', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, '51d502ceb0a41091beb46513618b5b04', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KY9JVRKF22GGFMXR', '06FB0TNQM5ZSRZARG7SXCC71NM', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, '7f4410ea5c072aaac3317a9ed4a99912', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7J40S0A8Q8PC0PDY8', '06FB0TNQM5ZSRZARG7SXCC71NM', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, 'ccd6da9f9358922bc665133b792c65ed', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', '06FB0TNQM5ZSRZARG7SXCC71NM', '2026-05-18 11:59:54', '2026-05-18 11:59:54', NULL, 'd89ba28926b6472133096d779a20f079', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SRMD12DMXCX6N7DM', '06FB0TNQM6E3X5WWRS09GCDGK8', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '129c875883b4e5a53cdc0683045ace03', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54N0ZDJRS7G6MP1XC', '06FB0TNQM6E3X5WWRS09GCDGK8', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '1b4d4dcf6933819d32df98dff4f89d83', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM653J6604KYDK6RT8R', '06FB0TNQM5AVSMKD15KBJYX48W', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '1c81cdedd045c46a2f1264bcd1067d11', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6E3X5WWRS09GCDGK8', '06FB0TNQM4V1PZ0EK6G60699EM', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '23b6fb946d9de5e730bd186a9ed350e0', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6E3X5WWRS09GCDGK8', '06FB0TNQM5AVSMKD15KBJYX48W', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '876ead25db98c91cadc5ea7caa65b513', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4EQAERS4J9X9RAETR', '06FB0TNQM4V1PZ0EK6G60699EM', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, '91e9aa2d0ffd1f32a52bb5cdf019f3fc', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM653J6604KYDK6RT8R', '06FB0TNQM5V9RNW2BGT5GHRTRG', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, 'a8c93af5185bec5604246916460fea08', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4EQAERS4J9X9RAETR', '06FB0TNQM653J6604KYDK6RT8R', '2026-05-22 15:59:56', '2026-05-22 15:59:56', NULL, 'f05aa1ae12d538345bc451d332395b9c', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KN50C7A723SEKZXG', '06FB0TNQM5V9RNW2BGT5GHRTRG', '2026-05-23 06:08:24', '2026-05-23 06:08:24', NULL, 'a0eacf85ca484d159a3dcc6c170f1760', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77GWREHKA9K45P020', '06FB0TNQM6YSQYCE096R2SFRXG', '2026-05-23 09:52:40', '2026-05-23 09:52:40', NULL, 'bc99438a38a478190cae7e43c4342526', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77GWREHKA9K45P020', '06FB0TNQM7BJS5E1JJGSJ9XHG0', '2026-05-23 09:52:43', '2026-05-23 09:52:43', NULL, '2a604702bb0023631d5a98810f695439', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77GWREHKA9K45P020', '06FB0TNQM4XM8EY708NAM1JK00', '2026-05-23 09:52:45', '2026-05-23 09:52:45', NULL, '4303c0c52bfcc9f3dbd48f784ef1adf8', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AHSN3BXZFFWS0EPM', '06FB0TNQM78W48QX3JZP0QYAVW', '2026-05-24 16:27:29', '2026-05-24 16:27:29', NULL, '01db76f1732be386944e3da80fcdc0b4', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AHSN3BXZFFWS0EPM', '06FB0TNQM7G7MK8MP3BGQC36HG', '2026-05-24 16:27:29', '2026-05-24 16:27:29', NULL, 'c71b201e5cbe1f75ccda37657808e448', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AHSN3BXZFFWS0EPM', '06FB0TNQM7KA37YDBJ89GJ27H4', '2026-05-24 16:27:29', '2026-05-24 16:27:29', NULL, 'c9369a0c50aa82e2afa9a7fa70534b61', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AHSN3BXZFFWS0EPM', '06FB0TNQM48P8T23ZPR0BRK6PR', '2026-05-24 16:27:37', '2026-05-24 16:27:37', NULL, '7f6b0dddd7ba67b4db049f3d69b7c807', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48P8T23ZPR0BRK6PR', '06FB0TNQM654XV2TG4K0RVV1CW', '2026-05-24 16:27:43', '2026-05-24 16:27:43', NULL, 'ad5cfd3ff355cd44017ea0898ea14780', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48P8T23ZPR0BRK6PR', '06FB0TNQM6FMAHXM1XAHHCQC60', '2026-05-24 16:27:46', '2026-05-24 16:27:46', NULL, '7365404994faf148b33ab5ddc83fa01d', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM654XV2TG4K0RVV1CW', '06FB0TNQM6FMAHXM1XAHHCQC60', '2026-05-24 16:27:52', '2026-05-24 16:27:52', NULL, 'e3930f2673b82e4e6e0938450c522946', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48P8T23ZPR0BRK6PR', '06FB0TNQM71N3YMS82BAYD3M9R', '2026-05-24 16:27:56', '2026-05-24 16:27:56', NULL, '856fdd59d24643888245f06c621f40b8', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM654XV2TG4K0RVV1CW', '06FB0TNQM6V6TKRPA106D5SMMM', '2026-05-25 09:16:24', '2026-05-25 09:16:24', NULL, '329210f950241271814a8ed1255aa993', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48P8T23ZPR0BRK6PR', '06FB0TNQM67ET3VQP90WRXVSMG', '2026-05-25 09:16:24', '2026-05-25 09:16:24', NULL, '8a8027ce60a1ec2566c0b32f67f7759b', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W624VD4JW1GHDHAR', '06FB0TNQM7WE0SD9N4HCP9BB64', '2026-05-25 09:26:48', '2026-05-25 09:26:48', NULL, '388878e0c968776f9bc7a61934dbd3d6', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YYG4X5R1GX97TJB4', '06FB0TNQM452XPSG99PA60ZZ48', '2026-05-31 18:01:19', '2026-05-31 18:01:19', NULL, '06723eeaf4ac4b3ca32e24df249de904', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); diff --git a/.pql/changelog/ticket_deps/2026-06.sql b/.pql/changelog/ticket_deps/2026-06.sql index bc126527..af601ed2 100644 --- a/.pql/changelog/ticket_deps/2026-06.sql +++ b/.pql/changelog/ticket_deps/2026-06.sql @@ -1,11 +1,11 @@ -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-204', 'T-205', '2026-06-01 18:48:57', '2026-06-01 18:48:57', NULL, '114c7894d0dad6ad52fdaf55ac973ba8', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'T-206', '2026-06-01 18:48:57', '2026-06-01 18:48:57', NULL, 'a7d8d53a36034ef6c6f48425d52da49e', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'T-206', '2026-06-01 18:48:58', '2026-06-01 18:48:58', NULL, '03d07c24f7a03b17a9d21de621494494', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-211', 'T-214', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'ad31bd62c76d0b1987008e2a9e8863ae', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-219', 'T-221', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'c9cc9e67b4af3cdcb71aee1a0b34bf9c', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-210', 'T-211', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'e481571d98a8569f88f339427198eff1', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-220', 'T-221', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'f054e1277bdd77ecfdac7b78902aa543', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-224', 'T-214', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'fb0b5ec4b479363fd4e4a4de1d9e5d82', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'T-264', '2026-06-07 08:49:16', '2026-06-07 08:49:16', NULL, '192fb02408f15e992ad6df469cae696e', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'T-271', '2026-06-07 10:42:46', '2026-06-07 10:42:46', NULL, 'de1cc2484bc5f4bab84aef45de148eb2', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); -INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'T-272', '2026-06-07 10:42:50', '2026-06-07 10:42:50', NULL, 'f2401654c1b6a99a98b601216d9d6091', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65R8CNFNE1MA5GGR0', '06FB0TNQM4PH1J6ENYT4PJZRHG', '2026-06-01 18:48:57', '2026-06-01 18:48:57', NULL, '1faf4292ceab6cfe3550dfedad554efe', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PH1J6ENYT4PJZRHG', '06FB0TNQM5REFZXMY8ESWN0Z90', '2026-06-01 18:48:57', '2026-06-01 18:48:57', NULL, 'ca9731f8247e19ee44e99f51b4c33ba2', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M83TTXHRXESFZY14', '06FB0TNQM5REFZXMY8ESWN0Z90', '2026-06-01 18:48:58', '2026-06-01 18:48:58', NULL, '097bef3723659e681584982688cb242f', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54WTNQDMYT0AVR4WM', '06FB0TNQM78J2H2S0R79SSSPW8', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '112f7b091860de0dc7246f2c1b0eea45', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6Y5SET5WGSS2W78F8', '06FB0TNQM758PXMJH16PA3JHFW', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '497eb0cb7db0b11194031874d6e8d89d', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7171NRB1T82TGP5RG', '06FB0TNQM78J2H2S0R79SSSPW8', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '5102b2f239c5c18631997cbc1f2d998c', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6EGSD0SB7NYNSP1BM', '06FB0TNQM758PXMJH16PA3JHFW', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '9d0c7bbff8117a3569cf3b7937228184', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4C2PSN2GVJY9JDSZG', '06FB0TNQM7171NRB1T82TGP5RG', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'd935e3d2978a3584efb0767e75737e84', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45HHRBCRMV2166Y6M', '06FB0TNQM5YYMMKFD5Z8WG9F60', '2026-06-07 08:49:16', '2026-06-07 08:49:16', NULL, '11d94aab7a635da1ffb610aa2ade2061', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM551VZ2NYV6PD0XQRC', '06FB0TNQM4XBRZJ6DEHFWREWC0', '2026-06-07 10:42:46', '2026-06-07 10:42:46', NULL, 'daa1e2dd097721230deca67500c1c00d', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); +INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM551VZ2NYV6PD0XQRC', '06FB0TNQM67PDXAFTPPW7B1WDR', '2026-06-07 10:42:50', '2026-06-07 10:42:50', NULL, '50bb0fcfafb5aacdd747bd9fcd6a3ea6', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash); diff --git a/.pql/changelog/ticket_history/0000-schema.sql b/.pql/changelog/ticket_history/0000-schema.sql index 3a49ee0f..f13a0107 100644 --- a/.pql/changelog/ticket_history/0000-schema.sql +++ b/.pql/changelog/ticket_history/0000-schema.sql @@ -1,15 +1,8 @@ --- Auto-generated by pql init. CREATE TABLE statements +-- Auto-generated by migrate-ids (D-26). CREATE TABLE statements -- for the planning schema; per-table dir keeps the changelog --- self-describing per D-15. CREATE TABLE IF NOT EXISTS is --- idempotent so running schema files from each directory in --- replay order is harmless. --- --- Importer parses the markers below to detect schema drift --- between the producing pql version and the local one — a --- bumped canonical_version means projection rules changed --- and replay must refuse rather than silently corrupt state. --- pql:created_by: 1.4.26 --- pql:canonical_version: 1 +-- self-describing per D-15. +-- pql:created_by: migrate-ids +-- pql:canonical_version: 2 CREATE TABLE IF NOT EXISTS decisions ( @@ -43,14 +36,23 @@ CREATE TABLE IF NOT EXISTS decision_refs ( PRIMARY KEY (source_id, target_id, ref_type) ); +-- Identity split (D-26): a ticket's stable, collision-proof identity is its +-- record_id (a locally-generated ULID, planning.NewRecordID); the friendly +-- T-NNN label lives in ticket_idmap and may be reconciled. Every structural +-- reference (parent, deps, history, labels) targets record_id, so a label +-- clash never corrupts the graph — only ticket_idmap needs a relabel. CREATE TABLE IF NOT EXISTS tickets ( - id TEXT PRIMARY KEY, + record_id TEXT PRIMARY KEY, type TEXT NOT NULL CHECK(type IN ('initiative','epic','story','task','bug')), - parent_id TEXT REFERENCES tickets(id), + parent_record_id TEXT REFERENCES tickets(record_id), title TEXT NOT NULL, description TEXT, - status TEXT NOT NULL DEFAULT 'backlog' - CHECK(status IN ('backlog','ready','in_progress','review','done','cancelled')), + -- No CHECK enumeration: the ticket status vocabulary is per-vault + -- configurable (ticket_statuses in .pql/config.yaml). Validation lives + -- in Go (planning.StatusSet), so adding/renaming statuses needs no + -- schema change. The DEFAULT is a harmless fallback — CreateTicket + -- always inserts the configured default explicitly. + status TEXT NOT NULL DEFAULT 'backlog', priority TEXT DEFAULT 'medium' CHECK(priority IN ('critical','high','medium','low')), assigned_to TEXT, @@ -63,19 +65,33 @@ CREATE TABLE IF NOT EXISTS tickets ( canonical_version INTEGER ); +-- ticket_idmap maps a record_id to its current friendly label (T-NNN). +-- ticket_id is intentionally NOT globally unique: two uncoordinated clones +-- can mint the same label, which surfaces as a duplicate-label collision +-- (detected at replay) and is fixed with "pql ticket relabel". +CREATE TABLE IF NOT EXISTS ticket_idmap ( + record_id TEXT PRIMARY KEY REFERENCES tickets(record_id), + ticket_id TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER +); + CREATE TABLE IF NOT EXISTS ticket_deps ( - blocker_id TEXT NOT NULL REFERENCES tickets(id), - blocked_id TEXT NOT NULL REFERENCES tickets(id), + blocker_record_id TEXT NOT NULL REFERENCES tickets(record_id), + blocked_record_id TEXT NOT NULL REFERENCES tickets(record_id), created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), deleted_at TEXT, hash TEXT, canonical_version INTEGER, - PRIMARY KEY (blocker_id, blocked_id) + PRIMARY KEY (blocker_record_id, blocked_record_id) ); CREATE TABLE IF NOT EXISTS ticket_history ( - ticket_id TEXT NOT NULL REFERENCES tickets(id), + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), field TEXT NOT NULL, old_value TEXT, new_value TEXT, @@ -89,14 +105,14 @@ CREATE TABLE IF NOT EXISTS ticket_history ( ); CREATE TABLE IF NOT EXISTS ticket_labels ( - ticket_id TEXT NOT NULL REFERENCES tickets(id), + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), label TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), deleted_at TEXT, hash TEXT, canonical_version INTEGER, - PRIMARY KEY (ticket_id, label) + PRIMARY KEY (ticket_record_id, label) ); CREATE TABLE IF NOT EXISTS meta ( @@ -109,6 +125,8 @@ CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status); CREATE INDEX IF NOT EXISTS idx_tickets_team ON tickets(team); CREATE INDEX IF NOT EXISTS idx_tickets_decision_ref ON tickets(decision_ref); CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON tickets(assigned_to); +CREATE INDEX IF NOT EXISTS idx_tickets_parent ON tickets(parent_record_id); +CREATE INDEX IF NOT EXISTS idx_ticket_idmap_label ON ticket_idmap(ticket_id); CREATE INDEX IF NOT EXISTS idx_decisions_domain ON decisions(domain); CREATE INDEX IF NOT EXISTS idx_decisions_type ON decisions(type); CREATE INDEX IF NOT EXISTS idx_decision_refs_target ON decision_refs(target_id); diff --git a/.pql/changelog/ticket_history/2026-04.sql b/.pql/changelog/ticket_history/2026-04.sql index a177773c..3fdf58ae 100644 --- a/.pql/changelog/ticket_history/2026-04.sql +++ b/.pql/changelog/ticket_history/2026-04.sql @@ -1,483 +1,161 @@ -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'backlog', 'ready', NULL, '2026-04-22 09:42:11', '2026-04-22 09:42:11', '2026-04-22 09:42:11', NULL, '66d7c49f6b2ccdd2ef88a635ec717ca2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'ready', 'in_progress', NULL, '2026-04-22 09:42:11', '2026-04-22 09:42:11', '2026-04-22 09:42:11', NULL, '87c83225076b22ecbc7fd150b8ccce5f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:45:21', '2026-04-22 11:45:21', '2026-04-22 11:45:21', NULL, '056f21749ff40c14feeb380b2425bb04', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'review', 'done', NULL, '2026-04-22 11:45:21', '2026-04-22 11:45:21', '2026-04-22 11:45:21', NULL, 'a345d1db748f86fb707b715ce4f45c82', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:12', '2026-04-22 11:46:12', '2026-04-22 11:46:12', NULL, 'd666982c7578a64cff619807467b0d22', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:16', '2026-04-22 11:46:16', '2026-04-22 11:46:16', NULL, 'bf092de792c1dba0a724e6c3ef0a2ca3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:26', '2026-04-22 11:46:26', '2026-04-22 11:46:26', NULL, '6648704125e10ddf9582b0fc8a91cbb8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'review', 'done', NULL, '2026-04-22 11:46:33', '2026-04-22 11:46:33', '2026-04-22 11:46:33', NULL, '28a5fbab4db244d0cc5c48e7e320ec71', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, '8e8f387c6cf81e8db06c212be3c75ff7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'b0d4dd5c1779606c861d1fd778ee9d0f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'review', 'done', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'b93dd369ad1260e6b1f74798544bafd1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'e5b9592394cabd32ce426a3916c27827', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'review', 'done', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '140ed6257373fb7d199c67da8452bb82', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '20cb7969c9c3af6a6deb93ee56a3a8f6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '456c32193f208cb4dcd78d722c8e5308', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '92282e57e514c08df603650c82fc5ad1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'review', 'done', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '2352d517e219af8edf4537f4145b5a9e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '37b7881534e16c2f369cdfb4a25ac511', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '7e4e0f286bd3bf1687cbbd49fca9e1f4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, 'c6ccbb7da6b303b5fd6ef4f5273c7d07', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:52', '2026-04-22 11:46:52', '2026-04-22 11:46:52', NULL, '99d159170d35cb6225a0b9e6f8e5a4f5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '171dab623718a0cb2a4af914c84e0d30', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '206793ce446e6ba302f209cda04dcb78', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '2a311168aab1126eadcb2e23433ba317', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '3098b6064c8b96eb459c5ed947ca97c4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '366f83e624514e442d539f704a0abbb1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '36c5d3bbd5f027fdb114391abdf1dda6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '381444444f3e04024e2293f61c774d1d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '46d32785bb5113ebcb050845e67ea280', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '5fde942d02640b9df4e75d1d745b2de5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '74dbfcfee3f60d46ba6e910ebb5fed1b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '7d19553f3714932803e565e8ac106220', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '9de91c0384a97d66eaa01e7f8164f389', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'c257d281cc9fc54be99fea49688ff720', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'cf47b1a47c543dabd8fe0c891c535285', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'd34d4be4170d93183c18754a60ea97e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'db0af7f083e8985026799a9d3334347d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '1a24e0b9fbc1472aacf17392a9241b99', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '7774f4c92e18371341b7c0332022d29a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '8589b12b48657f3513e801cd96dab11c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '92508b82168bd20d5a69c63a66271c34', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, 'dc2b5b3b5b348409f6d719f05130804b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '0dd7c9716ba7dab383c7890822ba9224', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '23c19fa354da9eb201212b23696da027', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '79e01d297b5cf559d37f2c0ee3661a51', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '88a0fb2513eebbd4627abd854eefb484', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '9ad3112f08548b5896fd2252dda48be0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'acba548461502b88a483649f181a65da', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'bd986c8233a7a251c0403c4df3ce8511', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'cfb6a1faab0ce219609f44055648cda1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'dabc0f13943decd855d39febfd2a18ae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'e340923ef96f70f142a0bee868bb8e67', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'f179c71abcd89f7ed9fa781220d2018c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'fae80bffb40c02fdf07691ef18837b14', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:54', '2026-04-22 12:03:54', '2026-04-22 12:03:54', NULL, '1693e0a6448fb98bd03fe0c4c65371fb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'review', 'done', NULL, '2026-04-22 12:03:54', '2026-04-22 12:03:54', '2026-04-22 12:03:54', NULL, '80d695ac3bfc5fe1f3615013dea3b06a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'ready', 'in_progress', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '33a14771999bc0062268447b4ffadc73', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'backlog', 'ready', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '5a2f1c3d37090ad5791c6a0e8a7aa80a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'ready', 'in_progress', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '7d84a743a1b9e1ce5c50172b89318153', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'backlog', 'ready', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, 'cad02006950fb6fa0d003553437d3bbe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'in_progress', 'review', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '10f0a16fa4e4899c7817dd5c44cc3ffe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'in_progress', 'review', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '2d697a30a6193eacf2a3ddf1661e861c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'review', 'done', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '591bb27186744d023df34105884fa58f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'review', 'done', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '817b9d8e92707eb588cb7f9eae7d424e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'status', 'backlog', 'cancelled', NULL, '2026-04-22 20:34:17', '2026-04-22 20:34:17', '2026-04-22 20:34:17', NULL, '5460d369f6cdef9c393a37e41481bcb5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '313d4c9dd6444a4573cbe9584ed18f61', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '4630269c56784316b975ca56b10a2337', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '5a8f7e0eda55bc0893c6597400a8d535', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, 'fac6b818dc68490a80ea32255f0ba404', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '0a7137f82fbb9307496b7e4133b15052', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '5aff1ed40d9a68a5b4d59aab0d24dd31', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '931bcaabbd64af0612fbe6eb9a4470d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, 'c2fca20d28b2a3a63a56eb2841a4bda9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '84e5b99f4fb99f8ae4d189ddaac2de74', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '8c7ba0fd4c8acc6065a24b80ae28d526', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, 'd91c6440f66e683a87be5e61a366cca7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, 'f9253848d9a95ce1823c8ba78a511fc0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '389d5abb35b3b73ccb38ac8bec3b92c7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '7e714e95f89966a13e436d2fe38e7443', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, 'b1aa8ff1f9b24cdfc9e0bebf21d38b99', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '39b407be88dbb31f530c8074f6bcecc7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '59df9c23d722ccf1c1ac18baee76c785', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '76b48a80abcba29b1047c794fd004234', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '297763c026010189ea0bd870f77a173e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '2cd947436be1c156eb77d233aeed5da2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '38826870354a1bf51bc2ff21f6da0ea3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '5105038efff2e3055213b1b1403b7610', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '67cc6c0934711b6a592e11e5edc6194c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '6e9e7d3ce9f74ed261912b1cb099dcad', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'a4e702776f3b553fb276782e539ebfc7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'd294248ba8ea651e78aeb6fed0457eb4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'da8dc2002a03eaee7fcd5504abee6f60', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '08f09f3bb9f1a0001110716930824e62', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '10005b4803d743aa21be0223c8a85d5c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '11ac23b546d0e22206af4221062f5912', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '127404fe79dfb79d0297b85d78235c68', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '15fb6606af61e11b6960799721e7cdcc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '19f6209ce4428172fb16941accb13566', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-27', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '212224b7dcc4b991119dc430ae179311', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '2fe8eed15b3ead999a69cdd08df5e14c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '3990623d651c604fec692edb70976d69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '44d273b92ad4c93159706c1f17fcb6a7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '46935c5a6d8ef12cf1c2252bd8d30368', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '551ea05a8d0cc28c0ff7010a139e6fe1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '6748fca610fc8a8503d69846231616fe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '7843cfda82ec1c1622c1a201f82acaf1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '822790bbace926feb2372b8665080932', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '9a1ae5742a1230e60279d9c72f8b0f75', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'parent_id', NULL, 'T-5', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a414bc8e4d32fa32483228a4e9c7be8c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'parent_id', NULL, 'T-3', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a80caae1913b8e9a1d5dab980bf0b77b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-40', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a895d51a2e6e8b448684933cf0224162', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'parent_id', NULL, 'T-3', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ad9a8a85686e33df17ae15631afa1213', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'aedb3c83402245f81cd10f04d15dcf8e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'b6dbb8114cd77c206d3b8bdf054736b9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-26', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'b7b189fe87f61d42e1d0318d23aeb1fe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'c80049d2c236f0d2512355183b766f0f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-25', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ccdf7bec2a75f5829eab4fdd57171622', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'parent_id', NULL, 'T-7', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'd13ed78c92d33e61e448c9e155adf870', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-41', 'parent_id', NULL, 'T-7', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'eb2a2ca41a57604b40556e81bf6480a9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'f0d27e423d40a998115e10ffd804f791', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'f5c891c9a5f7d5e3f43ded0dc07eb704', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ff609c22f964618c84b7c36988e87ae7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:01:08', '2026-04-24 07:01:08', '2026-04-24 07:01:08', NULL, '6ae864936d48565e880f06e984e9d978', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:01:08', '2026-04-24 07:01:08', '2026-04-24 07:01:08', NULL, 'ebebc38301a28001a09c9e7066c9aeb9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:01:48', '2026-04-24 07:01:48', '2026-04-24 07:01:48', NULL, '3cf128b79fe91447f72f42d8cffce9e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:01:48', '2026-04-24 07:01:48', '2026-04-24 07:01:48', NULL, '68f57f5e3a1005ca3009f944b8b634d7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:05:40', '2026-04-24 07:05:40', '2026-04-24 07:05:40', NULL, '50aa3e362dbe9682d98f6d5f71ea97b1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:05:50', '2026-04-24 07:05:50', '2026-04-24 07:05:50', NULL, '4cb951c3ea697881d8fa5b0e86cfdf96', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:05:51', '2026-04-24 07:05:51', '2026-04-24 07:05:51', NULL, '7516ffa5c72fa4e7fd067249393c3653', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:06:30', '2026-04-24 07:06:30', '2026-04-24 07:06:30', NULL, '268576bd9559485dd2ac5e7ffd1f1a79', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:06:32', '2026-04-24 07:06:32', '2026-04-24 07:06:32', NULL, 'ef46c3a3db4c4ba50ff1f0e1baa4c0e6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'review', 'done', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '1cd74d47eb547aa0bef71597eaaa2c5b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'in_progress', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '3b8c17d366d1710d03523303c9df83c8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'review', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '7eb9ca56292802301f93d0e2d680835e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, 'a1a837494c047ade786d79edb75296dd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-04-24 07:08:00', '2026-04-24 07:08:00', '2026-04-24 07:08:00', NULL, 'ac745b5254a0e8f085189c23d95acb7e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-04-24 07:08:29', '2026-04-24 07:08:29', '2026-04-24 07:08:29', NULL, '2adaf942a49bfeeac90aee1527a98444', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-04-24 07:08:29', '2026-04-24 07:08:29', '2026-04-24 07:08:29', NULL, 'e0d54ffba34677dc6e687be801ccec05', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'ready', NULL, '2026-04-24 07:08:35', '2026-04-24 07:08:35', '2026-04-24 07:08:35', NULL, '74e6fa2f524c21300467381c2f3ad4e3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:08:35', '2026-04-24 07:08:35', '2026-04-24 07:08:35', NULL, 'fbd19eabf442e6e0de60028c28956b8f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '7d3efe4ccaee7d45d1a01d28144c4f0a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'in_progress', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '914a2f5f5ef0807a6743f8811e71804b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'in_progress', 'ready', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'a1cd74c76fd10c353aa758cea0331c5c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'cancelled', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'cdbe85f80838e781048780db934d0115', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'cancelled', 'backlog', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'f24e8d138172597d7ddcf26672b67d5f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'f9a83fa1b41d56202c57e6a55b240f35', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'done', NULL, '2026-04-24 07:30:15', '2026-04-24 07:30:15', '2026-04-24 07:30:15', NULL, '99c4e5a68463201e7f1b7764eddfd2ae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'done', 'backlog', NULL, '2026-04-24 07:30:22', '2026-04-24 07:30:22', '2026-04-24 07:30:22', NULL, 'd0274058b130d8003123881b8a410f7f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:08:16', '2026-04-24 08:08:16', '2026-04-24 08:08:16', NULL, '83d70eb5a7f6e7175e0719fdb23243c2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 08:08:16', '2026-04-24 08:08:16', '2026-04-24 08:08:16', NULL, 'b5acf95cb059f0c573b78d755b938da2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:08:23', '2026-04-24 08:08:23', '2026-04-24 08:08:23', NULL, '4ae08b8af459af0679b05cf1f8ac3950', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'ready', NULL, '2026-04-24 08:08:38', '2026-04-24 08:08:38', '2026-04-24 08:08:38', NULL, 'c7e4cabf5d1fccad28f22d498e91193a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:08:40', '2026-04-24 08:08:40', '2026-04-24 08:08:40', NULL, '9b0937bc7eb82785407c367a90365c3a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'review', NULL, '2026-04-24 08:08:44', '2026-04-24 08:08:44', '2026-04-24 08:08:44', NULL, '90587f61d96b12ce53f2a4edc705fdd8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'review', 'in_progress', NULL, '2026-04-24 08:08:48', '2026-04-24 08:08:48', '2026-04-24 08:08:48', NULL, 'd02ac7ee61fc4dc420fb7597a4f326db', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'ready', NULL, '2026-04-24 08:16:15', '2026-04-24 08:16:15', '2026-04-24 08:16:15', NULL, '145613c94b4366bda03fb76c0f0747ce', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 08:16:19', '2026-04-24 08:16:19', '2026-04-24 08:16:19', NULL, '0d8e318e8060aece6e18788cee56f646', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:16:21', '2026-04-24 08:16:21', '2026-04-24 08:16:21', NULL, '3d8635cfdbaf9b0c22dcba9f15f3c1ad', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:16:47', '2026-04-24 08:16:47', '2026-04-24 08:16:47', NULL, '77f873a7a5fff47ce9b974a6999d03b4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'status', 'backlog', 'in_progress', NULL, '2026-04-24 08:16:56', '2026-04-24 08:16:56', '2026-04-24 08:16:56', NULL, 'e7610d68ec9871746b4a361e33b18f05', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:17:11', '2026-04-24 08:17:11', '2026-04-24 08:17:11', NULL, '9c3afbbfca207aeb78541dbd9d90e462', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'backlog', 'ready', NULL, '2026-04-22 09:42:11', '2026-04-22 09:42:11', '2026-04-22 09:42:11', NULL, '66d7c49f6b2ccdd2ef88a635ec717ca2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'ready', 'in_progress', NULL, '2026-04-22 09:42:11', '2026-04-22 09:42:11', '2026-04-22 09:42:11', NULL, '87c83225076b22ecbc7fd150b8ccce5f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:45:21', '2026-04-22 11:45:21', '2026-04-22 11:45:21', NULL, '056f21749ff40c14feeb380b2425bb04', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'review', 'done', NULL, '2026-04-22 11:45:21', '2026-04-22 11:45:21', '2026-04-22 11:45:21', NULL, 'a345d1db748f86fb707b715ce4f45c82', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:12', '2026-04-22 11:46:12', '2026-04-22 11:46:12', NULL, 'd666982c7578a64cff619807467b0d22', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:16', '2026-04-22 11:46:16', '2026-04-22 11:46:16', NULL, 'bf092de792c1dba0a724e6c3ef0a2ca3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:26', '2026-04-22 11:46:26', '2026-04-22 11:46:26', NULL, '6648704125e10ddf9582b0fc8a91cbb8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'review', 'done', NULL, '2026-04-22 11:46:33', '2026-04-22 11:46:33', '2026-04-22 11:46:33', NULL, '28a5fbab4db244d0cc5c48e7e320ec71', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, '8e8f387c6cf81e8db06c212be3c75ff7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'b0d4dd5c1779606c861d1fd778ee9d0f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'review', 'done', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'b93dd369ad1260e6b1f74798544bafd1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'e5b9592394cabd32ce426a3916c27827', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'review', 'done', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '140ed6257373fb7d199c67da8452bb82', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '20cb7969c9c3af6a6deb93ee56a3a8f6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '456c32193f208cb4dcd78d722c8e5308', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '92282e57e514c08df603650c82fc5ad1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'review', 'done', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '2352d517e219af8edf4537f4145b5a9e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '37b7881534e16c2f369cdfb4a25ac511', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '7e4e0f286bd3bf1687cbbd49fca9e1f4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, 'c6ccbb7da6b303b5fd6ef4f5273c7d07', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:52', '2026-04-22 11:46:52', '2026-04-22 11:46:52', NULL, '99d159170d35cb6225a0b9e6f8e5a4f5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '171dab623718a0cb2a4af914c84e0d30', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '206793ce446e6ba302f209cda04dcb78', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '2a311168aab1126eadcb2e23433ba317', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '3098b6064c8b96eb459c5ed947ca97c4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '366f83e624514e442d539f704a0abbb1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '36c5d3bbd5f027fdb114391abdf1dda6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '381444444f3e04024e2293f61c774d1d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '46d32785bb5113ebcb050845e67ea280', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '5fde942d02640b9df4e75d1d745b2de5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '74dbfcfee3f60d46ba6e910ebb5fed1b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '7d19553f3714932803e565e8ac106220', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '9de91c0384a97d66eaa01e7f8164f389', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'c257d281cc9fc54be99fea49688ff720', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'cf47b1a47c543dabd8fe0c891c535285', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'd34d4be4170d93183c18754a60ea97e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'db0af7f083e8985026799a9d3334347d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '1a24e0b9fbc1472aacf17392a9241b99', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '7774f4c92e18371341b7c0332022d29a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '8589b12b48657f3513e801cd96dab11c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '92508b82168bd20d5a69c63a66271c34', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, 'dc2b5b3b5b348409f6d719f05130804b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '0dd7c9716ba7dab383c7890822ba9224', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '23c19fa354da9eb201212b23696da027', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '79e01d297b5cf559d37f2c0ee3661a51', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '88a0fb2513eebbd4627abd854eefb484', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '9ad3112f08548b5896fd2252dda48be0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'acba548461502b88a483649f181a65da', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'bd986c8233a7a251c0403c4df3ce8511', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'cfb6a1faab0ce219609f44055648cda1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'dabc0f13943decd855d39febfd2a18ae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'e340923ef96f70f142a0bee868bb8e67', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'f179c71abcd89f7ed9fa781220d2018c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'fae80bffb40c02fdf07691ef18837b14', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:54', '2026-04-22 12:03:54', '2026-04-22 12:03:54', NULL, '1693e0a6448fb98bd03fe0c4c65371fb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'review', 'done', NULL, '2026-04-22 12:03:54', '2026-04-22 12:03:54', '2026-04-22 12:03:54', NULL, '80d695ac3bfc5fe1f3615013dea3b06a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'ready', 'in_progress', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '33a14771999bc0062268447b4ffadc73', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'backlog', 'ready', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '5a2f1c3d37090ad5791c6a0e8a7aa80a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'ready', 'in_progress', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '7d84a743a1b9e1ce5c50172b89318153', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'backlog', 'ready', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, 'cad02006950fb6fa0d003553437d3bbe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'in_progress', 'review', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '10f0a16fa4e4899c7817dd5c44cc3ffe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'in_progress', 'review', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '2d697a30a6193eacf2a3ddf1661e861c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'review', 'done', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '591bb27186744d023df34105884fa58f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'review', 'done', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '817b9d8e92707eb588cb7f9eae7d424e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'status', 'backlog', 'cancelled', NULL, '2026-04-22 20:34:17', '2026-04-22 20:34:17', '2026-04-22 20:34:17', NULL, '5460d369f6cdef9c393a37e41481bcb5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '313d4c9dd6444a4573cbe9584ed18f61', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '4630269c56784316b975ca56b10a2337', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '5a8f7e0eda55bc0893c6597400a8d535', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, 'fac6b818dc68490a80ea32255f0ba404', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '0a7137f82fbb9307496b7e4133b15052', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '5aff1ed40d9a68a5b4d59aab0d24dd31', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '931bcaabbd64af0612fbe6eb9a4470d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, 'c2fca20d28b2a3a63a56eb2841a4bda9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '84e5b99f4fb99f8ae4d189ddaac2de74', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '8c7ba0fd4c8acc6065a24b80ae28d526', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, 'd91c6440f66e683a87be5e61a366cca7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, 'f9253848d9a95ce1823c8ba78a511fc0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '389d5abb35b3b73ccb38ac8bec3b92c7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '7e714e95f89966a13e436d2fe38e7443', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, 'b1aa8ff1f9b24cdfc9e0bebf21d38b99', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '39b407be88dbb31f530c8074f6bcecc7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '59df9c23d722ccf1c1ac18baee76c785', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '76b48a80abcba29b1047c794fd004234', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '297763c026010189ea0bd870f77a173e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '2cd947436be1c156eb77d233aeed5da2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '38826870354a1bf51bc2ff21f6da0ea3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '5105038efff2e3055213b1b1403b7610', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '67cc6c0934711b6a592e11e5edc6194c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '6e9e7d3ce9f74ed261912b1cb099dcad', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'a4e702776f3b553fb276782e539ebfc7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'd294248ba8ea651e78aeb6fed0457eb4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'da8dc2002a03eaee7fcd5504abee6f60', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '08f09f3bb9f1a0001110716930824e62', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '10005b4803d743aa21be0223c8a85d5c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '11ac23b546d0e22206af4221062f5912', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '127404fe79dfb79d0297b85d78235c68', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '15fb6606af61e11b6960799721e7cdcc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '19f6209ce4428172fb16941accb13566', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-27', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '212224b7dcc4b991119dc430ae179311', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '2fe8eed15b3ead999a69cdd08df5e14c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '3990623d651c604fec692edb70976d69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '44d273b92ad4c93159706c1f17fcb6a7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '46935c5a6d8ef12cf1c2252bd8d30368', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '551ea05a8d0cc28c0ff7010a139e6fe1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '6748fca610fc8a8503d69846231616fe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '7843cfda82ec1c1622c1a201f82acaf1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '822790bbace926feb2372b8665080932', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '9a1ae5742a1230e60279d9c72f8b0f75', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'parent_id', NULL, 'T-5', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a414bc8e4d32fa32483228a4e9c7be8c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'parent_id', NULL, 'T-3', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a80caae1913b8e9a1d5dab980bf0b77b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-40', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a895d51a2e6e8b448684933cf0224162', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'parent_id', NULL, 'T-3', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ad9a8a85686e33df17ae15631afa1213', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'aedb3c83402245f81cd10f04d15dcf8e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'b6dbb8114cd77c206d3b8bdf054736b9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-26', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'b7b189fe87f61d42e1d0318d23aeb1fe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'c80049d2c236f0d2512355183b766f0f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-25', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ccdf7bec2a75f5829eab4fdd57171622', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'parent_id', NULL, 'T-7', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'd13ed78c92d33e61e448c9e155adf870', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-41', 'parent_id', NULL, 'T-7', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'eb2a2ca41a57604b40556e81bf6480a9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'f0d27e423d40a998115e10ffd804f791', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'f5c891c9a5f7d5e3f43ded0dc07eb704', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ff609c22f964618c84b7c36988e87ae7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:01:08', '2026-04-24 07:01:08', '2026-04-24 07:01:08', NULL, '6ae864936d48565e880f06e984e9d978', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:01:08', '2026-04-24 07:01:08', '2026-04-24 07:01:08', NULL, 'ebebc38301a28001a09c9e7066c9aeb9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:01:48', '2026-04-24 07:01:48', '2026-04-24 07:01:48', NULL, '3cf128b79fe91447f72f42d8cffce9e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:01:48', '2026-04-24 07:01:48', '2026-04-24 07:01:48', NULL, '68f57f5e3a1005ca3009f944b8b634d7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:05:40', '2026-04-24 07:05:40', '2026-04-24 07:05:40', NULL, '50aa3e362dbe9682d98f6d5f71ea97b1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:05:50', '2026-04-24 07:05:50', '2026-04-24 07:05:50', NULL, '4cb951c3ea697881d8fa5b0e86cfdf96', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:05:51', '2026-04-24 07:05:51', '2026-04-24 07:05:51', NULL, '7516ffa5c72fa4e7fd067249393c3653', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:06:30', '2026-04-24 07:06:30', '2026-04-24 07:06:30', NULL, '268576bd9559485dd2ac5e7ffd1f1a79', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:06:32', '2026-04-24 07:06:32', '2026-04-24 07:06:32', NULL, 'ef46c3a3db4c4ba50ff1f0e1baa4c0e6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'review', 'done', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '1cd74d47eb547aa0bef71597eaaa2c5b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'in_progress', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '3b8c17d366d1710d03523303c9df83c8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'review', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '7eb9ca56292802301f93d0e2d680835e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, 'a1a837494c047ade786d79edb75296dd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-04-24 07:08:00', '2026-04-24 07:08:00', '2026-04-24 07:08:00', NULL, 'ac745b5254a0e8f085189c23d95acb7e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-04-24 07:08:29', '2026-04-24 07:08:29', '2026-04-24 07:08:29', NULL, '2adaf942a49bfeeac90aee1527a98444', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-04-24 07:08:29', '2026-04-24 07:08:29', '2026-04-24 07:08:29', NULL, 'e0d54ffba34677dc6e687be801ccec05', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'ready', NULL, '2026-04-24 07:08:35', '2026-04-24 07:08:35', '2026-04-24 07:08:35', NULL, '74e6fa2f524c21300467381c2f3ad4e3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:08:35', '2026-04-24 07:08:35', '2026-04-24 07:08:35', NULL, 'fbd19eabf442e6e0de60028c28956b8f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '7d3efe4ccaee7d45d1a01d28144c4f0a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'in_progress', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '914a2f5f5ef0807a6743f8811e71804b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'in_progress', 'ready', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'a1cd74c76fd10c353aa758cea0331c5c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'cancelled', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'cdbe85f80838e781048780db934d0115', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'cancelled', 'backlog', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'f24e8d138172597d7ddcf26672b67d5f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'f9a83fa1b41d56202c57e6a55b240f35', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'done', NULL, '2026-04-24 07:30:15', '2026-04-24 07:30:15', '2026-04-24 07:30:15', NULL, '99c4e5a68463201e7f1b7764eddfd2ae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'done', 'backlog', NULL, '2026-04-24 07:30:22', '2026-04-24 07:30:22', '2026-04-24 07:30:22', NULL, 'd0274058b130d8003123881b8a410f7f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:08:16', '2026-04-24 08:08:16', '2026-04-24 08:08:16', NULL, '83d70eb5a7f6e7175e0719fdb23243c2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 08:08:16', '2026-04-24 08:08:16', '2026-04-24 08:08:16', NULL, 'b5acf95cb059f0c573b78d755b938da2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:08:23', '2026-04-24 08:08:23', '2026-04-24 08:08:23', NULL, '4ae08b8af459af0679b05cf1f8ac3950', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'ready', NULL, '2026-04-24 08:08:38', '2026-04-24 08:08:38', '2026-04-24 08:08:38', NULL, 'c7e4cabf5d1fccad28f22d498e91193a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:08:40', '2026-04-24 08:08:40', '2026-04-24 08:08:40', NULL, '9b0937bc7eb82785407c367a90365c3a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'review', NULL, '2026-04-24 08:08:44', '2026-04-24 08:08:44', '2026-04-24 08:08:44', NULL, '90587f61d96b12ce53f2a4edc705fdd8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'review', 'in_progress', NULL, '2026-04-24 08:08:48', '2026-04-24 08:08:48', '2026-04-24 08:08:48', NULL, 'd02ac7ee61fc4dc420fb7597a4f326db', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'ready', NULL, '2026-04-24 08:16:15', '2026-04-24 08:16:15', '2026-04-24 08:16:15', NULL, '145613c94b4366bda03fb76c0f0747ce', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 08:16:19', '2026-04-24 08:16:19', '2026-04-24 08:16:19', NULL, '0d8e318e8060aece6e18788cee56f646', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:16:21', '2026-04-24 08:16:21', '2026-04-24 08:16:21', NULL, '3d8635cfdbaf9b0c22dcba9f15f3c1ad', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:16:47', '2026-04-24 08:16:47', '2026-04-24 08:16:47', NULL, '77f873a7a5fff47ce9b974a6999d03b4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'status', 'backlog', 'in_progress', NULL, '2026-04-24 08:16:56', '2026-04-24 08:16:56', '2026-04-24 08:16:56', NULL, 'e7610d68ec9871746b4a361e33b18f05', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:17:11', '2026-04-24 08:17:11', '2026-04-24 08:17:11', NULL, '9c3afbbfca207aeb78541dbd9d90e462', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'backlog', 'ready', NULL, '2026-04-22 09:42:11', '2026-04-22 09:42:11', '2026-04-22 09:42:11', NULL, '66d7c49f6b2ccdd2ef88a635ec717ca2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'ready', 'in_progress', NULL, '2026-04-22 09:42:11', '2026-04-22 09:42:11', '2026-04-22 09:42:11', NULL, '87c83225076b22ecbc7fd150b8ccce5f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:45:21', '2026-04-22 11:45:21', '2026-04-22 11:45:21', NULL, '056f21749ff40c14feeb380b2425bb04', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'status', 'review', 'done', NULL, '2026-04-22 11:45:21', '2026-04-22 11:45:21', '2026-04-22 11:45:21', NULL, 'a345d1db748f86fb707b715ce4f45c82', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:12', '2026-04-22 11:46:12', '2026-04-22 11:46:12', NULL, 'd666982c7578a64cff619807467b0d22', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:16', '2026-04-22 11:46:16', '2026-04-22 11:46:16', NULL, 'bf092de792c1dba0a724e6c3ef0a2ca3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:26', '2026-04-22 11:46:26', '2026-04-22 11:46:26', NULL, '6648704125e10ddf9582b0fc8a91cbb8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'status', 'review', 'done', NULL, '2026-04-22 11:46:33', '2026-04-22 11:46:33', '2026-04-22 11:46:33', NULL, '28a5fbab4db244d0cc5c48e7e320ec71', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, '8e8f387c6cf81e8db06c212be3c75ff7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'b0d4dd5c1779606c861d1fd778ee9d0f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'review', 'done', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'b93dd369ad1260e6b1f74798544bafd1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'e5b9592394cabd32ce426a3916c27827', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'review', 'done', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '140ed6257373fb7d199c67da8452bb82', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '20cb7969c9c3af6a6deb93ee56a3a8f6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '456c32193f208cb4dcd78d722c8e5308', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '92282e57e514c08df603650c82fc5ad1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'review', 'done', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '2352d517e219af8edf4537f4145b5a9e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '37b7881534e16c2f369cdfb4a25ac511', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '7e4e0f286bd3bf1687cbbd49fca9e1f4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, 'c6ccbb7da6b303b5fd6ef4f5273c7d07', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:52', '2026-04-22 11:46:52', '2026-04-22 11:46:52', NULL, '99d159170d35cb6225a0b9e6f8e5a4f5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '171dab623718a0cb2a4af914c84e0d30', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '206793ce446e6ba302f209cda04dcb78', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '2a311168aab1126eadcb2e23433ba317', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '3098b6064c8b96eb459c5ed947ca97c4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '366f83e624514e442d539f704a0abbb1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '36c5d3bbd5f027fdb114391abdf1dda6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '381444444f3e04024e2293f61c774d1d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '46d32785bb5113ebcb050845e67ea280', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '5fde942d02640b9df4e75d1d745b2de5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '74dbfcfee3f60d46ba6e910ebb5fed1b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '7d19553f3714932803e565e8ac106220', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '9de91c0384a97d66eaa01e7f8164f389', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'c257d281cc9fc54be99fea49688ff720', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'cf47b1a47c543dabd8fe0c891c535285', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'd34d4be4170d93183c18754a60ea97e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'db0af7f083e8985026799a9d3334347d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '1a24e0b9fbc1472aacf17392a9241b99', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '7774f4c92e18371341b7c0332022d29a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '8589b12b48657f3513e801cd96dab11c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '92508b82168bd20d5a69c63a66271c34', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, 'dc2b5b3b5b348409f6d719f05130804b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '0dd7c9716ba7dab383c7890822ba9224', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '23c19fa354da9eb201212b23696da027', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '79e01d297b5cf559d37f2c0ee3661a51', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '88a0fb2513eebbd4627abd854eefb484', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '9ad3112f08548b5896fd2252dda48be0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'acba548461502b88a483649f181a65da', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'bd986c8233a7a251c0403c4df3ce8511', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'cfb6a1faab0ce219609f44055648cda1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'dabc0f13943decd855d39febfd2a18ae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'e340923ef96f70f142a0bee868bb8e67', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'f179c71abcd89f7ed9fa781220d2018c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'fae80bffb40c02fdf07691ef18837b14', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:54', '2026-04-22 12:03:54', '2026-04-22 12:03:54', NULL, '1693e0a6448fb98bd03fe0c4c65371fb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'status', 'review', 'done', NULL, '2026-04-22 12:03:54', '2026-04-22 12:03:54', '2026-04-22 12:03:54', NULL, '80d695ac3bfc5fe1f3615013dea3b06a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'ready', 'in_progress', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '33a14771999bc0062268447b4ffadc73', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'backlog', 'ready', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '5a2f1c3d37090ad5791c6a0e8a7aa80a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'ready', 'in_progress', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '7d84a743a1b9e1ce5c50172b89318153', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'backlog', 'ready', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, 'cad02006950fb6fa0d003553437d3bbe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'in_progress', 'review', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '10f0a16fa4e4899c7817dd5c44cc3ffe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'in_progress', 'review', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '2d697a30a6193eacf2a3ddf1661e861c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'status', 'review', 'done', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '591bb27186744d023df34105884fa58f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'status', 'review', 'done', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '817b9d8e92707eb588cb7f9eae7d424e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'status', 'backlog', 'cancelled', NULL, '2026-04-22 20:34:17', '2026-04-22 20:34:17', '2026-04-22 20:34:17', NULL, '5460d369f6cdef9c393a37e41481bcb5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '313d4c9dd6444a4573cbe9584ed18f61', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '4630269c56784316b975ca56b10a2337', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '5a8f7e0eda55bc0893c6597400a8d535', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, 'fac6b818dc68490a80ea32255f0ba404', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '0a7137f82fbb9307496b7e4133b15052', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '5aff1ed40d9a68a5b4d59aab0d24dd31', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '931bcaabbd64af0612fbe6eb9a4470d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, 'c2fca20d28b2a3a63a56eb2841a4bda9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '84e5b99f4fb99f8ae4d189ddaac2de74', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '8c7ba0fd4c8acc6065a24b80ae28d526', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, 'd91c6440f66e683a87be5e61a366cca7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, 'f9253848d9a95ce1823c8ba78a511fc0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '389d5abb35b3b73ccb38ac8bec3b92c7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '7e714e95f89966a13e436d2fe38e7443', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, 'b1aa8ff1f9b24cdfc9e0bebf21d38b99', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '39b407be88dbb31f530c8074f6bcecc7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '59df9c23d722ccf1c1ac18baee76c785', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '76b48a80abcba29b1047c794fd004234', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '297763c026010189ea0bd870f77a173e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '2cd947436be1c156eb77d233aeed5da2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '38826870354a1bf51bc2ff21f6da0ea3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '5105038efff2e3055213b1b1403b7610', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '67cc6c0934711b6a592e11e5edc6194c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '6e9e7d3ce9f74ed261912b1cb099dcad', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'a4e702776f3b553fb276782e539ebfc7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'd294248ba8ea651e78aeb6fed0457eb4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'da8dc2002a03eaee7fcd5504abee6f60', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '08f09f3bb9f1a0001110716930824e62', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '10005b4803d743aa21be0223c8a85d5c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '11ac23b546d0e22206af4221062f5912', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '127404fe79dfb79d0297b85d78235c68', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '15fb6606af61e11b6960799721e7cdcc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '19f6209ce4428172fb16941accb13566', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-27', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '212224b7dcc4b991119dc430ae179311', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '2fe8eed15b3ead999a69cdd08df5e14c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '3990623d651c604fec692edb70976d69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '44d273b92ad4c93159706c1f17fcb6a7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '46935c5a6d8ef12cf1c2252bd8d30368', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '551ea05a8d0cc28c0ff7010a139e6fe1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '6748fca610fc8a8503d69846231616fe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '7843cfda82ec1c1622c1a201f82acaf1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '822790bbace926feb2372b8665080932', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '9a1ae5742a1230e60279d9c72f8b0f75', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'parent_id', NULL, 'T-5', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a414bc8e4d32fa32483228a4e9c7be8c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'parent_id', NULL, 'T-3', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a80caae1913b8e9a1d5dab980bf0b77b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-40', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'a895d51a2e6e8b448684933cf0224162', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'parent_id', NULL, 'T-3', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ad9a8a85686e33df17ae15631afa1213', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'aedb3c83402245f81cd10f04d15dcf8e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'b6dbb8114cd77c206d3b8bdf054736b9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-26', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'b7b189fe87f61d42e1d0318d23aeb1fe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'c80049d2c236f0d2512355183b766f0f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-25', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ccdf7bec2a75f5829eab4fdd57171622', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'parent_id', NULL, 'T-7', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'd13ed78c92d33e61e448c9e155adf870', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-41', 'parent_id', NULL, 'T-7', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'eb2a2ca41a57604b40556e81bf6480a9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'f0d27e423d40a998115e10ffd804f791', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'f5c891c9a5f7d5e3f43ded0dc07eb704', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ff609c22f964618c84b7c36988e87ae7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:01:08', '2026-04-24 07:01:08', '2026-04-24 07:01:08', NULL, '6ae864936d48565e880f06e984e9d978', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:01:08', '2026-04-24 07:01:08', '2026-04-24 07:01:08', NULL, 'ebebc38301a28001a09c9e7066c9aeb9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:01:48', '2026-04-24 07:01:48', '2026-04-24 07:01:48', NULL, '3cf128b79fe91447f72f42d8cffce9e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:01:48', '2026-04-24 07:01:48', '2026-04-24 07:01:48', NULL, '68f57f5e3a1005ca3009f944b8b634d7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:05:40', '2026-04-24 07:05:40', '2026-04-24 07:05:40', NULL, '50aa3e362dbe9682d98f6d5f71ea97b1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:05:50', '2026-04-24 07:05:50', '2026-04-24 07:05:50', NULL, '4cb951c3ea697881d8fa5b0e86cfdf96', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:05:51', '2026-04-24 07:05:51', '2026-04-24 07:05:51', NULL, '7516ffa5c72fa4e7fd067249393c3653', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:06:30', '2026-04-24 07:06:30', '2026-04-24 07:06:30', NULL, '268576bd9559485dd2ac5e7ffd1f1a79', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:06:32', '2026-04-24 07:06:32', '2026-04-24 07:06:32', NULL, 'ef46c3a3db4c4ba50ff1f0e1baa4c0e6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'review', 'done', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '1cd74d47eb547aa0bef71597eaaa2c5b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'in_progress', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '3b8c17d366d1710d03523303c9df83c8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'review', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '7eb9ca56292802301f93d0e2d680835e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, 'a1a837494c047ade786d79edb75296dd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-04-24 07:08:00', '2026-04-24 07:08:00', '2026-04-24 07:08:00', NULL, 'ac745b5254a0e8f085189c23d95acb7e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-04-24 07:08:29', '2026-04-24 07:08:29', '2026-04-24 07:08:29', NULL, '2adaf942a49bfeeac90aee1527a98444', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-04-24 07:08:29', '2026-04-24 07:08:29', '2026-04-24 07:08:29', NULL, 'e0d54ffba34677dc6e687be801ccec05', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'ready', NULL, '2026-04-24 07:08:35', '2026-04-24 07:08:35', '2026-04-24 07:08:35', NULL, '74e6fa2f524c21300467381c2f3ad4e3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:08:35', '2026-04-24 07:08:35', '2026-04-24 07:08:35', NULL, 'fbd19eabf442e6e0de60028c28956b8f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '7d3efe4ccaee7d45d1a01d28144c4f0a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'in_progress', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '914a2f5f5ef0807a6743f8811e71804b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'in_progress', 'ready', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'a1cd74c76fd10c353aa758cea0331c5c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'cancelled', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'cdbe85f80838e781048780db934d0115', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'cancelled', 'backlog', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'f24e8d138172597d7ddcf26672b67d5f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'f9a83fa1b41d56202c57e6a55b240f35', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'done', NULL, '2026-04-24 07:30:15', '2026-04-24 07:30:15', '2026-04-24 07:30:15', NULL, '99c4e5a68463201e7f1b7764eddfd2ae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'done', 'backlog', NULL, '2026-04-24 07:30:22', '2026-04-24 07:30:22', '2026-04-24 07:30:22', NULL, 'd0274058b130d8003123881b8a410f7f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:08:16', '2026-04-24 08:08:16', '2026-04-24 08:08:16', NULL, '83d70eb5a7f6e7175e0719fdb23243c2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 08:08:16', '2026-04-24 08:08:16', '2026-04-24 08:08:16', NULL, 'b5acf95cb059f0c573b78d755b938da2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:08:23', '2026-04-24 08:08:23', '2026-04-24 08:08:23', NULL, '4ae08b8af459af0679b05cf1f8ac3950', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'ready', NULL, '2026-04-24 08:08:38', '2026-04-24 08:08:38', '2026-04-24 08:08:38', NULL, 'c7e4cabf5d1fccad28f22d498e91193a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:08:40', '2026-04-24 08:08:40', '2026-04-24 08:08:40', NULL, '9b0937bc7eb82785407c367a90365c3a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'review', NULL, '2026-04-24 08:08:44', '2026-04-24 08:08:44', '2026-04-24 08:08:44', NULL, '90587f61d96b12ce53f2a4edc705fdd8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'review', 'in_progress', NULL, '2026-04-24 08:08:48', '2026-04-24 08:08:48', '2026-04-24 08:08:48', NULL, 'd02ac7ee61fc4dc420fb7597a4f326db', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'ready', NULL, '2026-04-24 08:16:15', '2026-04-24 08:16:15', '2026-04-24 08:16:15', NULL, '145613c94b4366bda03fb76c0f0747ce', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'backlog', NULL, '2026-04-24 08:16:19', '2026-04-24 08:16:19', '2026-04-24 08:16:19', NULL, '0d8e318e8060aece6e18788cee56f646', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:16:21', '2026-04-24 08:16:21', '2026-04-24 08:16:21', NULL, '3d8635cfdbaf9b0c22dcba9f15f3c1ad', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:16:47', '2026-04-24 08:16:47', '2026-04-24 08:16:47', NULL, '77f873a7a5fff47ce9b974a6999d03b4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'status', 'backlog', 'in_progress', NULL, '2026-04-24 08:16:56', '2026-04-24 08:16:56', '2026-04-24 08:16:56', NULL, 'e7610d68ec9871746b4a361e33b18f05', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:17:11', '2026-04-24 08:17:11', '2026-04-24 08:17:11', NULL, '9c3afbbfca207aeb78541dbd9d90e462', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62E3MNW8TBTKSR9GM', 'status', 'backlog', 'ready', NULL, '2026-04-22 09:42:11', '2026-04-22 09:42:11', '2026-04-22 09:42:11', NULL, '3fba5e751d6a8c9a29335cad1550fa0f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62E3MNW8TBTKSR9GM', 'status', 'ready', 'in_progress', NULL, '2026-04-22 09:42:11', '2026-04-22 09:42:11', '2026-04-22 09:42:11', NULL, 'c4cb4ad45703aa943016359dc46469e8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62E3MNW8TBTKSR9GM', 'status', 'review', 'done', NULL, '2026-04-22 11:45:21', '2026-04-22 11:45:21', '2026-04-22 11:45:21', NULL, '146a823b003c92bcd8b8f7e74f2f9f39', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62E3MNW8TBTKSR9GM', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:45:21', '2026-04-22 11:45:21', '2026-04-22 11:45:21', NULL, 'bbf575b4892c07075dd436e44e65b2cc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KX7HEQJ65HZ566DG', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:12', '2026-04-22 11:46:12', '2026-04-22 11:46:12', NULL, 'f5088eafcad461a9a4ee4b4df88357d2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KX7HEQJ65HZ566DG', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:16', '2026-04-22 11:46:16', '2026-04-22 11:46:16', NULL, '556eba472e2de76accea58ee559ce7d2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KX7HEQJ65HZ566DG', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:26', '2026-04-22 11:46:26', '2026-04-22 11:46:26', NULL, '3a17808cf45fab2b987da0d0cbde98b6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KX7HEQJ65HZ566DG', 'status', 'review', 'done', NULL, '2026-04-22 11:46:33', '2026-04-22 11:46:33', '2026-04-22 11:46:33', NULL, '2b09b099a97c35e26f35ac37228e9411', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, '0492e38c415358802970cb077447f7d7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, '1ed839ad770ebbffdf23364dbcb766eb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'status', 'review', 'done', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, '4f52a76829285f14e71363d162c600e3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:37', '2026-04-22 11:46:37', '2026-04-22 11:46:37', NULL, 'efda926413d09bbd05a1c88275fc927f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GSR5WPC2T7CJM8XW', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '0c904f00c761b272e945045651b3c405', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GSR5WPC2T7CJM8XW', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '0f7b405a869b6c6d17bddd2189c5b52f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GSR5WPC2T7CJM8XW', 'status', 'review', 'done', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, '46d21523224743c31eb97a3c1e18d2d9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GSR5WPC2T7CJM8XW', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:44', '2026-04-22 11:46:44', '2026-04-22 11:46:44', NULL, 'a33b066448c87f7be0542d61dfad5f66', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XZA5399D2EHYCVFC', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '061a7151be46322c0128980c073356df', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XZA5399D2EHYCVFC', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '08480dd43012f8f6f37e212767d26be2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XZA5399D2EHYCVFC', 'status', 'review', 'done', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '7c004a225b6b82840101504aef4e758c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XZA5399D2EHYCVFC', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:46:48', '2026-04-22 11:46:48', '2026-04-22 11:46:48', NULL, '905c9d2df79619009b56009d193aa952', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7DV4ZS010XWZJ9A84', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:46:52', '2026-04-22 11:46:52', '2026-04-22 11:46:52', NULL, '9cafe6c3de8d9f5a3db8321ac7bcbafc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7YHYTWJGWPFJNHEAR', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '11c89804420370461fbd8fe880758f7e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79D19C0ZTXAVPKCCG', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '36f16d47e4acf599d2d78933bcc03c77', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TS48DVFDVJNZ5WGC', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '4162b254ba6ddb4daa8e85e428c5608c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TS48DVFDVJNZ5WGC', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '45a71ef6bad527950f92d9323c3edf0a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R0WRHSJV1MVCW31M', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '477dc88ff367b8a6bd393711a9dc934b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7YHYTWJGWPFJNHEAR', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '5d92aa65816538906b0cc1ed5ee87ed3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7YHYTWJGWPFJNHEAR', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '6b742f381bb7a072b02dfc55db62447f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TS48DVFDVJNZ5WGC', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '9842cfa577a4dc7eaf6792266bb365be', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R0WRHSJV1MVCW31M', 'status', 'review', 'done', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, '9dd7f0c00d8c7e3c7a67abaa8412bec9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79D19C0ZTXAVPKCCG', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'c557f84b4744487609e7ffb02fd2ed49', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R0WRHSJV1MVCW31M', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'cd7a68edc46c277c49fa5839990504ad', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79D19C0ZTXAVPKCCG', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'd387e7910739e3ecc6702b51f0d8b9e4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79D19C0ZTXAVPKCCG', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'e1d3d1a9ca033dd1d334d478c8802e32', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7YHYTWJGWPFJNHEAR', 'status', 'in_progress', 'review', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'e4253f407304ff571299945fabe31eee', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R0WRHSJV1MVCW31M', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'f22005f7ae58243f1fce0b07e6475235', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TS48DVFDVJNZ5WGC', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:47:11', '2026-04-22 11:47:11', '2026-04-22 11:47:11', NULL, 'feb8a00523b7118d285847f7d88e70d3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7TW8EJNJ2VP306A20', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, '4bad0ff51702836a574ecb8afb5bd47e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QHBTA4VE6TWQT43R', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, 'e64eeea1f1fcbea7c020191e6c94303d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7DV4ZS010XWZJ9A84', 'status', 'ready', 'in_progress', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, 'e767c59bd07f4ceb3dabbbbd9a8b25dc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59BAJSJ1JESDTPNER', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, 'f32b33eaddf68e8213bfbe698960da29', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM75XRV8AQG2HEQZEJ0', 'status', 'backlog', 'ready', NULL, '2026-04-22 11:52:59', '2026-04-22 11:52:59', '2026-04-22 11:52:59', NULL, 'feb5c5a54317cafb1ca8d77ba93cc81e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM75XRV8AQG2HEQZEJ0', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '08373a38596647969ebd74504544616a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM75XRV8AQG2HEQZEJ0', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '3a7ee18edc398147113c0ab45b4812eb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59BAJSJ1JESDTPNER', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '5adac012531c1b1f400ecb2290782e5b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QHBTA4VE6TWQT43R', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, '7952677f8c48b6dc6e9c6e05a78e80a8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QHBTA4VE6TWQT43R', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'a545724b00ae8c2f2b9ea09cb0a22eb0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59BAJSJ1JESDTPNER', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'ba68ce26a31c4bc0c56d5dcf1640b6e9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59BAJSJ1JESDTPNER', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'c1a7e08caf95080e168df4e611dba0d7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7TW8EJNJ2VP306A20', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'c97eb916069dd8267e623872edcd7452', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7TW8EJNJ2VP306A20', 'status', 'ready', 'in_progress', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'd520ce983ac294f25f96da3e22fe2257', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM75XRV8AQG2HEQZEJ0', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'db6a409471d1fcf96494fca0cf36b950', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7TW8EJNJ2VP306A20', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'eabc93b76de154cf79976608c0d00603', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QHBTA4VE6TWQT43R', 'status', 'review', 'done', NULL, '2026-04-22 12:03:48', '2026-04-22 12:03:48', '2026-04-22 12:03:48', NULL, 'ed6fc64e88ca1dc9065e72ebd6067e4d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7DV4ZS010XWZJ9A84', 'status', 'in_progress', 'review', NULL, '2026-04-22 12:03:54', '2026-04-22 12:03:54', '2026-04-22 12:03:54', NULL, '11f427298c60d6f583daa698b0b54e37', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7DV4ZS010XWZJ9A84', 'status', 'review', 'done', NULL, '2026-04-22 12:03:54', '2026-04-22 12:03:54', '2026-04-22 12:03:54', NULL, '8a18fc5ed3fbf30e4abddd0cfe100bff', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JH7WV2RH5DGEA2AC', 'status', 'backlog', 'ready', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, '74a219dafd7350205cac9eeda53552c2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6VMWWSP4BV8Q11W0M', 'status', 'backlog', 'ready', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, 'e7e1a901f6e799c0eae79c9dc2bb4a33', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JH7WV2RH5DGEA2AC', 'status', 'ready', 'in_progress', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, 'ec16cf36360eb2a7805bffed1a243ecf', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6VMWWSP4BV8Q11W0M', 'status', 'ready', 'in_progress', NULL, '2026-04-22 14:16:39', '2026-04-22 14:16:39', '2026-04-22 14:16:39', NULL, 'f49e07cca55f982a6a07162f55c0c6d8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6VMWWSP4BV8Q11W0M', 'status', 'in_progress', 'review', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '127e63f3c290f90e7c3aa847afd24923', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JH7WV2RH5DGEA2AC', 'status', 'review', 'done', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '488736bebe779b1281384ce5765a883e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JH7WV2RH5DGEA2AC', 'status', 'in_progress', 'review', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '76d283c661ddda877270447bf9ed3b0b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6VMWWSP4BV8Q11W0M', 'status', 'review', 'done', NULL, '2026-04-22 14:30:05', '2026-04-22 14:30:05', '2026-04-22 14:30:05', NULL, '8108195e0776ff2e2493720ef80c5b6b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6PMW57C70KBJVAV10', 'status', 'backlog', 'cancelled', NULL, '2026-04-22 20:34:17', '2026-04-22 20:34:17', '2026-04-22 20:34:17', NULL, '66eac257c948b529c5949d17ca5f1d11', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VVHNKNXPYD4XWRYW', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, '2031b1358057b8fad868649bb7484500', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GHHRANK5532VJVJR', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, 'a46451df6d3b67e3bcfbf48bb73c2e12', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM64DC28XV20JJ8KW00', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, 'c397fe7de79cc82035f57a5cdaa2b83d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WXV1RKPDK810C3FR', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:38:42', '2026-04-22 20:38:42', '2026-04-22 20:38:42', NULL, 'd471b281adf859311624bb19452d36ba', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WXV1RKPDK810C3FR', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '154c84814ce1eacfd09580762e35748e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VVHNKNXPYD4XWRYW', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, '2a3c5b0f2799072f0c593128ac99ddec', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM64DC28XV20JJ8KW00', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, 'e78fe6d9079f625573cf0993bb73a684', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GHHRANK5532VJVJR', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:38:49', '2026-04-22 20:38:49', '2026-04-22 20:38:49', NULL, 'ffade557a91afb2ce8d223a59c987d15', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GHHRANK5532VJVJR', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '0ad715f5bfdec4a3c8005f0f4cb857d6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VVHNKNXPYD4XWRYW', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '4cd3af18c76884911ac0eff87f93e4c0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WXV1RKPDK810C3FR', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '870fda31d4fbd8c39e4a6ed78551a089', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM64DC28XV20JJ8KW00', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:38:51', '2026-04-22 20:38:51', '2026-04-22 20:38:51', NULL, '92206091799f820dd2ae2778a92947fc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4005W4QV0ZPBMQ02C', 'status', 'in_progress', 'done', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '0798cec4e8ce91238498218a90fc2b4e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4005W4QV0ZPBMQ02C', 'status', 'backlog', 'ready', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '4d677f10ac70a801bc5d691e618dd098', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4005W4QV0ZPBMQ02C', 'status', 'ready', 'in_progress', NULL, '2026-04-22 20:56:58', '2026-04-22 20:56:58', '2026-04-22 20:56:58', NULL, '79fde318878ec91f2cd020be9db706c4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7MKCHC37G69SJGQ04', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '615893bb4159bca57088c5a8e202e834', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7MKCHC37G69SJGQ04', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, '7350f6584bee9eeadb6a41001f34876b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7MKCHC37G69SJGQ04', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:02:29', '2026-04-22 21:02:29', '2026-04-22 21:02:29', NULL, 'a4994cbb075a8921e4d0d505d922981c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KH7VR0BMZV5SSTXC', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '114825918ccc9039ad4653dc5c3b5785', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6JXWYYQ6MT6N8STYM', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '1bc4adcc9db4ab023130198ed8299bb4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5Q6AWSTKGT0P64TRR', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '2c6e445be22794878bd6784a5973c028', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KH7VR0BMZV5SSTXC', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '307d5b39c6fe3df3d2a620a591d7d5c8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5Q6AWSTKGT0P64TRR', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '798c86f77cdd2b3a1a337291215b45d6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6JXWYYQ6MT6N8STYM', 'status', 'backlog', 'ready', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '81141b1ca5d1d6ba526b33054189ebfa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6JXWYYQ6MT6N8STYM', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '83e47e7c7550ce0f3c724edfa773016f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KH7VR0BMZV5SSTXC', 'status', 'in_progress', 'done', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, '8b6ef7ffc9b872edbe8ccd513f0a239e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5Q6AWSTKGT0P64TRR', 'status', 'ready', 'in_progress', NULL, '2026-04-22 21:16:41', '2026-04-22 21:16:41', '2026-04-22 21:16:41', NULL, 'c040aca52959ae8febda5e0dd54252a2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QHBTA4VE6TWQT43R', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '007710b30c9e75b030b44dba9fa5f824', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79D19C0ZTXAVPKCCG', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '0c80d9ff14fd5992b92f0c9b8b9eb647', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6VMWWSP4BV8Q11W0M', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '18e5b09f781d974d4746fa984734872f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67JSC5RKS6M9182KG', 'parent_id', NULL, 'T-7', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '1fbe35313558f6277b818cff665009a9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YG22EV7BFTX5RTPR', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '20d5c46249f5b8810e17914929ec763e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM699HAWC76SHRA7AM4', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '251327841ab9971717b36c2cb79ca299', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7TW8EJNJ2VP306A20', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '2e3916450f98a766aac0c2abc301e6c4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5E41Q8TX8X55X4MZ0', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '4502258c81a7e75eafd3631e0cdf90b8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ZXPWX71ND4C699C', 'parent_id', NULL, 'T-3', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '4c190ec1739ed8ec68fe694687fa5fe3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JK0RHMRAH47K8ND4', 'parent_id', NULL, 'T-7', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '4ca23526443704936c352c2842f2f7f8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '50dc074f7f06d51e11fee8d00dd7dd74', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7YHYTWJGWPFJNHEAR', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '6a700a972bd1c1a0729bf27078def56f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5Q6AWSTKGT0P64TRR', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '773e27abfec30eeb016b441c1dc0d8f8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM75XRV8AQG2HEQZEJ0', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '881064c3f6f63638a754929f1078b011', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '9404bff31ca5c310db47f5c0e11c90ca', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48MDE8ZZ82VWNY994', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '96cf4f78cd06636178ba9a64d6b83f2d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R0WRHSJV1MVCW31M', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '9b17afe084710d3e198c7435ee0fb4b6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RDM2EKZX132GPV7M', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '9ca8eb0c58afc81c07a7538b8e504ad4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66TK8TQ1X5T9GK6V0', 'parent_id', NULL, 'T-5', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, '9f69eaa0855cbea3a8e715b7bb917624', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6PMW57C70KBJVAV10', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'b683b61fdb656a5f6df871190736771f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KH7VR0BMZV5SSTXC', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'c27a712580912bc125013ca9ee3330a1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4005W4QV0ZPBMQ02C', 'parent_id', NULL, 'T-4', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'cca49092a5f8f1912acd35c168101148', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4M63G18VAQFG6R3XW', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ce9352bab1e00b12f8ff794fca067732', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62E3MNW8TBTKSR9GM', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'dc8745c0d7b3445b0c78985f1f9995dc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TS48DVFDVJNZ5WGC', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'e1831cbdaa2e3ce0000a590874c08a76', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6JXWYYQ6MT6N8STYM', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'e5214fde5b73b4a7165107dc85057ec5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JH7WV2RH5DGEA2AC', 'parent_id', NULL, 'T-2', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'e6649095b63a9a991df2db00a7ad53ca', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W194B2421P2SF83R', 'parent_id', NULL, 'T-8', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'ee6e9785f9f760ab2c6b3f689afefad2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QHMB46NSHHPVQRF0', 'parent_id', NULL, 'T-3', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'f9b20bf38afe42bfdfd811ab282658ee', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59BAJSJ1JESDTPNER', 'parent_id', NULL, 'T-6', NULL, '2026-04-23 10:23:49', '2026-04-23 10:23:49', '2026-04-23 10:23:49', NULL, 'fb24ffd20df49eb6febd496a6c1c02bf', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:01:08', '2026-04-24 07:01:08', '2026-04-24 07:01:08', NULL, '728ad0916172fb20a1c4b19d0753e4db', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:01:08', '2026-04-24 07:01:08', '2026-04-24 07:01:08', NULL, 'bfd785a9e40e27979d287ec137ec01d1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:01:48', '2026-04-24 07:01:48', '2026-04-24 07:01:48', NULL, '3b1ea2037fa8d26c4293a2bd5d0fa007', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:01:48', '2026-04-24 07:01:48', '2026-04-24 07:01:48', NULL, 'd5329907e937e55e00a9d002da5ac6d4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:05:40', '2026-04-24 07:05:40', '2026-04-24 07:05:40', NULL, '5a372aa4814812aa716d853550fea046', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:05:50', '2026-04-24 07:05:50', '2026-04-24 07:05:50', NULL, 'ba35f95ff2763f26ebccff21a2b826af', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:05:51', '2026-04-24 07:05:51', '2026-04-24 07:05:51', NULL, '4e46180f02c86278deedd9f8f08d4d07', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:06:30', '2026-04-24 07:06:30', '2026-04-24 07:06:30', NULL, '8e985069c3c5eeb6b172f83430458810', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:06:32', '2026-04-24 07:06:32', '2026-04-24 07:06:32', NULL, '66c14397b8af87bceaace31b1dff2853', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'review', 'done', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '5733cc23693ca40190bd8c5063a7a022', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'in_progress', 'review', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, '9669c3ab67cb4477961d2ee3aa799a90', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'ready', 'in_progress', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, 'de6f0dd22f5f0e664793f74429322b8b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:07:55', '2026-04-24 07:07:55', '2026-04-24 07:07:55', NULL, 'e7b5b2d4e43d09ede3d177528718683a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'done', 'in_progress', NULL, '2026-04-24 07:08:00', '2026-04-24 07:08:00', '2026-04-24 07:08:00', NULL, '976f8fecced9b5322ed3403b8fbdf0dc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'in_progress', 'done', NULL, '2026-04-24 07:08:29', '2026-04-24 07:08:29', '2026-04-24 07:08:29', NULL, '281fe02b5f9467e50e0a186fc1afb77c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'done', 'in_progress', NULL, '2026-04-24 07:08:29', '2026-04-24 07:08:29', '2026-04-24 07:08:29', NULL, 'fd27037a9f94cf0df10016ff2c3b6e78', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'in_progress', 'ready', NULL, '2026-04-24 07:08:35', '2026-04-24 07:08:35', '2026-04-24 07:08:35', NULL, '52ed48f7463c234efeb35fcf45957e8a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:08:35', '2026-04-24 07:08:35', '2026-04-24 07:08:35', NULL, '8fb22a82912e93b9abff0da33d371093', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'backlog', 'cancelled', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '3e65f78d186b37308165679295b40408', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'cancelled', 'backlog', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '5b81f1c8048cd2b3ef1055ac9b04f958', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'ready', 'backlog', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '7348283db8af7104b475d0d84a0a7157', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'in_progress', 'ready', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '804f233159ce47a2c213778971fc00a1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'backlog', 'ready', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, '91171fb011294517240bd33360b08119', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'ready', 'in_progress', NULL, '2026-04-24 07:08:59', '2026-04-24 07:08:59', '2026-04-24 07:08:59', NULL, 'd0b87332faf5a67e7e6d36ff9c981c00', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'backlog', 'done', NULL, '2026-04-24 07:30:15', '2026-04-24 07:30:15', '2026-04-24 07:30:15', NULL, 'bf086ebf2d4e6b0a592f8386e916115b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'done', 'backlog', NULL, '2026-04-24 07:30:22', '2026-04-24 07:30:22', '2026-04-24 07:30:22', NULL, '8e29783cc997d80b6699bd14537e8613', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'ready', 'backlog', NULL, '2026-04-24 08:08:16', '2026-04-24 08:08:16', '2026-04-24 08:08:16', NULL, '1bc6c7c959742697cd8670771ebeb8d9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:08:16', '2026-04-24 08:08:16', '2026-04-24 08:08:16', NULL, '51a117e3e688d86d72e33a844dfb6217', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:08:23', '2026-04-24 08:08:23', '2026-04-24 08:08:23', NULL, 'bf1f3a13e6bc7df6adf20b545e6911aa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'in_progress', 'ready', NULL, '2026-04-24 08:08:38', '2026-04-24 08:08:38', '2026-04-24 08:08:38', NULL, '66d52940767ba7c91f848431ee618ba1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:08:40', '2026-04-24 08:08:40', '2026-04-24 08:08:40', NULL, '1154f797d2c87e3e5aff6c8407b97f7e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'in_progress', 'review', NULL, '2026-04-24 08:08:44', '2026-04-24 08:08:44', '2026-04-24 08:08:44', NULL, '99a8c33d31f16025ad3a27305ec703a0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'review', 'in_progress', NULL, '2026-04-24 08:08:48', '2026-04-24 08:08:48', '2026-04-24 08:08:48', NULL, '9475c2583b2f0d3742f792f5dcffd4aa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'in_progress', 'ready', NULL, '2026-04-24 08:16:15', '2026-04-24 08:16:15', '2026-04-24 08:16:15', NULL, 'df08122765cc76b0003b6e9ff54426fb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'ready', 'backlog', NULL, '2026-04-24 08:16:19', '2026-04-24 08:16:19', '2026-04-24 08:16:19', NULL, '4e273f400ffcfbf2bc926ee4e9575fbc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:16:21', '2026-04-24 08:16:21', '2026-04-24 08:16:21', NULL, '6e750d5ebacc51f2734e94675322e5c4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'ready', 'in_progress', NULL, '2026-04-24 08:16:47', '2026-04-24 08:16:47', '2026-04-24 08:16:47', NULL, '9e82a53ad5b5d58037280a6778e335fe', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4M63G18VAQFG6R3XW', 'status', 'backlog', 'in_progress', NULL, '2026-04-24 08:16:56', '2026-04-24 08:16:56', '2026-04-24 08:16:56', NULL, '8a9247ea1de65f4696dc7af64a5d2b7f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ZXPWX71ND4C699C', 'status', 'backlog', 'ready', NULL, '2026-04-24 08:17:11', '2026-04-24 08:17:11', '2026-04-24 08:17:11', NULL, '38b265daee03a4df5fb525e48b477388', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/ticket_history/2026-05.sql b/.pql/changelog/ticket_history/2026-05.sql index 65f2ac5c..6fb3b7c7 100644 --- a/.pql/changelog/ticket_history/2026-05.sql +++ b/.pql/changelog/ticket_history/2026-05.sql @@ -1,48 +1,48 @@ -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-62', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '43c1103bad9839fbd2c8058f147cfcba', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-61', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '65fbf26149226f3fc9c06a446bfa9f41', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-49', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '98b648e7a4a82730e43677c7320c8b0d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, 'c8b03bf5dadfdc5cb22bc5961919dc23', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'ready', 'in_progress', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, 'fdc5453ac922809e479b6b3418ed7755', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:34:52', '2026-05-03 20:34:52', '2026-05-03 20:34:52', NULL, '6da16f33a6aa1e1db733a0a02900e0c1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:36:31', '2026-05-03 20:36:31', '2026-05-03 20:36:31', NULL, 'd15ac0c4b6c7b6057243edcfbf7086f1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:37:03', '2026-05-03 20:37:03', '2026-05-03 20:37:03', NULL, '3f6f22aa9e420ec2fc869f2530df4c46', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:37:30', '2026-05-03 20:37:30', '2026-05-03 20:37:30', NULL, 'e6f3d8bda62656d17b174c8b9933dabc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'status', 'in_progress', 'done', NULL, '2026-05-03 20:40:22', '2026-05-03 20:40:22', '2026-05-03 20:40:22', NULL, '9ace75c4346f321670f7024eebfa0fb9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'in_progress', 'done', NULL, '2026-05-05 12:51:24', '2026-05-05 12:51:24', '2026-05-05 12:51:24', NULL, 'adedfd3ad28b56cf4e57e20bce3ec497', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '60751203904229cc5846ea313f747846', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '618b3042be98eecae2168446ffd2bfa6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '65e4ebb4c60a0feb7c2dec82f0c3faa1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'a3ac83b6f85fbcf6b44a4b0c28c79803', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'b427c00df34390343af95fcd285a09bd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '48865da069da205e0cf7ae7dd8caf27d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '680e39c278e717327dc2557e625abc1b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '8f1ea30ab9663ac07c04fe404937a9fd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '9284c52dba3b76b117e8d3a97420dd6a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, 'e5c9d41027d52b8fbe7edcc684130c2e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'in_progress', NULL, '2026-05-05 12:54:39', '2026-05-05 12:54:39', '2026-05-05 12:54:39', NULL, 'd8d87b2a3ea7073edde2157f6c90791e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'in_progress', 'done', NULL, '2026-05-05 12:59:05', '2026-05-05 12:59:05', '2026-05-05 12:59:05', NULL, '3ddd60cd6e2f138e184b3e4e8f892a21', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 12:59:50', '2026-05-05 12:59:50', '2026-05-05 12:59:50', NULL, 'a1e1b2c66830239411cac289bcb13050', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:02:34', '2026-05-05 13:02:34', '2026-05-05 13:02:34', NULL, 'ffe86cc4530e722c0622fbd4095d0f08', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:02:40', '2026-05-05 13:02:40', '2026-05-05 13:02:40', NULL, '3d48174c87be388f391611a4c9ebc4b0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:07:29', '2026-05-05 13:07:29', '2026-05-05 13:07:29', NULL, '579e4939f630479b5987b47de2106ca9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:07:34', '2026-05-05 13:07:34', '2026-05-05 13:07:34', NULL, 'd56b93ae20653ab26c0a99ab7c28fa69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:10:50', '2026-05-05 13:10:50', '2026-05-05 13:10:50', NULL, '5c98774f501f1a6c7bef328c3cd8c5b1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:10:57', '2026-05-05 13:10:57', '2026-05-05 13:10:57', NULL, '8558d256a078bda0713c7045d7f1c2bb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:12:20', '2026-05-05 13:12:20', '2026-05-05 13:12:20', NULL, 'aabaf34b3e5982ab7be5c07051e2c1e7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 07:45:02', '2026-05-06 07:45:02', '2026-05-06 07:45:02', NULL, 'e5e0baef07bf6908200527c340130a2e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'status', 'in_progress', 'done', NULL, '2026-05-06 07:56:10', '2026-05-06 07:56:10', '2026-05-06 07:56:10', NULL, '3069b52fc53518956fc0dc7792aa2e9c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'status', 'ready', 'done', NULL, '2026-05-06 09:17:22', '2026-05-06 09:17:22', '2026-05-06 09:17:22', NULL, '56b24bfcf31543cbde7e5c1af8055989', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 09:17:50', '2026-05-06 09:17:50', '2026-05-06 09:17:50', NULL, 'cb9fc3d3f6cc5858ce9af1f377257b26', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'status', 'in_progress', 'done', NULL, '2026-05-06 10:12:48', '2026-05-06 10:12:48', '2026-05-06 10:12:48', NULL, '133758a6509278aaada7d8f03ac72ae4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 10:17:57', '2026-05-06 10:17:57', '2026-05-06 10:17:57', NULL, '8991e0669094e0969787881506f373dc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'status', 'in_progress', 'done', NULL, '2026-05-06 12:17:27', '2026-05-06 12:17:27', '2026-05-06 12:17:27', NULL, '8dd4f7dba893357485c6a8293e49765e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'ready', 'in_progress', NULL, '2026-05-06 12:20:58', '2026-05-06 12:20:58', '2026-05-06 12:20:58', NULL, 'e9f7ebbd02eb9bf7f3faf10dca1cf8e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:21:15', '2026-05-06 13:21:15', '2026-05-06 13:21:15', NULL, '661729cdc568efa42627ea20bfcbeddb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:29:05', '2026-05-06 13:29:05', '2026-05-06 13:29:05', NULL, 'adb576167325591e1ec5e095a38bc6cb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:38:38', '2026-05-06 13:38:38', '2026-05-06 13:38:38', NULL, '4062a1b9aed02d2749b8fd079b6cd6a9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:38:44', '2026-05-06 13:38:44', '2026-05-06 13:38:44', NULL, '668f49fb8c27e4acf51144970d8cccb5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:45:12', '2026-05-06 13:45:12', '2026-05-06 13:45:12', NULL, '28dcd4602bb47f5ca1a8be7ecffd2597', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'description', NULL, '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. +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'status', 'in_progress', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '02917e0e6ca4c8f8eb3b538204eb57aa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM454GCK8ENRKRA44NW', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '039b7201fc0509ddcf72562c806b3884', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ZXPWX71ND4C699C', 'status', 'ready', 'in_progress', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '658d4a394d80aefd6d5a15ce3822519d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM76XDA3SZMPWR8WQSC', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '6becbdcb77ef86721ef94293d2ed16ea', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50DVPFP4WY74RQBB8', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, 'a42f27bbf2598f666a6c2ad95cf6661c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM699HAWC76SHRA7AM4', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:34:52', '2026-05-03 20:34:52', '2026-05-03 20:34:52', NULL, '76445d768daf65ad8296a46999029825', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QHMB46NSHHPVQRF0', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:36:31', '2026-05-03 20:36:31', '2026-05-03 20:36:31', NULL, '2e1487006e58e68e347ae79c44519627', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:37:03', '2026-05-03 20:37:03', '2026-05-03 20:37:03', NULL, '96a6e379fc5131074a63866cb3d68492', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:37:30', '2026-05-03 20:37:30', '2026-05-03 20:37:30', NULL, '8dac8582d495186f58a917d82f7ab995', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4M63G18VAQFG6R3XW', 'status', 'in_progress', 'done', NULL, '2026-05-03 20:40:22', '2026-05-03 20:40:22', '2026-05-03 20:40:22', NULL, 'a89a53603b405786bb739e70faee681b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ZXPWX71ND4C699C', 'status', 'in_progress', 'done', NULL, '2026-05-05 12:51:24', '2026-05-05 12:51:24', '2026-05-05 12:51:24', NULL, 'dacf0cf76fd2b154dfb36ea00f49b288', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JZ2AKN74FKMK3MR8', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '0014f6d2cc90977a24b557f274506237', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM606BMYDDGRSJBVPG8', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '5b1e82e91efc383f5e149843f14d00f8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5A8DYZ3BCJN1DEY7C', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'a364c63909ac701b73a2de919b2e9c53', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YBQP75NQQ4Y9DN9C', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'dfeb44e14ebd3a3478985d73befbe03e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7CEP425DNV3W7RFPM', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'e66ba5d1b061fd2924520f4fc928b348', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7CEP425DNV3W7RFPM', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '393936ee40363d6b104b9006f2b5668a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YBQP75NQQ4Y9DN9C', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '462dd9984e750731c88f66a75e443969', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JZ2AKN74FKMK3MR8', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '65ef25bc232d2130f5770bedf6d044e2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5A8DYZ3BCJN1DEY7C', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '6da4739e580812a83c3e56213ea4248e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM606BMYDDGRSJBVPG8', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '88aa1ab0a3b38341506a3309447e45ac', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'ready', 'in_progress', NULL, '2026-05-05 12:54:39', '2026-05-05 12:54:39', '2026-05-05 12:54:39', NULL, '24209ddce347092027ae768d6a3375d8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'status', 'in_progress', 'done', NULL, '2026-05-05 12:59:05', '2026-05-05 12:59:05', '2026-05-05 12:59:05', NULL, 'a5fdd6bb7200ed1d2ddcfe310daee584', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70QNVQY1MQGP3A33G', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 12:59:50', '2026-05-05 12:59:50', '2026-05-05 12:59:50', NULL, 'b91a3d24417048d20d0cfa4a31fc2c38', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70QNVQY1MQGP3A33G', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:02:34', '2026-05-05 13:02:34', '2026-05-05 13:02:34', NULL, 'a0f2ba7633a99aabe07b3b87af31d11c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q4XW93E3WA1SD9KW', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:02:40', '2026-05-05 13:02:40', '2026-05-05 13:02:40', NULL, 'a3eb5620a59f4873c9a987d5c28e1694', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q4XW93E3WA1SD9KW', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:07:29', '2026-05-05 13:07:29', '2026-05-05 13:07:29', NULL, '2e577e5dffb78585fc27823d5567d47e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AVABFH067XGTTCVM', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:07:34', '2026-05-05 13:07:34', '2026-05-05 13:07:34', NULL, '47ef1c908c2c78d605cbf2ec068a5b71', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AVABFH067XGTTCVM', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:10:50', '2026-05-05 13:10:50', '2026-05-05 13:10:50', NULL, 'bb5b64c15995499da1360dbca8fa03c4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM460GRBMZMV8E5339M', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:10:57', '2026-05-05 13:10:57', '2026-05-05 13:10:57', NULL, 'eb6423a0d02d9d772bfa0391d8d4ab21', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM460GRBMZMV8E5339M', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:12:20', '2026-05-05 13:12:20', '2026-05-05 13:12:20', NULL, '55a991855c7562f5cc3c8e4807da3487', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM57AEVK5E0GWVPGAY8', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 07:45:02', '2026-05-06 07:45:02', '2026-05-06 07:45:02', NULL, '9ab5fb7a217f619165c764caf7a2d563', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM57AEVK5E0GWVPGAY8', 'status', 'in_progress', 'done', NULL, '2026-05-06 07:56:10', '2026-05-06 07:56:10', '2026-05-06 07:56:10', NULL, '4db6826baa6000da1b109e236bc6a746', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM699HAWC76SHRA7AM4', 'status', 'ready', 'done', NULL, '2026-05-06 09:17:22', '2026-05-06 09:17:22', '2026-05-06 09:17:22', NULL, '8e84943b90dfcf422b8b696aaf754b7e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5X2K66A0MQY2VZ7H4', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 09:17:50', '2026-05-06 09:17:50', '2026-05-06 09:17:50', NULL, 'e2a8410055d41283fd5232df1c05f63c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5X2K66A0MQY2VZ7H4', 'status', 'in_progress', 'done', NULL, '2026-05-06 10:12:48', '2026-05-06 10:12:48', '2026-05-06 10:12:48', NULL, 'acafcb044df11c62f3e27c08ab15d93e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PMYC2E1X99HX4MDM', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 10:17:57', '2026-05-06 10:17:57', '2026-05-06 10:17:57', NULL, '85e9ac6a11a1ef2bfea49c7292309612', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PMYC2E1X99HX4MDM', 'status', 'in_progress', 'done', NULL, '2026-05-06 12:17:27', '2026-05-06 12:17:27', '2026-05-06 12:17:27', NULL, 'eeac3c68cfb77c92a6c0fade30087fa6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QHMB46NSHHPVQRF0', 'status', 'ready', 'in_progress', NULL, '2026-05-06 12:20:58', '2026-05-06 12:20:58', '2026-05-06 12:20:58', NULL, '7a7ea8be56b1fe029c6c4e1c33c8328f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QHMB46NSHHPVQRF0', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:21:15', '2026-05-06 13:21:15', '2026-05-06 13:21:15', NULL, '09a9059b8ec199dcbe6d39132362bd3c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42RQZVEWTF9CJSRF8', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:29:05', '2026-05-06 13:29:05', '2026-05-06 13:29:05', NULL, 'e642640e0bf97126b08be745559a17d4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42RQZVEWTF9CJSRF8', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:38:38', '2026-05-06 13:38:38', '2026-05-06 13:38:38', NULL, 'ce4ded2530174c56ad7a53eed3d6cdcd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SS2V5Z1M1XJGQX0M', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:38:44', '2026-05-06 13:38:44', '2026-05-06 13:38:44', NULL, 'ea560b783e9b4814637be5d21c5368e3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SS2V5Z1M1XJGQX0M', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:45:12', '2026-05-06 13:45:12', '2026-05-06 13:45:12', NULL, '69961e06d5537aa49afee0aed2f1c1e4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM52JT96V5067SM0DMR', 'description', NULL, '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. Neither happens today. @@ -56,17 +56,17 @@ Neither happens today. - `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone. - Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner). -**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.', NULL, '2026-05-06 13:46:26', '2026-05-06 13:46:26', '2026-05-06 13:46:26', NULL, 'c7dbde5c6c6c89492665abdff0f59be4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:46:29', '2026-05-06 13:46:29', '2026-05-06 13:46:29', NULL, '9d047426e61ddd7d29072333da718c03', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'description', NULL, '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. +**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.', NULL, '2026-05-06 13:46:26', '2026-05-06 13:46:26', '2026-05-06 13:46:26', NULL, 'bbac5d78d4ea04aa6f5b35b10f67c064', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM52JT96V5067SM0DMR', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:46:29', '2026-05-06 13:46:29', '2026-05-06 13:46:29', NULL, '51422cc20811d6f13dac599c515db1b6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'description', NULL, '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. **Acceptance:** - New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact. - Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run. - 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). -**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).', NULL, '2026-05-06 15:08:22', '2026-05-06 15:08:22', '2026-05-06 15:08:22', NULL, '396d0d316f7ee331776d4547f9b54b9a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-7', 'description', NULL, '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. +**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).', NULL, '2026-05-06 15:08:22', '2026-05-06 15:08:22', '2026-05-06 15:08:22', NULL, '420dc911b12ade6206b4de87137a7d03', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7162Z26FDWXG35VH0', 'description', NULL, '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. **Canvas pane (`builtin.canvas`):** - Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. @@ -80,8 +80,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Filter pane: tag include/exclude, file glob, depth-from-active. - pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. -Both 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).', NULL, '2026-05-06 15:08:37', '2026-05-06 15:08:37', '2026-05-06 15:08:37', NULL, '1a91dfb3b80a90e5dd6d84b3df9afef8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-8', 'description', NULL, 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype. +Both 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).', NULL, '2026-05-06 15:08:37', '2026-05-06 15:08:37', '2026-05-06 15:08:37', NULL, '2ba061f563ee7be9f86ecc77b05cacd2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6T6580D8ABDVTMNZW', 'description', NULL, 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype. **Extension API (third-party Lua):** - The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc. @@ -104,8 +104,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Self-update mechanism — see T-47. - License manifest auto-regen as part of the release build. -Big epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.', NULL, '2026-05-06 15:08:53', '2026-05-06 15:08:53', '2026-05-06 15:08:53', NULL, '55bf0c36dc75468fa6a1d3caf532c2e9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'description', NULL, '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. +Big epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.', NULL, '2026-05-06 15:08:53', '2026-05-06 15:08:53', '2026-05-06 15:08:53', NULL, 'effacc5046f69b59acb91984e957b1e6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', 'description', NULL, '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. **Acceptance:** - `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. @@ -116,8 +116,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, **Implementation hints:** - Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). - Keybinding goes in `lib/kernel/src/commands/keybindings.dart` per the D-54 keymap. -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', NULL, '2026-05-06 15:09:05', '2026-05-06 15:09:05', '2026-05-06 15:09:05', NULL, '352e6c0b2af96f63924ad1d1ef4c80f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'description', NULL, '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. +- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', NULL, '2026-05-06 15:09:05', '2026-05-06 15:09:05', '2026-05-06 15:09:05', NULL, '7509b32b25ea52de36c4cc9fc85cf669', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AACHWPFWZDCJ3CPW', 'description', NULL, '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. **Acceptance:** - 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). @@ -129,8 +129,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Switch to a variable JetBrainsMono and request a specific weight axis position. - Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). -Link-back: T-70 is the workaround; this is the proper fix.', NULL, '2026-05-06 15:09:20', '2026-05-06 15:09:20', '2026-05-06 15:09:20', NULL, '780a2a4a39f15c3ba81648ceb6fdba37', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'description', NULL, '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. +Link-back: T-70 is the workaround; this is the proper fix.', NULL, '2026-05-06 15:09:20', '2026-05-06 15:09:20', '2026-05-06 15:09:20', NULL, '57f6d934e3c5aee37f5c56a5bab00761', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53N56Z60B4SQ5VTRG', 'description', NULL, '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. **Acceptance:** - 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`). @@ -138,8 +138,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode. - The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed. -**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.', NULL, '2026-05-06 15:09:36', '2026-05-06 15:09:36', '2026-05-06 15:09:36', NULL, '31108dcc7844bf74515dfc1cda0993e9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-80', 'description', NULL, '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: +**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.', NULL, '2026-05-06 15:09:36', '2026-05-06 15:09:36', '2026-05-06 15:09:36', NULL, 'f8124e35b0a14cf41b672153376d2cbe', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM734YZ060Q63H40EYG', 'description', NULL, '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: **Errno constants (audit item #23):** - 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`. @@ -152,8 +152,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Pick one logger interface (kernel `log` already exists for the app side), wire `DaemonServer` and the daemon-side handlers to use it. - Dispatch error messages should prefix with the request `cmd` so log correlation works (audit item #26). -**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.', NULL, '2026-05-06 15:09:50', '2026-05-06 15:09:50', '2026-05-06 15:09:50', NULL, 'e4255ebfc7f9bd8537ced342f2925d77', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-81', 'description', NULL, '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: +**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.', NULL, '2026-05-06 15:09:50', '2026-05-06 15:09:50', '2026-05-06 15:09:50', NULL, 'de933c124694c5f930db21b2e26a6e62', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79CNBXJ2S3CFQR7VM', 'description', NULL, '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: - **#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`. - **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed. @@ -164,8 +164,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade. - **#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. -Land each as a small focused commit; ticket closes when all items above are merged.', NULL, '2026-05-06 15:10:09', '2026-05-06 15:10:09', '2026-05-06 15:10:09', NULL, 'ad1a34f56d30171570970965b5ef3560', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'description', NULL, 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). +Land each as a small focused commit; ticket closes when all items above are merged.', NULL, '2026-05-06 15:10:09', '2026-05-06 15:10:09', '2026-05-06 15:10:09', NULL, '7f1e94c9c7571cb7e9478564161f0174', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JR6QZDMKE167C4MR', 'description', NULL, 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). **Acceptance:** - 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). @@ -176,8 +176,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, **Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. -**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.', NULL, '2026-05-06 15:10:22', '2026-05-06 15:10:22', '2026-05-06 15:10:22', NULL, '0a750764071d26afaa1821ca9ee2f3bf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-88', 'description', NULL, '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. +**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.', NULL, '2026-05-06 15:10:22', '2026-05-06 15:10:22', '2026-05-06 15:10:22', NULL, '66ba056fd649c7835e615908e82317a6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7Y0DFP66HSP073XHC', 'description', NULL, '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. **Acceptance:** - A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases. @@ -186,14 +186,14 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, **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). -**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.', NULL, '2026-05-06 15:10:35', '2026-05-06 15:10:35', '2026-05-06 15:10:35', NULL, '49050e00e3787be467be278d157bb69e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '0d9f946e59752f061cc431121ac0f708', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'in_progress', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '4a53482a4ec3d3938e8b0bdfd0d9f4d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'backlog', 'ready', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, 'e4e9ad42effcd95445b086c527f3471c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:45:51', '2026-05-06 15:45:51', '2026-05-06 15:45:51', NULL, '0b148a1ff820009a9e2f533ee6fef7f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-05-06 15:51:26', '2026-05-06 15:51:26', '2026-05-06 15:51:26', NULL, '79dc3457b9819570fbd35d7cffb80e0d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:58:30', '2026-05-06 15:58:30', '2026-05-06 15:58:30', NULL, '78f6f98d8e4b94f6f0fd49f324ee057a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', '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. +**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.', NULL, '2026-05-06 15:10:35', '2026-05-06 15:10:35', '2026-05-06 15:10:35', NULL, 'ac492374b49e5dc487283d30efd29458', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'ready', 'in_progress', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '30cb80cbfa811b341865cc9cbd338732', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM52JT96V5067SM0DMR', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '48c7f62d16fa0aa5a5aa659803cd91b7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AACHWPFWZDCJ3CPW', 'status', 'backlog', 'ready', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '4db65223e727d569356388b2742d8f07', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:45:51', '2026-05-06 15:45:51', '2026-05-06 15:45:51', NULL, 'f74465a1b685dbb85c72f9cd1eb1cb9b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'done', 'in_progress', NULL, '2026-05-06 15:51:26', '2026-05-06 15:51:26', '2026-05-06 15:51:26', NULL, '5963ab059a08331140639d44f7eaf50d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:58:30', '2026-05-06 15:58:30', '2026-05-06 15:58:30', NULL, 'e5c2ea95a29c0b06dd61a008700ec6f9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AACHWPFWZDCJ3CPW', '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. **Acceptance:** - 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). @@ -228,11 +228,11 @@ Investigation showed the perceived over-bolding in the Claude pane was synthetic 3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195. 4. 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). -Workaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.', NULL, '2026-05-06 16:13:35', '2026-05-06 16:13:35', '2026-05-06 16:13:35', NULL, 'baa15a2631bef74a4c1f732cee12de1c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'ready', 'in_progress', NULL, '2026-05-06 16:13:39', '2026-05-06 16:13:39', '2026-05-06 16:13:39', NULL, '8e3c8ef2cfa48a2805eef455ec8f5830', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 16:13:39', '2026-05-06 16:13:39', '2026-05-06 16:13:39', NULL, 'eb816113ca1ef0ef3514af5f8d3391d3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'in_progress', 'done', NULL, '2026-05-06 20:29:28', '2026-05-06 20:29:28', '2026-05-06 20:29:28', NULL, 'ba203ed16c6e042457e8e751bf56932a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'description', NULL, 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: +Workaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.', NULL, '2026-05-06 16:13:35', '2026-05-06 16:13:35', '2026-05-06 16:13:35', NULL, '91bc31300929a516e85b361e7f09fdae', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53N56Z60B4SQ5VTRG', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 16:13:39', '2026-05-06 16:13:39', '2026-05-06 16:13:39', NULL, '8142b38bb00ee8ea2c8331f89ba3226e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AACHWPFWZDCJ3CPW', 'status', 'ready', 'in_progress', NULL, '2026-05-06 16:13:39', '2026-05-06 16:13:39', '2026-05-06 16:13:39', NULL, 'fee75df87e18a232dc2b0e749ae2aa8e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AACHWPFWZDCJ3CPW', 'status', 'in_progress', 'done', NULL, '2026-05-06 20:29:28', '2026-05-06 20:29:28', '2026-05-06 20:29:28', NULL, '5e844a58752ee763cdd245e96aa0fc55', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TNXTJCQBH9VADC4M', 'description', NULL, 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: | area | hit / total | % | |---|---|---| @@ -277,9 +277,9 @@ The gate plumbing lands first so future test additions are visible as floor bump - `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. **Cross-references:** D-66 (this epic''s decision record), D-29 (pre-push gate scope), "Own the rendering stack" guardrail (CLAUDE.md). -', NULL, '2026-05-06 20:37:40', '2026-05-06 20:37:40', '2026-05-06 20:37:40', NULL, 'c5f416e95632e206449476de472fc4e5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'parent_id', NULL, 'T-89', NULL, '2026-05-06 20:38:24', '2026-05-06 20:38:24', '2026-05-06 20:38:24', NULL, '11840a870c33e5e94b6adcd3cfb5f10a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'description', NULL, '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. +', NULL, '2026-05-06 20:37:40', '2026-05-06 20:37:40', '2026-05-06 20:37:40', NULL, '04bad642c368a4dfa9aa9f8fa95a83f4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74S58G8XZV1766VD0', 'parent_id', NULL, 'T-89', NULL, '2026-05-06 20:38:24', '2026-05-06 20:38:24', '2026-05-06 20:38:24', NULL, '4248a44b8fc321273006c9377f6a9f76', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74S58G8XZV1766VD0', 'description', NULL, '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. **What lands:** @@ -305,16 +305,16 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - `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. - `coverage/lcov.info` is the standard Dart output path of `flutter test --coverage`. Don''t relocate. - `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). -', NULL, '2026-05-06 20:39:05', '2026-05-06 20:39:05', '2026-05-06 20:39:05', NULL, 'a7af4c58b2e4ccbc80d128d6ba64ba0a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, '74247471621e8f69e888066727cffcda', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, 'a81a6e3ef23af1c113f46e467fdde278', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'in_progress', 'ready', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, 'e2692b0d73a61655c310846dda2b2ae2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'ready', 'ready', NULL, '2026-05-06 20:40:13', '2026-05-06 20:40:13', '2026-05-06 20:40:13', NULL, 'cabddbfab05fa8c3e3705311f895d619', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'in_progress', 'in_progress', NULL, '2026-05-06 20:40:16', '2026-05-06 20:40:16', '2026-05-06 20:40:16', NULL, '02f69af134363ce27d815ad3081379f3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'status', 'in_progress', 'in_progress', NULL, '2026-05-06 20:40:16', '2026-05-06 20:40:16', '2026-05-06 20:40:16', NULL, '9695c13174226c39e5bf059d88d8e855', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'in_progress', 'done', NULL, '2026-05-06 20:44:47', '2026-05-06 20:44:47', '2026-05-06 20:44:47', NULL, '8d3f9f4effc90eacfcefd2701a33ede6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'parent_id', NULL, 'T-89', NULL, '2026-05-06 20:49:47', '2026-05-06 20:49:47', '2026-05-06 20:49:47', NULL, 'baf63005fb488a699872715061d2473c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'description', NULL, 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. +', NULL, '2026-05-06 20:39:05', '2026-05-06 20:39:05', '2026-05-06 20:39:05', NULL, '7dd713f72f94b7dc6f7187b225d538cc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53N56Z60B4SQ5VTRG', 'status', 'in_progress', 'ready', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, '007b4bf10acae9bb2b8bb3246f81a216', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TNXTJCQBH9VADC4M', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, '9c6d73dea93267d7a1af1155d996d352', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74S58G8XZV1766VD0', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, 'c657c444cb54d73c12e7b935dfdec2c7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53N56Z60B4SQ5VTRG', 'status', 'ready', 'ready', NULL, '2026-05-06 20:40:13', '2026-05-06 20:40:13', '2026-05-06 20:40:13', NULL, '4f3da986a0dc5d3c194d88c864de2410', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TNXTJCQBH9VADC4M', 'status', 'in_progress', 'in_progress', NULL, '2026-05-06 20:40:16', '2026-05-06 20:40:16', '2026-05-06 20:40:16', NULL, 'a2d7327189ada2bf9c8170b18e527353', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74S58G8XZV1766VD0', 'status', 'in_progress', 'in_progress', NULL, '2026-05-06 20:40:16', '2026-05-06 20:40:16', '2026-05-06 20:40:16', NULL, 'ba8f37f9c4e9ba0c8e7af385e51d0608', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74S58G8XZV1766VD0', 'status', 'in_progress', 'done', NULL, '2026-05-06 20:44:47', '2026-05-06 20:44:47', '2026-05-06 20:44:47', NULL, '86c5f08b28e712b1a97450aa69aaaf33', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4HGK6KR0314P2M32M', 'parent_id', NULL, 'T-89', NULL, '2026-05-06 20:49:47', '2026-05-06 20:49:47', '2026-05-06 20:49:47', NULL, 'd557ad339e5050661b1b895084e6311b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4HGK6KR0314P2M32M', 'description', NULL, 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. **Today''s distribution within `lib/src/terminal/src/`:** @@ -358,9 +358,9 @@ Land tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/ - Mouse / keyboard *protocol* round-trips against real TUIs (that''s T-74 and integration work). **Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing). -', NULL, '2026-05-06 20:50:29', '2026-05-06 20:50:29', '2026-05-06 20:50:29', NULL, 'a165eb2d32141e72feb1ffdf9d98d58e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:50:32', '2026-05-06 20:50:32', '2026-05-06 20:50:32', NULL, 'da5f03732cdcd40106cf47c852191706', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'description', NULL, '**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`). +', NULL, '2026-05-06 20:50:29', '2026-05-06 20:50:29', '2026-05-06 20:50:29', NULL, '7714ce2fb36991ae675e87f8e9302178', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4HGK6KR0314P2M32M', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:50:32', '2026-05-06 20:50:32', '2026-05-06 20:50:32', NULL, 'a0d7d9f121733b789ae09ea6f67daff1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5EP8B65T0AR52TQFW', 'description', NULL, '**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`). **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. @@ -392,10 +392,10 @@ Don''t pick one without re-reading the full reflow flow + every caller of `reflo **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. **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. -', NULL, '2026-05-07 05:52:26', '2026-05-07 05:52:26', '2026-05-07 05:52:26', NULL, '1e8625251297bbad6ee96d80b05b606b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'status', 'backlog', 'in_progress', NULL, '2026-05-07 05:56:47', '2026-05-07 05:56:47', '2026-05-07 05:56:47', NULL, '3fd3c03fde722d5d5c30813b2ce42822', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'status', 'in_progress', 'done', NULL, '2026-05-07 05:58:51', '2026-05-07 05:58:51', '2026-05-07 05:58:51', NULL, '4ffd69490cbda8da72a60b642182213a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'description', NULL, '**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`. +', NULL, '2026-05-07 05:52:26', '2026-05-07 05:52:26', '2026-05-07 05:52:26', NULL, '67aca369f92a8881ade0d950df9b1f17', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5EP8B65T0AR52TQFW', 'status', 'backlog', 'in_progress', NULL, '2026-05-07 05:56:47', '2026-05-07 05:56:47', '2026-05-07 05:56:47', NULL, '4e2c7022fcfdf3cdd945ae3b97257961', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5EP8B65T0AR52TQFW', 'status', 'in_progress', 'done', NULL, '2026-05-07 05:58:51', '2026-05-07 05:58:51', '2026-05-07 05:58:51', NULL, 'f16a213febb18f72a9d4c62b68e2a283', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77477EN2R5872G95M', 'description', NULL, '**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`. **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: @@ -428,8 +428,8 @@ Net result: the public `TerminalView.onTapUp` API is silently a no-op. Any calle **Surfaced from:** T-91 test sweep on `terminal_view.dart`. Documented in the test file as "skipped — see T-93". **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. -', NULL, '2026-05-07 06:34:22', '2026-05-07 06:34:22', '2026-05-07 06:34:22', NULL, 'e549f5ed0e98298ba308aa75a3f674c5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'description', '**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`. +', NULL, '2026-05-07 06:34:22', '2026-05-07 06:34:22', '2026-05-07 06:34:22', NULL, 'edfdd6d82d57c75cae23e1317fb66713', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77477EN2R5872G95M', 'description', '**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`. **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: @@ -498,11 +498,11 @@ Recommendation: **option B**. The "remove unused public API" pattern matches the **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. **Cross-references:** T-91 (epic parent), T-89 (coverage epic). -', NULL, '2026-05-07 06:35:32', '2026-05-07 06:35:32', '2026-05-07 06:35:32', NULL, '2f51a45e06d526d9b47dbc617ea965f8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'status', 'backlog', 'in_progress', NULL, '2026-05-07 06:45:24', '2026-05-07 06:45:24', '2026-05-07 06:45:24', NULL, '82fb868231b1806c9a1cd802110856f7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'status', 'in_progress', 'done', NULL, '2026-05-07 19:04:55', '2026-05-07 19:04:55', '2026-05-07 19:04:55', NULL, 'bc269d5f335553e768041e72019dc3b0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-94', 'status', 'backlog', 'cancelled', NULL, '2026-05-07 19:08:52', '2026-05-07 19:08:52', '2026-05-07 19:08:52', NULL, '5f534e88c93e7eef4ad838692ee7b725', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-95', 'description', NULL, '**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). +', NULL, '2026-05-07 06:35:32', '2026-05-07 06:35:32', '2026-05-07 06:35:32', NULL, '0322c0a437a9cda8b90df7122faaaa32', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77477EN2R5872G95M', 'status', 'backlog', 'in_progress', NULL, '2026-05-07 06:45:24', '2026-05-07 06:45:24', '2026-05-07 06:45:24', NULL, '4834a8db61652c266dc97e6aa7f17d67', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77477EN2R5872G95M', 'status', 'in_progress', 'done', NULL, '2026-05-07 19:04:55', '2026-05-07 19:04:55', '2026-05-07 19:04:55', NULL, '0aa5e5e62eafb7d886bf7b42ec8479a4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48X0763Z9DZSM0HFR', 'status', 'backlog', 'cancelled', NULL, '2026-05-07 19:08:52', '2026-05-07 19:08:52', '2026-05-07 19:08:52', NULL, '9c5c4759adb93fe27b6f1030daaeef70', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TDSA2PDCV814E3YW', 'description', NULL, '**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). **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. @@ -552,1118 +552,8 @@ The `PanGestureRecognizer` in `TerminalGestureDetector` is registered with `supp **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. **Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic). -', NULL, '2026-05-08 11:01:01', '2026-05-08 11:01:01', '2026-05-08 11:01:01', NULL, '8088d054dd8c7946075ed6325724dfb3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-62', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '43c1103bad9839fbd2c8058f147cfcba', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-61', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '65fbf26149226f3fc9c06a446bfa9f41', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-49', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '98b648e7a4a82730e43677c7320c8b0d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, 'c8b03bf5dadfdc5cb22bc5961919dc23', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'ready', 'in_progress', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, 'fdc5453ac922809e479b6b3418ed7755', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:34:52', '2026-05-03 20:34:52', '2026-05-03 20:34:52', NULL, '6da16f33a6aa1e1db733a0a02900e0c1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:36:31', '2026-05-03 20:36:31', '2026-05-03 20:36:31', NULL, 'd15ac0c4b6c7b6057243edcfbf7086f1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:37:03', '2026-05-03 20:37:03', '2026-05-03 20:37:03', NULL, '3f6f22aa9e420ec2fc869f2530df4c46', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:37:30', '2026-05-03 20:37:30', '2026-05-03 20:37:30', NULL, 'e6f3d8bda62656d17b174c8b9933dabc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'status', 'in_progress', 'done', NULL, '2026-05-03 20:40:22', '2026-05-03 20:40:22', '2026-05-03 20:40:22', NULL, '9ace75c4346f321670f7024eebfa0fb9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'in_progress', 'done', NULL, '2026-05-05 12:51:24', '2026-05-05 12:51:24', '2026-05-05 12:51:24', NULL, 'adedfd3ad28b56cf4e57e20bce3ec497', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '60751203904229cc5846ea313f747846', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '618b3042be98eecae2168446ffd2bfa6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '65e4ebb4c60a0feb7c2dec82f0c3faa1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'a3ac83b6f85fbcf6b44a4b0c28c79803', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'b427c00df34390343af95fcd285a09bd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '48865da069da205e0cf7ae7dd8caf27d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '680e39c278e717327dc2557e625abc1b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '8f1ea30ab9663ac07c04fe404937a9fd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '9284c52dba3b76b117e8d3a97420dd6a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, 'e5c9d41027d52b8fbe7edcc684130c2e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'in_progress', NULL, '2026-05-05 12:54:39', '2026-05-05 12:54:39', '2026-05-05 12:54:39', NULL, 'd8d87b2a3ea7073edde2157f6c90791e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'in_progress', 'done', NULL, '2026-05-05 12:59:05', '2026-05-05 12:59:05', '2026-05-05 12:59:05', NULL, '3ddd60cd6e2f138e184b3e4e8f892a21', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 12:59:50', '2026-05-05 12:59:50', '2026-05-05 12:59:50', NULL, 'a1e1b2c66830239411cac289bcb13050', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:02:34', '2026-05-05 13:02:34', '2026-05-05 13:02:34', NULL, 'ffe86cc4530e722c0622fbd4095d0f08', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:02:40', '2026-05-05 13:02:40', '2026-05-05 13:02:40', NULL, '3d48174c87be388f391611a4c9ebc4b0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:07:29', '2026-05-05 13:07:29', '2026-05-05 13:07:29', NULL, '579e4939f630479b5987b47de2106ca9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:07:34', '2026-05-05 13:07:34', '2026-05-05 13:07:34', NULL, 'd56b93ae20653ab26c0a99ab7c28fa69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:10:50', '2026-05-05 13:10:50', '2026-05-05 13:10:50', NULL, '5c98774f501f1a6c7bef328c3cd8c5b1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:10:57', '2026-05-05 13:10:57', '2026-05-05 13:10:57', NULL, '8558d256a078bda0713c7045d7f1c2bb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:12:20', '2026-05-05 13:12:20', '2026-05-05 13:12:20', NULL, 'aabaf34b3e5982ab7be5c07051e2c1e7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 07:45:02', '2026-05-06 07:45:02', '2026-05-06 07:45:02', NULL, 'e5e0baef07bf6908200527c340130a2e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'status', 'in_progress', 'done', NULL, '2026-05-06 07:56:10', '2026-05-06 07:56:10', '2026-05-06 07:56:10', NULL, '3069b52fc53518956fc0dc7792aa2e9c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'status', 'ready', 'done', NULL, '2026-05-06 09:17:22', '2026-05-06 09:17:22', '2026-05-06 09:17:22', NULL, '56b24bfcf31543cbde7e5c1af8055989', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 09:17:50', '2026-05-06 09:17:50', '2026-05-06 09:17:50', NULL, 'cb9fc3d3f6cc5858ce9af1f377257b26', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'status', 'in_progress', 'done', NULL, '2026-05-06 10:12:48', '2026-05-06 10:12:48', '2026-05-06 10:12:48', NULL, '133758a6509278aaada7d8f03ac72ae4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 10:17:57', '2026-05-06 10:17:57', '2026-05-06 10:17:57', NULL, '8991e0669094e0969787881506f373dc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'status', 'in_progress', 'done', NULL, '2026-05-06 12:17:27', '2026-05-06 12:17:27', '2026-05-06 12:17:27', NULL, '8dd4f7dba893357485c6a8293e49765e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'ready', 'in_progress', NULL, '2026-05-06 12:20:58', '2026-05-06 12:20:58', '2026-05-06 12:20:58', NULL, 'e9f7ebbd02eb9bf7f3faf10dca1cf8e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:21:15', '2026-05-06 13:21:15', '2026-05-06 13:21:15', NULL, '661729cdc568efa42627ea20bfcbeddb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:29:05', '2026-05-06 13:29:05', '2026-05-06 13:29:05', NULL, 'adb576167325591e1ec5e095a38bc6cb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:38:38', '2026-05-06 13:38:38', '2026-05-06 13:38:38', NULL, '4062a1b9aed02d2749b8fd079b6cd6a9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:38:44', '2026-05-06 13:38:44', '2026-05-06 13:38:44', NULL, '668f49fb8c27e4acf51144970d8cccb5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:45:12', '2026-05-06 13:45:12', '2026-05-06 13:45:12', NULL, '28dcd4602bb47f5ca1a8be7ecffd2597', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'description', NULL, '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. - -Neither happens today. - -**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`). - -**Gap 2 — app shutdown leaks secondaries.** Nothing reaps `clide-claude--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. - -**Acceptance:** -- Closing a secondary tab in the Claude pane runs `tmux -L clide kill-session -t ` for that secondary''s session name (use existing `secondarySessionName(repoRoot, n)`). -- App shutdown enumerates `clide-claude--*` sessions on the clide socket and kills the non-primary ones. Primary is left detached (D-41 explicit). -- `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone. -- Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner). - -**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.', NULL, '2026-05-06 13:46:26', '2026-05-06 13:46:26', '2026-05-06 13:46:26', NULL, 'c7dbde5c6c6c89492665abdff0f59be4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:46:29', '2026-05-06 13:46:29', '2026-05-06 13:46:29', NULL, '9d047426e61ddd7d29072333da718c03', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'description', NULL, '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. - -**Acceptance:** -- New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact. -- Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run. -- 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). - -**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).', NULL, '2026-05-06 15:08:22', '2026-05-06 15:08:22', '2026-05-06 15:08:22', NULL, '396d0d316f7ee331776d4547f9b54b9a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-7', 'description', NULL, '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. - -**Canvas pane (`builtin.canvas`):** -- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. -- Pan/zoom via `InteractiveViewer`; node selection, drag, resize. -- Edit affordances: add note from file picker, add text node, draw edge between nodes. -- Persist layout back to the `.canvas` file on disk. - -**Graph view (`builtin.graph`):** -- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks. -- Hover highlights connected subgraph; click opens the note in the editor. -- Filter pane: tag include/exclude, file glob, depth-from-active. -- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. - -Both 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).', NULL, '2026-05-06 15:08:37', '2026-05-06 15:08:37', '2026-05-06 15:08:37', NULL, '1a91dfb3b80a90e5dd6d84b3df9afef8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-8', 'description', NULL, 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype. - -**Extension API (third-party Lua):** -- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc. -- Manifest schema, capability gating, sandboxed FS/IPC access. -- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15). -- Marketplace / distribution story is OUT OF SCOPE for Tier 6 — local-install only. - -**Settings UI (`builtin.settings-ui`):** -- Schema-driven settings panel reading from the kernel SettingsStore. -- Render strategy: form fields keyed off the schema each subsystem registers. -- Edits write back to `.clide/settings.yaml`. - -**Theming UI (`builtin.theme-picker` extends):** -- Live preview of the four bundled themes (D-44). -- Custom theme: import YAML, validate against schema, register at runtime. -- Per-component override surface (long horizon). - -**Distributable builds:** -- AppImage / Flatpak for Linux, .dmg for macOS — see T-46. -- Self-update mechanism — see T-47. -- License manifest auto-regen as part of the release build. - -Big epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.', NULL, '2026-05-06 15:08:53', '2026-05-06 15:08:53', '2026-05-06 15:08:53', NULL, '55bf0c36dc75468fa6a1d3caf532c2e9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'description', NULL, '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. - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Keybinding goes in `lib/kernel/src/commands/keybindings.dart` per the D-54 keymap. -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', NULL, '2026-05-06 15:09:05', '2026-05-06 15:09:05', '2026-05-06 15:09:05', NULL, '352e6c0b2af96f63924ad1d1ef4c80f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'description', NULL, '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - -Link-back: T-70 is the workaround; this is the proper fix.', NULL, '2026-05-06 15:09:20', '2026-05-06 15:09:20', '2026-05-06 15:09:20', NULL, '780a2a4a39f15c3ba81648ceb6fdba37', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'description', NULL, '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. - -**Acceptance:** -- 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`). -- Tested with `vim` (mouse mode `a`), `htop`, `less` — all should respond to mouse selection / click correctly. -- Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode. -- The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed. - -**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.', NULL, '2026-05-06 15:09:36', '2026-05-06 15:09:36', '2026-05-06 15:09:36', NULL, '31108dcc7844bf74515dfc1cda0993e9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-80', 'description', NULL, '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: - -**Errno constants (audit item #23):** -- 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`. -- Centralize them in `lib/src/pty/errors.dart` or a sibling `posix.dart` as named constants. -- Existing `lib/src/ipc/errno_mapping.dart` already has a `PosixErrno` class — extend it or move to a shared location both layers import from. - -**Logger standardization (audit item #22, partial #26):** -- `lib/src/ipc/server.dart` uses `stderr.writeln(...)` directly; the rest of the daemon either uses no logger or a custom one. -- The Flutter-host process often consumes stderr, so log lines disappear silently. -- Pick one logger interface (kernel `log` already exists for the app side), wire `DaemonServer` and the daemon-side handlers to use it. -- Dispatch error messages should prefix with the request `cmd` so log correlation works (audit item #26). - -**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.', NULL, '2026-05-06 15:09:50', '2026-05-06 15:09:50', '2026-05-06 15:09:50', NULL, 'e4255ebfc7f9bd8537ced342f2925d77', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-81', 'description', NULL, '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: - -- **#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`. -- **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed. -- **#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. -- **#21** — `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd. -- **#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. -- **#27** — `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure. -- **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade. -- **#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. - -Land each as a small focused commit; ticket closes when all items above are merged.', NULL, '2026-05-06 15:10:09', '2026-05-06 15:10:09', '2026-05-06 15:10:09', NULL, 'ad1a34f56d30171570970965b5ef3560', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'description', NULL, 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). - -**Acceptance:** -- 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). -- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45). -- WCAG-AA contrast gate (D-22) passes against the theme. -- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution. -- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu. - -**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. - -**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.', NULL, '2026-05-06 15:10:22', '2026-05-06 15:10:22', '2026-05-06 15:10:22', NULL, '0a750764071d26afaa1821ca9ee2f3bf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-88', 'description', NULL, '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. - -**Acceptance:** -- A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases. -- When a new release lands with a CVE label or fixes a CVE-tagged git release, schedule a bump; otherwise track the version drift. -- 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/`. - -**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). - -**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.', NULL, '2026-05-06 15:10:35', '2026-05-06 15:10:35', '2026-05-06 15:10:35', NULL, '49050e00e3787be467be278d157bb69e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '0d9f946e59752f061cc431121ac0f708', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'in_progress', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '4a53482a4ec3d3938e8b0bdfd0d9f4d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'backlog', 'ready', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, 'e4e9ad42effcd95445b086c527f3471c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:45:51', '2026-05-06 15:45:51', '2026-05-06 15:45:51', NULL, '0b148a1ff820009a9e2f533ee6fef7f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-05-06 15:51:26', '2026-05-06 15:51:26', '2026-05-06 15:51:26', NULL, '79dc3457b9819570fbd35d7cffb80e0d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:58:30', '2026-05-06 15:58:30', '2026-05-06 15:58:30', NULL, '78f6f98d8e4b94f6f0fd49f324ee057a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - -Link-back: T-70 is the workaround; this is the proper fix.', '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - ---- -Refinement (2026-05-06): - -Investigation 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. - -**Concrete fix shape:** -1. `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/`. -2. `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). -3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195. -4. 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). - -Workaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.', NULL, '2026-05-06 16:13:35', '2026-05-06 16:13:35', '2026-05-06 16:13:35', NULL, 'baa15a2631bef74a4c1f732cee12de1c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'ready', 'in_progress', NULL, '2026-05-06 16:13:39', '2026-05-06 16:13:39', '2026-05-06 16:13:39', NULL, '8e3c8ef2cfa48a2805eef455ec8f5830', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 16:13:39', '2026-05-06 16:13:39', '2026-05-06 16:13:39', NULL, 'eb816113ca1ef0ef3514af5f8d3391d3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'in_progress', 'done', NULL, '2026-05-06 20:29:28', '2026-05-06 20:29:28', '2026-05-06 20:29:28', NULL, 'ba203ed16c6e042457e8e751bf56932a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'description', NULL, 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: - -| area | hit / total | % | -|---|---|---| -| `lib/src/terminal/` | 12 / 3167 | **0.4%** | -| `lib/widgets/` | 537 / 1184 | 45.4% | -| `lib/src/pql/` | 45 / 94 | 47.9% | -| `lib/builtin/` | 269 / 550 | 48.9% | -| `lib/kernel/` | 889 / 1691 | 52.6% | -| `lib/src/daemon/` | 320 / 584 | 54.8% | -| `lib/src/pty/` | 130 / 216 | 60.2% | -| `lib/src/git/` | 356 / 518 | 68.7% | -| `lib/src/ipc/` | 110 / 156 | 70.5% | -| `lib/src/files/` | 98 / 133 | 73.7% | -| `lib/extension/` | 41 / 54 | 75.9% | -| `lib/src/editor/` | 107 / 129 | 82.9% | -| `lib/src/panes/` | 70 / 73 | 95.9% | - -The 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. - -**End state:** -- Total line coverage ≥ 95% (no per-area carve-outs). -- Pre-push gate hard-fails on coverage drops via a committed floor (`coverage/floor.txt`); floor ratchets up only. -- Per D-66 the gate lands at the *current* floor in the first child ticket; subsequent floor bumps come with the test-writing children. - -**Acceptance criteria for closing this epic:** -1. `coverage/floor.txt` reads `95` (or higher). -2. `flutter test --coverage --exclude-tags forkpty` produces ≥95% line coverage. -3. The pre-push gate computes coverage and rejects pushes below the floor. -4. 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. - -**Strategy / child shape:** -The 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. - -1. **Gate plumbing** (first child) — install the ratchet, lock in current floor, expose `make coverage-gate` and wire it into `make push-check`. -2. **`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.). -3. **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. -4. **Final ratchet** — once total ≥ 95%, set floor to 95 and close the epic. - -**Out of scope:** -- Branch coverage (lcov data is weak for branch on Dart; line coverage is the contract). -- Integration / E2E coverage (only widget + unit feed lcov). -- `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. - -**Cross-references:** D-66 (this epic''s decision record), D-29 (pre-push gate scope), "Own the rendering stack" guardrail (CLAUDE.md). -', NULL, '2026-05-06 20:37:40', '2026-05-06 20:37:40', '2026-05-06 20:37:40', NULL, 'c5f416e95632e206449476de472fc4e5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'parent_id', NULL, 'T-89', NULL, '2026-05-06 20:38:24', '2026-05-06 20:38:24', '2026-05-06 20:38:24', NULL, '11840a870c33e5e94b6adcd3cfb5f10a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'description', NULL, '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. - -**What lands:** - -1. **`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). -2. **`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. -3. **`Makefile` `coverage-gate` target** — wraps `ci/coverage_gate.sh` for local use. -4. **`make push-check`** — adds `coverage-gate` to its dependency list. Pre-push hook (already wired via `.githooks/pre-push`) picks this up automatically. -5. **`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. - -**Acceptance:** -- `make coverage-gate` passes on a clean checkout (floor matches reality). -- `make push-check` includes the gate. -- A test deletion would fail `make coverage-gate` (verify by experiment, not just by reading the script — invariant has to actually hold). -- Pre-push hook runs the gate (verify by attempting a deliberately-failing push and seeing it blocked). -- `coverage/lcov.info` is gitignored (it''s regenerated on every run; only `floor.txt` is committed). - -**Out of scope:** -- Bumping the floor toward 95% — that''s the test-writing children''s job. -- Branch coverage — D-66 explicitly excludes. -- Per-area floors — D-66 chose a single global floor for simplicity. - -**Notes:** -- `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. -- `coverage/lcov.info` is the standard Dart output path of `flutter test --coverage`. Don''t relocate. -- `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). -', NULL, '2026-05-06 20:39:05', '2026-05-06 20:39:05', '2026-05-06 20:39:05', NULL, 'a7af4c58b2e4ccbc80d128d6ba64ba0a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, '74247471621e8f69e888066727cffcda', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, 'a81a6e3ef23af1c113f46e467fdde278', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'in_progress', 'ready', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, 'e2692b0d73a61655c310846dda2b2ae2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'ready', 'ready', NULL, '2026-05-06 20:40:13', '2026-05-06 20:40:13', '2026-05-06 20:40:13', NULL, 'cabddbfab05fa8c3e3705311f895d619', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'in_progress', 'in_progress', NULL, '2026-05-06 20:40:16', '2026-05-06 20:40:16', '2026-05-06 20:40:16', NULL, '02f69af134363ce27d815ad3081379f3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'status', 'in_progress', 'in_progress', NULL, '2026-05-06 20:40:16', '2026-05-06 20:40:16', '2026-05-06 20:40:16', NULL, '9695c13174226c39e5bf059d88d8e855', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'in_progress', 'done', NULL, '2026-05-06 20:44:47', '2026-05-06 20:44:47', '2026-05-06 20:44:47', NULL, '8d3f9f4effc90eacfcefd2701a33ede6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'parent_id', NULL, 'T-89', NULL, '2026-05-06 20:49:47', '2026-05-06 20:49:47', '2026-05-06 20:49:47', NULL, 'baf63005fb488a699872715061d2473c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'description', NULL, 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. - -**Today''s distribution within `lib/src/terminal/src/`:** - -| sub-area | hit / total | % | shape | -|---|---|---|---| -| `core/buffer/` | 0 / 581 | 0.0% | pure data structures | -| `core/escape/` | 1 / 473 | 0.2% | pure parser logic | -| `core/input/` | 4 / 292 | 1.4% | key + keytab tables | -| `core/(root)` | 0 / 188 | 0.0% | callback wiring | -| `core/mouse/` | 3 / 51 | 5.9% | encoder logic | -| `ui/` | 3 / 823 | 0.4% | painters + render objects | -| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` | -| `utils/` | 1 / 251 | 0.4% | circular buffer etc. | - -**Strategy — pure logic first, then UI:** - -1. **`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. -2. **`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. -3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode. -4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast. -5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests. -6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests. -7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive. - -**Acceptance:** -- `lib/src/terminal/` line coverage ≥ 95%. -- All tests fast (<5 s for the area''s test files combined; no real PTY shell-out). -- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files. -- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage. -- `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). - -**Strategy on commit cadence:** -Land tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch: -- Adds tests under `test/terminal//`. -- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows). -- Documents in CHANGELOG under "Changed" (test-only changes can also fold into one CHANGELOG entry per area). - -**Out of scope:** -- 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. -- Branch coverage (D-66 explicitly excludes). -- Mouse / keyboard *protocol* round-trips against real TUIs (that''s T-74 and integration work). - -**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing). -', NULL, '2026-05-06 20:50:29', '2026-05-06 20:50:29', '2026-05-06 20:50:29', NULL, 'a165eb2d32141e72feb1ffdf9d98d58e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:50:32', '2026-05-06 20:50:32', '2026-05-06 20:50:32', NULL, 'da5f03732cdcd40106cf47c852191706', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'description', NULL, '**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`). - -**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. - -**Trigger paths in production code:** - -1. `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. -2. `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. -3. `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. - -**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. - -**Acceptance:** -- New unit test in `test/terminal/core/core_test.dart` (or a sibling) that: - - Constructs a partially-filled source line and an anchor past `trimmedLength`, - - Runs reflow shrink, - - Asserts the anchor''s owning line is *in the reflow output list*, not dangling. -- 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. -- No regression in the existing reflow tests (the tail-clamp cases, wide-char boundaries, multi-line wrapped runs). - -**Investigation notes (do not skip):** - -The fix is conceptually small — the reparented tail anchor must land on a line that gets emitted. Two candidate shapes: - -- **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. -- **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). - -Don''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. - -**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. - -**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. -', NULL, '2026-05-07 05:52:26', '2026-05-07 05:52:26', '2026-05-07 05:52:26', NULL, '1e8625251297bbad6ee96d80b05b606b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'status', 'backlog', 'in_progress', NULL, '2026-05-07 05:56:47', '2026-05-07 05:56:47', '2026-05-07 05:56:47', NULL, '3fd3c03fde722d5d5c30813b2ce42822', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'status', 'in_progress', 'done', NULL, '2026-05-07 05:58:51', '2026-05-07 05:58:51', '2026-05-07 05:58:51', NULL, '4ffd69490cbda8da72a60b642182213a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'description', NULL, '**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`. - -**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: - -1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304). -2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67). -3. `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. - -Independently, `TerminalGestureHandler` exposes an `onSingleTapUp` parameter that *is* properly wired (handler line 139 calls `_tapUp(widget.onSingleTapUp, ...)`), but `TerminalView` never passes a value for it. - -Net 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. - -**Trigger paths in production:** - -`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. - -**Acceptance:** -- `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). -- `TerminalView.onSingleTapUp` either becomes a public parameter on `TerminalView` (mirroring TerminalGestureHandler), or the docstring on `onTapUp` is clarified to match what actually happens. -- 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`. -- 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. - -**Investigation notes:** - -- `TerminalGestureDetector` declares both `onTapUp` and `onSingleTapUp` (lines 31 / 33). Only the latter is ever invoked. The `onTapUp` field is therefore dead surface. -- 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: - - **Option A** — fire `onTapUp` on every up event (raw); fire `onSingleTapUp` only on confirmed singles. Most flexible. - - **Option B** — collapse to a single hook (`onTapUp`) and remove `onSingleTapUp`. Simpler but loses the double-tap distinction. -- Either option needs verification against the existing single/double-tap timer flow. Don''t pick without re-reading how `_isDoubleTap` is used. - -**Surfaced from:** T-91 test sweep on `terminal_view.dart`. Documented in the test file as "skipped — see T-93". - -**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. -', NULL, '2026-05-07 06:34:22', '2026-05-07 06:34:22', '2026-05-07 06:34:22', NULL, 'e549f5ed0e98298ba308aa75a3f674c5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'description', '**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`. - -**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: - -1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304). -2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67). -3. `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. - -Independently, `TerminalGestureHandler` exposes an `onSingleTapUp` parameter that *is* properly wired (handler line 139 calls `_tapUp(widget.onSingleTapUp, ...)`), but `TerminalView` never passes a value for it. - -Net 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. - -**Trigger paths in production:** - -`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. - -**Acceptance:** -- `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). -- `TerminalView.onSingleTapUp` either becomes a public parameter on `TerminalView` (mirroring TerminalGestureHandler), or the docstring on `onTapUp` is clarified to match what actually happens. -- 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`. -- 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. - -**Investigation notes:** - -- `TerminalGestureDetector` declares both `onTapUp` and `onSingleTapUp` (lines 31 / 33). Only the latter is ever invoked. The `onTapUp` field is therefore dead surface. -- 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: - - **Option A** — fire `onTapUp` on every up event (raw); fire `onSingleTapUp` only on confirmed singles. Most flexible. - - **Option B** — collapse to a single hook (`onTapUp`) and remove `onSingleTapUp`. Simpler but loses the double-tap distinction. -- Either option needs verification against the existing single/double-tap timer flow. Don''t pick without re-reading how `_isDoubleTap` is used. - -**Surfaced from:** T-91 test sweep on `terminal_view.dart`. Documented in the test file as "skipped — see T-93". - -**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. -', '**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). - -**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: - -1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304). -2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67). -3. `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. - -The `_onTapUp` method on `TerminalViewState` itself is also dead — it''s only referenced from the dead-field path above. - -**Two layers of "dead" here:** - -- The **wiring** is dead — nothing inside the terminal renderer calls into `widget.onTapUp` after the field is set. -- 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. - -So 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.) - -**Two clean resolutions:** - -- **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: - - `onTapUp` fires on every tap up (raw); `onSingleTapUp` fires only on confirmed singles. - - Collapse the two into a single hook and document the contract. -- **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. - -Recommendation: **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. - -**Acceptance:** - -- Either: - - **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. - - **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. -- No regression in secondary-tap behaviour (those parameters are wired correctly via `TapGestureRecognizer.onSecondary*`). - -**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. - -**Cross-references:** T-91 (epic parent), T-89 (coverage epic). -', NULL, '2026-05-07 06:35:32', '2026-05-07 06:35:32', '2026-05-07 06:35:32', NULL, '2f51a45e06d526d9b47dbc617ea965f8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'status', 'backlog', 'in_progress', NULL, '2026-05-07 06:45:24', '2026-05-07 06:45:24', '2026-05-07 06:45:24', NULL, '82fb868231b1806c9a1cd802110856f7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'status', 'in_progress', 'done', NULL, '2026-05-07 19:04:55', '2026-05-07 19:04:55', '2026-05-07 19:04:55', NULL, 'bc269d5f335553e768041e72019dc3b0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-94', 'status', 'backlog', 'cancelled', NULL, '2026-05-07 19:08:52', '2026-05-07 19:08:52', '2026-05-07 19:08:52', NULL, '5f534e88c93e7eef4ad838692ee7b725', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-95', 'description', NULL, '**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). - -**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. - -Trace: - -1. `TerminalGestureHandler.build` (gesture_handler.dart:65–82) wires: - ```dart - onTertiaryTapDown: onSecondaryTapDown, - onTertiaryTapUp: onSecondaryTapUp, - ``` - 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**. -2. The state''s `onTertiaryTapDown` and `onTertiaryTapUp` methods (lines 147–152) are therefore **dead** — they''re declared but never invoked anywhere. -3. `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. -4. 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`. - -**Two layers of "dead" again:** - -- The wiring is wrong, so middle-clicks dispatch as if they were right-clicks. -- 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). - -**User-visible impact:** - -A 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. - -**Resolutions, like T-93:** - -- **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. -- **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. - -Recommendation: **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. - -**Aside:** there''s a related dead branch in `onDragStart` (line 176): -```dart -details.kind == PointerDeviceKind.mouse - ? renderTerminal.selectCharacters(details.localPosition) - : renderTerminal.selectWord(details.localPosition); -``` -The `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. - -**Acceptance:** - -- Either: - - **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. - - **B:** `onTertiaryTapDown` / `onTertiaryTapUp` removed from `TerminalGestureHandler`, `TerminalGestureDetector`, and the state''s tertiary methods. Existing tests still pass. -- The `selectWord` branch in `onDragStart` is either reachable (extend PanGestureRecognizer.supportedDevices) or removed. - -**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. - -**Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic). -', NULL, '2026-05-08 11:01:01', '2026-05-08 11:01:01', '2026-05-08 11:01:01', NULL, '8088d054dd8c7946075ed6325724dfb3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-62', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '43c1103bad9839fbd2c8058f147cfcba', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-61', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '65fbf26149226f3fc9c06a446bfa9f41', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-49', 'status', 'backlog', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, '98b648e7a4a82730e43677c7320c8b0d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'status', 'in_progress', 'done', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, 'c8b03bf5dadfdc5cb22bc5961919dc23', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'ready', 'in_progress', NULL, '2026-05-03 20:33:10', '2026-05-03 20:33:10', '2026-05-03 20:33:10', NULL, 'fdc5453ac922809e479b6b3418ed7755', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:34:52', '2026-05-03 20:34:52', '2026-05-03 20:34:52', NULL, '6da16f33a6aa1e1db733a0a02900e0c1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:36:31', '2026-05-03 20:36:31', '2026-05-03 20:36:31', NULL, 'd15ac0c4b6c7b6057243edcfbf7086f1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:37:03', '2026-05-03 20:37:03', '2026-05-03 20:37:03', NULL, '3f6f22aa9e420ec2fc869f2530df4c46', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'backlog', 'ready', NULL, '2026-05-03 20:37:30', '2026-05-03 20:37:30', '2026-05-03 20:37:30', NULL, 'e6f3d8bda62656d17b174c8b9933dabc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'status', 'in_progress', 'done', NULL, '2026-05-03 20:40:22', '2026-05-03 20:40:22', '2026-05-03 20:40:22', NULL, '9ace75c4346f321670f7024eebfa0fb9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'status', 'in_progress', 'done', NULL, '2026-05-05 12:51:24', '2026-05-05 12:51:24', '2026-05-05 12:51:24', NULL, 'adedfd3ad28b56cf4e57e20bce3ec497', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '60751203904229cc5846ea313f747846', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '618b3042be98eecae2168446ffd2bfa6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, '65e4ebb4c60a0feb7c2dec82f0c3faa1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'a3ac83b6f85fbcf6b44a4b0c28c79803', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'status', 'backlog', 'done', NULL, '2026-05-05 12:53:11', '2026-05-05 12:53:11', '2026-05-05 12:53:11', NULL, 'b427c00df34390343af95fcd285a09bd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '48865da069da205e0cf7ae7dd8caf27d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '680e39c278e717327dc2557e625abc1b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '8f1ea30ab9663ac07c04fe404937a9fd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, '9284c52dba3b76b117e8d3a97420dd6a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'status', 'done', 'done', NULL, '2026-05-05 12:53:15', '2026-05-05 12:53:15', '2026-05-05 12:53:15', NULL, 'e5c9d41027d52b8fbe7edcc684130c2e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'ready', 'in_progress', NULL, '2026-05-05 12:54:39', '2026-05-05 12:54:39', '2026-05-05 12:54:39', NULL, 'd8d87b2a3ea7073edde2157f6c90791e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'status', 'in_progress', 'done', NULL, '2026-05-05 12:59:05', '2026-05-05 12:59:05', '2026-05-05 12:59:05', NULL, '3ddd60cd6e2f138e184b3e4e8f892a21', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 12:59:50', '2026-05-05 12:59:50', '2026-05-05 12:59:50', NULL, 'a1e1b2c66830239411cac289bcb13050', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:02:34', '2026-05-05 13:02:34', '2026-05-05 13:02:34', NULL, 'ffe86cc4530e722c0622fbd4095d0f08', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:02:40', '2026-05-05 13:02:40', '2026-05-05 13:02:40', NULL, '3d48174c87be388f391611a4c9ebc4b0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:07:29', '2026-05-05 13:07:29', '2026-05-05 13:07:29', NULL, '579e4939f630479b5987b47de2106ca9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:07:34', '2026-05-05 13:07:34', '2026-05-05 13:07:34', NULL, 'd56b93ae20653ab26c0a99ab7c28fa69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:10:50', '2026-05-05 13:10:50', '2026-05-05 13:10:50', NULL, '5c98774f501f1a6c7bef328c3cd8c5b1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'status', 'backlog', 'in_progress', NULL, '2026-05-05 13:10:57', '2026-05-05 13:10:57', '2026-05-05 13:10:57', NULL, '8558d256a078bda0713c7045d7f1c2bb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'status', 'in_progress', 'done', NULL, '2026-05-05 13:12:20', '2026-05-05 13:12:20', '2026-05-05 13:12:20', NULL, 'aabaf34b3e5982ab7be5c07051e2c1e7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 07:45:02', '2026-05-06 07:45:02', '2026-05-06 07:45:02', NULL, 'e5e0baef07bf6908200527c340130a2e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'status', 'in_progress', 'done', NULL, '2026-05-06 07:56:10', '2026-05-06 07:56:10', '2026-05-06 07:56:10', NULL, '3069b52fc53518956fc0dc7792aa2e9c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'status', 'ready', 'done', NULL, '2026-05-06 09:17:22', '2026-05-06 09:17:22', '2026-05-06 09:17:22', NULL, '56b24bfcf31543cbde7e5c1af8055989', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 09:17:50', '2026-05-06 09:17:50', '2026-05-06 09:17:50', NULL, 'cb9fc3d3f6cc5858ce9af1f377257b26', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'status', 'in_progress', 'done', NULL, '2026-05-06 10:12:48', '2026-05-06 10:12:48', '2026-05-06 10:12:48', NULL, '133758a6509278aaada7d8f03ac72ae4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 10:17:57', '2026-05-06 10:17:57', '2026-05-06 10:17:57', NULL, '8991e0669094e0969787881506f373dc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'status', 'in_progress', 'done', NULL, '2026-05-06 12:17:27', '2026-05-06 12:17:27', '2026-05-06 12:17:27', NULL, '8dd4f7dba893357485c6a8293e49765e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'ready', 'in_progress', NULL, '2026-05-06 12:20:58', '2026-05-06 12:20:58', '2026-05-06 12:20:58', NULL, 'e9f7ebbd02eb9bf7f3faf10dca1cf8e1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:21:15', '2026-05-06 13:21:15', '2026-05-06 13:21:15', NULL, '661729cdc568efa42627ea20bfcbeddb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:29:05', '2026-05-06 13:29:05', '2026-05-06 13:29:05', NULL, 'adb576167325591e1ec5e095a38bc6cb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:38:38', '2026-05-06 13:38:38', '2026-05-06 13:38:38', NULL, '4062a1b9aed02d2749b8fd079b6cd6a9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:38:44', '2026-05-06 13:38:44', '2026-05-06 13:38:44', NULL, '668f49fb8c27e4acf51144970d8cccb5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'status', 'in_progress', 'done', NULL, '2026-05-06 13:45:12', '2026-05-06 13:45:12', '2026-05-06 13:45:12', NULL, '28dcd4602bb47f5ca1a8be7ecffd2597', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'description', NULL, '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. - -Neither happens today. - -**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`). - -**Gap 2 — app shutdown leaks secondaries.** Nothing reaps `clide-claude--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. - -**Acceptance:** -- Closing a secondary tab in the Claude pane runs `tmux -L clide kill-session -t ` for that secondary''s session name (use existing `secondarySessionName(repoRoot, n)`). -- App shutdown enumerates `clide-claude--*` sessions on the clide socket and kills the non-primary ones. Primary is left detached (D-41 explicit). -- `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone. -- Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner). - -**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.', NULL, '2026-05-06 13:46:26', '2026-05-06 13:46:26', '2026-05-06 13:46:26', NULL, 'c7dbde5c6c6c89492665abdff0f59be4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 13:46:29', '2026-05-06 13:46:29', '2026-05-06 13:46:29', NULL, '9d047426e61ddd7d29072333da718c03', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'description', NULL, '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. - -**Acceptance:** -- New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact. -- Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run. -- 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). - -**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).', NULL, '2026-05-06 15:08:22', '2026-05-06 15:08:22', '2026-05-06 15:08:22', NULL, '396d0d316f7ee331776d4547f9b54b9a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-7', 'description', NULL, '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. - -**Canvas pane (`builtin.canvas`):** -- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. -- Pan/zoom via `InteractiveViewer`; node selection, drag, resize. -- Edit affordances: add note from file picker, add text node, draw edge between nodes. -- Persist layout back to the `.canvas` file on disk. - -**Graph view (`builtin.graph`):** -- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks. -- Hover highlights connected subgraph; click opens the note in the editor. -- Filter pane: tag include/exclude, file glob, depth-from-active. -- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. - -Both 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).', NULL, '2026-05-06 15:08:37', '2026-05-06 15:08:37', '2026-05-06 15:08:37', NULL, '1a91dfb3b80a90e5dd6d84b3df9afef8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-8', 'description', NULL, 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype. - -**Extension API (third-party Lua):** -- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc. -- Manifest schema, capability gating, sandboxed FS/IPC access. -- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15). -- Marketplace / distribution story is OUT OF SCOPE for Tier 6 — local-install only. - -**Settings UI (`builtin.settings-ui`):** -- Schema-driven settings panel reading from the kernel SettingsStore. -- Render strategy: form fields keyed off the schema each subsystem registers. -- Edits write back to `.clide/settings.yaml`. - -**Theming UI (`builtin.theme-picker` extends):** -- Live preview of the four bundled themes (D-44). -- Custom theme: import YAML, validate against schema, register at runtime. -- Per-component override surface (long horizon). - -**Distributable builds:** -- AppImage / Flatpak for Linux, .dmg for macOS — see T-46. -- Self-update mechanism — see T-47. -- License manifest auto-regen as part of the release build. - -Big epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.', NULL, '2026-05-06 15:08:53', '2026-05-06 15:08:53', '2026-05-06 15:08:53', NULL, '55bf0c36dc75468fa6a1d3caf532c2e9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'description', NULL, '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. - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Keybinding goes in `lib/kernel/src/commands/keybindings.dart` per the D-54 keymap. -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', NULL, '2026-05-06 15:09:05', '2026-05-06 15:09:05', '2026-05-06 15:09:05', NULL, '352e6c0b2af96f63924ad1d1ef4c80f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'description', NULL, '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - -Link-back: T-70 is the workaround; this is the proper fix.', NULL, '2026-05-06 15:09:20', '2026-05-06 15:09:20', '2026-05-06 15:09:20', NULL, '780a2a4a39f15c3ba81648ceb6fdba37', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'description', NULL, '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. - -**Acceptance:** -- 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`). -- Tested with `vim` (mouse mode `a`), `htop`, `less` — all should respond to mouse selection / click correctly. -- Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode. -- The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed. - -**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.', NULL, '2026-05-06 15:09:36', '2026-05-06 15:09:36', '2026-05-06 15:09:36', NULL, '31108dcc7844bf74515dfc1cda0993e9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-80', 'description', NULL, '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: - -**Errno constants (audit item #23):** -- 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`. -- Centralize them in `lib/src/pty/errors.dart` or a sibling `posix.dart` as named constants. -- Existing `lib/src/ipc/errno_mapping.dart` already has a `PosixErrno` class — extend it or move to a shared location both layers import from. - -**Logger standardization (audit item #22, partial #26):** -- `lib/src/ipc/server.dart` uses `stderr.writeln(...)` directly; the rest of the daemon either uses no logger or a custom one. -- The Flutter-host process often consumes stderr, so log lines disappear silently. -- Pick one logger interface (kernel `log` already exists for the app side), wire `DaemonServer` and the daemon-side handlers to use it. -- Dispatch error messages should prefix with the request `cmd` so log correlation works (audit item #26). - -**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.', NULL, '2026-05-06 15:09:50', '2026-05-06 15:09:50', '2026-05-06 15:09:50', NULL, 'e4255ebfc7f9bd8537ced342f2925d77', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-81', 'description', NULL, '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: - -- **#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`. -- **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed. -- **#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. -- **#21** — `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd. -- **#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. -- **#27** — `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure. -- **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade. -- **#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. - -Land each as a small focused commit; ticket closes when all items above are merged.', NULL, '2026-05-06 15:10:09', '2026-05-06 15:10:09', '2026-05-06 15:10:09', NULL, 'ad1a34f56d30171570970965b5ef3560', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'description', NULL, 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). - -**Acceptance:** -- 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). -- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45). -- WCAG-AA contrast gate (D-22) passes against the theme. -- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution. -- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu. - -**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. - -**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.', NULL, '2026-05-06 15:10:22', '2026-05-06 15:10:22', '2026-05-06 15:10:22', NULL, '0a750764071d26afaa1821ca9ee2f3bf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-88', 'description', NULL, '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. - -**Acceptance:** -- A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases. -- When a new release lands with a CVE label or fixes a CVE-tagged git release, schedule a bump; otherwise track the version drift. -- 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/`. - -**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). - -**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.', NULL, '2026-05-06 15:10:35', '2026-05-06 15:10:35', '2026-05-06 15:10:35', NULL, '49050e00e3787be467be278d157bb69e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '0d9f946e59752f061cc431121ac0f708', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'ready', 'in_progress', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, '4a53482a4ec3d3938e8b0bdfd0d9f4d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'backlog', 'ready', NULL, '2026-05-06 15:39:53', '2026-05-06 15:39:53', '2026-05-06 15:39:53', NULL, 'e4e9ad42effcd95445b086c527f3471c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:45:51', '2026-05-06 15:45:51', '2026-05-06 15:45:51', NULL, '0b148a1ff820009a9e2f533ee6fef7f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'done', 'in_progress', NULL, '2026-05-06 15:51:26', '2026-05-06 15:51:26', '2026-05-06 15:51:26', NULL, '79dc3457b9819570fbd35d7cffb80e0d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'status', 'in_progress', 'done', NULL, '2026-05-06 15:58:30', '2026-05-06 15:58:30', '2026-05-06 15:58:30', NULL, '78f6f98d8e4b94f6f0fd49f324ee057a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - -Link-back: T-70 is the workaround; this is the proper fix.', '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - ---- -Refinement (2026-05-06): - -Investigation 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. - -**Concrete fix shape:** -1. `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/`. -2. `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). -3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195. -4. 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). - -Workaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.', NULL, '2026-05-06 16:13:35', '2026-05-06 16:13:35', '2026-05-06 16:13:35', NULL, 'baa15a2631bef74a4c1f732cee12de1c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'ready', 'in_progress', NULL, '2026-05-06 16:13:39', '2026-05-06 16:13:39', '2026-05-06 16:13:39', NULL, '8e3c8ef2cfa48a2805eef455ec8f5830', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 16:13:39', '2026-05-06 16:13:39', '2026-05-06 16:13:39', NULL, 'eb816113ca1ef0ef3514af5f8d3391d3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'status', 'in_progress', 'done', NULL, '2026-05-06 20:29:28', '2026-05-06 20:29:28', '2026-05-06 20:29:28', NULL, 'ba203ed16c6e042457e8e751bf56932a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'description', NULL, 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: - -| area | hit / total | % | -|---|---|---| -| `lib/src/terminal/` | 12 / 3167 | **0.4%** | -| `lib/widgets/` | 537 / 1184 | 45.4% | -| `lib/src/pql/` | 45 / 94 | 47.9% | -| `lib/builtin/` | 269 / 550 | 48.9% | -| `lib/kernel/` | 889 / 1691 | 52.6% | -| `lib/src/daemon/` | 320 / 584 | 54.8% | -| `lib/src/pty/` | 130 / 216 | 60.2% | -| `lib/src/git/` | 356 / 518 | 68.7% | -| `lib/src/ipc/` | 110 / 156 | 70.5% | -| `lib/src/files/` | 98 / 133 | 73.7% | -| `lib/extension/` | 41 / 54 | 75.9% | -| `lib/src/editor/` | 107 / 129 | 82.9% | -| `lib/src/panes/` | 70 / 73 | 95.9% | - -The 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. - -**End state:** -- Total line coverage ≥ 95% (no per-area carve-outs). -- Pre-push gate hard-fails on coverage drops via a committed floor (`coverage/floor.txt`); floor ratchets up only. -- Per D-66 the gate lands at the *current* floor in the first child ticket; subsequent floor bumps come with the test-writing children. - -**Acceptance criteria for closing this epic:** -1. `coverage/floor.txt` reads `95` (or higher). -2. `flutter test --coverage --exclude-tags forkpty` produces ≥95% line coverage. -3. The pre-push gate computes coverage and rejects pushes below the floor. -4. 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. - -**Strategy / child shape:** -The 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. - -1. **Gate plumbing** (first child) — install the ratchet, lock in current floor, expose `make coverage-gate` and wire it into `make push-check`. -2. **`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.). -3. **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. -4. **Final ratchet** — once total ≥ 95%, set floor to 95 and close the epic. - -**Out of scope:** -- Branch coverage (lcov data is weak for branch on Dart; line coverage is the contract). -- Integration / E2E coverage (only widget + unit feed lcov). -- `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. - -**Cross-references:** D-66 (this epic''s decision record), D-29 (pre-push gate scope), "Own the rendering stack" guardrail (CLAUDE.md). -', NULL, '2026-05-06 20:37:40', '2026-05-06 20:37:40', '2026-05-06 20:37:40', NULL, 'c5f416e95632e206449476de472fc4e5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'parent_id', NULL, 'T-89', NULL, '2026-05-06 20:38:24', '2026-05-06 20:38:24', '2026-05-06 20:38:24', NULL, '11840a870c33e5e94b6adcd3cfb5f10a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'description', NULL, '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. - -**What lands:** - -1. **`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). -2. **`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. -3. **`Makefile` `coverage-gate` target** — wraps `ci/coverage_gate.sh` for local use. -4. **`make push-check`** — adds `coverage-gate` to its dependency list. Pre-push hook (already wired via `.githooks/pre-push`) picks this up automatically. -5. **`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. - -**Acceptance:** -- `make coverage-gate` passes on a clean checkout (floor matches reality). -- `make push-check` includes the gate. -- A test deletion would fail `make coverage-gate` (verify by experiment, not just by reading the script — invariant has to actually hold). -- Pre-push hook runs the gate (verify by attempting a deliberately-failing push and seeing it blocked). -- `coverage/lcov.info` is gitignored (it''s regenerated on every run; only `floor.txt` is committed). - -**Out of scope:** -- Bumping the floor toward 95% — that''s the test-writing children''s job. -- Branch coverage — D-66 explicitly excludes. -- Per-area floors — D-66 chose a single global floor for simplicity. - -**Notes:** -- `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. -- `coverage/lcov.info` is the standard Dart output path of `flutter test --coverage`. Don''t relocate. -- `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). -', NULL, '2026-05-06 20:39:05', '2026-05-06 20:39:05', '2026-05-06 20:39:05', NULL, 'a7af4c58b2e4ccbc80d128d6ba64ba0a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, '74247471621e8f69e888066727cffcda', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, 'a81a6e3ef23af1c113f46e467fdde278', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'in_progress', 'ready', NULL, '2026-05-06 20:39:45', '2026-05-06 20:39:45', '2026-05-06 20:39:45', NULL, 'e2692b0d73a61655c310846dda2b2ae2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'ready', 'ready', NULL, '2026-05-06 20:40:13', '2026-05-06 20:40:13', '2026-05-06 20:40:13', NULL, 'cabddbfab05fa8c3e3705311f895d619', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'in_progress', 'in_progress', NULL, '2026-05-06 20:40:16', '2026-05-06 20:40:16', '2026-05-06 20:40:16', NULL, '02f69af134363ce27d815ad3081379f3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'status', 'in_progress', 'in_progress', NULL, '2026-05-06 20:40:16', '2026-05-06 20:40:16', '2026-05-06 20:40:16', NULL, '9695c13174226c39e5bf059d88d8e855', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'status', 'in_progress', 'done', NULL, '2026-05-06 20:44:47', '2026-05-06 20:44:47', '2026-05-06 20:44:47', NULL, '8d3f9f4effc90eacfcefd2701a33ede6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'parent_id', NULL, 'T-89', NULL, '2026-05-06 20:49:47', '2026-05-06 20:49:47', '2026-05-06 20:49:47', NULL, 'baf63005fb488a699872715061d2473c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'description', NULL, 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. - -**Today''s distribution within `lib/src/terminal/src/`:** - -| sub-area | hit / total | % | shape | -|---|---|---|---| -| `core/buffer/` | 0 / 581 | 0.0% | pure data structures | -| `core/escape/` | 1 / 473 | 0.2% | pure parser logic | -| `core/input/` | 4 / 292 | 1.4% | key + keytab tables | -| `core/(root)` | 0 / 188 | 0.0% | callback wiring | -| `core/mouse/` | 3 / 51 | 5.9% | encoder logic | -| `ui/` | 3 / 823 | 0.4% | painters + render objects | -| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` | -| `utils/` | 1 / 251 | 0.4% | circular buffer etc. | - -**Strategy — pure logic first, then UI:** - -1. **`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. -2. **`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. -3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode. -4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast. -5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests. -6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests. -7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive. - -**Acceptance:** -- `lib/src/terminal/` line coverage ≥ 95%. -- All tests fast (<5 s for the area''s test files combined; no real PTY shell-out). -- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files. -- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage. -- `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). - -**Strategy on commit cadence:** -Land tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch: -- Adds tests under `test/terminal//`. -- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows). -- Documents in CHANGELOG under "Changed" (test-only changes can also fold into one CHANGELOG entry per area). - -**Out of scope:** -- 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. -- Branch coverage (D-66 explicitly excludes). -- Mouse / keyboard *protocol* round-trips against real TUIs (that''s T-74 and integration work). - -**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing). -', NULL, '2026-05-06 20:50:29', '2026-05-06 20:50:29', '2026-05-06 20:50:29', NULL, 'a165eb2d32141e72feb1ffdf9d98d58e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'status', 'backlog', 'in_progress', NULL, '2026-05-06 20:50:32', '2026-05-06 20:50:32', '2026-05-06 20:50:32', NULL, 'da5f03732cdcd40106cf47c852191706', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'description', NULL, '**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`). - -**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. - -**Trigger paths in production code:** - -1. `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. -2. `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. -3. `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. - -**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. - -**Acceptance:** -- New unit test in `test/terminal/core/core_test.dart` (or a sibling) that: - - Constructs a partially-filled source line and an anchor past `trimmedLength`, - - Runs reflow shrink, - - Asserts the anchor''s owning line is *in the reflow output list*, not dangling. -- 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. -- No regression in the existing reflow tests (the tail-clamp cases, wide-char boundaries, multi-line wrapped runs). - -**Investigation notes (do not skip):** - -The fix is conceptually small — the reparented tail anchor must land on a line that gets emitted. Two candidate shapes: - -- **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. -- **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). - -Don''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. - -**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. - -**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. -', NULL, '2026-05-07 05:52:26', '2026-05-07 05:52:26', '2026-05-07 05:52:26', NULL, '1e8625251297bbad6ee96d80b05b606b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'status', 'backlog', 'in_progress', NULL, '2026-05-07 05:56:47', '2026-05-07 05:56:47', '2026-05-07 05:56:47', NULL, '3fd3c03fde722d5d5c30813b2ce42822', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'status', 'in_progress', 'done', NULL, '2026-05-07 05:58:51', '2026-05-07 05:58:51', '2026-05-07 05:58:51', NULL, '4ffd69490cbda8da72a60b642182213a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'description', NULL, '**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`. - -**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: - -1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304). -2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67). -3. `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. - -Independently, `TerminalGestureHandler` exposes an `onSingleTapUp` parameter that *is* properly wired (handler line 139 calls `_tapUp(widget.onSingleTapUp, ...)`), but `TerminalView` never passes a value for it. - -Net 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. - -**Trigger paths in production:** - -`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. - -**Acceptance:** -- `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). -- `TerminalView.onSingleTapUp` either becomes a public parameter on `TerminalView` (mirroring TerminalGestureHandler), or the docstring on `onTapUp` is clarified to match what actually happens. -- 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`. -- 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. - -**Investigation notes:** - -- `TerminalGestureDetector` declares both `onTapUp` and `onSingleTapUp` (lines 31 / 33). Only the latter is ever invoked. The `onTapUp` field is therefore dead surface. -- 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: - - **Option A** — fire `onTapUp` on every up event (raw); fire `onSingleTapUp` only on confirmed singles. Most flexible. - - **Option B** — collapse to a single hook (`onTapUp`) and remove `onSingleTapUp`. Simpler but loses the double-tap distinction. -- Either option needs verification against the existing single/double-tap timer flow. Don''t pick without re-reading how `_isDoubleTap` is used. - -**Surfaced from:** T-91 test sweep on `terminal_view.dart`. Documented in the test file as "skipped — see T-93". - -**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. -', NULL, '2026-05-07 06:34:22', '2026-05-07 06:34:22', '2026-05-07 06:34:22', NULL, 'e549f5ed0e98298ba308aa75a3f674c5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'description', '**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`. - -**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: - -1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304). -2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67). -3. `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. - -Independently, `TerminalGestureHandler` exposes an `onSingleTapUp` parameter that *is* properly wired (handler line 139 calls `_tapUp(widget.onSingleTapUp, ...)`), but `TerminalView` never passes a value for it. - -Net 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. - -**Trigger paths in production:** - -`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. - -**Acceptance:** -- `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). -- `TerminalView.onSingleTapUp` either becomes a public parameter on `TerminalView` (mirroring TerminalGestureHandler), or the docstring on `onTapUp` is clarified to match what actually happens. -- 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`. -- 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. - -**Investigation notes:** - -- `TerminalGestureDetector` declares both `onTapUp` and `onSingleTapUp` (lines 31 / 33). Only the latter is ever invoked. The `onTapUp` field is therefore dead surface. -- 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: - - **Option A** — fire `onTapUp` on every up event (raw); fire `onSingleTapUp` only on confirmed singles. Most flexible. - - **Option B** — collapse to a single hook (`onTapUp`) and remove `onSingleTapUp`. Simpler but loses the double-tap distinction. -- Either option needs verification against the existing single/double-tap timer flow. Don''t pick without re-reading how `_isDoubleTap` is used. - -**Surfaced from:** T-91 test sweep on `terminal_view.dart`. Documented in the test file as "skipped — see T-93". - -**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. -', '**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). - -**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: - -1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304). -2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67). -3. `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. - -The `_onTapUp` method on `TerminalViewState` itself is also dead — it''s only referenced from the dead-field path above. - -**Two layers of "dead" here:** - -- The **wiring** is dead — nothing inside the terminal renderer calls into `widget.onTapUp` after the field is set. -- 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. - -So 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.) - -**Two clean resolutions:** - -- **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: - - `onTapUp` fires on every tap up (raw); `onSingleTapUp` fires only on confirmed singles. - - Collapse the two into a single hook and document the contract. -- **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. - -Recommendation: **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. - -**Acceptance:** - -- Either: - - **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. - - **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. -- No regression in secondary-tap behaviour (those parameters are wired correctly via `TapGestureRecognizer.onSecondary*`). - -**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. - -**Cross-references:** T-91 (epic parent), T-89 (coverage epic). -', NULL, '2026-05-07 06:35:32', '2026-05-07 06:35:32', '2026-05-07 06:35:32', NULL, '2f51a45e06d526d9b47dbc617ea965f8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'status', 'backlog', 'in_progress', NULL, '2026-05-07 06:45:24', '2026-05-07 06:45:24', '2026-05-07 06:45:24', NULL, '82fb868231b1806c9a1cd802110856f7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'status', 'in_progress', 'done', NULL, '2026-05-07 19:04:55', '2026-05-07 19:04:55', '2026-05-07 19:04:55', NULL, 'bc269d5f335553e768041e72019dc3b0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-94', 'status', 'backlog', 'cancelled', NULL, '2026-05-07 19:08:52', '2026-05-07 19:08:52', '2026-05-07 19:08:52', NULL, '5f534e88c93e7eef4ad838692ee7b725', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-95', 'description', NULL, '**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). - -**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. - -Trace: - -1. `TerminalGestureHandler.build` (gesture_handler.dart:65–82) wires: - ```dart - onTertiaryTapDown: onSecondaryTapDown, - onTertiaryTapUp: onSecondaryTapUp, - ``` - 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**. -2. The state''s `onTertiaryTapDown` and `onTertiaryTapUp` methods (lines 147–152) are therefore **dead** — they''re declared but never invoked anywhere. -3. `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. -4. 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`. - -**Two layers of "dead" again:** - -- The wiring is wrong, so middle-clicks dispatch as if they were right-clicks. -- 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). - -**User-visible impact:** - -A 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. - -**Resolutions, like T-93:** - -- **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. -- **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. - -Recommendation: **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. - -**Aside:** there''s a related dead branch in `onDragStart` (line 176): -```dart -details.kind == PointerDeviceKind.mouse - ? renderTerminal.selectCharacters(details.localPosition) - : renderTerminal.selectWord(details.localPosition); -``` -The `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. - -**Acceptance:** - -- Either: - - **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. - - **B:** `onTertiaryTapDown` / `onTertiaryTapUp` removed from `TerminalGestureHandler`, `TerminalGestureDetector`, and the state''s tertiary methods. Existing tests still pass. -- The `selectWord` branch in `onDragStart` is either reachable (extend PanGestureRecognizer.supportedDevices) or removed. - -**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. - -**Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic). -', NULL, '2026-05-08 11:01:01', '2026-05-08 11:01:01', '2026-05-08 11:01:01', NULL, '8088d054dd8c7946075ed6325724dfb3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-96', 'description', 'The reader isolate spawned by `NativePty._spawnReaderAsync` (`lib/src/pty/native_pty.dart`) hangs intermittently — about 5% of fresh spawns — with poll() on the master fd never returning POLLIN or POLLHUP, even when the child has clearly written and exited. Probed: 2 hangs out of 30 sequential `/bin/sh -c "printf hello-pty"` spawns over a 30s/spawn timeout. +', NULL, '2026-05-08 11:01:01', '2026-05-08 11:01:01', '2026-05-08 11:01:01', NULL, 'e04d4190e72fab968dd321a21fcfd69a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WCDTR41VN9P1EGMC', 'description', 'The reader isolate spawned by `NativePty._spawnReaderAsync` (`lib/src/pty/native_pty.dart`) hangs intermittently — about 5% of fresh spawns — with poll() on the master fd never returning POLLIN or POLLHUP, even when the child has clearly written and exited. Probed: 2 hangs out of 30 sequential `/bin/sh -c "printf hello-pty"` spawns over a 30s/spawn timeout. **Symptom:** test sees zero `pane.output` events and no `pane.exit`; the reader isolate sits in `poll(pfd, 1, 100)` returning 0 forever. The hang is per-spawn; a fresh `NativePty.start` recovers cleanly. @@ -1705,23 +595,23 @@ Why not always? Lock state at fork() time is timing-dependent. ~95% of the time - `lib/src/pty/native_pty.dart:170-191` (child-side post-fork code that deadlocks) - `lib/src/pty/native_pty.dart:235-262` (reader spawn — not the bug; downstream symptom) - glibc posix_spawn docs: https://www.gnu.org/software/libc/manual/html_node/Process-Creation-Example.html -', NULL, '2026-05-17 18:05:33', '2026-05-17 18:05:33', '2026-05-17 18:05:33', NULL, '56d9ba7c49068a498a003e900cde416f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-96', 'status', 'backlog', 'done', NULL, '2026-05-17 18:11:18', '2026-05-17 18:11:18', '2026-05-17 18:11:18', NULL, '3215fc763c4f1dce75ea9e0bb2b9bbb6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-98', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:51:25', '2026-05-17 18:51:25', '2026-05-17 18:51:25', NULL, '0369c8264ec2c67b3bf3f28057404ec0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-98', 'status', 'in_progress', 'done', NULL, '2026-05-17 18:55:57', '2026-05-17 18:55:57', '2026-05-17 18:55:57', NULL, 'cb0ae539d5665bd9720b327d60a2cbed', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-101', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:58:56', '2026-05-17 18:58:56', '2026-05-17 18:58:56', NULL, '55d14b4493a159695d4b40bbd19fff23', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-102', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:58:56', '2026-05-17 18:58:56', '2026-05-17 18:58:56', NULL, '7fa075d7232df5302ffdfb5d1c3d79f2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-101', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:01:39', '2026-05-17 19:01:39', '2026-05-17 19:01:39', NULL, '56e8da656c5fc53af241900e6313805e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-102', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:01:39', '2026-05-17 19:01:39', '2026-05-17 19:01:39', NULL, '7f09d2a1501d3d072e071822c191f253', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-103', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:03:18', '2026-05-17 19:03:18', '2026-05-17 19:03:18', NULL, '2635d907f94402ec506d3b56dfc25b21', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-106', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:03:18', '2026-05-17 19:03:18', '2026-05-17 19:03:18', NULL, 'cc39b3fd718e99423823e640950ccf1b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-113', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:03:18', '2026-05-17 19:03:18', '2026-05-17 19:03:18', NULL, 'cc8605833ba5cb97e068eb5a6d57d8f4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-106', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:18:43', '2026-05-17 19:18:43', '2026-05-17 19:18:43', NULL, '8dccc83b93c5005db744a100a5b3baea', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-113', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:18:43', '2026-05-17 19:18:43', '2026-05-17 19:18:43', NULL, '8f7812831e564005eb796708543c54b3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-103', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:18:43', '2026-05-17 19:18:43', '2026-05-17 19:18:43', NULL, 'c7d1b76cb6854efb7e9990cc7204d899', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-100', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:23:24', '2026-05-17 19:23:24', '2026-05-17 19:23:24', NULL, '607b82f096b5d694fb75b7daf64dc2e0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-117', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:28:31', '2026-05-17 19:28:31', '2026-05-17 19:28:31', NULL, 'b5c56dff8a2c7d6f14ecf19231b63268', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-110', 'description', '`app.dart:90-148` routes all shortcuts through one root `KeyboardListener` — no per-context scoping; will conflict with text-input fields once any pane wants to capture keys. `KeybindingResolver.fromKeyEvent` keys off `logicalKey.keyLabel` which is layout-dependent (US-QWERTY `Ctrl+/` differs from AZERTY `Ctrl+:`). +', NULL, '2026-05-17 18:05:33', '2026-05-17 18:05:33', '2026-05-17 18:05:33', NULL, 'e93e31a0d5a9298f4de799932a29b3c9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WCDTR41VN9P1EGMC', 'status', 'backlog', 'done', NULL, '2026-05-17 18:11:18', '2026-05-17 18:11:18', '2026-05-17 18:11:18', NULL, '10ee5f4a42f937b6b725d404aa1d6796', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F3V130BQXQH6HDVC', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:51:25', '2026-05-17 18:51:25', '2026-05-17 18:51:25', NULL, 'aac90043b781712714b9eeb71bc0b11c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F3V130BQXQH6HDVC', 'status', 'in_progress', 'done', NULL, '2026-05-17 18:55:57', '2026-05-17 18:55:57', '2026-05-17 18:55:57', NULL, 'd91ffcc01cd9e94cc849d8e433f28e4a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7NRKR9J1QTDSP9650', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:58:56', '2026-05-17 18:58:56', '2026-05-17 18:58:56', NULL, '2b026924c62fd3bcf925c7ebb08fae10', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QNZM15V1ZK32YS6G', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:58:56', '2026-05-17 18:58:56', '2026-05-17 18:58:56', NULL, 'd813277c30d4e338942ca3906406dc2f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7NRKR9J1QTDSP9650', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:01:39', '2026-05-17 19:01:39', '2026-05-17 19:01:39', NULL, '160b0a51df390ddec235c0246aacbe42', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QNZM15V1ZK32YS6G', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:01:39', '2026-05-17 19:01:39', '2026-05-17 19:01:39', NULL, '6286c1f7daa86d29f9b6371fa6efd71e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM493QAZNJTJQHYFGCC', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:03:18', '2026-05-17 19:03:18', '2026-05-17 19:03:18', NULL, '97409a77d8008155f27446e8b85ed45d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6MP9JD93ZE0CDGFFW', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:03:18', '2026-05-17 19:03:18', '2026-05-17 19:03:18', NULL, 'bebf93aa779be4d60c10f3f339bff731', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WW3008NZRJWJ88HG', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:03:18', '2026-05-17 19:03:18', '2026-05-17 19:03:18', NULL, 'e39e7ec9d08992649a7ebb0171068a2f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WW3008NZRJWJ88HG', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:18:43', '2026-05-17 19:18:43', '2026-05-17 19:18:43', NULL, '53fa3993c9f6153566036c657d74cf68', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM493QAZNJTJQHYFGCC', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:18:43', '2026-05-17 19:18:43', '2026-05-17 19:18:43', NULL, '759304f97ac1bd127a9712487cdb843b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6MP9JD93ZE0CDGFFW', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:18:43', '2026-05-17 19:18:43', '2026-05-17 19:18:43', NULL, 'f43b0c8a407f7b4ced027fca45927c55', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4CWANNSKNF2X5JEJ4', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:23:24', '2026-05-17 19:23:24', '2026-05-17 19:23:24', NULL, 'f6f3f18887c1c5c46111acb3c405e6cf', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65SSVGN5M36T4QNDC', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:28:31', '2026-05-17 19:28:31', '2026-05-17 19:28:31', NULL, 'f4d5a6c55d478953a642b1bdbd9ef15d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6MWAKNB2NE3ZZ0XKW', 'description', '`app.dart:90-148` routes all shortcuts through one root `KeyboardListener` — no per-context scoping; will conflict with text-input fields once any pane wants to capture keys. `KeybindingResolver.fromKeyEvent` keys off `logicalKey.keyLabel` which is layout-dependent (US-QWERTY `Ctrl+/` differs from AZERTY `Ctrl+:`). **Fix:** 1. Replace the root `KeyboardListener` with scoped `Shortcuts`/`Actions` per slot. @@ -1738,9 +628,9 @@ The consultant findings here — single root `KeyboardListener` will conflict wi Remaining cleanup (deletion of the now-vestigial `KeybindingResolver` class + the legacy `KeyboardListener` wrap once Flutter Shortcuts widget integration is on every feature) is small and lands as part of T-100 or its own follow-up. -Original text: Replace root KeyboardListener with scoped Shortcuts/Actions; move KebindingResolver off layout-dependent keyLabel.', NULL, '2026-05-17 19:39:08', '2026-05-17 19:39:08', '2026-05-17 19:39:08', NULL, '6364d644fbbfa903390ee78b5b402367', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-110', 'status', 'backlog', 'done', NULL, '2026-05-17 19:39:12', '2026-05-17 19:39:12', '2026-05-17 19:39:12', NULL, '6073b1bbc12001914d78d4cb419705dc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', '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. +Original text: Replace root KeyboardListener with scoped Shortcuts/Actions; move KebindingResolver off layout-dependent keyLabel.', NULL, '2026-05-17 19:39:08', '2026-05-17 19:39:08', '2026-05-17 19:39:08', NULL, '4fe67f53227d766b1a22b7525d582964', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6MWAKNB2NE3ZZ0XKW', 'status', 'backlog', 'done', NULL, '2026-05-17 19:39:12', '2026-05-17 19:39:12', '2026-05-17 19:39:12', NULL, 'b0053edfaed038c4b67cb73993d8c505', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', '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. **Acceptance:** - `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. @@ -1764,19 +654,16 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, **Implementation hints:** - Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). - Bindings live in the keymap (T-117) — not in `lib/kernel/src/commands/keybindings.dart` (that file is legacy). -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', NULL, '2026-05-17 19:39:23', '2026-05-17 19:39:23', '2026-05-17 19:39:23', NULL, '50203b7db93490ac779fe496865cf3a0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-64', '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.', '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. +- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', NULL, '2026-05-17 19:39:23', '2026-05-17 19:39:23', '2026-05-17 19:39:23', NULL, '15f4f40ab5603b535426cf92d0748e28', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM60QRRNEEWG84VWKXC', 'description', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows. -**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today. +**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.). **Acceptance:** -1. `assets/keymaps/vscode.yaml` ships covering the documented VS Code default keybindings. -2. `KeymapService.setPreset("vscode")` activates the preset and all asserted bindings resolve as expected. -3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, …). -4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p → quick-open command, ctrl+shift+p → palette). - -**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).', NULL, '2026-05-17 19:39:37', '2026-05-17 19:39:37', '2026-05-17 19:39:37', NULL, 'a97758128bd490f603f11a68883a7193', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', '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.', '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. +1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings. +2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve. +3. A regression test exercises a representative subset (e.g. shift+shift → quick-open command — see Q-9 if the search-everywhere overlay needs its own intent).', NULL, '2026-05-17 19:39:37', '2026-05-17 19:39:37', '2026-05-17 19:39:37', NULL, '1d2b5bc9cd0104ddc65584d5e37fccac', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V7GHH42RHV04DPTR', '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.', '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. **Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place; modal Vim presets are more involved than the VS Code preset (T-64) because modes need to be expressed as scope flags (`vim.normal`, `vim.insert`, `vim.visual`) that the when-clause grammar can branch on. Implementation work: @@ -1787,43 +674,46 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, **Acceptance:** 1. `assets/keymaps/vim.yaml` ships covering the documented Vim default keybindings for editor / navigation / panes. 2. `KeymapService.setPreset("vim")` + the mode-tracking service together produce correct mode transitions. -3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert).', NULL, '2026-05-17 19:39:37', '2026-05-17 19:39:37', '2026-05-17 19:39:37', NULL, 'bf10f0149ccaa1e02050280b28305816', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-66', 'description', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows. +3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert).', NULL, '2026-05-17 19:39:37', '2026-05-17 19:39:37', '2026-05-17 19:39:37', NULL, '8ce561041d6225942a2dc42aa1c11f1f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42M9RK4399B4F4WSG', '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.', '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. -**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.). +**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today. **Acceptance:** -1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings. -2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve. -3. A regression test exercises a representative subset (e.g. shift+shift → quick-open command — see Q-9 if the search-everywhere overlay needs its own intent).', NULL, '2026-05-17 19:39:37', '2026-05-17 19:39:37', '2026-05-17 19:39:37', NULL, 'c1129f4cb3454b99e1cbf645ca56bea5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-117', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:39:40', '2026-05-17 19:39:40', '2026-05-17 19:39:40', NULL, '75b2fd14ceb7daf28645ccf84cb041f1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-100', 'status', 'in_progress', 'in_progress', NULL, '2026-05-17 19:40:44', '2026-05-17 19:40:44', '2026-05-17 19:40:44', NULL, '65600862f5787501dcd057756cdb4313', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-100', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:47:50', '2026-05-17 19:47:50', '2026-05-17 19:47:50', NULL, '7d3c0ad4c47d3271ac35c5159e6e9d0e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-105', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:52:39', '2026-05-17 19:52:39', '2026-05-17 19:52:39', NULL, '129acb5d6b1882cb6ed4c9be46fd3e81', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-105', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:56:28', '2026-05-17 19:56:28', '2026-05-17 19:56:28', NULL, '62dfdadc31b2c231afd837fb46f0a114', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-108', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:58:13', '2026-05-17 19:58:13', '2026-05-17 19:58:13', NULL, 'a5dbfe5881e152e82538181b0fe56609', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-108', 'status', 'in_progress', 'done', NULL, '2026-05-17 20:05:38', '2026-05-17 20:05:38', '2026-05-17 20:05:38', NULL, '6815472fbfaa6d7740945b42d4c87f57', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-112', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 20:06:30', '2026-05-17 20:06:30', '2026-05-17 20:06:30', NULL, '52271781bf987a984d9f219956034d1f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-112', 'status', 'in_progress', 'done', NULL, '2026-05-17 20:11:16', '2026-05-17 20:11:16', '2026-05-17 20:11:16', NULL, '6d51964a3ad7f1948f54a7c769336841', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-114', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 20:13:36', '2026-05-17 20:13:36', '2026-05-17 20:13:36', NULL, '0bad40fa7f165f828bb0c0757044491e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-118', 'status', 'backlog', 'done', NULL, '2026-05-17 20:53:41', '2026-05-17 20:53:41', '2026-05-17 20:53:41', NULL, 'ff0126655805fed0ebe45f313c5ceff7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-114', 'status', 'in_progress', 'done', NULL, '2026-05-18 07:04:38', '2026-05-18 07:04:38', '2026-05-18 07:04:38', NULL, '8cf198f8c824fa8f46277b7e22e68f0c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-111', 'status', 'in_progress', 'done', NULL, '2026-05-18 07:43:45', '2026-05-18 07:43:45', '2026-05-18 07:43:45', NULL, '792efcf7c772de7cda7069ea3fc3ac0a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-104', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 07:49:44', '2026-05-18 07:49:44', '2026-05-18 07:49:44', NULL, 'bf7f2746c252f8b8893f4fdc06d459b8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-104', 'status', 'in_progress', 'done', NULL, '2026-05-18 07:53:10', '2026-05-18 07:53:10', '2026-05-18 07:53:10', NULL, 'ce2b66658977edc3fddc78079d7852e5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-109', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 08:02:06', '2026-05-18 08:02:06', '2026-05-18 08:02:06', NULL, 'ee75fea173ae492a6ad8a1d888a2142d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-109', 'status', 'in_progress', 'done', NULL, '2026-05-18 08:07:23', '2026-05-18 08:07:23', '2026-05-18 08:07:23', NULL, '9dd61b76946909aa44edaf9d1c1741a0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-121', 'status', 'backlog', 'done', NULL, '2026-05-18 08:22:19', '2026-05-18 08:22:19', '2026-05-18 08:22:19', NULL, '861af9256207314d681e51abc77c386f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-115', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 08:23:34', '2026-05-18 08:23:34', '2026-05-18 08:23:34', NULL, '94f4a894c1dddfa87ee8306a9758c808', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-115', 'status', 'in_progress', 'done', NULL, '2026-05-18 08:26:05', '2026-05-18 08:26:05', '2026-05-18 08:26:05', NULL, 'b8978359fc1b83ea0bcfc6cd5d34d5de', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-115', 'status', 'done', 'done', NULL, '2026-05-18 09:30:59', '2026-05-18 09:30:59', '2026-05-18 09:30:59', NULL, '2039c525646caaf89a735454f7278162', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-107', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 10:27:25', '2026-05-18 10:27:25', '2026-05-18 10:27:25', NULL, 'e40d2c9ddd9915ad2e32302bd555c99f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-107', 'status', 'in_progress', 'done', NULL, '2026-05-18 10:30:30', '2026-05-18 10:30:30', '2026-05-18 10:30:30', NULL, 'd5ecc2578b5b48991d07531eb2166529', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-116', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 10:35:47', '2026-05-18 10:35:47', '2026-05-18 10:35:47', NULL, '59b70e93c913952d4f13b9ad2320e060', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-116', 'status', 'in_progress', 'done', NULL, '2026-05-18 11:22:46', '2026-05-18 11:22:46', '2026-05-18 11:22:46', NULL, '16fd285587d7a37090d2a21f4307c8e8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'ready', 'in_progress', NULL, '2026-05-18 11:49:28', '2026-05-18 11:49:28', '2026-05-18 11:49:28', NULL, 'e25e13a5afcbc376b3c57312c6f320ea', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'status', 'in_progress', 'done', NULL, '2026-05-18 11:51:40', '2026-05-18 11:51:40', '2026-05-18 11:51:40', NULL, '4343b4758e9ffbf3e046d11d65dfeb1f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'description', 'First slice of T-99(a). Foundation for the rest. +1. `assets/keymaps/vscode.yaml` ships covering the documented VS Code default keybindings. +2. `KeymapService.setPreset("vscode")` activates the preset and all asserted bindings resolve as expected. +3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, …). +4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p → quick-open command, ctrl+shift+p → palette). + +**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).', NULL, '2026-05-17 19:39:37', '2026-05-17 19:39:37', '2026-05-17 19:39:37', NULL, 'a70098892707371c14599cd1050d89b9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65SSVGN5M36T4QNDC', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:39:40', '2026-05-17 19:39:40', '2026-05-17 19:39:40', NULL, '8ae1038fd1701a0484732e8e20772c0f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4CWANNSKNF2X5JEJ4', 'status', 'in_progress', 'in_progress', NULL, '2026-05-17 19:40:44', '2026-05-17 19:40:44', '2026-05-17 19:40:44', NULL, 'e91f3adc4acec66fcfb2c5fd73324a30', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4CWANNSKNF2X5JEJ4', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:47:50', '2026-05-17 19:47:50', '2026-05-17 19:47:50', NULL, '51a7c0ed8d79e1d1f516bc4d556f411f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63H3CXCKB13E3RNHW', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:52:39', '2026-05-17 19:52:39', '2026-05-17 19:52:39', NULL, '9edfe3f6b37d786f5d00b17181fefc06', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63H3CXCKB13E3RNHW', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:56:28', '2026-05-17 19:56:28', '2026-05-17 19:56:28', NULL, 'e60554d0996f98460b97819d0362d810', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40PV7G53JMHHNYPYG', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 19:58:13', '2026-05-17 19:58:13', '2026-05-17 19:58:13', NULL, '9fa8b484c180f26b75933e4ac01a3828', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40PV7G53JMHHNYPYG', 'status', 'in_progress', 'done', NULL, '2026-05-17 20:05:38', '2026-05-17 20:05:38', '2026-05-17 20:05:38', NULL, '3dfd5a8b13f1387e1b15e79636f32a84', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4ZNR15BSANVX07N1R', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 20:06:30', '2026-05-17 20:06:30', '2026-05-17 20:06:30', NULL, 'f9616e2b024c2760eab01bcd30b56079', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4ZNR15BSANVX07N1R', 'status', 'in_progress', 'done', NULL, '2026-05-17 20:11:16', '2026-05-17 20:11:16', '2026-05-17 20:11:16', NULL, 'a65c4cbc1e4a557e4a35c38e4ed2d965', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7FG92QKNFJSCAP888', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 20:13:36', '2026-05-17 20:13:36', '2026-05-17 20:13:36', NULL, '70af64557c1523b663e5d3a6c9071c90', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W20PVMHX1BBJ4TVW', 'status', 'backlog', 'done', NULL, '2026-05-17 20:53:41', '2026-05-17 20:53:41', '2026-05-17 20:53:41', NULL, '7b3ef7fc72604df888823ce2e81e4707', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7FG92QKNFJSCAP888', 'status', 'in_progress', 'done', NULL, '2026-05-18 07:04:38', '2026-05-18 07:04:38', '2026-05-18 07:04:38', NULL, '1ac37706c269d08f3328e37373036aaa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7Y9KHNVRZ41VYW1YM', 'status', 'in_progress', 'done', NULL, '2026-05-18 07:43:45', '2026-05-18 07:43:45', '2026-05-18 07:43:45', NULL, '54a83b58503f712db6a66f81e1fcaee1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5W67PPRVA8SAJXHFG', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 07:49:44', '2026-05-18 07:49:44', '2026-05-18 07:49:44', NULL, 'f9f660ab131c46cb9b047dbb488eb7bf', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5W67PPRVA8SAJXHFG', 'status', 'in_progress', 'done', NULL, '2026-05-18 07:53:10', '2026-05-18 07:53:10', '2026-05-18 07:53:10', NULL, '9952cd327c3ed571e25d175669ac191f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TTAC53CQYWDQ91H0', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 08:02:06', '2026-05-18 08:02:06', '2026-05-18 08:02:06', NULL, '87ed992b6d2de7501488da5c4e64792a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TTAC53CQYWDQ91H0', 'status', 'in_progress', 'done', NULL, '2026-05-18 08:07:23', '2026-05-18 08:07:23', '2026-05-18 08:07:23', NULL, 'bf1c77708b8b9cfd1f23ef5f9a543b8e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45DS16DX0NKMN9Q3G', 'status', 'backlog', 'done', NULL, '2026-05-18 08:22:19', '2026-05-18 08:22:19', '2026-05-18 08:22:19', NULL, '185150db20d6aefa9d9ed5131fbc8264', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM614M7ES54V3SETAQM', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 08:23:34', '2026-05-18 08:23:34', '2026-05-18 08:23:34', NULL, '9b5feb0e1db2a8e496b060cad1c7963f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM614M7ES54V3SETAQM', 'status', 'in_progress', 'done', NULL, '2026-05-18 08:26:05', '2026-05-18 08:26:05', '2026-05-18 08:26:05', NULL, 'b010c3299440040aea27725ae1241358', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM614M7ES54V3SETAQM', 'status', 'done', 'done', NULL, '2026-05-18 09:30:59', '2026-05-18 09:30:59', '2026-05-18 09:30:59', NULL, '2185f1e5d19711b8f43661f5085ae710', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7FXS713CKYDZ284NC', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 10:27:25', '2026-05-18 10:27:25', '2026-05-18 10:27:25', NULL, '8e81149ffa0a14a80a1f1f8fa71455fd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7FXS713CKYDZ284NC', 'status', 'in_progress', 'done', NULL, '2026-05-18 10:30:30', '2026-05-18 10:30:30', '2026-05-18 10:30:30', NULL, '9a58be38bf3b684d9fa3baefed4a92d1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4H5C2Z6GCBCQ85QGR', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 10:35:47', '2026-05-18 10:35:47', '2026-05-18 10:35:47', NULL, '6d917d529afc150f10d985d360a3b967', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4H5C2Z6GCBCQ85QGR', 'status', 'in_progress', 'done', NULL, '2026-05-18 11:22:46', '2026-05-18 11:22:46', '2026-05-18 11:22:46', NULL, 'd41074acbccaf30a6073c372a7a29437', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53N56Z60B4SQ5VTRG', 'status', 'ready', 'in_progress', NULL, '2026-05-18 11:49:28', '2026-05-18 11:49:28', '2026-05-18 11:49:28', NULL, '59589a2d756089fd6615096c60bfeaa6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53N56Z60B4SQ5VTRG', 'status', 'in_progress', 'done', NULL, '2026-05-18 11:51:40', '2026-05-18 11:51:40', '2026-05-18 11:51:40', NULL, '879b32124030afaf7d68ba087cf359ac', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', 'description', 'First slice of T-99(a). Foundation for the rest. Open a per-user unix-domain socket on Flutter app boot. Accept JSON-lines per the existing IpcRequest envelope. Route each request through the existing DaemonDispatcher (already wired in main.dart via daemonClientFactory). Tear down on app shutdown. @@ -1851,24 +741,24 @@ Open a per-workspace unix-domain socket on Flutter app boot. Accept JSON-lines p 5. Stale socket on boot (left over from a crashed clide) is detected and unlinked before binding — same `live-daemon probe` pattern already used elsewhere in the codebase. 6. No client yet — that lands in T-126. -Source: T-99 sketch. Coordinates with: T-127 (InProcessClient swap), T-130 (MCP).', NULL, '2026-05-18 12:42:56', '2026-05-18 12:42:56', '2026-05-18 12:42:56', NULL, 'abb7a039f73d430cdba594b2a4ac381a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 12:45:10', '2026-05-18 12:45:10', '2026-05-18 12:45:10', NULL, 'aa1b70849399f4caee043d40e48d1edf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'status', 'in_progress', 'done', NULL, '2026-05-18 12:51:08', '2026-05-18 12:51:08', '2026-05-18 12:51:08', NULL, 'b928ce09ae0b7dbb77bf93f8c0593657', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-125', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 15:49:20', '2026-05-18 15:49:20', '2026-05-18 15:49:20', NULL, 'ac0838fac9e3c97f1728256910776bed', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-125', 'status', 'in_progress', 'done', NULL, '2026-05-18 15:51:09', '2026-05-18 15:51:09', '2026-05-18 15:51:09', NULL, '938624de487aa2f02e034424cae40c77', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-126', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 15:54:20', '2026-05-18 15:54:20', '2026-05-18 15:54:20', NULL, '3266e7ea943867f49ff20a90b7f12ae6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-126', 'status', 'in_progress', 'done', NULL, '2026-05-18 16:04:29', '2026-05-18 16:04:29', '2026-05-18 16:04:29', NULL, 'b6c783f599585e55ecc36f69ccbcefb1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 09:57:18', '2026-05-19 09:57:18', '2026-05-19 09:57:18', NULL, '64a14cb5d8b9274fd9d72d1e7474040c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'status', 'in_progress', 'done', NULL, '2026-05-19 10:03:26', '2026-05-19 10:03:26', '2026-05-19 10:03:26', NULL, 'b8bf877d2057f7baa5acf6af7a13d529', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-128', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 10:05:09', '2026-05-19 10:05:09', '2026-05-19 10:05:09', NULL, '763a80ec25b5fe712e5ad44b75dbb22f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-128', 'status', 'in_progress', 'done', NULL, '2026-05-19 10:06:46', '2026-05-19 10:06:46', '2026-05-19 10:06:46', NULL, 'cc53b46764f28515d735aa77c8eeef2c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-129', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 12:06:39', '2026-05-19 12:06:39', '2026-05-19 12:06:39', NULL, '575584f46d21b51bab3e69da1b940718', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-129', 'status', 'in_progress', 'done', NULL, '2026-05-19 12:19:51', '2026-05-19 12:19:51', '2026-05-19 12:19:51', NULL, 'e9bf50652357521c7ee74bd6577e8108', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-130', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 12:24:44', '2026-05-19 12:24:44', '2026-05-19 12:24:44', NULL, '63dd7e0c4346a5d3cdd81c9c477339c3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-130', 'status', 'in_progress', 'done', NULL, '2026-05-19 12:33:52', '2026-05-19 12:33:52', '2026-05-19 12:33:52', NULL, 'f3060e086423af96f99684e3718cac45', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-131', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 13:10:27', '2026-05-19 13:10:27', '2026-05-19 13:10:27', NULL, 'c49e76c430de0cc42d7919ade9c5cfa5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'status', 'backlog', 'ready', NULL, '2026-05-19 13:14:01', '2026-05-19 13:14:01', '2026-05-19 13:14:01', NULL, '726631190b62bd38fb34f7201d2f27ee', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'description', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. +Source: T-99 sketch. Coordinates with: T-127 (InProcessClient swap), T-130 (MCP).', NULL, '2026-05-18 12:42:56', '2026-05-18 12:42:56', '2026-05-18 12:42:56', NULL, '7ac14a6f8a8ba6ec8f4cf138956b46bb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 12:45:10', '2026-05-18 12:45:10', '2026-05-18 12:45:10', NULL, '02c3171a24474a20bbe49b21a96e6e9c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', 'status', 'in_progress', 'done', NULL, '2026-05-18 12:51:08', '2026-05-18 12:51:08', '2026-05-18 12:51:08', NULL, 'ffb7647d89437c6890e2dc2e1c6b8cb8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KY9JVRKF22GGFMXR', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 15:49:20', '2026-05-18 15:49:20', '2026-05-18 15:49:20', NULL, '6a488712e7154ef4c128ca928db0e23c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KY9JVRKF22GGFMXR', 'status', 'in_progress', 'done', NULL, '2026-05-18 15:51:09', '2026-05-18 15:51:09', '2026-05-18 15:51:09', NULL, 'd7076683f38aba1b28f50de0cbcdab41', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78E2NAM4YK3A2AV6R', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 15:54:20', '2026-05-18 15:54:20', '2026-05-18 15:54:20', NULL, 'd5b1ed541c6fd4d70242b8439888ed04', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78E2NAM4YK3A2AV6R', 'status', 'in_progress', 'done', NULL, '2026-05-18 16:04:29', '2026-05-18 16:04:29', '2026-05-18 16:04:29', NULL, '6bb520de9df19930fcaa9b279f1185f4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7J40S0A8Q8PC0PDY8', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 09:57:18', '2026-05-19 09:57:18', '2026-05-19 09:57:18', NULL, 'e9b3ac3e3eafe285d2403a8f23036873', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7J40S0A8Q8PC0PDY8', 'status', 'in_progress', 'done', NULL, '2026-05-19 10:03:26', '2026-05-19 10:03:26', '2026-05-19 10:03:26', NULL, '6d59a79e0be2351acd7413d5cef64aea', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KEY6593FMW8Y40VC', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 10:05:09', '2026-05-19 10:05:09', '2026-05-19 10:05:09', NULL, 'cee6b37bf74c28029a92aec7818950d4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KEY6593FMW8Y40VC', 'status', 'in_progress', 'done', NULL, '2026-05-19 10:06:46', '2026-05-19 10:06:46', '2026-05-19 10:06:46', NULL, 'cbbf70d8a605106007aa9f967004c7a6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40XWGSN99DBDQDHJR', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 12:06:39', '2026-05-19 12:06:39', '2026-05-19 12:06:39', NULL, 'c076f132f34ea46743decb5674e2983b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40XWGSN99DBDQDHJR', 'status', 'in_progress', 'done', NULL, '2026-05-19 12:19:51', '2026-05-19 12:19:51', '2026-05-19 12:19:51', NULL, 'ebff32b846426f2a80d2c04cfa480d32', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5MW2V3QCMWKMY4VQM', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 12:24:44', '2026-05-19 12:24:44', '2026-05-19 12:24:44', NULL, '5aaff832e2c56b337cd2098fd304fb1b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5MW2V3QCMWKMY4VQM', 'status', 'in_progress', 'done', NULL, '2026-05-19 12:33:52', '2026-05-19 12:33:52', '2026-05-19 12:33:52', NULL, '1b865a197bdff755e910934d70168021', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5ZSRZARG7SXCC71NM', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 13:10:27', '2026-05-19 13:10:27', '2026-05-19 13:10:27', NULL, 'd52228bd699440b8a3d06cb0051aa860', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54DENVAR46RXCCQQM', 'status', 'backlog', 'ready', NULL, '2026-05-19 13:14:01', '2026-05-19 13:14:01', '2026-05-19 13:14:01', NULL, 'd0d80b843e17d0c2acaaf56168a67f13', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47ARMYK2H24KJ0DAG', 'description', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. This needs design before code: - Where the schema lives (per-handler? a central registry?) @@ -1888,12 +778,12 @@ Design still open: - Where the schema lives (per-handler? central registry?). - Whether it generates handler boilerplate or wraps existing handlers. -Source: consultants.md "Security — Findings — [Major]" item 1.', NULL, '2026-05-19 13:14:12', '2026-05-19 13:14:12', '2026-05-19 13:14:12', NULL, '3511c5e7731f568de7af33c1970ad97e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-99', 'status', 'backlog', 'done', NULL, '2026-05-19 13:14:41', '2026-05-19 13:14:41', '2026-05-19 13:14:41', NULL, '5ff47ab0fc7fb8cb8e9e039be48ada2a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-131', 'status', 'in_progress', 'done', NULL, '2026-05-19 13:14:41', '2026-05-19 13:14:41', '2026-05-19 13:14:41', NULL, 'a57b3fbe4e6815fcbb28691a08130801', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'status', 'ready', 'in_progress', NULL, '2026-05-19 14:44:43', '2026-05-19 14:44:43', '2026-05-19 14:44:43', NULL, '1d5048cf9a031c504aa3df570a7b0cae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'status', 'in_progress', 'done', NULL, '2026-05-20 07:00:06', '2026-05-20 07:00:06', '2026-05-20 07:00:06', NULL, '1f2c2bcc722417d8f4c5bfa27faf0c93', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'description', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. +Source: consultants.md "Security — Findings — [Major]" item 1.', NULL, '2026-05-19 13:14:12', '2026-05-19 13:14:12', '2026-05-19 13:14:12', NULL, '3427323d3bd3517eb0e8dedb28a6efef', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5ZSRZARG7SXCC71NM', 'status', 'in_progress', 'done', NULL, '2026-05-19 13:14:41', '2026-05-19 13:14:41', '2026-05-19 13:14:41', NULL, 'a17d5be1e4420574542229e7d27c9d35', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM76Q97D8R01CRTTA9R', 'status', 'backlog', 'done', NULL, '2026-05-19 13:14:41', '2026-05-19 13:14:41', '2026-05-19 13:14:41', NULL, 'd5b58743a4e139fd663a01997e8469ef', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54DENVAR46RXCCQQM', 'status', 'ready', 'in_progress', NULL, '2026-05-19 14:44:43', '2026-05-19 14:44:43', '2026-05-19 14:44:43', NULL, 'df201c0c544298618007c3ee0e230e41', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54DENVAR46RXCCQQM', 'status', 'in_progress', 'done', NULL, '2026-05-20 07:00:06', '2026-05-20 07:00:06', '2026-05-20 07:00:06', NULL, 'f672ac4dff68835a39899da2ac9e6423', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47ARMYK2H24KJ0DAG', 'description', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. Re-scope (2026-05-19, post T-99): the wire contract is now real — IpcRequest envelopes flow over the unix socket (per D-70/71/72) and through the C clide client which serialises raw argv under the `_argv` sentinel (per D-72/D-6). The argv parser (lib/src/cli/argv_to_request.dart) is the de-facto schema today; T-120 should formalise it by: @@ -1924,11 +814,11 @@ Acceptance: 4. No third-party validation dep; no lint suppressions. 5. Tests cover: schema accept/reject per constraint kind, dispatcher-level rejection, and that an unschema-d command still dispatches. -Source: consultants.md "Security — Findings — [Major]" item 1. Decision: D-74.', NULL, '2026-05-20 15:54:35', '2026-05-20 15:54:35', '2026-05-20 15:54:35', NULL, '7f972739c82134897869432a3978d6ec', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'status', 'backlog', 'in_progress', NULL, '2026-05-20 15:54:38', '2026-05-20 15:54:38', '2026-05-20 15:54:38', NULL, 'fa11190ac1bd9cd8f67e3041a8ca7236', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'status', 'in_progress', 'done', NULL, '2026-05-20 16:02:35', '2026-05-20 16:02:35', '2026-05-20 16:02:35', NULL, 'efffdd07c7281977d37ce0ad4285a36a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-97', 'status', 'backlog', 'done', NULL, '2026-05-20 16:02:46', '2026-05-20 16:02:46', '2026-05-20 16:02:46', NULL, '9c36d26b5ac3702441a87c7f88dbf830', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'description', 'T-115''s render-only widget test at test/builtin/welcome/widget_test.dart ''sticky-startup toggle renders on a seeded recent row'' verifies the toggle is constructed but does not exercise the tap → setStickyStartup path. Adding a tap-driven assertion hung the 10-minute Flutter test timeout consistently (find.byTooltip, find.bySemanticsLabel, and find.byKey + tap variants all reproduced). +Source: consultants.md "Security — Findings — [Major]" item 1. Decision: D-74.', NULL, '2026-05-20 15:54:35', '2026-05-20 15:54:35', '2026-05-20 15:54:35', NULL, 'e6041f7e0470590f30a6bb80af6b18c7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47ARMYK2H24KJ0DAG', 'status', 'backlog', 'in_progress', NULL, '2026-05-20 15:54:38', '2026-05-20 15:54:38', '2026-05-20 15:54:38', NULL, 'dc89817bf00d2908e902209cc0730467', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47ARMYK2H24KJ0DAG', 'status', 'in_progress', 'done', NULL, '2026-05-20 16:02:35', '2026-05-20 16:02:35', '2026-05-20 16:02:35', NULL, '82baedcc5e37cfcb0b82ab22c7ddd79d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM56CF9W94FSFDSBKGW', 'status', 'backlog', 'done', NULL, '2026-05-20 16:02:46', '2026-05-20 16:02:46', '2026-05-20 16:02:46', NULL, 'ae49e66e1ed65303d871edfe8a06b563', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM41S6RRTMTY9TYZA50', 'description', 'T-115''s render-only widget test at test/builtin/welcome/widget_test.dart ''sticky-startup toggle renders on a seeded recent row'' verifies the toggle is constructed but does not exercise the tap → setStickyStartup path. Adding a tap-driven assertion hung the 10-minute Flutter test timeout consistently (find.byTooltip, find.bySemanticsLabel, and find.byKey + tap variants all reproduced). Hypothesis: ClideTooltip wraps the toggle in a MouseRegion whose onEnter awaits Future.delayed(showDelay). Some path during tap simulation (or surrounding pump scheduling) keeps that timer pending, and the test runner waits the full 10-minute idle timeout before declaring it stuck. @@ -1952,9 +842,9 @@ Next steps: - Decide whether this is a real ClideTappable bug (nested tappables would also hang the live app welcome screen with recents) or strictly a canSizeOverlay-intrinsic-sizing test artifact. Check the running app: open welcome with >=1 recent project and confirm it does NOT hang. If the app is fine, the fix is harness-side (give WelcomeView tight constraints so no intrinsic pass), and the shared harness or a welcome-specific harness needs adjusting. If the app DOES hang, ClideTappable has a real nested-layout bug — fix it (likely in lib/widgets/src/clide_tappable.dart intrinsic/layout handling) and this becomes higher priority. - Once unblocked, this also unblocks lib/builtin/welcome/src/welcome_view.dart coverage (74 uncovered lines, the dominant lib/builtin/ gap toward the T-89 95% target). -ProjectManager sticky-startup logic is already covered by test/kernel/src/project_test.dart (14 cases).', NULL, '2026-05-22 06:55:02', '2026-05-22 06:55:02', '2026-05-22 06:55:02', NULL, 'b7c13548cef5d962c7b5b4500ea39b9e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'priority', 'low', 'medium', NULL, '2026-05-22 06:55:02', '2026-05-22 06:55:02', '2026-05-22 06:55:02', NULL, 'e7d5dd1b89eded661de7f6d64edea04c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'description', 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: +ProjectManager sticky-startup logic is already covered by test/kernel/src/project_test.dart (14 cases).', NULL, '2026-05-22 06:55:02', '2026-05-22 06:55:02', '2026-05-22 06:55:02', NULL, '45912dee362057b31a6cc3a5ac1e655b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM41S6RRTMTY9TYZA50', 'priority', 'low', 'medium', NULL, '2026-05-22 06:55:02', '2026-05-22 06:55:02', '2026-05-22 06:55:02', NULL, 'ed04aae88743fa7e9c163efd27b1eb69', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TNXTJCQBH9VADC4M', 'description', 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: | area | hit / total | % | |---|---|---| @@ -2070,10 +960,30 @@ The gate plumbing lands first so future test additions are visible as floor bump - `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. **Cross-references:** D-66 (this epic''s decision record), D-29 (pre-push gate scope), "Own the rendering stack" guardrail (CLAUDE.md). -', NULL, '2026-05-22 13:37:43', '2026-05-22 13:37:43', '2026-05-22 13:37:43', NULL, 'b83aa45596e35a95e689b922c66c27ef', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'status', 'in_progress', 'done', NULL, '2026-05-22 13:37:46', '2026-05-22 13:37:46', '2026-05-22 13:37:46', NULL, '5698b8e8cbd5113a84791ae2dad26c6d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'status', 'in_progress', 'done', NULL, '2026-05-22 13:37:46', '2026-05-22 13:37:46', '2026-05-22 13:37:46', NULL, 'dad314c05e04d8f17357ac2ea054a9bd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-134', 'description', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7.', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7. +', NULL, '2026-05-22 13:37:43', '2026-05-22 13:37:43', '2026-05-22 13:37:43', NULL, '67756f59ec0115154ccdff7864a0e382', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TNXTJCQBH9VADC4M', 'status', 'in_progress', 'done', NULL, '2026-05-22 13:37:46', '2026-05-22 13:37:46', '2026-05-22 13:37:46', NULL, '9af456821e9ddb37ed0de6c456f04d08', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4HGK6KR0314P2M32M', 'status', 'in_progress', 'done', NULL, '2026-05-22 13:37:46', '2026-05-22 13:37:46', '2026-05-22 13:37:46', NULL, 'e08e69b0b58edd48b5c6d82dd59a2695', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SRMD12DMXCX6N7DM', 'description', 'Make clide''s native text widgets selectable so a SelectionArea can select across them. Change lib/widgets/src/clide_markdown.dart RichText (line ~68) -> Text.rich; same for lib/widgets/src/clide_code_block.dart; add a selectionBackground token to SurfaceTokens (lib/kernel/src/theme/tokens.dart + resolver + bundled theme YAMLs); verify copy via existing ClideClipboard (lib/kernel/src/clipboard.dart). Acceptance: a SelectionArea-wrapped column of markdown + code blocks supports select-across-blocks + Ctrl+C copy; widget tests; a11y unaffected; coverage >= floor. Reusable foundation. Blocks T5.', 'Make clide''s native text widgets selectable so a SelectionArea can select across them. Change lib/widgets/src/clide_markdown.dart RichText (line ~68) -> Text.rich; same for lib/widgets/src/clide_code_block.dart; add a selectionBackground token to SurfaceTokens (lib/kernel/src/theme/tokens.dart + resolver + bundled theme YAMLs); verify copy via existing ClideClipboard (lib/kernel/src/clipboard.dart). Acceptance: a SelectionArea-wrapped column of markdown + code blocks supports select-across-blocks + Ctrl+C copy; widget tests; a11y unaffected; coverage >= floor. Reusable foundation. Blocks T5. + +--- +Refinement (2026-05-22, refinement-agent review): +- Convert ALL RichText sites, not one: clide_markdown.dart lines 68, 238, 242 + clide_code_block.dart:79 -> Text.rich (Text.rich wires the SelectionArea selectionRegistrar; same underlying render object). +- Tables (clide_markdown.dart:230) and tappable WidgetSpan links (ClideTappable ~293-310) do NOT participate in SelectionArea. v1 scope: text + code blocks are the selectable surface; tables/link-spans are known non-selectable islands (revisit later). Acceptance = select+copy across text/code blocks. +- selectionBackground token: add to SurfaceTokens (field + TokenKeys + TokenKeys.all) + resolver.dart (_defaultSurfaceMap default + constructor). Bundled theme YAMLs do NOT need it (default map covers it); per-theme override only if the default reads wrong. Do NOT retune existing palettes (published themes are user contracts). +- Add a selection contrast pair (globalForeground on selectionBackground) to lib/kernel/src/theme/contrast.dart canonicalPairs; re-run test/a11y. (Note: summer-night.yaml is missing from contrast_test bundledPaths -- pre-existing inconsistency, out of scope.) +- COPY PATH CORRECTION: SelectionArea Ctrl+C goes through Flutter services.Clipboard directly, NOT ClideClipboard. Test verifies Clipboard.getData returns the selection after copy. +- Coverage floor is 95%.', NULL, '2026-05-22 16:41:09', '2026-05-22 16:41:09', '2026-05-22 16:41:09', NULL, '60e49800b82e42c5c180b11d97d658a6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54N0ZDJRS7G6MP1XC', 'description', 'New lib/builtin/claude/src/transcript_reader.dart. Resolve munged project path (/ -> -) + session-id; tail the append-only JSONL; parse records (type: user/assistant/attachment/tool; message; agentId; isSidechain) into ConversationItem models; drift-guard on the version field (log + degrade on unknown schema). Paths validated this session: ~/.claude/projects//.jsonl (lead) and /subagents/agent-.jsonl (teammates). Acceptance: unit tests against a snapshotted JSONL fixture (assistant text, tool_use, tool_result); appended lines stream in; unknown version degrades gracefully + logs. Blocks T5.', 'New lib/builtin/claude/src/transcript_reader.dart. Resolve munged project path (/ -> -) + session-id; tail the append-only JSONL; parse records (type: user/assistant/attachment/tool; message; agentId; isSidechain) into ConversationItem models; drift-guard on the version field (log + degrade on unknown schema). Paths validated this session: ~/.claude/projects//.jsonl (lead) and /subagents/agent-.jsonl (teammates). Acceptance: unit tests against a snapshotted JSONL fixture (assistant text, tool_use, tool_result); appended lines stream in; unknown version degrades gracefully + logs. Blocks T5. + +--- +Refinement (2026-05-22, refinement-agent review): READY. Verified against real transcripts. +- Munge = absolutePath.replaceAll(''/'',''-'') (KEEP the leading ''-''; dirs start with -var-). +- Envelope fields: type, uuid, parentUuid, isSidechain, sessionId, version, timestamp. Render: user (message.content = String OR array of {type:text|tool_result}); assistant (message.content = array of {type:thinking|text|tool_use}). Skip attachment/system/last-prompt/permission-mode/file-history-snapshot/queue-operation (bookkeeping). +- SESSION-ID: claude does not expose its session UUID via argv/env (verified in claude_pane spawn). Approach: newest *.jsonl by mtime in ~/.claude/projects//; poll mtime and switch the tail when a newer session appears. +- Subagent transcripts: //subagents/agent-.jsonl + sibling .meta.json ({agentType,description}); each record carries agentId. +- Tail: dart:io File.openRead with a byte-position cursor (File.length + RandomAccessFile.setPosition on poll). Do NOT use lib/src/files/watcher.dart. Zero new deps. +- Logger: lib/kernel/src/log.dart for the version drift-guard. Coverage floor 95%.', NULL, '2026-05-22 16:41:09', '2026-05-22 16:41:09', '2026-05-22 16:41:09', NULL, '623338a8cffa82f2aeef7c4d989af63e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4EQAERS4J9X9RAETR', 'description', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7.', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7. --- Refinement (2026-05-22, refinement-agent review): @@ -2084,36 +994,16 @@ Refinement (2026-05-22, refinement-agent review): - tmux -L clide socket may not exist unless the app is running; spike creates its own: tmux -L clide new-session -d -s teamhost ... claude. - Check 4 (paste): scope to @path file references (verifiable via send-keys headlessly). Clipboard-image needs an interactive display ($DISPLAY) -- note as interactive-only. - ACP directionality (an-idea.md s5) is OUT OF SCOPE: Zed-client concern; clide owns its GUI and does not use ACP. -- Deliverable: findings note at docs/spikes/cc-team-internals-2.1.148.md (in-repo), feeding D-75 when authored (T-133). D-75 is claimed-but-unauthored (T-133''s job) -- the reference is forward-looking, not dangling.', NULL, '2026-05-22 16:41:09', '2026-05-22 16:41:09', '2026-05-22 16:41:09', NULL, '05456ee9ae72721893f6850158d59791', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-135', 'description', 'Make clide''s native text widgets selectable so a SelectionArea can select across them. Change lib/widgets/src/clide_markdown.dart RichText (line ~68) -> Text.rich; same for lib/widgets/src/clide_code_block.dart; add a selectionBackground token to SurfaceTokens (lib/kernel/src/theme/tokens.dart + resolver + bundled theme YAMLs); verify copy via existing ClideClipboard (lib/kernel/src/clipboard.dart). Acceptance: a SelectionArea-wrapped column of markdown + code blocks supports select-across-blocks + Ctrl+C copy; widget tests; a11y unaffected; coverage >= floor. Reusable foundation. Blocks T5.', 'Make clide''s native text widgets selectable so a SelectionArea can select across them. Change lib/widgets/src/clide_markdown.dart RichText (line ~68) -> Text.rich; same for lib/widgets/src/clide_code_block.dart; add a selectionBackground token to SurfaceTokens (lib/kernel/src/theme/tokens.dart + resolver + bundled theme YAMLs); verify copy via existing ClideClipboard (lib/kernel/src/clipboard.dart). Acceptance: a SelectionArea-wrapped column of markdown + code blocks supports select-across-blocks + Ctrl+C copy; widget tests; a11y unaffected; coverage >= floor. Reusable foundation. Blocks T5. - ---- -Refinement (2026-05-22, refinement-agent review): -- Convert ALL RichText sites, not one: clide_markdown.dart lines 68, 238, 242 + clide_code_block.dart:79 -> Text.rich (Text.rich wires the SelectionArea selectionRegistrar; same underlying render object). -- Tables (clide_markdown.dart:230) and tappable WidgetSpan links (ClideTappable ~293-310) do NOT participate in SelectionArea. v1 scope: text + code blocks are the selectable surface; tables/link-spans are known non-selectable islands (revisit later). Acceptance = select+copy across text/code blocks. -- selectionBackground token: add to SurfaceTokens (field + TokenKeys + TokenKeys.all) + resolver.dart (_defaultSurfaceMap default + constructor). Bundled theme YAMLs do NOT need it (default map covers it); per-theme override only if the default reads wrong. Do NOT retune existing palettes (published themes are user contracts). -- Add a selection contrast pair (globalForeground on selectionBackground) to lib/kernel/src/theme/contrast.dart canonicalPairs; re-run test/a11y. (Note: summer-night.yaml is missing from contrast_test bundledPaths -- pre-existing inconsistency, out of scope.) -- COPY PATH CORRECTION: SelectionArea Ctrl+C goes through Flutter services.Clipboard directly, NOT ClideClipboard. Test verifies Clipboard.getData returns the selection after copy. -- Coverage floor is 95%.', NULL, '2026-05-22 16:41:09', '2026-05-22 16:41:09', '2026-05-22 16:41:09', NULL, '5e218db621577a309b493611dfb72b63', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-136', 'description', 'New lib/builtin/claude/src/transcript_reader.dart. Resolve munged project path (/ -> -) + session-id; tail the append-only JSONL; parse records (type: user/assistant/attachment/tool; message; agentId; isSidechain) into ConversationItem models; drift-guard on the version field (log + degrade on unknown schema). Paths validated this session: ~/.claude/projects//.jsonl (lead) and /subagents/agent-.jsonl (teammates). Acceptance: unit tests against a snapshotted JSONL fixture (assistant text, tool_use, tool_result); appended lines stream in; unknown version degrades gracefully + logs. Blocks T5.', 'New lib/builtin/claude/src/transcript_reader.dart. Resolve munged project path (/ -> -) + session-id; tail the append-only JSONL; parse records (type: user/assistant/attachment/tool; message; agentId; isSidechain) into ConversationItem models; drift-guard on the version field (log + degrade on unknown schema). Paths validated this session: ~/.claude/projects//.jsonl (lead) and /subagents/agent-.jsonl (teammates). Acceptance: unit tests against a snapshotted JSONL fixture (assistant text, tool_use, tool_result); appended lines stream in; unknown version degrades gracefully + logs. Blocks T5. - ---- -Refinement (2026-05-22, refinement-agent review): READY. Verified against real transcripts. -- Munge = absolutePath.replaceAll(''/'',''-'') (KEEP the leading ''-''; dirs start with -var-). -- Envelope fields: type, uuid, parentUuid, isSidechain, sessionId, version, timestamp. Render: user (message.content = String OR array of {type:text|tool_result}); assistant (message.content = array of {type:thinking|text|tool_use}). Skip attachment/system/last-prompt/permission-mode/file-history-snapshot/queue-operation (bookkeeping). -- SESSION-ID: claude does not expose its session UUID via argv/env (verified in claude_pane spawn). Approach: newest *.jsonl by mtime in ~/.claude/projects//; poll mtime and switch the tail when a newer session appears. -- Subagent transcripts: //subagents/agent-.jsonl + sibling .meta.json ({agentType,description}); each record carries agentId. -- Tail: dart:io File.openRead with a byte-position cursor (File.length + RandomAccessFile.setPosition on poll). Do NOT use lib/src/files/watcher.dart. Zero new deps. -- Logger: lib/kernel/src/log.dart for the version drift-guard. Coverage floor 95%.', NULL, '2026-05-22 16:41:09', '2026-05-22 16:41:09', '2026-05-22 16:41:09', NULL, 'c040ed1bf6bad363c3d199bead879374', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-136', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 16:46:34', '2026-05-22 16:46:34', '2026-05-22 16:46:34', NULL, '176db3f23c4a758fb601c50c9ae2644b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-134', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 16:46:34', '2026-05-22 16:46:34', '2026-05-22 16:46:34', NULL, '5e5f91eab17e4acefcc0c9e286787742', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-135', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 16:46:34', '2026-05-22 16:46:34', '2026-05-22 16:46:34', NULL, '79aed523589f14481c115538ae8b357c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-133', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 16:46:34', '2026-05-22 16:46:34', '2026-05-22 16:46:34', NULL, 'e11fb2f37bf99262989f1881a7deed9c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-133', 'status', 'in_progress', 'done', NULL, '2026-05-22 17:37:23', '2026-05-22 17:37:23', '2026-05-22 17:37:23', NULL, '010ab0fda58e4be2ea699c102e1b2695', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-135', 'status', 'in_progress', 'done', NULL, '2026-05-22 17:37:23', '2026-05-22 17:37:23', '2026-05-22 17:37:23', NULL, '1bfd08b75a99bbf3c46408b0c5f799f2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-136', 'status', 'in_progress', 'done', NULL, '2026-05-22 17:37:23', '2026-05-22 17:37:23', '2026-05-22 17:37:23', NULL, 'eea74c531ac4796fe6dba221018e80dd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-134', 'status', 'in_progress', 'done', NULL, '2026-05-22 17:55:11', '2026-05-22 17:55:11', '2026-05-22 17:55:11', NULL, '13385e9b6de3d3979b03c22de9db73f6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-134', 'description', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7. +- Deliverable: findings note at docs/spikes/cc-team-internals-2.1.148.md (in-repo), feeding D-75 when authored (T-133). D-75 is claimed-but-unauthored (T-133''s job) -- the reference is forward-looking, not dangling.', NULL, '2026-05-22 16:41:09', '2026-05-22 16:41:09', '2026-05-22 16:41:09', NULL, 'e3b9b7960c78336f7fa4a095db06dc7c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4EQAERS4J9X9RAETR', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 16:46:34', '2026-05-22 16:46:34', '2026-05-22 16:46:34', NULL, '6d9714ad43872b9b8d583938d2d6cf2f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54N0ZDJRS7G6MP1XC', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 16:46:34', '2026-05-22 16:46:34', '2026-05-22 16:46:34', NULL, '9a3a408b461511d3499694056ea023f6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54G017Y29APWJTCN0', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 16:46:34', '2026-05-22 16:46:34', '2026-05-22 16:46:34', NULL, 'e7e368ffee0558811342780494a4d519', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SRMD12DMXCX6N7DM', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 16:46:34', '2026-05-22 16:46:34', '2026-05-22 16:46:34', NULL, 'e953d97d758c00f62fbd03809ca945f7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54G017Y29APWJTCN0', 'status', 'in_progress', 'done', NULL, '2026-05-22 17:37:23', '2026-05-22 17:37:23', '2026-05-22 17:37:23', NULL, '72faa8c2f7a4f819e44407048a7a75b5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54N0ZDJRS7G6MP1XC', 'status', 'in_progress', 'done', NULL, '2026-05-22 17:37:23', '2026-05-22 17:37:23', '2026-05-22 17:37:23', NULL, '762f8c0db683609496fff161afdfa762', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SRMD12DMXCX6N7DM', 'status', 'in_progress', 'done', NULL, '2026-05-22 17:37:23', '2026-05-22 17:37:23', '2026-05-22 17:37:23', NULL, '8874f30724718d5ad12a0aaaac8e4da3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4EQAERS4J9X9RAETR', 'status', 'in_progress', 'done', NULL, '2026-05-22 17:55:11', '2026-05-22 17:55:11', '2026-05-22 17:55:11', NULL, '091515e1edaeaabe8cfd1926ba91cc1e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4EQAERS4J9X9RAETR', 'description', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7. --- Refinement (2026-05-22, refinement-agent review): @@ -2138,21 +1028,21 @@ Refinement (2026-05-22, refinement-agent review): - Deliverable: findings note at docs/spikes/cc-team-internals-2.1.148.md (in-repo), feeding D-75 when authored (T-133). D-75 is claimed-but-unauthored (T-133''s job) -- the reference is forward-looking, not dangling. --- -Findings (2026-05-22): docs/spikes/cc-team-internals-2.1.148.md. All 5 checks answered from real artifacts + tmux manual + synthetic control-mode test (no live run/quota). Key risk for T-139: pane->teammate->transcript identity joins only on agentType (config <-> .meta.json), ambiguous for same-type teammates -> needs joinedAt/mtime or lead-transcript tiebreaker. tmux 3.6a has no %pane-died; poll list-panes -a. Teammate transcripts at //subagents/agent-.jsonl + .meta.json{agentType,description}.', NULL, '2026-05-22 17:55:11', '2026-05-22 17:55:11', '2026-05-22 17:55:11', NULL, 'dbf4f56d1ec9d2234f255826c5f88f68', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-137', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 17:58:07', '2026-05-22 17:58:07', '2026-05-22 17:58:07', NULL, '4e67262d4b04f8541e71fe8f03106fbf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-137', 'status', 'in_progress', 'done', NULL, '2026-05-22 18:14:32', '2026-05-22 18:14:32', '2026-05-22 18:14:32', NULL, 'a284c7249dda3c63a52a202bad07f105', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-138', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 21:08:05', '2026-05-22 21:08:05', '2026-05-22 21:08:05', NULL, 'c3dfefe24d912081cd89ac0be066b641', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-138', 'status', 'in_progress', 'done', NULL, '2026-05-22 21:49:09', '2026-05-22 21:49:09', '2026-05-22 21:49:09', NULL, 'e4be02e339d638da70a4e7ce0f850691', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-142', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 22:09:09', '2026-05-22 22:09:09', '2026-05-22 22:09:09', NULL, 'b799657bf436bc44e306ef71658c80e5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-142', 'status', 'in_progress', 'done', NULL, '2026-05-22 22:18:02', '2026-05-22 22:18:02', '2026-05-22 22:18:02', NULL, '374be08610920e404c8394bc10b1e12a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-143', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 22:31:25', '2026-05-22 22:31:25', '2026-05-22 22:31:25', NULL, 'e2a16eb8af0afc30a5d0fc88c523e507', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-143', 'status', 'in_progress', 'done', NULL, '2026-05-22 22:36:09', '2026-05-22 22:36:09', '2026-05-22 22:36:09', NULL, '9b8859ea2e8db7083bfb9d2ab17c1c57', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-144', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 22:41:28', '2026-05-22 22:41:28', '2026-05-22 22:41:28', NULL, 'eda6f75e88401ee5b445e7edfc312f82', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-144', 'status', 'in_progress', 'done', NULL, '2026-05-22 22:43:45', '2026-05-22 22:43:45', '2026-05-22 22:43:45', NULL, 'da17b2710e17b1821882d47724790cbb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-139', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 22:50:41', '2026-05-22 22:50:41', '2026-05-22 22:50:41', NULL, 'ad063f0c3c65e5b35d3dcea036412ad9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-139', 'status', 'in_progress', 'done', NULL, '2026-05-22 23:16:28', '2026-05-22 23:16:28', '2026-05-22 23:16:28', NULL, 'add82dc118e2081500f19cbb321272d4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-140', 'status', 'backlog', 'done', NULL, '2026-05-22 23:31:46', '2026-05-22 23:31:46', '2026-05-22 23:31:46', NULL, 'e6204c6f0a0dbd38396a69dfef2c7080', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-145', 'description', NULL, 'A compact per-session status strip in the lead Claude pane header AND each teammate tile header (T-140), showing: current model, permission mode (accept-edits / plan / default / bypass), and context-window usage %. +Findings (2026-05-22): docs/spikes/cc-team-internals-2.1.148.md. All 5 checks answered from real artifacts + tmux manual + synthetic control-mode test (no live run/quota). Key risk for T-139: pane->teammate->transcript identity joins only on agentType (config <-> .meta.json), ambiguous for same-type teammates -> needs joinedAt/mtime or lead-transcript tiebreaker. tmux 3.6a has no %pane-died; poll list-panes -a. Teammate transcripts at //subagents/agent-.jsonl + .meta.json{agentType,description}.', NULL, '2026-05-22 17:55:11', '2026-05-22 17:55:11', '2026-05-22 17:55:11', NULL, '852f023b15d1b431d3cb4b4a3b0e6d4c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6E3X5WWRS09GCDGK8', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 17:58:07', '2026-05-22 17:58:07', '2026-05-22 17:58:07', NULL, '119331652c100e0f67793c9fcf1e189c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6E3X5WWRS09GCDGK8', 'status', 'in_progress', 'done', NULL, '2026-05-22 18:14:32', '2026-05-22 18:14:32', '2026-05-22 18:14:32', NULL, '7cb7a6a1f58d3c58b3c6d25e236ed8fc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4V1PZ0EK6G60699EM', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 21:08:05', '2026-05-22 21:08:05', '2026-05-22 21:08:05', NULL, 'ae44ec2f04ebd641d263a8a400dd4e39', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4V1PZ0EK6G60699EM', 'status', 'in_progress', 'done', NULL, '2026-05-22 21:49:09', '2026-05-22 21:49:09', '2026-05-22 21:49:09', NULL, 'eb025d3c98e8e8c674f2f38e9ea7bad0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427ZFSTWJ21EBK5FG', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 22:09:09', '2026-05-22 22:09:09', '2026-05-22 22:09:09', NULL, 'fbd368fd0e20dab4a3dcb81da4ec2fbe', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427ZFSTWJ21EBK5FG', 'status', 'in_progress', 'done', NULL, '2026-05-22 22:18:02', '2026-05-22 22:18:02', '2026-05-22 22:18:02', NULL, '3ce22dd528b17c315e1bd9bd88621e72', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YWAC8SSGQBK31T98', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 22:31:25', '2026-05-22 22:31:25', '2026-05-22 22:31:25', NULL, '4fbbf0ea850fa1e3d8c89ed59553c951', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YWAC8SSGQBK31T98', 'status', 'in_progress', 'done', NULL, '2026-05-22 22:36:09', '2026-05-22 22:36:09', '2026-05-22 22:36:09', NULL, 'a3cfd0c0167696ae0bd029ea4b5b9fcb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BV0CXWCT04Z81EG0', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 22:41:28', '2026-05-22 22:41:28', '2026-05-22 22:41:28', NULL, '40ae1847463c75dffc6d72d2ec93877d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BV0CXWCT04Z81EG0', 'status', 'in_progress', 'done', NULL, '2026-05-22 22:43:45', '2026-05-22 22:43:45', '2026-05-22 22:43:45', NULL, '20d617e0b1a47e3176d384bec402bf6c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM653J6604KYDK6RT8R', 'status', 'backlog', 'in_progress', NULL, '2026-05-22 22:50:41', '2026-05-22 22:50:41', '2026-05-22 22:50:41', NULL, 'e2e2ade9e7875fb57a2cba98ec802895', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM653J6604KYDK6RT8R', 'status', 'in_progress', 'done', NULL, '2026-05-22 23:16:28', '2026-05-22 23:16:28', '2026-05-22 23:16:28', NULL, '832a6105c592a0b5cd444c92f080b3bd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AVSMKD15KBJYX48W', 'status', 'backlog', 'done', NULL, '2026-05-22 23:31:46', '2026-05-22 23:31:46', '2026-05-22 23:31:46', NULL, 'd4837452b103b25a960bda5d62b3624d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KN50C7A723SEKZXG', 'description', NULL, 'A compact per-session status strip in the lead Claude pane header AND each teammate tile header (T-140), showing: current model, permission mode (accept-edits / plan / default / bypass), and context-window usage %. Shared status source (this ticket owns it): extract per-session status in the drift-contained reader/observer (D-75) — do NOT parse CC internals in the widgets. - Model: team config member.model, overridden by the latest assistant message.model in the transcript when present. @@ -2164,8 +1054,8 @@ Delivery: emit a per-session status to the UI via the bus/events (e.g. a TeamMem Applies to the lead session and every teammate. Re-validate field names against a live team run / CC version bump (undocumented, per T-134). Acceptance: lead pane header and each teammate tile header show current model + an accept-edits/plan-mode badge + a context-usage indicator, updating live from the transcript. Blocked by T-137 (done), T-140 (done). -', NULL, '2026-05-23 06:07:59', '2026-05-23 06:07:59', '2026-05-23 06:07:59', NULL, '26bb1c89f3fef8d916f3eb6a1ba9d065', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-141', 'description', 'New sidebar panel contributed to Slots.sidebar: roster from ~/.claude/teams config; live status verb / elapsed / down+up tokens / edit-mode from transcript tailing; team token budget pulled from the Anthropic usage API on a timer. Acceptance: roster matches list-panes/config; budget refreshes on the timer; appears in team mode. Blocked by T7.', 'New sidebar panel (contributed to Slots.sidebar), visible only in team mode: a roster row per team member that MIRRORS the per-session status surfaced by T-145, plus team-wide meta. +', NULL, '2026-05-23 06:07:59', '2026-05-23 06:07:59', '2026-05-23 06:07:59', NULL, '57e453eaaf1013e185a1f4367c7e8522', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V9RNW2BGT5GHRTRG', 'description', 'New sidebar panel contributed to Slots.sidebar: roster from ~/.claude/teams config; live status verb / elapsed / down+up tokens / edit-mode from transcript tailing; team token budget pulled from the Anthropic usage API on a timer. Acceptance: roster matches list-panes/config; budget refreshes on the timer; appears in team mode. Blocked by T7.', 'New sidebar panel (contributed to Slots.sidebar), visible only in team mode: a roster row per team member that MIRRORS the per-session status surfaced by T-145, plus team-wide meta. Per-member row: - Identity from the team config + observer: name, agentType, color; membership/lifecycle from TeamMemberJoined/Left. @@ -2176,8 +1066,8 @@ Team-wide: - Team/account token budget pulled from the Anthropic usage API on a timer (distinct from per-conversation context above). Acceptance: roster matches list-panes/config; each row mirrors the member''s model + accept-edits/plan badge + context-usage; status verb and token deltas update live; account budget refreshes on its timer; panel appears only when a team is active. Depends on T-145 (status source) + T-139 (done). -', NULL, '2026-05-23 06:08:24', '2026-05-23 06:08:24', '2026-05-23 06:08:24', NULL, 'cfa1ce349be286d89a1f8e169605f04d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-146', 'description', NULL, 'Regression from T-137: every Claude pane renders the newest .jsonl in the workspace''s munged dir, so concurrent sessions in one workspace (primary + secondaries) all converge on the same transcript — a secondary tab shows the primary''s conversation (and the primary follows whichever session is newest). The pre-T-137 terminal pane rendered each tab''s own PTY, so tabs were independent. +', NULL, '2026-05-23 06:08:24', '2026-05-23 06:08:24', '2026-05-23 06:08:24', NULL, 'e58aa9c04c228369ad48215b75ad0cd9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM558PCHET90BBSHGTC', 'description', NULL, 'Regression from T-137: every Claude pane renders the newest .jsonl in the workspace''s munged dir, so concurrent sessions in one workspace (primary + secondaries) all converge on the same transcript — a secondary tab shows the primary''s conversation (and the primary follows whichever session is newest). The pre-T-137 terminal pane rendered each tab''s own PTY, so tabs were independent. Fix: bind each pane to a specific Claude session id. - claude CLI supports `--session-id ` (verified via --help). Spawn each pane''s claude with its own UUID; a transcript is named .jsonl, so the pane''s transcript is /.jsonl. @@ -2190,62 +1080,62 @@ Migration caveat: an existing tmux session created by the OLD code (no --session Open question to validate live: whether `claude --session-id ` resumes vs. errors when re-spawned after the tmux session was killed (display works either way since the file is .jsonl, but worth confirming claude starts). Acceptance: a secondary tab shows its OWN fresh conversation, independent of the primary; the primary keeps its conversation; both update live. Regression of T-137; relates to D-41. -', NULL, '2026-05-23 06:19:51', '2026-05-23 06:19:51', '2026-05-23 06:19:51', NULL, '9e46b8eec7cc5e42132249a81d4925d1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-146', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 06:19:51', '2026-05-23 06:19:51', '2026-05-23 06:19:51', NULL, 'dd4ec6cfbbc3118cd4cf02604b91108b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-146', 'status', 'in_progress', 'done', NULL, '2026-05-23 06:30:47', '2026-05-23 06:30:47', '2026-05-23 06:30:47', NULL, '93f9c6814b9e82e5b60ac0e57fff5afe', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-147', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 07:03:18', '2026-05-23 07:03:18', '2026-05-23 07:03:18', NULL, 'b0bc1949f98ef05a05717cef80ba194f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-147', 'status', 'in_progress', 'done', NULL, '2026-05-23 07:06:54', '2026-05-23 07:06:54', '2026-05-23 07:06:54', NULL, 'b6b88954fdc04ecf7847dea204a92e14', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-149', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 07:14:36', '2026-05-23 07:14:36', '2026-05-23 07:14:36', NULL, '75bb1fa2d964f284910201b1c15995b9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-149', 'status', 'in_progress', 'done', NULL, '2026-05-23 07:27:32', '2026-05-23 07:27:32', '2026-05-23 07:27:32', NULL, 'd020ddfea99355676800af1e39249c85', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-145', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 08:03:56', '2026-05-23 08:03:56', '2026-05-23 08:03:56', NULL, 'e50db235944f48a5f581ae64a3589385', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-145', 'status', 'in_progress', 'done', NULL, '2026-05-23 08:43:19', '2026-05-23 08:43:19', '2026-05-23 08:43:19', NULL, 'c9cf511becf7e542d465701e51dba4e2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-150', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 09:04:54', '2026-05-23 09:04:54', '2026-05-23 09:04:54', NULL, 'fcc43ef1e1ae3c1dfa0e9c94ffa35bed', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-150', 'status', 'in_progress', 'done', NULL, '2026-05-23 09:33:03', '2026-05-23 09:33:03', '2026-05-23 09:33:03', NULL, '58c9b0734a70fe8c7083c3cc456a1bd6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-151', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 09:54:31', '2026-05-23 09:54:31', '2026-05-23 09:54:31', NULL, '52df08bff6479b6a84c128f5a63ea312', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-151', 'status', 'in_progress', 'done', NULL, '2026-05-23 10:20:12', '2026-05-23 10:20:12', '2026-05-23 10:20:12', NULL, 'ed8ce28a1b0c7e995887dcfd32456aec', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-153', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 10:31:31', '2026-05-23 10:31:31', '2026-05-23 10:31:31', NULL, 'd5a1f02df5c8be27b9dc62b1ce277d67', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-153', 'status', 'in_progress', 'done', NULL, '2026-05-23 10:35:56', '2026-05-23 10:35:56', '2026-05-23 10:35:56', NULL, 'ddf23a60cdfbfcd79b5d68af10f37928', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-152', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 10:37:57', '2026-05-23 10:37:57', '2026-05-23 10:37:57', NULL, 'e12bfc4f8896514fb13450e0f83cf6c6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-152', 'status', 'in_progress', 'done', NULL, '2026-05-23 10:43:52', '2026-05-23 10:43:52', '2026-05-23 10:43:52', NULL, 'edab8c3daaabe51797afbe70b5f9bfde', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-156', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 11:30:35', '2026-05-23 11:30:35', '2026-05-23 11:30:35', NULL, '66000ad896a8ccd2ec91d2ae6c539f01', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-156', 'status', 'in_progress', 'done', NULL, '2026-05-23 12:05:19', '2026-05-23 12:05:19', '2026-05-23 12:05:19', NULL, '24db72624201c89a5c0bcb3b11840275', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-154', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 14:40:01', '2026-05-23 14:40:01', '2026-05-23 14:40:01', NULL, 'aff6dbb3095efd46cf786a53d8622d42', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-155', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 14:40:05', '2026-05-23 14:40:05', '2026-05-23 14:40:05', NULL, 'e46f5eba5bd678190954744c2f8338bc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-154', 'status', 'in_progress', 'done', NULL, '2026-05-23 14:50:17', '2026-05-23 14:50:17', '2026-05-23 14:50:17', NULL, '121736159d2d58380447b6a2803cb80b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-155', 'status', 'in_progress', 'done', NULL, '2026-05-23 14:50:17', '2026-05-23 14:50:17', '2026-05-23 14:50:17', NULL, 'b471e923f78ed91f05ca6f2dcbd5c85c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-148', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 14:51:55', '2026-05-23 14:51:55', '2026-05-23 14:51:55', NULL, '3002f91cea5919aaefc3e4bc530c7ac2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-141', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 14:51:55', '2026-05-23 14:51:55', '2026-05-23 14:51:55', NULL, '76975080bd78de4d6f69937fd256d4ef', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-148', 'status', 'in_progress', 'done', NULL, '2026-05-23 15:05:29', '2026-05-23 15:05:29', '2026-05-23 15:05:29', NULL, 'd9d9e4c96d94060f929c7c09935d127c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-141', 'status', 'in_progress', 'done', NULL, '2026-05-23 20:49:44', '2026-05-23 20:49:44', '2026-05-23 20:49:44', NULL, 'fb2405478b0d422d39a924ccedec5022', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 20:52:47', '2026-05-23 20:52:47', '2026-05-23 20:52:47', NULL, 'b2b31ab5fe70fb5027773775b0945bd5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'status', 'in_progress', 'done', NULL, '2026-05-23 20:57:07', '2026-05-23 20:57:07', '2026-05-23 20:57:07', NULL, 'ce114090979d31512a9aa09f1c9311a1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-159', 'status', 'backlog', 'done', NULL, '2026-05-24 08:54:30', '2026-05-24 08:54:30', '2026-05-24 08:54:30', NULL, '478956f24d051f603aab2c7ceec328b2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-161', 'status', 'backlog', 'done', NULL, '2026-05-24 09:45:07', '2026-05-24 09:45:07', '2026-05-24 09:45:07', NULL, 'd7b99c3dff59fc5a70a7866567fbfe0f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-165', 'status', 'backlog', 'in_progress', NULL, '2026-05-24 16:32:59', '2026-05-24 16:32:59', '2026-05-24 16:32:59', NULL, '1dbcae2366b429ad26e65f1a9cfebc33', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-173', 'status', 'backlog', 'in_progress', NULL, '2026-05-24 17:00:14', '2026-05-24 17:00:14', '2026-05-24 17:00:14', NULL, 'c0306e7ca9f755e69c76cdba93bca934', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-173', 'status', 'in_progress', 'done', NULL, '2026-05-24 17:10:07', '2026-05-24 17:10:07', '2026-05-24 17:10:07', NULL, 'f5831d8bfa3e484546cd5dd0c75bbf22', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-166', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 06:23:55', '2026-05-25 06:23:55', '2026-05-25 06:23:55', NULL, '36cc84f2de6519355a55014b107ecea8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-165', 'status', 'in_progress', 'done', NULL, '2026-05-25 06:29:25', '2026-05-25 06:29:25', '2026-05-25 06:29:25', NULL, '34961afff4d3384bced64de93c977460', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-166', 'status', 'in_progress', 'done', NULL, '2026-05-25 06:31:08', '2026-05-25 06:31:08', '2026-05-25 06:31:08', NULL, '3d6b8f30317a82d0aa810b6da47896be', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-176', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 07:11:44', '2026-05-25 07:11:44', '2026-05-25 07:11:44', NULL, '7be78982c929ee18d8665a8e662c380d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-177', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 07:11:44', '2026-05-25 07:11:44', '2026-05-25 07:11:44', NULL, '855a73a398eb53240d12729ac47e0051', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-175', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 07:11:44', '2026-05-25 07:11:44', '2026-05-25 07:11:44', NULL, '8e9a19896103139fe8e43aa4b7212768', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-175', 'status', 'in_progress', 'done', NULL, '2026-05-25 07:25:02', '2026-05-25 07:25:02', '2026-05-25 07:25:02', NULL, '3e63273af832240b9f782fc41ca0047d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-177', 'status', 'in_progress', 'done', NULL, '2026-05-25 07:25:02', '2026-05-25 07:25:02', '2026-05-25 07:25:02', NULL, '73982bc82e335f2d0fd80633aef0c082', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-176', 'status', 'in_progress', 'done', NULL, '2026-05-25 07:27:57', '2026-05-25 07:27:57', '2026-05-25 07:27:57', NULL, '926d5d2694207c65d0f620b2834962aa', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-179', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 08:10:25', '2026-05-25 08:10:25', '2026-05-25 08:10:25', NULL, '059ff72ce3229f45259b097721bfb08f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-178', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 08:10:25', '2026-05-25 08:10:25', '2026-05-25 08:10:25', NULL, '389b95ddaaef1a5ce318bda840b1cde5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-178', 'status', 'in_progress', 'done', NULL, '2026-05-25 08:21:46', '2026-05-25 08:21:46', '2026-05-25 08:21:46', NULL, 'ddaad91987208966c2720dee370738ed', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-179', 'status', 'in_progress', 'done', NULL, '2026-05-25 08:26:21', '2026-05-25 08:26:21', '2026-05-25 08:26:21', NULL, '58dc0afa577b9aee651d683aa1b9bc69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'description', 'A comprehensive read view (with edit affordances where sensible) of the Claude environment, as the Config sub-tab of the sidebar (T-182). Sources: ClaudeConfig (D-76 / T-151, already reads skills, commands, settings, permissions from ~/.claude + .claude layered) plus the stream-json init/initialize payloads (agents, output styles + available styles, MCP servers, models, plugins). Sections: SKILLS, AGENTS, COMMANDS, PERMISSIONS (allow/ask/deny, colour-coded), HOOKS, MCP SERVERS, SETTINGS (model, output style). Show local+global layering (local wins) and that it is watched/live. Covers the interaction-type surfaces documented on anthropic.com so the user can see everything configured for Claude in one place. Acceptance: each section lists its entries from ClaudeConfig + init data; permissions colour-coded by rule kind; layering + source shown; updates live on a watched .claude change; widget tests. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182 (sub-tab shell).', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Each category is an EXPANDABLE accordion section (no ellipsis truncation): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS, MCP SERVERS, SETTINGS, each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what that skill/agent/command says. Non-file entries render inline: permissions (allow/ask/deny, colour-coded), MCP servers, model, output style; SETTINGS can open settings.json. Show local+global layering (local wins) and that it is watched/live. Acceptance: sections expand/collapse to the full list; clicking a file-backed item opens its .md in the reader; permissions colour-coded by rule kind; updates live on a watched .claude change; widget tests for expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', NULL, '2026-05-25 09:31:37', '2026-05-25 09:31:37', '2026-05-25 09:31:37', NULL, '473e88ee9fa895f5dc30fcac5b001fa6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'description', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Each category is an EXPANDABLE accordion section (no ellipsis truncation): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS, MCP SERVERS, SETTINGS, each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what that skill/agent/command says. Non-file entries render inline: permissions (allow/ask/deny, colour-coded), MCP servers, model, output style; SETTINGS can open settings.json. Show local+global layering (local wins) and that it is watched/live. Acceptance: sections expand/collapse to the full list; clicking a file-backed item opens its .md in the reader; permissions colour-coded by rule kind; updates live on a watched .claude change; widget tests for expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered, local wins) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Layout: SETTINGS is a FIXED key->value table pinned at the top (not collapsible) — model, output style, permission mode, source. Everything variable-length is an EXPANDABLE accordion section showing the FULL list when expanded, never truncated (truncating to first-N + ellipsis falsely prioritises the first entries and makes the list pointless): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS (grouped allow/ask/deny, colour-coded, each rule on its own line), MCP SERVERS — each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what it says. Watched/live on .claude changes. Acceptance: settings render as a top table; every variable section expands to its complete list (no ellipsis); clicking a file-backed item opens its .md in the reader; permissions colour-coded by kind; widget tests for table + expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', NULL, '2026-05-25 09:35:47', '2026-05-25 09:35:47', '2026-05-25 09:35:47', NULL, '33086bf1d766da9bb8e12ddeeaa8ed92', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'description', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view. Default to Activity. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; existing stats/team content slots into Activity/Team unchanged; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view (T-183). Default to Activity. SHARED GEOMETRY: Activity values and the Config settings table use the SAME two-column label->value table (same column x + row pitch + section-header style) so toggling between tabs does not visually jump. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; Activity + Config settings render on the shared table so toggling is seamless; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', NULL, '2026-05-25 09:38:15', '2026-05-25 09:38:15', '2026-05-25 09:38:15', NULL, 'a41f10a11412d0f7ee0cd5e0e7700488', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 10:49:30', '2026-05-25 10:49:30', '2026-05-25 10:49:30', NULL, '3269bcb75eb13dffc13be53c9f71ca73', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'status', 'in_progress', 'done', NULL, '2026-05-25 12:12:29', '2026-05-25 12:12:29', '2026-05-25 12:12:29', NULL, 'c93755e2b277ae1b120a58fe3ea59246', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-170', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 12:12:33', '2026-05-25 12:12:33', '2026-05-25 12:12:33', NULL, '494120eadfe8d6ba67c065fa63180cda', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-170', 'status', 'in_progress', 'done', NULL, '2026-05-25 19:38:16', '2026-05-25 19:38:16', '2026-05-25 19:38:16', NULL, '6a30f33668d2d24a78badd8fe680e724', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 20:01:12', '2026-05-25 20:01:12', '2026-05-25 20:01:12', NULL, 'b0f1c94a5b064c098881defb61d27ad4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-168', 'description', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165.', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165. +', NULL, '2026-05-23 06:19:51', '2026-05-23 06:19:51', '2026-05-23 06:19:51', NULL, 'bd7df5842fedc116701cd2c724b413cb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM558PCHET90BBSHGTC', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 06:19:51', '2026-05-23 06:19:51', '2026-05-23 06:19:51', NULL, 'fcec889a61815521dfa95635ea7ca193', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM558PCHET90BBSHGTC', 'status', 'in_progress', 'done', NULL, '2026-05-23 06:30:47', '2026-05-23 06:30:47', '2026-05-23 06:30:47', NULL, '667a77184091cf8c694084217edbf39f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47FEEMHPE8J5VVBKM', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 07:03:18', '2026-05-23 07:03:18', '2026-05-23 07:03:18', NULL, '844fe6e4c3129d5474987b5f666448e1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47FEEMHPE8J5VVBKM', 'status', 'in_progress', 'done', NULL, '2026-05-23 07:06:54', '2026-05-23 07:06:54', '2026-05-23 07:06:54', NULL, '7abca75bf75a9fa47792665679016209', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GDS1R8H2TW12WX7G', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 07:14:36', '2026-05-23 07:14:36', '2026-05-23 07:14:36', NULL, '51429a4b88869492350d6632a0c81e69', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GDS1R8H2TW12WX7G', 'status', 'in_progress', 'done', NULL, '2026-05-23 07:27:32', '2026-05-23 07:27:32', '2026-05-23 07:27:32', NULL, '0c0c07c7900ac16b16ffe2488430618e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KN50C7A723SEKZXG', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 08:03:56', '2026-05-23 08:03:56', '2026-05-23 08:03:56', NULL, '725ce8dda74a92369d6201ee60df96dc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KN50C7A723SEKZXG', 'status', 'in_progress', 'done', NULL, '2026-05-23 08:43:19', '2026-05-23 08:43:19', '2026-05-23 08:43:19', NULL, '173492b6e16f29bcb26ff991421c4d38', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6F9XA7F2BX9P18BR4', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 09:04:54', '2026-05-23 09:04:54', '2026-05-23 09:04:54', NULL, '574f2e3318fb9df0140e7d4c2366e604', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6F9XA7F2BX9P18BR4', 'status', 'in_progress', 'done', NULL, '2026-05-23 09:33:03', '2026-05-23 09:33:03', '2026-05-23 09:33:03', NULL, 'a29c2c18a7672f81421c49323028a510', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77GWREHKA9K45P020', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 09:54:31', '2026-05-23 09:54:31', '2026-05-23 09:54:31', NULL, 'ad633bae3df7b9fa3f707d6170c37c83', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77GWREHKA9K45P020', 'status', 'in_progress', 'done', NULL, '2026-05-23 10:20:12', '2026-05-23 10:20:12', '2026-05-23 10:20:12', NULL, '259ab5800a4967d7c5aef3117052fe5c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7BJS5E1JJGSJ9XHG0', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 10:31:31', '2026-05-23 10:31:31', '2026-05-23 10:31:31', NULL, '2b1b28038e82e4d075a03451ba600c9a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7BJS5E1JJGSJ9XHG0', 'status', 'in_progress', 'done', NULL, '2026-05-23 10:35:56', '2026-05-23 10:35:56', '2026-05-23 10:35:56', NULL, 'abbef6a5994a36b7db4f057d2f426467', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YSQYCE096R2SFRXG', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 10:37:57', '2026-05-23 10:37:57', '2026-05-23 10:37:57', NULL, 'b9b78000f385559b7d2590b71fd764c3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YSQYCE096R2SFRXG', 'status', 'in_progress', 'done', NULL, '2026-05-23 10:43:52', '2026-05-23 10:43:52', '2026-05-23 10:43:52', NULL, '96dc6ff19521a51006507c6c5a94c816', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5B47G9GG22N9KBNEG', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 11:30:35', '2026-05-23 11:30:35', '2026-05-23 11:30:35', NULL, '04fb4c4af1eb319d858c63d44f3ef396', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5B47G9GG22N9KBNEG', 'status', 'in_progress', 'done', NULL, '2026-05-23 12:05:19', '2026-05-23 12:05:19', '2026-05-23 12:05:19', NULL, '591a034c97d4d7018c748a9958335351', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XM8EY708NAM1JK00', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 14:40:01', '2026-05-23 14:40:01', '2026-05-23 14:40:01', NULL, '9716ae47176c49b8eabe59bdbb087da9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R9Q3T7CBMTHM2GFR', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 14:40:05', '2026-05-23 14:40:05', '2026-05-23 14:40:05', NULL, '1bc2e6b6922d37cb6679ccbbff428c4c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R9Q3T7CBMTHM2GFR', 'status', 'in_progress', 'done', NULL, '2026-05-23 14:50:17', '2026-05-23 14:50:17', '2026-05-23 14:50:17', NULL, '961e63c628c87749e1e60182df791fe4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XM8EY708NAM1JK00', 'status', 'in_progress', 'done', NULL, '2026-05-23 14:50:17', '2026-05-23 14:50:17', '2026-05-23 14:50:17', NULL, 'b19b6d8b1e526b5410671d190b28d433', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V9RNW2BGT5GHRTRG', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 14:51:55', '2026-05-23 14:51:55', '2026-05-23 14:51:55', NULL, 'b7cf6692eb418539abd43524e295d410', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6CYT7NW4WE8ZP1TXR', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 14:51:55', '2026-05-23 14:51:55', '2026-05-23 14:51:55', NULL, 'd04169b1131ca0667be94be47e74dc41', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6CYT7NW4WE8ZP1TXR', 'status', 'in_progress', 'done', NULL, '2026-05-23 15:05:29', '2026-05-23 15:05:29', '2026-05-23 15:05:29', NULL, 'e860ffd0c6f7f98565d7bf1ad7315ed2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V9RNW2BGT5GHRTRG', 'status', 'in_progress', 'done', NULL, '2026-05-23 20:49:44', '2026-05-23 20:49:44', '2026-05-23 20:49:44', NULL, '2b59c3e2b107df11737f5a9956b1f4df', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QZ3JJF1704AV5JCW', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 20:52:47', '2026-05-23 20:52:47', '2026-05-23 20:52:47', NULL, '9afbb8b3cbdf29fbfa8fea199c3fc164', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QZ3JJF1704AV5JCW', 'status', 'in_progress', 'done', NULL, '2026-05-23 20:57:07', '2026-05-23 20:57:07', '2026-05-23 20:57:07', NULL, 'b154cad25c54f1c57a8734cdc3246a12', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KD2DKTGHC6CF1MJ4', 'status', 'backlog', 'done', NULL, '2026-05-24 08:54:30', '2026-05-24 08:54:30', '2026-05-24 08:54:30', NULL, '51a3232801075f9eed739b4aef1265d8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SPRJZ519RZK1ZPAW', 'status', 'backlog', 'done', NULL, '2026-05-24 09:45:07', '2026-05-24 09:45:07', '2026-05-24 09:45:07', NULL, '2b6c87f988406da11e09e91d3943963f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AHSN3BXZFFWS0EPM', 'status', 'backlog', 'in_progress', NULL, '2026-05-24 16:32:59', '2026-05-24 16:32:59', '2026-05-24 16:32:59', NULL, '8828d2be152998ed681ad2dfef8b9637', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5932T486Z1F1M6X6W', 'status', 'backlog', 'in_progress', NULL, '2026-05-24 17:00:14', '2026-05-24 17:00:14', '2026-05-24 17:00:14', NULL, '5151cf12ef3f758f7bb9b3e8e5ff91cf', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5932T486Z1F1M6X6W', 'status', 'in_progress', 'done', NULL, '2026-05-24 17:10:07', '2026-05-24 17:10:07', '2026-05-24 17:10:07', NULL, 'fe9de0c3ac6ab75415268499e7e10b1b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7KA37YDBJ89GJ27H4', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 06:23:55', '2026-05-25 06:23:55', '2026-05-25 06:23:55', NULL, '48adf0dd5e50aa44aa995ceee8c072a4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AHSN3BXZFFWS0EPM', 'status', 'in_progress', 'done', NULL, '2026-05-25 06:29:25', '2026-05-25 06:29:25', '2026-05-25 06:29:25', NULL, 'ef29563b32b7b3aa06af467f7d8001c4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7KA37YDBJ89GJ27H4', 'status', 'in_progress', 'done', NULL, '2026-05-25 06:31:08', '2026-05-25 06:31:08', '2026-05-25 06:31:08', NULL, '0f423cf936d430f40a9dda3a17b74292', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7K30Y9WAYZ3D330YR', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 07:11:44', '2026-05-25 07:11:44', '2026-05-25 07:11:44', NULL, 'c20bb3f139164291ad60216b9ea207b2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RP0Q312AZJ8S1Y6R', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 07:11:44', '2026-05-25 07:11:44', '2026-05-25 07:11:44', NULL, 'd9493c6bb8b215461244885567b33a60', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AXEWDKD4VQNQCACM', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 07:11:44', '2026-05-25 07:11:44', '2026-05-25 07:11:44', NULL, 'ea16c7cad02681e2a2ba57fc6776f9ff', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7K30Y9WAYZ3D330YR', 'status', 'in_progress', 'done', NULL, '2026-05-25 07:25:02', '2026-05-25 07:25:02', '2026-05-25 07:25:02', NULL, '43845eeebfeea4e4bcaa88fefa57b77e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RP0Q312AZJ8S1Y6R', 'status', 'in_progress', 'done', NULL, '2026-05-25 07:25:02', '2026-05-25 07:25:02', '2026-05-25 07:25:02', NULL, 'd06e9c1fc4f20306b09d6f13a8b0e58b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AXEWDKD4VQNQCACM', 'status', 'in_progress', 'done', NULL, '2026-05-25 07:27:57', '2026-05-25 07:27:57', '2026-05-25 07:27:57', NULL, 'cfb0838f7e36c29df16f558c7742b1cc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4DTGYPAB29MRDN000', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 08:10:25', '2026-05-25 08:10:25', '2026-05-25 08:10:25', NULL, '1e88b432487ee6133d34ee5052609f8a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VNVEAEYBX3S9Z8Q8', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 08:10:25', '2026-05-25 08:10:25', '2026-05-25 08:10:25', NULL, 'add56bb035fb7633a33ba133b08d4733', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VNVEAEYBX3S9Z8Q8', 'status', 'in_progress', 'done', NULL, '2026-05-25 08:21:46', '2026-05-25 08:21:46', '2026-05-25 08:21:46', NULL, '038625fcc5321c2eac9d6d2056865a4b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4DTGYPAB29MRDN000', 'status', 'in_progress', 'done', NULL, '2026-05-25 08:26:21', '2026-05-25 08:26:21', '2026-05-25 08:26:21', NULL, '47a0755c50a43979a14d835bbb22db52', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WE0SD9N4HCP9BB64', 'description', 'A comprehensive read view (with edit affordances where sensible) of the Claude environment, as the Config sub-tab of the sidebar (T-182). Sources: ClaudeConfig (D-76 / T-151, already reads skills, commands, settings, permissions from ~/.claude + .claude layered) plus the stream-json init/initialize payloads (agents, output styles + available styles, MCP servers, models, plugins). Sections: SKILLS, AGENTS, COMMANDS, PERMISSIONS (allow/ask/deny, colour-coded), HOOKS, MCP SERVERS, SETTINGS (model, output style). Show local+global layering (local wins) and that it is watched/live. Covers the interaction-type surfaces documented on anthropic.com so the user can see everything configured for Claude in one place. Acceptance: each section lists its entries from ClaudeConfig + init data; permissions colour-coded by rule kind; layering + source shown; updates live on a watched .claude change; widget tests. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182 (sub-tab shell).', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Each category is an EXPANDABLE accordion section (no ellipsis truncation): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS, MCP SERVERS, SETTINGS, each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what that skill/agent/command says. Non-file entries render inline: permissions (allow/ask/deny, colour-coded), MCP servers, model, output style; SETTINGS can open settings.json. Show local+global layering (local wins) and that it is watched/live. Acceptance: sections expand/collapse to the full list; clicking a file-backed item opens its .md in the reader; permissions colour-coded by rule kind; updates live on a watched .claude change; widget tests for expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', NULL, '2026-05-25 09:31:37', '2026-05-25 09:31:37', '2026-05-25 09:31:37', NULL, 'd671af300196429eaf7d2fc700ee5ff3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WE0SD9N4HCP9BB64', 'description', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Each category is an EXPANDABLE accordion section (no ellipsis truncation): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS, MCP SERVERS, SETTINGS, each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what that skill/agent/command says. Non-file entries render inline: permissions (allow/ask/deny, colour-coded), MCP servers, model, output style; SETTINGS can open settings.json. Show local+global layering (local wins) and that it is watched/live. Acceptance: sections expand/collapse to the full list; clicking a file-backed item opens its .md in the reader; permissions colour-coded by rule kind; updates live on a watched .claude change; widget tests for expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered, local wins) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Layout: SETTINGS is a FIXED key->value table pinned at the top (not collapsible) — model, output style, permission mode, source. Everything variable-length is an EXPANDABLE accordion section showing the FULL list when expanded, never truncated (truncating to first-N + ellipsis falsely prioritises the first entries and makes the list pointless): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS (grouped allow/ask/deny, colour-coded, each rule on its own line), MCP SERVERS — each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what it says. Watched/live on .claude changes. Acceptance: settings render as a top table; every variable section expands to its complete list (no ellipsis); clicking a file-backed item opens its .md in the reader; permissions colour-coded by kind; widget tests for table + expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', NULL, '2026-05-25 09:35:47', '2026-05-25 09:35:47', '2026-05-25 09:35:47', NULL, '359336a22df27b4b0c3271ec18632dc1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W624VD4JW1GHDHAR', 'description', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view. Default to Activity. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; existing stats/team content slots into Activity/Team unchanged; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view (T-183). Default to Activity. SHARED GEOMETRY: Activity values and the Config settings table use the SAME two-column label->value table (same column x + row pitch + section-header style) so toggling between tabs does not visually jump. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; Activity + Config settings render on the shared table so toggling is seamless; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', NULL, '2026-05-25 09:38:15', '2026-05-25 09:38:15', '2026-05-25 09:38:15', NULL, 'c79870ad4aae669c953ceded77fba441', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48P8T23ZPR0BRK6PR', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 10:49:30', '2026-05-25 10:49:30', '2026-05-25 10:49:30', NULL, 'b5149722317a11d7d973963fdb1aedfd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48P8T23ZPR0BRK6PR', 'status', 'in_progress', 'done', NULL, '2026-05-25 12:12:29', '2026-05-25 12:12:29', '2026-05-25 12:12:29', NULL, 'b298099933c749fa715780a9962d6daa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM654XV2TG4K0RVV1CW', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 12:12:33', '2026-05-25 12:12:33', '2026-05-25 12:12:33', NULL, '01c09cb52e61e2163c57f48b82d0e9d1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM654XV2TG4K0RVV1CW', 'status', 'in_progress', 'done', NULL, '2026-05-25 19:38:16', '2026-05-25 19:38:16', '2026-05-25 19:38:16', NULL, '657ca376123b3a0ca6c8819056006da3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W624VD4JW1GHDHAR', 'status', 'backlog', 'in_progress', NULL, '2026-05-25 20:01:12', '2026-05-25 20:01:12', '2026-05-25 20:01:12', NULL, '0d3f77e85d264f5cdab536ac72071083', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78W48QX3JZP0QYAVW', 'description', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165.', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165. --- Refinement (2026-05-30, /whats-next) — five seam decisions resolved: @@ -2262,8 +1152,8 @@ Refinement (2026-05-30, /whats-next) — five seam decisions resolved: Files an implementer touches: conversation_view.dart, conversation_controller.dart, transcript_reader.dart, stream_json_session.dart, claude_pane.dart, plus test/builtin/claude/*. Commit the in-flight prompt_card.dart change first (disjoint surface — permission-card Write-description dedup). -Sequencing within the batch: T-160 → T-168.', NULL, '2026-05-30 10:44:06', '2026-05-30 10:44:06', '2026-05-30 10:44:06', NULL, 'f5ea965167b5b95bbe5f24a445e05cc9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-160', 'description', 'Observed via make run (2026-05-24): the bottom status bar shows a RenderFlex ''overflowed by 77 PIXELS'' stripe after the in-pane context line (opus 4.7 · default · 21k ctx · 10 skills); the ClideMarquee never engages. +Sequencing within the batch: T-160 → T-168.', NULL, '2026-05-30 10:44:06', '2026-05-30 10:44:06', '2026-05-30 10:44:06', NULL, '3dbf140d0ba468b9bad30cb8b35f1369', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R8YY69YAARCVE4AC', 'description', 'Observed via make run (2026-05-24): the bottom status bar shows a RenderFlex ''overflowed by 77 PIXELS'' stripe after the in-pane context line (opus 4.7 · default · 21k ctx · 10 skills); the ClideMarquee never engages. Cause: StatusbarHost (lib/app.dart ~1143) lays items in a Row [left items..., Spacer(), right items...], each at INTRINSIC width with no Flexible. PaneContextStatusItem wraps its text in ConstrainedBox(maxWidth:360) + ClideMarquee, but because the text (~280px) is under 360 the slot takes content width, so the marquee''s viewport == its content and it never scrolls. T-154 appended ''· N skills'', lengthening the line enough that git-branch + context slot + right items exceed the bar width -> the Row overflows (~77px) instead of the slot shrinking + marquee scrolling. @@ -2274,59 +1164,59 @@ Cause: StatusbarHost (lib/app.dart ~1143) lays items in a Row [left items..., Sp Fix direction: give the in-pane context slot a flexible/bounded width in the status bar (e.g. wrap it in Flexible(fit: loose), or have the bar allot it a shrinkable share) so it yields width when the bar is tight and ClideMarquee scrolls within the allotted width. Verify the interaction with the Spacer and the right-aligned items (priority>=100), and reconsider the fixed maxWidth:360. Acceptance: at narrow widths the in-pane slot marquee-scrolls and the status bar never shows an overflow stripe; widget test at a constrained width. Introduced by T-150 (marquee) + T-154 (longer line). --- -Refinement (2026-05-30, /whats-next): pulled into the T-167/T-168 batch and sequenced FIRST. T-168 adds live cost / permission-mode / context / rate-limit to this same in-pane status slot; the Flexible/marquee fix here must land before T-168 lengthens the line. After this fix, T-168 can safely add fields to the slot.', NULL, '2026-05-30 10:44:10', '2026-05-30 10:44:10', '2026-05-30 10:44:10', NULL, '4ad98d4a7db42e295fba6dff0988ad3b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-167', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 10:48:32', '2026-05-30 10:48:32', '2026-05-30 10:48:32', NULL, '229c9cb92eb8554dbba51ca65e1eeba9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-168', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 10:48:32', '2026-05-30 10:48:32', '2026-05-30 10:48:32', NULL, '4469254d98eba45964ca51d5855e5014', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-160', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 10:48:32', '2026-05-30 10:48:32', '2026-05-30 10:48:32', NULL, 'e410b6f78d0e4d96bc37f3a351d72767', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-160', 'status', 'in_progress', 'done', NULL, '2026-05-30 11:05:56', '2026-05-30 11:05:56', '2026-05-30 11:05:56', NULL, '0024103be5bf0ab0653b9dbe03c52f71', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-167', 'status', 'in_progress', 'done', NULL, '2026-05-30 11:05:56', '2026-05-30 11:05:56', '2026-05-30 11:05:56', NULL, 'fee8b67a0ad9c7c1c7ec7d9f75f42bfc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-168', 'status', 'in_progress', 'done', NULL, '2026-05-30 11:44:28', '2026-05-30 11:44:28', '2026-05-30 11:44:28', NULL, '152c34a28e7005386a4a6829d942e569', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-172', 'description', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator).', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator). +Refinement (2026-05-30, /whats-next): pulled into the T-167/T-168 batch and sequenced FIRST. T-168 adds live cost / permission-mode / context / rate-limit to this same in-pane status slot; the Flexible/marquee fix here must land before T-168 lengthens the line. After this fix, T-168 can safely add fields to the slot.', NULL, '2026-05-30 10:44:10', '2026-05-30 10:44:10', '2026-05-30 10:44:10', NULL, '17942f0f1d099cb61de11342b6f2f346', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R8YY69YAARCVE4AC', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 10:48:32', '2026-05-30 10:48:32', '2026-05-30 10:48:32', NULL, '6d6a5d385c6e5def98bda99960dc34fc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78W48QX3JZP0QYAVW', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 10:48:32', '2026-05-30 10:48:32', '2026-05-30 10:48:32', NULL, 'af205cc08c4c5d3b230787f511ef026c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7G7MK8MP3BGQC36HG', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 10:48:32', '2026-05-30 10:48:32', '2026-05-30 10:48:32', NULL, 'bd26c63b6599df5c0df11dd52ab09941', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7G7MK8MP3BGQC36HG', 'status', 'in_progress', 'done', NULL, '2026-05-30 11:05:56', '2026-05-30 11:05:56', '2026-05-30 11:05:56', NULL, '363b5ae914800bff49dccc4be4d08d29', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R8YY69YAARCVE4AC', 'status', 'in_progress', 'done', NULL, '2026-05-30 11:05:56', '2026-05-30 11:05:56', '2026-05-30 11:05:56', NULL, '3c483a15ae5767a669ad99dcd433dbea', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78W48QX3JZP0QYAVW', 'status', 'in_progress', 'done', NULL, '2026-05-30 11:44:28', '2026-05-30 11:44:28', '2026-05-30 11:44:28', NULL, '496cec0e8ec5b3fc79aee72e953db2ac', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71N3YMS82BAYD3M9R', 'description', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator).', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator). --- -Refinement (2026-05-30, /whats-next): --fork-session CONFIRMED against the live binary (claude --help: "When resuming, create a new session ID instead of reusing the original (use with --resume or --continue)"). Fork argv = [''--resume'', , ''--fork-session''] (no --session-id; the new branch gets its own id from the init event). Add forkSourceSessionId to SpawnSpec (session_orchestrator.dart); build argv via a forkSessionArgs helper in session_naming.dart; add ''fork'' to kClideOwnedCommands + a case in claude_pane._send; add a fork button to _memberRow in claude_meta_sidebar.dart. SEQUENCE AFTER T-171 (shares _memberRow + orchestrator).', NULL, '2026-05-30 17:12:17', '2026-05-30 17:12:17', '2026-05-30 17:12:17', NULL, '901eb461070872a8dcb61f21546382d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-171', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 17:12:46', '2026-05-30 17:12:46', '2026-05-30 17:12:46', NULL, '04314327ba504ef372a338ed737650a8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-174', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 17:12:46', '2026-05-30 17:12:46', '2026-05-30 17:12:46', NULL, '940531017064b45bd962a8fe7369ec6d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-171', 'status', 'in_progress', 'done', NULL, '2026-05-30 21:11:26', '2026-05-30 21:11:26', '2026-05-30 21:11:26', NULL, '26cb27e63ad95ae94017ada95daef9d2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-174', 'status', 'in_progress', 'done', NULL, '2026-05-30 21:11:26', '2026-05-30 21:11:26', '2026-05-30 21:11:26', NULL, '4df20d29d49e7dd95dd9caafbe956cb2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 21:14:56', '2026-05-30 21:14:56', '2026-05-30 21:14:56', NULL, '6426dc0355b68f0fd572e52d53101fc4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'status', 'in_progress', 'done', NULL, '2026-05-30 21:25:11', '2026-05-30 21:25:11', '2026-05-30 21:25:11', NULL, '81d8d388c7e4f53a4514f05cf5dbf37b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-172', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 21:25:12', '2026-05-30 21:25:12', '2026-05-30 21:25:12', NULL, 'e42556ec9d67731533b85e6da2a1bc9c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-172', 'status', 'in_progress', 'done', NULL, '2026-05-30 21:34:56', '2026-05-30 21:34:56', '2026-05-30 21:34:56', NULL, 'f97c3bb01ea2bc5f969b34d4de09617a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-180', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 21:34:57', '2026-05-30 21:34:57', '2026-05-30 21:34:57', NULL, 'd73e085a28c3c4b2a7ce515d33012de4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-180', 'status', 'in_progress', 'done', NULL, '2026-05-31 08:49:07', '2026-05-31 08:49:07', '2026-05-31 08:49:07', NULL, '7a3ab42f4a4571c00dbb55b2079e1b9b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 08:49:08', '2026-05-31 08:49:08', '2026-05-31 08:49:08', NULL, 'e7e9fd59ba8adf45431b759b693d4022', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'status', 'in_progress', 'done', NULL, '2026-05-31 09:30:11', '2026-05-31 09:30:11', '2026-05-31 09:30:11', NULL, 'd9c300f2f35571747572037bc8cb6983', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-184', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 09:49:55', '2026-05-31 09:49:55', '2026-05-31 09:49:55', NULL, 'c307a16d7851ec46ee57ff8422380ff7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-184', 'status', 'in_progress', 'done', NULL, '2026-05-31 10:17:09', '2026-05-31 10:17:09', '2026-05-31 10:17:09', NULL, '8d23aadcaeebbf9b08ba0ccf495578bf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-185', 'status', 'backlog', 'done', NULL, '2026-05-31 10:22:02', '2026-05-31 10:22:02', '2026-05-31 10:22:02', NULL, 'a91a00cea5b083cc7c555aa0662495c2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-164', 'status', 'backlog', 'done', NULL, '2026-05-31 10:22:03', '2026-05-31 10:22:03', '2026-05-31 10:22:03', NULL, '2738e39138046b8e3704fc64d6f8d787', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-95', 'status', 'backlog', 'done', NULL, '2026-05-31 11:20:21', '2026-05-31 11:20:21', '2026-05-31 11:20:21', NULL, '0236a359632b1fcf101f68a25131abb5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-162', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 11:42:11', '2026-05-31 11:42:11', '2026-05-31 11:42:11', NULL, '39d0ae4510e70f877b5c07417d486ac1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-187', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 11:42:11', '2026-05-31 11:42:11', '2026-05-31 11:42:11', NULL, 'b4eea5fc17737821e8e9ce3d53ea62ba', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-188', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 11:42:11', '2026-05-31 11:42:11', '2026-05-31 11:42:11', NULL, 'fd62fc2dc8e6278288a7c52ad450ab0d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-192', 'description', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow.', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow. +Refinement (2026-05-30, /whats-next): --fork-session CONFIRMED against the live binary (claude --help: "When resuming, create a new session ID instead of reusing the original (use with --resume or --continue)"). Fork argv = [''--resume'', , ''--fork-session''] (no --session-id; the new branch gets its own id from the init event). Add forkSourceSessionId to SpawnSpec (session_orchestrator.dart); build argv via a forkSessionArgs helper in session_naming.dart; add ''fork'' to kClideOwnedCommands + a case in claude_pane._send; add a fork button to _memberRow in claude_meta_sidebar.dart. SEQUENCE AFTER T-171 (shares _memberRow + orchestrator).', NULL, '2026-05-30 17:12:17', '2026-05-30 17:12:17', '2026-05-30 17:12:17', NULL, '46fd16552607707191c8e6683fc4079c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZB63YYNFXM9DQNRR', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 17:12:46', '2026-05-30 17:12:46', '2026-05-30 17:12:46', NULL, '13850741e0bdf5638e31fe8a30a24558', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6FMAHXM1XAHHCQC60', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 17:12:46', '2026-05-30 17:12:46', '2026-05-30 17:12:46', NULL, 'c94d2f60abda4e94863c2e0d85a910ec', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6FMAHXM1XAHHCQC60', 'status', 'in_progress', 'done', NULL, '2026-05-30 21:11:26', '2026-05-30 21:11:26', '2026-05-30 21:11:26', NULL, '7e7b728b1f5c6b75e387225c5ca3c109', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZB63YYNFXM9DQNRR', 'status', 'in_progress', 'done', NULL, '2026-05-30 21:11:26', '2026-05-30 21:11:26', '2026-05-30 21:11:26', NULL, 'e390d222f46bd217baaa532b81d2000f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ET3VQP90WRXVSMG', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 21:14:56', '2026-05-30 21:14:56', '2026-05-30 21:14:56', NULL, '154bbde190501324b57a5adbeecb9d7c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ET3VQP90WRXVSMG', 'status', 'in_progress', 'done', NULL, '2026-05-30 21:25:11', '2026-05-30 21:25:11', '2026-05-30 21:25:11', NULL, 'e99b0c2d67f1258785215340f994467d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71N3YMS82BAYD3M9R', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 21:25:12', '2026-05-30 21:25:12', '2026-05-30 21:25:12', NULL, 'f60ddcfb66f82471f1e92898594fd9b9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71N3YMS82BAYD3M9R', 'status', 'in_progress', 'done', NULL, '2026-05-30 21:34:56', '2026-05-30 21:34:56', '2026-05-30 21:34:56', NULL, '14b789b3e4637e2564da47279c1d7ab1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6V6TKRPA106D5SMMM', 'status', 'backlog', 'in_progress', NULL, '2026-05-30 21:34:57', '2026-05-30 21:34:57', '2026-05-30 21:34:57', NULL, 'ccc9d164ab8f832687a365c2eeade57d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6V6TKRPA106D5SMMM', 'status', 'in_progress', 'done', NULL, '2026-05-31 08:49:07', '2026-05-31 08:49:07', '2026-05-31 08:49:07', NULL, '7f684128771b794441c3dbd3fbb2e38c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WE0SD9N4HCP9BB64', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 08:49:08', '2026-05-31 08:49:08', '2026-05-31 08:49:08', NULL, 'f62a6a8645acd20f66eb2d7bfc17947f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WE0SD9N4HCP9BB64', 'status', 'in_progress', 'done', NULL, '2026-05-31 09:30:11', '2026-05-31 09:30:11', '2026-05-31 09:30:11', NULL, '6bea3617418268da0542d9ab31ab937a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55DZN2RFEVP6CG48G', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 09:49:55', '2026-05-31 09:49:55', '2026-05-31 09:49:55', NULL, '75abfd23772b6d65854ba863a04bc174', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55DZN2RFEVP6CG48G', 'status', 'in_progress', 'done', NULL, '2026-05-31 10:17:09', '2026-05-31 10:17:09', '2026-05-31 10:17:09', NULL, 'afcdbb54533d0d6b5b3dd53bf9c8ec57', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM52PRNJM86A1DZTQ1C', 'status', 'backlog', 'done', NULL, '2026-05-31 10:22:02', '2026-05-31 10:22:02', '2026-05-31 10:22:02', NULL, 'ddebf67cd107080eedfb0038400c1075', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5HM3N07VB3RPFJCFM', 'status', 'backlog', 'done', NULL, '2026-05-31 10:22:03', '2026-05-31 10:22:03', '2026-05-31 10:22:03', NULL, '09f0bebae685155e4f8b95e633286408', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TDSA2PDCV814E3YW', 'status', 'backlog', 'done', NULL, '2026-05-31 11:20:21', '2026-05-31 11:20:21', '2026-05-31 11:20:21', NULL, '55b8bb65f06bf258ce840b3ea0c06355', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5316HVGDYD2W0DC50', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 11:42:11', '2026-05-31 11:42:11', '2026-05-31 11:42:11', NULL, '17e4dc75a0a64c01800a69dad395ba77', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GVWHMRTMQYRTRKD4', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 11:42:11', '2026-05-31 11:42:11', '2026-05-31 11:42:11', NULL, '5b0c21d81eebd6f3f05f70fda56d457b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM514YJBF8AY8Q4N7Q4', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 11:42:11', '2026-05-31 11:42:11', '2026-05-31 11:42:11', NULL, 'b32c50859476ddc3df97e45f266b03d7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4DAQ2CEFESG9JGMZ8', 'description', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow.', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow. -Add a per-test timeout to the gate''s flutter test (e.g. ci/test.sh: ''flutter test --coverage --timeout 60s''). Motivation discovered 2026-05-31: a hung pumpAndSettle has a 10-MINUTE default timeout, so a few hanging widget tests wedge make push-check for 30+ minutes — which is almost certainly why a MacBook session resorted to ''git push --no-verify'' (the gate never returned; it was not disabled, it was hung). A tight per-test timeout makes any future hang fail fast (60s) instead of wedging a pre-push, so the gate stays usable and nobody is tempted to bypass it. Pair with: keep writing pump()-bounded tests instead of pumpAndSettle() where a view has overlapping async loads (see the T-188 decision_reader_test hang).', NULL, '2026-05-31 12:31:58', '2026-05-31 12:31:58', '2026-05-31 12:31:58', NULL, 'e42f8392c580eee1269d266cb0fb9749', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-187', 'status', 'in_progress', 'done', NULL, '2026-05-31 14:17:22', '2026-05-31 14:17:22', '2026-05-31 14:17:22', NULL, '4bef7314b0b25fbed23934e915a2ce59', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-162', 'status', 'in_progress', 'done', NULL, '2026-05-31 14:17:22', '2026-05-31 14:17:22', '2026-05-31 14:17:22', NULL, '677a9a319e57dffeb7158b17a9a61392', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-188', 'status', 'in_progress', 'done', NULL, '2026-05-31 14:17:22', '2026-05-31 14:17:22', '2026-05-31 14:17:22', NULL, '9e04aeba4367316d03afb0855a306520', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-192', 'description', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow. +Add a per-test timeout to the gate''s flutter test (e.g. ci/test.sh: ''flutter test --coverage --timeout 60s''). Motivation discovered 2026-05-31: a hung pumpAndSettle has a 10-MINUTE default timeout, so a few hanging widget tests wedge make push-check for 30+ minutes — which is almost certainly why a MacBook session resorted to ''git push --no-verify'' (the gate never returned; it was not disabled, it was hung). A tight per-test timeout makes any future hang fail fast (60s) instead of wedging a pre-push, so the gate stays usable and nobody is tempted to bypass it. Pair with: keep writing pump()-bounded tests instead of pumpAndSettle() where a view has overlapping async loads (see the T-188 decision_reader_test hang).', NULL, '2026-05-31 12:31:58', '2026-05-31 12:31:58', '2026-05-31 12:31:58', NULL, 'abccf78d334044d4dd87cb522ffe7344', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5316HVGDYD2W0DC50', 'status', 'in_progress', 'done', NULL, '2026-05-31 14:17:22', '2026-05-31 14:17:22', '2026-05-31 14:17:22', NULL, '3768d650053cadd4942030faac177455', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GVWHMRTMQYRTRKD4', 'status', 'in_progress', 'done', NULL, '2026-05-31 14:17:22', '2026-05-31 14:17:22', '2026-05-31 14:17:22', NULL, 'e24827beb8373107ba00b8c2bd27e7a9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM514YJBF8AY8Q4N7Q4', 'status', 'in_progress', 'done', NULL, '2026-05-31 14:17:22', '2026-05-31 14:17:22', '2026-05-31 14:17:22', NULL, 'ea691dd9f6d798e362d572adb940942d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4DAQ2CEFESG9JGMZ8', 'description', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow. Add a per-test timeout to the gate''s flutter test (e.g. ci/test.sh: ''flutter test --coverage --timeout 60s''). Motivation discovered 2026-05-31: a hung pumpAndSettle has a 10-MINUTE default timeout, so a few hanging widget tests wedge make push-check for 30+ minutes — which is almost certainly why a MacBook session resorted to ''git push --no-verify'' (the gate never returned; it was not disabled, it was hung). A tight per-test timeout makes any future hang fail fast (60s) instead of wedging a pre-push, so the gate stays usable and nobody is tempted to bypass it. Pair with: keep writing pump()-bounded tests instead of pumpAndSettle() where a view has overlapping async loads (see the T-188 decision_reader_test hang).', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow. Add a per-test timeout to the gate''s flutter test (e.g. ci/test.sh: ''flutter test --coverage --timeout 60s''). Motivation discovered 2026-05-31: a hung pumpAndSettle has a 10-MINUTE default timeout, so a few hanging widget tests wedge make push-check for 30+ minutes — which is almost certainly why a MacBook session resorted to ''git push --no-verify'' (the gate never returned; it was not disabled, it was hung). A tight per-test timeout makes any future hang fail fast (60s) instead of wedging a pre-push, so the gate stays usable and nobody is tempted to bypass it. Pair with: keep writing pump()-bounded tests instead of pumpAndSettle() where a view has overlapping async loads (see the T-188 decision_reader_test hang). -Measured 2026-05-31 (16-core box, warm cache, full flutter suite --exclude-tags pty): coverage default-concurrency = ~36s; coverage --concurrency=12 = ~37s (NO improvement — coverage runs are concurrency-insensitive, the instrumentation/collection dominates); NO-coverage --concurrency=12 = ~21s. Conclusions: (1) Concurrency only helps the NON-coverage path — do NOT bother adding --concurrency to the coverage gate, it buys nothing. (2) The dev inner-loop win is concrete: a no-coverage ''make test'' with --concurrency=12 runs ~21s vs ~36s today (~40% faster) — this is how we ''beat 40s'' for the edit loop. (3) The push-check coverage run is floored at ~36s by coverage itself; getting below that needs a different coverage approach (package:coverage via VM service, or coverage-on-changed-files) — out of scope for the quick win. So the actionable set is unchanged: no-coverage+concurrency dev target, coverage-only-in-push, drop the redundant a11y pass, add a per-test --timeout for hang-safety.', NULL, '2026-05-31 14:46:39', '2026-05-31 14:46:39', '2026-05-31 14:46:39', NULL, 'b6e7ea80f4abe35cbfc4ba9f2ff80e06', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-189', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 16:13:39', '2026-05-31 16:13:39', '2026-05-31 16:13:39', NULL, '51dba55aeb4ac02a4a4d7e72f09b3e3e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-191', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 16:13:39', '2026-05-31 16:13:39', '2026-05-31 16:13:39', NULL, '76e72b3d04c0456e8a39d8ca7dba7644', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-190', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 16:13:39', '2026-05-31 16:13:39', '2026-05-31 16:13:39', NULL, 'cc7072933e46f402bf65c02595843185', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-186', 'status', 'backlog', 'done', NULL, '2026-05-31 16:40:19', '2026-05-31 16:40:19', '2026-05-31 16:40:19', NULL, '8f5241e0d76f6cef282f5a04d274345a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-193', 'description', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified.', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified. +Measured 2026-05-31 (16-core box, warm cache, full flutter suite --exclude-tags pty): coverage default-concurrency = ~36s; coverage --concurrency=12 = ~37s (NO improvement — coverage runs are concurrency-insensitive, the instrumentation/collection dominates); NO-coverage --concurrency=12 = ~21s. Conclusions: (1) Concurrency only helps the NON-coverage path — do NOT bother adding --concurrency to the coverage gate, it buys nothing. (2) The dev inner-loop win is concrete: a no-coverage ''make test'' with --concurrency=12 runs ~21s vs ~36s today (~40% faster) — this is how we ''beat 40s'' for the edit loop. (3) The push-check coverage run is floored at ~36s by coverage itself; getting below that needs a different coverage approach (package:coverage via VM service, or coverage-on-changed-files) — out of scope for the quick win. So the actionable set is unchanged: no-coverage+concurrency dev target, coverage-only-in-push, drop the redundant a11y pass, add a per-test --timeout for hang-safety.', NULL, '2026-05-31 14:46:39', '2026-05-31 14:46:39', '2026-05-31 14:46:39', NULL, 'ba2722225276d113f707be2d98ef6a76', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BWXW61EVYGMZD47G', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 16:13:39', '2026-05-31 16:13:39', '2026-05-31 16:13:39', NULL, '6f0b0c7fef733d814aa6665eba2f3038', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM56FKPV4WSPWA0KMB4', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 16:13:39', '2026-05-31 16:13:39', '2026-05-31 16:13:39', NULL, 'b051f21a9b31559ef2dbbdd5c7daa000', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5ABRPK2SPW8VY7RP4', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 16:13:39', '2026-05-31 16:13:39', '2026-05-31 16:13:39', NULL, 'c97d552c8b2f4b5987264967bcf80322', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6S7FDAXQX32JNCSFG', 'status', 'backlog', 'done', NULL, '2026-05-31 16:40:19', '2026-05-31 16:40:19', '2026-05-31 16:40:19', NULL, 'd098e9d2337855c1e0467092fdd44602', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM709YPWECH1E66XXD4', 'description', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified.', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified. -MECHANISM (the systemic answer, per discussion): tests are parallel by default (--concurrency global = invisible parallel=true); express ''parallel=false'' for a vulnerable test via a TAG + a serial pass. The ''pty'' tag is the half-built precedent: it splits PTY tests into a separate ''dart test'' pass but did NOT serialize it (and leaned on retry:2). DONE 2026-05-31: that pty pass now runs --concurrency=1 and the retry:2 band-aids are removed — PTY/registry flake verified stable 4/4 runs. REMAINING: generalize to a ''serial'' tag for FLUTTER-side vulnerable tests (transcript_publisher''s ''republishes reader items onto the bus''): declare ''serial'' in dart_test.yaml; the parallel flutter run adds --exclude-tags serial; add a serial flutter pass (flutter test --tags serial --concurrency=1). WRINKLE for the gate: two flutter --coverage passes can''t just concatenate lcov (duplicate SF records double-count) — must real-merge (package:coverage format_coverage / lcov -a), OR run the serial pass without coverage if those tests'' source lines are covered elsewhere (check transcript_publisher.dart coverage from other tests first). ALTERNATIVE for the single transcript_publisher case: fix its await to be deterministic (await the bus delivery / expectLater on the stream) rather than quarantine — cheaper if it''s a test-timing bug vs a real bus race. Acceptance: no flaky test runs in the parallel pool; vulnerable tests are tagged + run serially; gate is deterministic across repeated full runs.', NULL, '2026-05-31 17:01:58', '2026-05-31 17:01:58', '2026-05-31 17:01:58', NULL, 'efff36d6e6969000e1085c073788425c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-193', 'status', 'backlog', 'done', NULL, '2026-05-31 17:26:25', '2026-05-31 17:26:25', '2026-05-31 17:26:25', NULL, '36b91e385984e40c8497f50498ed5ca6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-51', '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.', '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. +MECHANISM (the systemic answer, per discussion): tests are parallel by default (--concurrency global = invisible parallel=true); express ''parallel=false'' for a vulnerable test via a TAG + a serial pass. The ''pty'' tag is the half-built precedent: it splits PTY tests into a separate ''dart test'' pass but did NOT serialize it (and leaned on retry:2). DONE 2026-05-31: that pty pass now runs --concurrency=1 and the retry:2 band-aids are removed — PTY/registry flake verified stable 4/4 runs. REMAINING: generalize to a ''serial'' tag for FLUTTER-side vulnerable tests (transcript_publisher''s ''republishes reader items onto the bus''): declare ''serial'' in dart_test.yaml; the parallel flutter run adds --exclude-tags serial; add a serial flutter pass (flutter test --tags serial --concurrency=1). WRINKLE for the gate: two flutter --coverage passes can''t just concatenate lcov (duplicate SF records double-count) — must real-merge (package:coverage format_coverage / lcov -a), OR run the serial pass without coverage if those tests'' source lines are covered elsewhere (check transcript_publisher.dart coverage from other tests first). ALTERNATIVE for the single transcript_publisher case: fix its await to be deterministic (await the bus delivery / expectLater on the stream) rather than quarantine — cheaper if it''s a test-timing bug vs a real bus race. Acceptance: no flaky test runs in the parallel pool; vulnerable tests are tagged + run serially; gate is deterministic across repeated full runs.', NULL, '2026-05-31 17:01:58', '2026-05-31 17:01:58', '2026-05-31 17:01:58', NULL, 'f5e7a6c1113f1829a851bd294f47102a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM709YPWECH1E66XXD4', 'status', 'backlog', 'done', NULL, '2026-05-31 17:26:25', '2026-05-31 17:26:25', '2026-05-31 17:26:25', NULL, '0b2e775f840024121a0fee32f30ada93', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QPGVP54EKJPCNQQM', '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.', '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. Refinement (2026-05-31): @@ -2338,8 +1228,8 @@ Recent files — none exists today (ProjectManager.recents tracks projects, not Overlay mirrors the ClidePalette chrome and reuses the 200ms-debounced ClideFilterBox. New QuickOpenIntent bound to ctrl+p / meta+p, guarded so it does not fire while the command palette is open (when: !palette.open — verify the keymap when-parser supports negation; otherwise pick a non-conflicting default). ctrl+p is currently palette.selectPrevious. -Files: new lib/builtin/files/src/quick_open_overlay.dart + extension registration; new lib/kernel/src/recent_files.dart; files_commands.dart (files.walk); keymap intents + default.yaml; app.dart Stack mount alongside ClidePalette.', NULL, '2026-05-31 18:01:12', '2026-05-31 18:01:12', '2026-05-31 18:01:12', NULL, '4dd53f3fcb284e9231352c47561fafcf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', '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.', '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. +Files: new lib/builtin/files/src/quick_open_overlay.dart + extension registration; new lib/kernel/src/recent_files.dart; files_commands.dart (files.walk); keymap intents + default.yaml; app.dart Stack mount alongside ClidePalette.', NULL, '2026-05-31 18:01:12', '2026-05-31 18:01:12', '2026-05-31 18:01:12', NULL, 'b9fa1b8f9172b06ec5fe80b8b58a782b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YYG4X5R1GX97TJB4', '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.', '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. Refinement (2026-05-31) — see D-79: @@ -2355,8 +1245,8 @@ Open-at-line — editor.open takes only {path} today. Extend it with an optional Sidebar panel follows the files/git/decisions TabContribution(slot: Slots.sidebar) pattern; custom widgets, no Material/Cupertino. -Files: new lib/builtin/search/{extension,search_controller,search_panel_view}.dart; new lib/src/daemon/search_commands.dart (search.grep + isolate pool); lib/src/files ignore layering; editor_commands.dart + registry.dart (line param); IPC schema + dispatcher registration; CLI `clide search grep` verb.', NULL, '2026-05-31 18:01:13', '2026-05-31 18:01:13', '2026-05-31 18:01:13', NULL, '6a634c4a87cdf591534f65892865272e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'description', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config. +Files: new lib/builtin/search/{extension,search_controller,search_panel_view}.dart; new lib/src/daemon/search_commands.dart (search.grep + isolate pool); lib/src/files ignore layering; editor_commands.dart + registry.dart (line param); IPC schema + dispatcher registration; CLI `clide search grep` verb.', NULL, '2026-05-31 18:01:13', '2026-05-31 18:01:13', '2026-05-31 18:01:13', NULL, 'b44c48acf9d16e609a9b86579526b468', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM452XPSG99PA60ZZ48', 'description', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config. Refinement (2026-05-31): @@ -2372,10 +1262,10 @@ Safety (user decision) — REQUIRE A CLEAN GIT WORKING TREE before apply: refuse Ignore — same full ignore_files: layering as T-52 (D-4). -Files: lib/src/daemon/files_commands.dart (files.write); new replacement-preview controller/view under lib/builtin/search/; reuse path_safety + diff render primitives; DialogRouter confirm; IPC schema + CLI parity verbs.', NULL, '2026-05-31 18:01:14', '2026-05-31 18:01:14', '2026-05-31 18:01:14', NULL, '4d4ee79e82da8d3985dbf016ed6315ba', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 18:01:23', '2026-05-31 18:01:23', '2026-05-31 18:01:23', NULL, '1715b4493ba4beda216d701cf0c41121', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-51', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 18:01:23', '2026-05-31 18:01:23', '2026-05-31 18:01:23', NULL, '386ac9084ce3fe9c035a56bba3cd1c42', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'status', 'in_progress', 'done', NULL, '2026-05-31 18:54:02', '2026-05-31 18:54:02', '2026-05-31 18:54:02', NULL, 'bac566c015a7d599cbb2fc9b3855400b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-51', 'status', 'in_progress', 'done', NULL, '2026-05-31 18:54:02', '2026-05-31 18:54:02', '2026-05-31 18:54:02', NULL, 'fafab627f731815e6f83bfaf310543a6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 19:28:39', '2026-05-31 19:28:39', '2026-05-31 19:28:39', NULL, '5e29490c3b36aa2dce8dece3d0d0091f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'status', 'in_progress', 'done', NULL, '2026-05-31 19:41:13', '2026-05-31 19:41:13', '2026-05-31 19:41:13', NULL, 'f5297165f78050d960c9301633d06a5c', 1) ON CONFLICT(hash) DO NOTHING; +Files: lib/src/daemon/files_commands.dart (files.write); new replacement-preview controller/view under lib/builtin/search/; reuse path_safety + diff render primitives; DialogRouter confirm; IPC schema + CLI parity verbs.', NULL, '2026-05-31 18:01:14', '2026-05-31 18:01:14', '2026-05-31 18:01:14', NULL, '8cbf7aaad32f3a00e248cd1d393f2d42', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YYG4X5R1GX97TJB4', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 18:01:23', '2026-05-31 18:01:23', '2026-05-31 18:01:23', NULL, '8fa7f955a82ae9432885cc5d2bcaaf5e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QPGVP54EKJPCNQQM', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 18:01:23', '2026-05-31 18:01:23', '2026-05-31 18:01:23', NULL, 'dac7a84f7bda49dc331969fcc0818faa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YYG4X5R1GX97TJB4', 'status', 'in_progress', 'done', NULL, '2026-05-31 18:54:02', '2026-05-31 18:54:02', '2026-05-31 18:54:02', NULL, 'b5bb82e8bd10cd7b71f2fbc3d8fe5f14', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QPGVP54EKJPCNQQM', 'status', 'in_progress', 'done', NULL, '2026-05-31 18:54:02', '2026-05-31 18:54:02', '2026-05-31 18:54:02', NULL, 'f4e68dbd60a19a8c470bee496a42c896', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM452XPSG99PA60ZZ48', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 19:28:39', '2026-05-31 19:28:39', '2026-05-31 19:28:39', NULL, '142962ee579df3f2c413941b21f71a78', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM452XPSG99PA60ZZ48', 'status', 'in_progress', 'done', NULL, '2026-05-31 19:41:13', '2026-05-31 19:41:13', '2026-05-31 19:41:13', NULL, '87b89029f7f9f8027fbbbc27efd63c85', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index 644a8ee7..a6b85fd3 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -1,19 +1,19 @@ -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-194', 'status', 'backlog', 'done', NULL, '2026-06-01 06:49:47', '2026-06-01 06:49:47', '2026-06-01 06:49:47', NULL, '09bfe6f27513d7d16f7139543961b4f8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-195', 'status', 'backlog', 'done', NULL, '2026-06-01 07:08:16', '2026-06-01 07:08:16', '2026-06-01 07:08:16', NULL, 'c2fb7c6ede9e970a8186b5932141f472', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-197', 'status', 'backlog', 'done', NULL, '2026-06-01 10:45:14', '2026-06-01 10:45:14', '2026-06-01 10:45:14', NULL, '7d9a5a6040ccaff63f95c106c088f7b5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-196', 'status', 'backlog', 'done', NULL, '2026-06-01 10:45:14', '2026-06-01 10:45:14', '2026-06-01 10:45:14', NULL, 'a6afc45a7b24dc8dc65a0a6cda319f7d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-198', 'status', 'backlog', 'done', NULL, '2026-06-01 11:31:39', '2026-06-01 11:31:39', '2026-06-01 11:31:39', NULL, '5abb12dc3c3b72a4bccf1760204fe1ae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-199', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 11:38:40', '2026-06-01 11:38:40', '2026-06-01 11:38:40', NULL, '44bad6e0417988025dae8606879de2a4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-199', 'status', 'in_progress', 'done', NULL, '2026-06-01 12:35:04', '2026-06-01 12:35:04', '2026-06-01 12:35:04', NULL, '56c14e217540c5789e70317aa8b6461f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-200', 'status', 'backlog', 'done', NULL, '2026-06-01 14:04:24', '2026-06-01 14:04:24', '2026-06-01 14:04:24', NULL, 'e77f8d64e4f9723af700dd46f962dea6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-201', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 14:27:48', '2026-06-01 14:27:48', '2026-06-01 14:27:48', NULL, '98ad7183de73e87fe2192e3bcc12680b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-201', 'status', 'in_progress', 'done', NULL, '2026-06-01 14:45:39', '2026-06-01 14:45:39', '2026-06-01 14:45:39', NULL, '3073ba674f5d929f9d226e5351811433', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-197', 'description', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race.', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race. +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7MFNNCC0MMY2TCNRM', 'status', 'backlog', 'done', NULL, '2026-06-01 06:49:47', '2026-06-01 06:49:47', '2026-06-01 06:49:47', NULL, 'd01cd9475509936871f5e07807a097d4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4M2Z9YZ0SA81E5Q08', 'status', 'backlog', 'done', NULL, '2026-06-01 07:08:16', '2026-06-01 07:08:16', '2026-06-01 07:08:16', NULL, 'ae07d922d86b83eea2fbb963bbcb6d92', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7G8QBJ2PNQ0VREN24', 'status', 'backlog', 'done', NULL, '2026-06-01 10:45:14', '2026-06-01 10:45:14', '2026-06-01 10:45:14', NULL, '1037eb231ea647a4e17aea463f708b32', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6NTYAB8K6T50F8W00', 'status', 'backlog', 'done', NULL, '2026-06-01 10:45:14', '2026-06-01 10:45:14', '2026-06-01 10:45:14', NULL, '8fe7feadcc03bb1e3ceeaeca835361c1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6HQXNNF05GXWK7BWM', 'status', 'backlog', 'done', NULL, '2026-06-01 11:31:39', '2026-06-01 11:31:39', '2026-06-01 11:31:39', NULL, 'd4ad9825be347244bc6624b52d76f34c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM68APBCQTMGJAZ2B9C', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 11:38:40', '2026-06-01 11:38:40', '2026-06-01 11:38:40', NULL, '7609e453ad138021a14142027f2f9194', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM68APBCQTMGJAZ2B9C', 'status', 'in_progress', 'done', NULL, '2026-06-01 12:35:04', '2026-06-01 12:35:04', '2026-06-01 12:35:04', NULL, '25b0507cee1798ad969ed31b7e498bff', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4RAKSX59W1HJCZ1M8', 'status', 'backlog', 'done', NULL, '2026-06-01 14:04:24', '2026-06-01 14:04:24', '2026-06-01 14:04:24', NULL, '8be6673880b1cc36c8e0199ca088ae27', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM57GY6PTG78SD3961M', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 14:27:48', '2026-06-01 14:27:48', '2026-06-01 14:27:48', NULL, 'f7ab0f672fac7ca65089062bf84f7a68', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM57GY6PTG78SD3961M', 'status', 'in_progress', 'done', NULL, '2026-06-01 14:45:39', '2026-06-01 14:45:39', '2026-06-01 14:45:39', NULL, 'a1942351599d79a7a3a17ca1119f38ca', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7G8QBJ2PNQ0VREN24', 'description', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race.', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race. -Reopened fix (2026-06-01): the original fix called panels.activateTab(Slots.workspace, ''editor.active''), but _WorkspaceSlot renders its editor split off arrangement.editorOpen, NOT the active tab — so the editor never appeared. Real fix: EditorExtension.activate now calls arrangement.openEditor() on editor.opened / active-changed(non-null) and closeEditor() on active-changed(null). Test asserts arrangement.editorOpen.', NULL, '2026-06-01 16:36:38', '2026-06-01 16:36:38', '2026-06-01 16:36:38', NULL, '62283198922795b643960d5beab56dba', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-203', 'status', 'backlog', 'done', NULL, '2026-06-01 16:48:56', '2026-06-01 16:48:56', '2026-06-01 16:48:56', NULL, '2cd8d1f09a2cd432491f4ee1fecbadc0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-202', 'status', 'backlog', 'done', NULL, '2026-06-01 16:48:56', '2026-06-01 16:48:56', '2026-06-01 16:48:56', NULL, '2e730a377952b4ec2dd11561a97fcd00', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', '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. +Reopened fix (2026-06-01): the original fix called panels.activateTab(Slots.workspace, ''editor.active''), but _WorkspaceSlot renders its editor split off arrangement.editorOpen, NOT the active tab — so the editor never appeared. Real fix: EditorExtension.activate now calls arrangement.openEditor() on editor.opened / active-changed(non-null) and closeEditor() on active-changed(null). Test asserts arrangement.editorOpen.', NULL, '2026-06-01 16:36:38', '2026-06-01 16:36:38', '2026-06-01 16:36:38', NULL, 'f726405105c00c3b78d27ea41dbddb69', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM643F7Z648KNFPT7GR', 'status', 'backlog', 'done', NULL, '2026-06-01 16:48:56', '2026-06-01 16:48:56', '2026-06-01 16:48:56', NULL, '6e7ea043976756151dce11d92afe46eb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5A3984KFPD4NKRG6M', 'status', 'backlog', 'done', NULL, '2026-06-01 16:48:56', '2026-06-01 16:48:56', '2026-06-01 16:48:56', NULL, 'b7ac3a9ef35ca1e86e9f4b7d1c5a716a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V7GHH42RHV04DPTR', '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. **Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place; modal Vim presets are more involved than the VS Code preset (T-64) because modes need to be expressed as scope flags (`vim.normal`, `vim.insert`, `vim.visual`) that the when-clause grammar can branch on. Implementation work: @@ -37,88 +37,88 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, 2. `KeymapService.setPreset("vim")` + the mode-tracking service together produce correct mode transitions. 3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert). -Refinement (2026-06-01): scoped Vim-first for a Vim-power-user demo this weekend. ''Author a YAML file'' was wrong — T-117 shipped single-chord resolution + scope flags + when-clauses only. Decomposed into children: T-204 (fix dead default preset — live bug), T-205 (key-sequence + count resolution), T-206 (modal editor motion/edit intents), T-207 (Vim mode service + status indicator). T-65 itself becomes assets/keymaps/vim.yaml + regression tests once children land. T-64/T-66 deferred (single-chord, easy; T-205 hands JetBrains shift+shift later). Foundation is shared, not Vim-only.', NULL, '2026-06-01 18:49:02', '2026-06-01 18:49:02', '2026-06-01 18:49:02', NULL, '3f379507780d78aa9f76b28f4a5c3179', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-204', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 18:49:17', '2026-06-01 18:49:17', '2026-06-01 18:49:17', NULL, 'b3a889c39a03676489bf19b8d3343cf9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 18:51:08', '2026-06-01 18:51:08', '2026-06-01 18:51:08', NULL, 'cbbf55fdf6ac11f3146f41d0a6c0ad2f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-204', 'status', 'in_progress', 'done', NULL, '2026-06-01 18:51:08', '2026-06-01 18:51:08', '2026-06-01 18:51:08', NULL, 'f9fbf947660b802f85c45290c6e7a9c2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'status', 'in_progress', 'done', NULL, '2026-06-01 18:59:19', '2026-06-01 18:59:19', '2026-06-01 18:59:19', NULL, '2bd6e471177259606aa9d27975f89dde', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 19:07:16', '2026-06-01 19:07:16', '2026-06-01 19:07:16', NULL, 'e1f8c83cfe8956b83080ac25e515f86d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'status', 'in_progress', 'done', NULL, '2026-06-01 19:12:01', '2026-06-01 19:12:01', '2026-06-01 19:12:01', NULL, '061ffbffafbfdfe7ecec8f278fbc6bf0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-206', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 19:16:58', '2026-06-01 19:16:58', '2026-06-01 19:16:58', NULL, '4b1e494c63361eb99d59e0491ff841c3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-206', 'status', 'in_progress', 'done', NULL, '2026-06-01 19:27:18', '2026-06-01 19:27:18', '2026-06-01 19:27:18', NULL, '9ec3404aa10ff5c655124c4e5b857a31', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 19:29:15', '2026-06-01 19:29:15', '2026-06-01 19:29:15', NULL, '021a754b25453a1548ee9278f2719072', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-211', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 08:57:22', '2026-06-03 08:57:22', '2026-06-03 08:57:22', NULL, '5ffa9c6d4624f86acf4b959aa43fde6f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-210', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 08:57:22', '2026-06-03 08:57:22', '2026-06-03 08:57:22', NULL, 'b8cd1a9e012f2fef2e1fed265b647d36', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-213', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 08:57:22', '2026-06-03 08:57:22', '2026-06-03 08:57:22', NULL, 'fc0e30131455fa1db38505ca0aa222e4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-213', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:00:37', '2026-06-03 09:00:37', '2026-06-03 09:00:37', NULL, '7df53985fc88d02f9087ccfcf78770ae', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-210', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:00:37', '2026-06-03 09:00:37', '2026-06-03 09:00:37', NULL, '84dd4f5fa45cb78da9a0b11c73fb149b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-211', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:00:37', '2026-06-03 09:00:37', '2026-06-03 09:00:37', NULL, 'aa86077555916ee351369e23a6ca155b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-224', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:02:26', '2026-06-03 09:02:26', '2026-06-03 09:02:26', NULL, 'e3b4e65b26974834bbb073ddebf667f7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-224', 'description', 'Gap 5 from self-analysis.md — foundational. Two distinct ''Claude inside clide'' stories with different gaps: (A) the clide-HOSTED stream-json session (D-77/D-78) — the user''s primary Claude pane, which itself needs `clide` on PATH to drive the surrounding IDE; (B) an EXTERNAL agent (e.g. a Claude Code harness, TERM_PROGRAM=zed as observed) driving via `clide ...` IPC, which clide cannot fully observe because plain file reads / `make test` / `git` bypass clide. Decide the intended model — hosted, external, or both — and record it under governance/ (claim via `pql decisions claim`). Gates how Epic B bootstraps and how Epic C frames observability.', 'Gap 5 from self-analysis.md — foundational. Two distinct ''Claude inside clide'' stories with different gaps: (A) the clide-HOSTED stream-json session (D-77/D-78) — the user''s primary Claude pane, which itself needs `clide` on PATH to drive the surrounding IDE; (B) an EXTERNAL agent (e.g. a Claude Code harness, TERM_PROGRAM=zed as observed) driving via `clide ...` IPC, which clide cannot fully observe because plain file reads / `make test` / `git` bypass clide. Decide the intended model — hosted, external, or both — and record it under governance/ (claim via `pql decisions claim`). Gates how Epic B bootstraps and how Epic C frames observability. +Refinement (2026-06-01): scoped Vim-first for a Vim-power-user demo this weekend. ''Author a YAML file'' was wrong — T-117 shipped single-chord resolution + scope flags + when-clauses only. Decomposed into children: T-204 (fix dead default preset — live bug), T-205 (key-sequence + count resolution), T-206 (modal editor motion/edit intents), T-207 (Vim mode service + status indicator). T-65 itself becomes assets/keymaps/vim.yaml + regression tests once children land. T-64/T-66 deferred (single-chord, easy; T-205 hands JetBrains shift+shift later). Foundation is shared, not Vim-only.', NULL, '2026-06-01 18:49:02', '2026-06-01 18:49:02', '2026-06-01 18:49:02', NULL, 'afdcc3d8c3172572295bda81d14d57c3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65R8CNFNE1MA5GGR0', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 18:49:17', '2026-06-01 18:49:17', '2026-06-01 18:49:17', NULL, 'd1d00e87c8a85caf4423088b1f878986', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65R8CNFNE1MA5GGR0', 'status', 'in_progress', 'done', NULL, '2026-06-01 18:51:08', '2026-06-01 18:51:08', '2026-06-01 18:51:08', NULL, '0eb0b244f4042b1c824377cd1c1f6ee2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M83TTXHRXESFZY14', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 18:51:08', '2026-06-01 18:51:08', '2026-06-01 18:51:08', NULL, '916d8491cc454c7d44a104d3b812508e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M83TTXHRXESFZY14', 'status', 'in_progress', 'done', NULL, '2026-06-01 18:59:19', '2026-06-01 18:59:19', '2026-06-01 18:59:19', NULL, 'b22efca89b75981440429ab2ea7b3145', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PH1J6ENYT4PJZRHG', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 19:07:16', '2026-06-01 19:07:16', '2026-06-01 19:07:16', NULL, '0431c8509983a583f02561f9a2a98584', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PH1J6ENYT4PJZRHG', 'status', 'in_progress', 'done', NULL, '2026-06-01 19:12:01', '2026-06-01 19:12:01', '2026-06-01 19:12:01', NULL, 'e7711ec17cf8ce4889d9add12fe0ec78', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5REFZXMY8ESWN0Z90', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 19:16:58', '2026-06-01 19:16:58', '2026-06-01 19:16:58', NULL, '8706054876918dcd7b72d627ad5a0886', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5REFZXMY8ESWN0Z90', 'status', 'in_progress', 'done', NULL, '2026-06-01 19:27:18', '2026-06-01 19:27:18', '2026-06-01 19:27:18', NULL, '0c14bf0e4b32866f53fddea3a4d39874', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V7GHH42RHV04DPTR', 'status', 'backlog', 'in_progress', NULL, '2026-06-01 19:29:15', '2026-06-01 19:29:15', '2026-06-01 19:29:15', NULL, '40a6c8cde0f6e877c94789be50d6072e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4C2PSN2GVJY9JDSZG', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 08:57:22', '2026-06-03 08:57:22', '2026-06-03 08:57:22', NULL, '187776e23e5382336dca17791c1b91ad', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48QJV4N7J62436GC0', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 08:57:22', '2026-06-03 08:57:22', '2026-06-03 08:57:22', NULL, '4fbd68d687c6114c261d72d01694c130', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7171NRB1T82TGP5RG', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 08:57:22', '2026-06-03 08:57:22', '2026-06-03 08:57:22', NULL, 'bb31db8ac05e0781039b5f2723f52900', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48QJV4N7J62436GC0', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:00:37', '2026-06-03 09:00:37', '2026-06-03 09:00:37', NULL, '35bb14c9f80fb36fa2d7decd958a588f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4C2PSN2GVJY9JDSZG', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:00:37', '2026-06-03 09:00:37', '2026-06-03 09:00:37', NULL, '6bd82e40072507fa0e384385c54d7569', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7171NRB1T82TGP5RG', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:00:37', '2026-06-03 09:00:37', '2026-06-03 09:00:37', NULL, 'afa0e367a353939ab53a9b6d5407c16e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54WTNQDMYT0AVR4WM', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:02:26', '2026-06-03 09:02:26', '2026-06-03 09:02:26', NULL, 'b8797e42960837b03a01d99703ad4e9e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54WTNQDMYT0AVR4WM', 'description', 'Gap 5 from self-analysis.md — foundational. Two distinct ''Claude inside clide'' stories with different gaps: (A) the clide-HOSTED stream-json session (D-77/D-78) — the user''s primary Claude pane, which itself needs `clide` on PATH to drive the surrounding IDE; (B) an EXTERNAL agent (e.g. a Claude Code harness, TERM_PROGRAM=zed as observed) driving via `clide ...` IPC, which clide cannot fully observe because plain file reads / `make test` / `git` bypass clide. Decide the intended model — hosted, external, or both — and record it under governance/ (claim via `pql decisions claim`). Gates how Epic B bootstraps and how Epic C frames observability.', 'Gap 5 from self-analysis.md — foundational. Two distinct ''Claude inside clide'' stories with different gaps: (A) the clide-HOSTED stream-json session (D-77/D-78) — the user''s primary Claude pane, which itself needs `clide` on PATH to drive the surrounding IDE; (B) an EXTERNAL agent (e.g. a Claude Code harness, TERM_PROGRAM=zed as observed) driving via `clide ...` IPC, which clide cannot fully observe because plain file reads / `make test` / `git` bypass clide. Decide the intended model — hosted, external, or both — and record it under governance/ (claim via `pql decisions claim`). Gates how Epic B bootstraps and how Epic C frames observability. -Resolved (2026-06-03): recorded as D-83. Decision — clide commits to BOTH agent models with the clide-HOSTED stream-json session (D-77/D-78) as the PRIMARY dogfood target (the one clide spawns, so the one Epic B/T-214 bootstraps: CLIDE_SOCK/CLIDE_WORKSPACE + PATH + context note + Bash(clide *) allow rule), and the EXTERNAL CLI driver (D-68) as a first-class but SECONDARY, best-effort integration (manual install via T-212; no promise to observe its non-clide tool use). Epic C/T-218 parity is scoped to clide''s own surfaces reflected through the CLI in both directions; an external agent''s side-channel reads/tests/git are out of parity scope.', NULL, '2026-06-03 09:05:48', '2026-06-03 09:05:48', '2026-06-03 09:05:48', NULL, 'c6b6237b6f3d7f072a4b9b0a4cc30bc9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-224', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:05:50', '2026-06-03 09:05:50', '2026-06-03 09:05:50', NULL, 'e6b1c1f085d251762e927b9ea9d20076', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-215', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:09:43', '2026-06-03 09:09:43', '2026-06-03 09:09:43', NULL, '6fc70c3a2c6102268eefaa757bf6b1d9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-217', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:09:43', '2026-06-03 09:09:43', '2026-06-03 09:09:43', NULL, '7751a9475bab7a458df1fa29ba115167', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-216', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:09:43', '2026-06-03 09:09:43', '2026-06-03 09:09:43', NULL, 'a21641c8d9b1e859255cceffe93db738', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-217', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:18:28', '2026-06-03 09:18:28', '2026-06-03 09:18:28', NULL, '980afb083f759f6210c2ef24fea52c37', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-216', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:18:28', '2026-06-03 09:18:28', '2026-06-03 09:18:28', NULL, 'a76b209ff9d4985dd2f78a858a64c8cc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-215', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:18:28', '2026-06-03 09:18:28', '2026-06-03 09:18:28', NULL, 'ff89bb398cb6f4911b1c5aac4e307834', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-214', 'status', 'backlog', 'done', NULL, '2026-06-03 09:18:38', '2026-06-03 09:18:38', '2026-06-03 09:18:38', NULL, '6266403d6e099befafc78b5d368e938a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-220', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:27:42', '2026-06-03 09:27:42', '2026-06-03 09:27:42', NULL, '53dffc8221a283770c138cc386633e21', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-219', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:27:42', '2026-06-03 09:27:42', '2026-06-03 09:27:42', NULL, '54df040ce5ad0726393f51cf2742e540', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'status', 'backlog', 'ready', NULL, '2026-06-03 09:45:20', '2026-06-03 09:45:20', '2026-06-03 09:45:20', NULL, 'a0a8931ac0f3cc89e8410d6f910479c4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-227', 'parent_id', NULL, 'T-229', NULL, '2026-06-03 09:51:40', '2026-06-03 09:51:40', '2026-06-03 09:51:40', NULL, '12656b6b9cde447b592fd199d7d47645', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-228', 'parent_id', NULL, 'T-229', NULL, '2026-06-03 09:51:40', '2026-06-03 09:51:40', '2026-06-03 09:51:40', NULL, '17b018dd9f5b4c6234dad6074155f4fc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-163', 'parent_id', 'T-132', 'T-229', NULL, '2026-06-03 09:51:40', '2026-06-03 09:51:40', '2026-06-03 09:51:40', NULL, '5dc4470123514f2886b99d983c3f14f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-228', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:54:14', '2026-06-03 09:54:14', '2026-06-03 09:54:14', NULL, 'e9fad0773bf9d930028187db8f219d9f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-163', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 10:02:02', '2026-06-03 10:02:02', '2026-06-03 10:02:02', NULL, 'a5f405a38487027074c841401ab2d45c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-228', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:02:02', '2026-06-03 10:02:02', '2026-06-03 10:02:02', NULL, 'de2818775d8d462446c341b2e74aed2e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-163', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:05:55', '2026-06-03 10:05:55', '2026-06-03 10:05:55', NULL, '36b6f761d7005750a192f2f4aa5e60b4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-227', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 10:05:55', '2026-06-03 10:05:55', '2026-06-03 10:05:55', NULL, 'ec0f5acabfaacca93fb1547bb2eb4280', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-227', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:09:26', '2026-06-03 10:09:26', '2026-06-03 10:09:26', NULL, '4fab6a35e4135bbaf278cd094822f1ea', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-229', 'status', 'backlog', 'done', NULL, '2026-06-03 10:09:26', '2026-06-03 10:09:26', '2026-06-03 10:09:26', NULL, 'a0e3cc0b5550fe7d408cdbda7675b525', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-219', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:31:10', '2026-06-03 10:31:10', '2026-06-03 10:31:10', NULL, '6a980de3525b2d933850f49854bc5202', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-220', 'description', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`.', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`. +Resolved (2026-06-03): recorded as D-83. Decision — clide commits to BOTH agent models with the clide-HOSTED stream-json session (D-77/D-78) as the PRIMARY dogfood target (the one clide spawns, so the one Epic B/T-214 bootstraps: CLIDE_SOCK/CLIDE_WORKSPACE + PATH + context note + Bash(clide *) allow rule), and the EXTERNAL CLI driver (D-68) as a first-class but SECONDARY, best-effort integration (manual install via T-212; no promise to observe its non-clide tool use). Epic C/T-218 parity is scoped to clide''s own surfaces reflected through the CLI in both directions; an external agent''s side-channel reads/tests/git are out of parity scope.', NULL, '2026-06-03 09:05:48', '2026-06-03 09:05:48', '2026-06-03 09:05:48', NULL, 'a4f8bca0289b2780e9f33e8e06526d18', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54WTNQDMYT0AVR4WM', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:05:50', '2026-06-03 09:05:50', '2026-06-03 09:05:50', NULL, '6d719202dfc0f7ede73f662877866339', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TP6KXTB1GHKBW1G4', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:09:43', '2026-06-03 09:09:43', '2026-06-03 09:09:43', NULL, '0ac74e2c7e822baccd7c3ece16e7d45a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WYF8ESBSGAQF1S48', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:09:43', '2026-06-03 09:09:43', '2026-06-03 09:09:43', NULL, '4e2c7153e283f0adae41bf07065f52d2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM51CNEJP52P79RDVJW', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:09:43', '2026-06-03 09:09:43', '2026-06-03 09:09:43', NULL, '7d241cc8007d77fae8b9e16831cbab0f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TP6KXTB1GHKBW1G4', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:18:28', '2026-06-03 09:18:28', '2026-06-03 09:18:28', NULL, '200a9081bd33a4605032d41299d0c941', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM51CNEJP52P79RDVJW', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:18:28', '2026-06-03 09:18:28', '2026-06-03 09:18:28', NULL, '2f7785e83b6d61ac50f496c63c08ed9f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WYF8ESBSGAQF1S48', 'status', 'in_progress', 'done', NULL, '2026-06-03 09:18:28', '2026-06-03 09:18:28', '2026-06-03 09:18:28', NULL, '41464449dc880ed73c650939c86dea8d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78J2H2S0R79SSSPW8', 'status', 'backlog', 'done', NULL, '2026-06-03 09:18:38', '2026-06-03 09:18:38', '2026-06-03 09:18:38', NULL, 'a2404bd89e073a8d1de6ddc0cfdf1ed1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6EGSD0SB7NYNSP1BM', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:27:42', '2026-06-03 09:27:42', '2026-06-03 09:27:42', NULL, '0308666b00afe834a02b5609cf60bb0d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6Y5SET5WGSS2W78F8', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:27:42', '2026-06-03 09:27:42', '2026-06-03 09:27:42', NULL, '1619d3e444a106191b1cfda6ba904d9a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GJRWZVHZF24TPPS4', 'status', 'backlog', 'ready', NULL, '2026-06-03 09:45:20', '2026-06-03 09:45:20', '2026-06-03 09:45:20', NULL, '973b1a79789f833bb05211bf7a2b4ee3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XZQJAK31WRVQ0HGW', 'parent_id', NULL, 'T-229', NULL, '2026-06-03 09:51:40', '2026-06-03 09:51:40', '2026-06-03 09:51:40', NULL, '050fec48818561926864cde99393b993', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SA9TEX5CNMTG67MW', 'parent_id', 'T-132', 'T-229', NULL, '2026-06-03 09:51:40', '2026-06-03 09:51:40', '2026-06-03 09:51:40', NULL, '531388662e6a061043a78e1686d6b6df', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5PFAYE4XH7WV6VDW8', 'parent_id', NULL, 'T-229', NULL, '2026-06-03 09:51:40', '2026-06-03 09:51:40', '2026-06-03 09:51:40', NULL, '9f2293a98e0b87246feaac42f150a826', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XZQJAK31WRVQ0HGW', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 09:54:14', '2026-06-03 09:54:14', '2026-06-03 09:54:14', NULL, 'ca8dd4bb0889d0ff8d8592ddc1d6cde0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XZQJAK31WRVQ0HGW', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:02:02', '2026-06-03 10:02:02', '2026-06-03 10:02:02', NULL, 'a61466ef8c2a93b46b35daacc65139af', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SA9TEX5CNMTG67MW', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 10:02:02', '2026-06-03 10:02:02', '2026-06-03 10:02:02', NULL, 'c74a338b9ca8abc44e5757385f182117', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5PFAYE4XH7WV6VDW8', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 10:05:55', '2026-06-03 10:05:55', '2026-06-03 10:05:55', NULL, '3d9ffdedd706c9d22a8a1b5ed3d79368', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SA9TEX5CNMTG67MW', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:05:55', '2026-06-03 10:05:55', '2026-06-03 10:05:55', NULL, 'b18f69871ba45d519a33aaddb77ea445', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5PFAYE4XH7WV6VDW8', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:09:26', '2026-06-03 10:09:26', '2026-06-03 10:09:26', NULL, '7854c38c9e89ea8dd00b01feb717123d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM68T660K07YH3X5DH0', 'status', 'backlog', 'done', NULL, '2026-06-03 10:09:26', '2026-06-03 10:09:26', '2026-06-03 10:09:26', NULL, '8b19904e021d0fddd1b9e66c5e7afdd8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6Y5SET5WGSS2W78F8', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:31:10', '2026-06-03 10:31:10', '2026-06-03 10:31:10', NULL, '7e93a13c82485ac3c85432616f7fc3ae', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6EGSD0SB7NYNSP1BM', 'description', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`.', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`. -Resolved (2026-06-03): done by prior editor work, not new code. Verified by reading the flow: openWorkspaceFile (file_open.dart) routes non-.md opens through the editor.open IPC verb -> EditorRegistry.open -> _setActive, so file-tree/quick-open opens already populate ''editor active''/''editor list''; tab switches go through editor.activate; and editor_view._onTextChanged pushes editor.set-content on caret-only moves too (its guard skips only when BOTH text and selection are unchanged), so selection is reflected. The probe saw editor active=null only because no code file was open (just the Claude pane / a markdown reader) -- correct, not a gap. The genuine remainder -- the read-only markdown/decisions READER''s currently-viewed file (routed via the D-81 ReaderNav bus, not editor.open, and rightly NOT an EditorRegistry buffer) -- is folded into T-221 (clide status: focused file + selection). Editor-reflects-opens claim is code-read, to be confirmed on the next live run.', NULL, '2026-06-03 10:59:22', '2026-06-03 10:59:22', '2026-06-03 10:59:22', NULL, 'bd4822cc92db54d2038eea04e994a7b5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-220', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:59:25', '2026-06-03 10:59:25', '2026-06-03 10:59:25', NULL, '606a719a18827b25c4af85fccb0eef8c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-221', 'description', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields.', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields. +Resolved (2026-06-03): done by prior editor work, not new code. Verified by reading the flow: openWorkspaceFile (file_open.dart) routes non-.md opens through the editor.open IPC verb -> EditorRegistry.open -> _setActive, so file-tree/quick-open opens already populate ''editor active''/''editor list''; tab switches go through editor.activate; and editor_view._onTextChanged pushes editor.set-content on caret-only moves too (its guard skips only when BOTH text and selection are unchanged), so selection is reflected. The probe saw editor active=null only because no code file was open (just the Claude pane / a markdown reader) -- correct, not a gap. The genuine remainder -- the read-only markdown/decisions READER''s currently-viewed file (routed via the D-81 ReaderNav bus, not editor.open, and rightly NOT an EditorRegistry buffer) -- is folded into T-221 (clide status: focused file + selection). Editor-reflects-opens claim is code-read, to be confirmed on the next live run.', NULL, '2026-06-03 10:59:22', '2026-06-03 10:59:22', '2026-06-03 10:59:22', NULL, 'd1839e13b815c81ca14968e9380e2da4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6EGSD0SB7NYNSP1BM', 'status', 'in_progress', 'done', NULL, '2026-06-03 10:59:25', '2026-06-03 10:59:25', '2026-06-03 10:59:25', NULL, 'd71b7b9293734e4022bd942fd2cb2cf2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM758PXMJH16PA3JHFW', 'description', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields.', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields. -Scope addition (2026-06-03, from T-220): clide status must surface the focused file + selection from BOTH the editor (EditorRegistry.active, already populated) AND the read-only markdown/decisions reader''s current file (D-81 ReaderNav), since viewer files rightly do not live in EditorRegistry. So ''focused file'' = whichever of {active editor buffer, active reader doc} the user is currently looking at. Pane field comes from the T-219 view-pane snapshot; git summary from git.status; layout from the arrangement.', NULL, '2026-06-03 10:59:31', '2026-06-03 10:59:31', '2026-06-03 10:59:31', NULL, 'd248f5407a3e698ad641ed9393b71607', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-221', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 11:17:49', '2026-06-03 11:17:49', '2026-06-03 11:17:49', NULL, '243f91496d8b597612063bb3bf1ba5a6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'status', 'ready', 'in_progress', NULL, '2026-06-03 11:38:13', '2026-06-03 11:38:13', '2026-06-03 11:38:13', NULL, '183df518819e0e8ab0822ead80d66dc7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'description', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82.', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82. +Scope addition (2026-06-03, from T-220): clide status must surface the focused file + selection from BOTH the editor (EditorRegistry.active, already populated) AND the read-only markdown/decisions reader''s current file (D-81 ReaderNav), since viewer files rightly do not live in EditorRegistry. So ''focused file'' = whichever of {active editor buffer, active reader doc} the user is currently looking at. Pane field comes from the T-219 view-pane snapshot; git summary from git.status; layout from the arrangement.', NULL, '2026-06-03 10:59:31', '2026-06-03 10:59:31', '2026-06-03 10:59:31', NULL, '589b102131052de0cf8eeaabfeef3cb6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM758PXMJH16PA3JHFW', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 11:17:49', '2026-06-03 11:17:49', '2026-06-03 11:17:49', NULL, 'e62ce5f754be4992321967879bcf8152', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GJRWZVHZF24TPPS4', 'status', 'ready', 'in_progress', NULL, '2026-06-03 11:38:13', '2026-06-03 11:38:13', '2026-06-03 11:38:13', NULL, '60597124c6a7f8deea5263098811234e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GJRWZVHZF24TPPS4', 'description', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82.', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82. -Refinement (2026-06-03): Shift+Tab is REJECTED as the trigger — Tab/Shift+Tab are real a11y focus-traversal intents (focus.next/focus.previous) since T-204, so hijacking Shift+Tab would break keyboard navigation. Instead: (a) Ctrl/Cmd+M cycles, intercepted at the focused Claude composer (so it targets that pane''s session, no global ''find focused session'' needed); (b) the status-line mode label becomes a FOCUSABLE button — cycles on click and on Enter/Space when focused (a11y-native, Tab reaches it); (c) a ''Claude: Cycle permission mode'' palette command. Safe trio default->acceptEdits->plan->default; bypassPermissions only via explicit confirmed path.', NULL, '2026-06-03 11:38:13', '2026-06-03 11:38:13', '2026-06-03 11:38:13', NULL, 'b67641486ea393fd30314a7202e4add7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'status', 'done', 'in_progress', NULL, '2026-06-03 11:38:13', '2026-06-03 11:38:13', '2026-06-03 11:38:13', NULL, 'db3f97f062272f92dee5f266006d0c0d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'description', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator).', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator). +Refinement (2026-06-03): Shift+Tab is REJECTED as the trigger — Tab/Shift+Tab are real a11y focus-traversal intents (focus.next/focus.previous) since T-204, so hijacking Shift+Tab would break keyboard navigation. Instead: (a) Ctrl/Cmd+M cycles, intercepted at the focused Claude composer (so it targets that pane''s session, no global ''find focused session'' needed); (b) the status-line mode label becomes a FOCUSABLE button — cycles on click and on Enter/Space when focused (a11y-native, Tab reaches it); (c) a ''Claude: Cycle permission mode'' palette command. Safe trio default->acceptEdits->plan->default; bypassPermissions only via explicit confirmed path.', NULL, '2026-06-03 11:38:13', '2026-06-03 11:38:13', '2026-06-03 11:38:13', NULL, '770abc73c9c46301ac5f88da05e58742', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ET3VQP90WRXVSMG', 'status', 'done', 'in_progress', NULL, '2026-06-03 11:38:13', '2026-06-03 11:38:13', '2026-06-03 11:38:13', NULL, 'ace0fa6e21b83ac23c1046c0a20572a9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ET3VQP90WRXVSMG', 'status', 'in_progress', 'done', NULL, '2026-06-03 11:41:17', '2026-06-03 11:41:17', '2026-06-03 11:41:17', NULL, 'a954ee95cfbb232e73107dd38a39f533', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ET3VQP90WRXVSMG', 'description', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator).', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator). -Verified done (2026-06-03): the cockpit per-agent permission-mode cycle badge is implemented and wired in claude_meta_sidebar.dart — _PermissionModeBadge (safe cycle default->acceptEdits->plan, Shift-click bypass behind a confirm, live mode from SessionStatus). No further work; closing. The primary-pane equivalent is T-226.', NULL, '2026-06-03 11:41:17', '2026-06-03 11:41:17', '2026-06-03 11:41:17', NULL, '1a0f586aa78d40cb14e96592912176ca', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'status', 'in_progress', 'done', NULL, '2026-06-03 11:41:17', '2026-06-03 11:41:17', '2026-06-03 11:41:17', NULL, '791d993f7f46e80bbb5a4608f55a6038', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'status', 'in_progress', 'done', NULL, '2026-06-03 11:45:56', '2026-06-03 11:45:56', '2026-06-03 11:45:56', NULL, '4f31f40c969af9dc60411414f71489b4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-221', 'status', 'in_progress', 'done', NULL, '2026-06-03 11:50:02', '2026-06-03 11:50:02', '2026-06-03 11:50:02', NULL, '141f9d7024902bf3dc1c50337c6aa543', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-218', 'status', 'backlog', 'done', NULL, '2026-06-03 11:50:08', '2026-06-03 11:50:08', '2026-06-03 11:50:08', NULL, '9afcf2b444b85f4c7c5e6ba14d73a04d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'status', 'done', 'done', NULL, '2026-06-03 11:50:23', '2026-06-03 11:50:23', '2026-06-03 11:50:23', NULL, '0abbcea442a884b74a28ff8146dad9d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-231', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 13:21:05', '2026-06-03 13:21:05', '2026-06-03 13:21:05', NULL, '881bfdf235311aad2e6aa1ae3e8140d1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'status', 'backlog', 'ready', NULL, '2026-06-03 13:23:43', '2026-06-03 13:23:43', '2026-06-03 13:23:43', NULL, '8c28d5cfbe764a82c8cc592e77513b9f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'status', 'backlog', 'ready', NULL, '2026-06-03 13:24:18', '2026-06-03 13:24:18', '2026-06-03 13:24:18', NULL, '7fed4ac1978468aef2f95e5ebdeaabc7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-231', 'status', 'in_progress', 'done', NULL, '2026-06-03 13:30:05', '2026-06-03 13:30:05', '2026-06-03 13:30:05', NULL, '00d811eac9a20406a824de9134055fda', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-212', 'description', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell.', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell. +Verified done (2026-06-03): the cockpit per-agent permission-mode cycle badge is implemented and wired in claude_meta_sidebar.dart — _PermissionModeBadge (safe cycle default->acceptEdits->plan, Shift-click bypass behind a confirm, live mode from SessionStatus). No further work; closing. The primary-pane equivalent is T-226.', NULL, '2026-06-03 11:41:17', '2026-06-03 11:41:17', '2026-06-03 11:41:17', NULL, 'eb80295065cb5e3fcb8bfcbc346e6774', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GJRWZVHZF24TPPS4', 'status', 'in_progress', 'done', NULL, '2026-06-03 11:45:56', '2026-06-03 11:45:56', '2026-06-03 11:45:56', NULL, 'fb9d92168548d7f2530ad4b90410e194', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM758PXMJH16PA3JHFW', 'status', 'in_progress', 'done', NULL, '2026-06-03 11:50:02', '2026-06-03 11:50:02', '2026-06-03 11:50:02', NULL, '45affa5ad9b05a3d510b3b5433a7bcf5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AMEYEBTF8YGJ4BVG', 'status', 'backlog', 'done', NULL, '2026-06-03 11:50:08', '2026-06-03 11:50:08', '2026-06-03 11:50:08', NULL, '6a100bfce5f8800ac6c963ccc89e21d0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GJRWZVHZF24TPPS4', 'status', 'done', 'done', NULL, '2026-06-03 11:50:23', '2026-06-03 11:50:23', '2026-06-03 11:50:23', NULL, '08a641c9a728aae240e84d837412dafb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427BVZYN6MD7ZZX74', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 13:21:05', '2026-06-03 13:21:05', '2026-06-03 13:21:05', NULL, '98e2b543462f5539cde6c4b324e56a2c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GH6VP8TZA95NMYK0', 'status', 'backlog', 'ready', NULL, '2026-06-03 13:23:43', '2026-06-03 13:23:43', '2026-06-03 13:23:43', NULL, '9553d7677bf355d235e0cf7258ec39dd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JR6QZDMKE167C4MR', 'status', 'backlog', 'ready', NULL, '2026-06-03 13:24:18', '2026-06-03 13:24:18', '2026-06-03 13:24:18', NULL, '8994ae3199b15e8ee6a87be2dafbf3e7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427BVZYN6MD7ZZX74', 'status', 'in_progress', 'done', NULL, '2026-06-03 13:30:05', '2026-06-03 13:30:05', '2026-06-03 13:30:05', NULL, 'af6cdaa34ddfc160531feac4a04607b5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78Q3VYWSE830CTVB4', 'description', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell.', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell. -Refinement (2026-06-03, from live dogfooding): scope should include PROACTIVE detection on launch, not just a palette command. When the clide IDE starts in a repo, check whether ''clide'' resolves on PATH AND points to the C client (not a stale symlink to the GUI bundle runner) -- we hit exactly this: ~/.local/bin/clide was a May-6 symlink to ~/.local/lib/clide/clide (the Flutter GUI), so a bare ''clide pane list'' launched a second app instead of querying. If missing or stale, prompt/offer to install (copy the bundled C client to a PATH dir, VS Code ''Install code command'' style) and report success. This is what lets a fresh agent actually reach the CLI (D-83 names the hosted session primary, but an external agent benefits too). Detecting ''stale GUI symlink'' specifically: the target should be an ELF/Mach-O executable, not a symlink into the bundle. Alternative path the user raised: instead of/alongside this, make the /ide MCP surface reachable (T-225) -- but CLI is primary per D-68.', NULL, '2026-06-03 13:33:43', '2026-06-03 13:33:43', '2026-06-03 13:33:43', NULL, 'e887b93f1b67cf8d7f1919c6954b2232', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-232', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 13:35:22', '2026-06-03 13:35:22', '2026-06-03 13:35:22', NULL, 'd5c275180bff8e53fbdab085b1f238e5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-232', 'description', 'Major dogfood finding (2026-06-03), verified live: parameterized subsystem commands are NOT reachable from the clide CLI. The C client sends raw argv; parseArgv (lib/src/cli/argv_to_request.dart) turns ''clide editor open X'' into args={positional:[X]} (also flags:{}, passthrough:[]). But the typed handlers read NAMED top-level keys: editor.open reads args[''path''] (editor_commands.dart:64), files.read reads args[''path''], pane.close/editor.activate read args[''id''], etc. Nothing maps positional/flags -> those names, so every arg-taking verb returns ''X is required'' from the CLI. Confirmed: ''clide editor open pubspec.yaml'' and ''--path=pubspec.yaml'' both -> ''path is required''; ''clide files read pubspec.yaml'' -> ''files.read requires a path''. grep shows ONLY the new ui_command.dart reads args[''positional'']. Net: an external agent can OBSERVE (no-arg reads: status, git status, files root, pane list, editor active) but cannot DRIVE anything parameterized -- undercutting CLI-first (D-1) and the D-6 parity premise behind the whole T-208 initiative. Needs a decision on the mapping contract: most ergonomic is a per-command POSITIONAL/flag schema declared where the handler registers (extends the co-registered schema of D-74) so ''clide editor open '' binds positional[0]->path; alternative is lifting --flags into top-level named args. Then either remap in argv_dispatch/parseArgv before dispatch, or have handlers read a normalized accessor. High priority: this is the gating bug for ''Give Claude hands''.', 'Major dogfood finding (2026-06-03), verified live: parameterized subsystem commands are NOT reachable from the clide CLI. The C client sends raw argv; parseArgv (lib/src/cli/argv_to_request.dart) turns ''clide editor open X'' into args={positional:[X]} (also flags:{}, passthrough:[]). But the typed handlers read NAMED top-level keys: editor.open reads args[''path''] (editor_commands.dart:64), files.read reads args[''path''], pane.close/editor.activate read args[''id''], etc. Nothing maps positional/flags -> those names, so every arg-taking verb returns ''X is required'' from the CLI. Confirmed: ''clide editor open pubspec.yaml'' and ''--path=pubspec.yaml'' both -> ''path is required''; ''clide files read pubspec.yaml'' -> ''files.read requires a path''. grep shows ONLY the new ui_command.dart reads args[''positional'']. Net: an external agent can OBSERVE (no-arg reads: status, git status, files root, pane list, editor active) but cannot DRIVE anything parameterized -- undercutting CLI-first (D-1) and the D-6 parity premise behind the whole T-208 initiative. Needs a decision on the mapping contract: most ergonomic is a per-command POSITIONAL/flag schema declared where the handler registers (extends the co-registered schema of D-74) so ''clide editor open '' binds positional[0]->path; alternative is lifting --flags into top-level named args. Then either remap in argv_dispatch/parseArgv before dispatch, or have handlers read a normalized accessor. High priority: this is the gating bug for ''Give Claude hands''. +Refinement (2026-06-03, from live dogfooding): scope should include PROACTIVE detection on launch, not just a palette command. When the clide IDE starts in a repo, check whether ''clide'' resolves on PATH AND points to the C client (not a stale symlink to the GUI bundle runner) -- we hit exactly this: ~/.local/bin/clide was a May-6 symlink to ~/.local/lib/clide/clide (the Flutter GUI), so a bare ''clide pane list'' launched a second app instead of querying. If missing or stale, prompt/offer to install (copy the bundled C client to a PATH dir, VS Code ''Install code command'' style) and report success. This is what lets a fresh agent actually reach the CLI (D-83 names the hosted session primary, but an external agent benefits too). Detecting ''stale GUI symlink'' specifically: the target should be an ELF/Mach-O executable, not a symlink into the bundle. Alternative path the user raised: instead of/alongside this, make the /ide MCP surface reachable (T-225) -- but CLI is primary per D-68.', NULL, '2026-06-03 13:33:43', '2026-06-03 13:33:43', '2026-06-03 13:33:43', NULL, 'e144d47ac16f8badb7f35b086d0686f4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BJCK5YG3R0NEEF70', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 13:35:22', '2026-06-03 13:35:22', '2026-06-03 13:35:22', NULL, '41afdaa7ca24597e843fc8ec43f02e61', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BJCK5YG3R0NEEF70', 'description', 'Major dogfood finding (2026-06-03), verified live: parameterized subsystem commands are NOT reachable from the clide CLI. The C client sends raw argv; parseArgv (lib/src/cli/argv_to_request.dart) turns ''clide editor open X'' into args={positional:[X]} (also flags:{}, passthrough:[]). But the typed handlers read NAMED top-level keys: editor.open reads args[''path''] (editor_commands.dart:64), files.read reads args[''path''], pane.close/editor.activate read args[''id''], etc. Nothing maps positional/flags -> those names, so every arg-taking verb returns ''X is required'' from the CLI. Confirmed: ''clide editor open pubspec.yaml'' and ''--path=pubspec.yaml'' both -> ''path is required''; ''clide files read pubspec.yaml'' -> ''files.read requires a path''. grep shows ONLY the new ui_command.dart reads args[''positional'']. Net: an external agent can OBSERVE (no-arg reads: status, git status, files root, pane list, editor active) but cannot DRIVE anything parameterized -- undercutting CLI-first (D-1) and the D-6 parity premise behind the whole T-208 initiative. Needs a decision on the mapping contract: most ergonomic is a per-command POSITIONAL/flag schema declared where the handler registers (extends the co-registered schema of D-74) so ''clide editor open '' binds positional[0]->path; alternative is lifting --flags into top-level named args. Then either remap in argv_dispatch/parseArgv before dispatch, or have handlers read a normalized accessor. High priority: this is the gating bug for ''Give Claude hands''.', 'Major dogfood finding (2026-06-03), verified live: parameterized subsystem commands are NOT reachable from the clide CLI. The C client sends raw argv; parseArgv (lib/src/cli/argv_to_request.dart) turns ''clide editor open X'' into args={positional:[X]} (also flags:{}, passthrough:[]). But the typed handlers read NAMED top-level keys: editor.open reads args[''path''] (editor_commands.dart:64), files.read reads args[''path''], pane.close/editor.activate read args[''id''], etc. Nothing maps positional/flags -> those names, so every arg-taking verb returns ''X is required'' from the CLI. Confirmed: ''clide editor open pubspec.yaml'' and ''--path=pubspec.yaml'' both -> ''path is required''; ''clide files read pubspec.yaml'' -> ''files.read requires a path''. grep shows ONLY the new ui_command.dart reads args[''positional'']. Net: an external agent can OBSERVE (no-arg reads: status, git status, files root, pane list, editor active) but cannot DRIVE anything parameterized -- undercutting CLI-first (D-1) and the D-6 parity premise behind the whole T-208 initiative. Needs a decision on the mapping contract: most ergonomic is a per-command POSITIONAL/flag schema declared where the handler registers (extends the co-registered schema of D-74) so ''clide editor open '' binds positional[0]->path; alternative is lifting --flags into top-level named args. Then either remap in argv_dispatch/parseArgv before dispatch, or have handlers read a normalized accessor. High priority: this is the gating bug for ''Give Claude hands''. -Resolved (2026-06-03): NO new mechanism or decision needed -- the contract already existed. D-74''s CommandSchema.normalize (lib/src/ipc/command_schema.dart) already folds the argv shape {positional,flags} into named args using a declared positional ordering, and the dispatcher already runs normalize+validate. The arg-taking commands simply never registered a schema (adoption is opt-in per D-74). Fix = adopt it: added positional schemas (non-required, so missing-arg errors stay as handlers produce them; only effect is positional->named mapping + numeric coercion of line/cols/rows) to editor.open/activate/read/save/close, files.read/ls, pane.close/focus/resize/write. Handlers unchanged. Tests: CLI-shape ({positional:[...]}) dispatch now binds (editor/pane/files command tests). DEFERRED (still named-arg only; in-process UI works, CLI-arg niche): editor.insert/replace-selection/set-selection/set-content (content/selection encoding + active-buffer fallback make positional ambiguous), pane.spawn (argv is a list, not scalar positional), git.* arg verbs (agents use plain git; D-83 external-agent scope). NOTE: takes effect on app RESTART, not hot reload -- the dispatcher is built once at boot.', NULL, '2026-06-03 13:41:38', '2026-06-03 13:41:38', '2026-06-03 13:41:38', NULL, '0655a8efb4c977b404daedd86d926761', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-232', 'status', 'in_progress', 'done', NULL, '2026-06-03 13:41:42', '2026-06-03 13:41:42', '2026-06-03 13:41:42', NULL, '17995cc58fd4260f4901357531eeafc4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'status', 'backlog', 'ready', NULL, '2026-06-03 14:25:47', '2026-06-03 14:25:47', '2026-06-03 14:25:47', NULL, 'e8216367ab4337bdbaa8fc97c596e111', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, '3db43a5f8b3855b2a78b4f6294dfeb97', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, '7a7042a498a0f09a072d911fad8d33d7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, 'ec0e54053e51dea1fecb48eebc69e387', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'status', 'in_progress', 'done', NULL, '2026-06-03 14:56:07', '2026-06-03 14:56:07', '2026-06-03 14:56:07', NULL, '9b160e380defb6a1ac78f200585745c8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'status', 'in_progress', 'done', NULL, '2026-06-03 15:04:07', '2026-06-03 15:04:07', '2026-06-03 15:04:07', NULL, 'a498a47a4c52685fb7a33cbf004b47f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'description', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png. +Resolved (2026-06-03): NO new mechanism or decision needed -- the contract already existed. D-74''s CommandSchema.normalize (lib/src/ipc/command_schema.dart) already folds the argv shape {positional,flags} into named args using a declared positional ordering, and the dispatcher already runs normalize+validate. The arg-taking commands simply never registered a schema (adoption is opt-in per D-74). Fix = adopt it: added positional schemas (non-required, so missing-arg errors stay as handlers produce them; only effect is positional->named mapping + numeric coercion of line/cols/rows) to editor.open/activate/read/save/close, files.read/ls, pane.close/focus/resize/write. Handlers unchanged. Tests: CLI-shape ({positional:[...]}) dispatch now binds (editor/pane/files command tests). DEFERRED (still named-arg only; in-process UI works, CLI-arg niche): editor.insert/replace-selection/set-selection/set-content (content/selection encoding + active-buffer fallback make positional ambiguous), pane.spawn (argv is a list, not scalar positional), git.* arg verbs (agents use plain git; D-83 external-agent scope). NOTE: takes effect on app RESTART, not hot reload -- the dispatcher is built once at boot.', NULL, '2026-06-03 13:41:38', '2026-06-03 13:41:38', '2026-06-03 13:41:38', NULL, '00f04276595d3ca0eacea70ace311514', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BJCK5YG3R0NEEF70', 'status', 'in_progress', 'done', NULL, '2026-06-03 13:41:42', '2026-06-03 13:41:42', '2026-06-03 13:41:42', NULL, '7af8d830470a89fef73b0ca8341cceff', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM492DTRZJK9CGQPGDC', 'status', 'backlog', 'ready', NULL, '2026-06-03 14:25:47', '2026-06-03 14:25:47', '2026-06-03 14:25:47', NULL, 'e3619215908a44a1d821ae9c0935649b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JR6QZDMKE167C4MR', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, '4c3010b9a2f5f653efe8ea06ad644457', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GH6VP8TZA95NMYK0', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, '5c31029277155620830aa75e3fb14224', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM492DTRZJK9CGQPGDC', 'status', 'ready', 'in_progress', NULL, '2026-06-03 14:27:26', '2026-06-03 14:27:26', '2026-06-03 14:27:26', NULL, '9692b5e80c02c9a55ed66de8875f8714', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JR6QZDMKE167C4MR', 'status', 'in_progress', 'done', NULL, '2026-06-03 14:56:07', '2026-06-03 14:56:07', '2026-06-03 14:56:07', NULL, '5df5897d91f2310e585ec8df8507b02c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM492DTRZJK9CGQPGDC', 'status', 'in_progress', 'done', NULL, '2026-06-03 15:04:07', '2026-06-03 15:04:07', '2026-06-03 15:04:07', NULL, '28d42a5626ada0071e158767c9223017', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GH6VP8TZA95NMYK0', 'description', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png. Fold runs of consecutive ''meta'' items into one live, collapsible activity card: - The card shows the MOST RECENT meta line as a live ticker and updates in place as new ones stream in — a running ''what''s happening now''. A step count (''14 steps'') sits on the right. @@ -174,32 +174,32 @@ Acceptance: 5. Failed tool results surface as their own (sticky) row and break the cluster. 6. Keyboard + screen-reader accessible (expand/collapse, step count announced). -Implemented (2026-06-03): pure grouping in activity_cluster.dart (groupConversation + FoldLevel none/tools/thinking/everything + RenderGroup StickyItem/FoldedCluster), fully unit-tested. Activity card + grouping wired into conversation_view.dart (collapsed live ticker of the latest step + step count; click/Enter expands to the folded steps in order; Semantics announces count + expanded/collapsed). Default level = L1 (tools): tool calls+results fold; user/Claude prose, FAILED results (sticky, surfaced), diffs (Edit/Write), and thinking stay first-class. Added FoldLevel.none (no folding) used by the existing item-level renderer tests. DEFERRED (acceptance 4''s persistence): the fold level is a switchable ConversationView parameter (proven by tests; none/L1/L2/L3 re-group) but is NOT yet wired to a persisted user setting + a UI control — the live pane uses the L1 default. Follow-up: read it from settings (ctx.settings) + a control to change it. Did NOT match the wireframe pixel-for-pixel; functional shape per the refinement decisions.', NULL, '2026-06-03 15:17:10', '2026-06-03 15:17:10', '2026-06-03 15:17:10', NULL, 'be73ea1e861930fc12fd9d6050cc6841', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'status', 'in_progress', 'done', NULL, '2026-06-03 15:17:33', '2026-06-03 15:17:33', '2026-06-03 15:17:33', NULL, '069a613ac7439a5d19da505712da8341', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-237', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 15:43:55', '2026-06-03 15:43:55', '2026-06-03 15:43:55', NULL, '8505c7a515c60be219fdaafd5c0c4acc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-237', 'status', 'in_progress', 'done', NULL, '2026-06-03 21:11:32', '2026-06-03 21:11:32', '2026-06-03 21:11:32', NULL, 'ab4f9e209d16f3ee692c97de1f9b565e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-237', 'description', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists.', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists. +Implemented (2026-06-03): pure grouping in activity_cluster.dart (groupConversation + FoldLevel none/tools/thinking/everything + RenderGroup StickyItem/FoldedCluster), fully unit-tested. Activity card + grouping wired into conversation_view.dart (collapsed live ticker of the latest step + step count; click/Enter expands to the folded steps in order; Semantics announces count + expanded/collapsed). Default level = L1 (tools): tool calls+results fold; user/Claude prose, FAILED results (sticky, surfaced), diffs (Edit/Write), and thinking stay first-class. Added FoldLevel.none (no folding) used by the existing item-level renderer tests. DEFERRED (acceptance 4''s persistence): the fold level is a switchable ConversationView parameter (proven by tests; none/L1/L2/L3 re-group) but is NOT yet wired to a persisted user setting + a UI control — the live pane uses the L1 default. Follow-up: read it from settings (ctx.settings) + a control to change it. Did NOT match the wireframe pixel-for-pixel; functional shape per the refinement decisions.', NULL, '2026-06-03 15:17:10', '2026-06-03 15:17:10', '2026-06-03 15:17:10', NULL, 'e320516c4fee5c1d23de4772712a6fe6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GH6VP8TZA95NMYK0', 'status', 'in_progress', 'done', NULL, '2026-06-03 15:17:33', '2026-06-03 15:17:33', '2026-06-03 15:17:33', NULL, '45157bbf9208bebe5bb78b5650fc259c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM46ANXPW02XGD3D3NG', 'status', 'backlog', 'in_progress', NULL, '2026-06-03 15:43:55', '2026-06-03 15:43:55', '2026-06-03 15:43:55', NULL, 'f4802b804089ec788e4c87ee78d34aee', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM46ANXPW02XGD3D3NG', 'status', 'in_progress', 'done', NULL, '2026-06-03 21:11:32', '2026-06-03 21:11:32', '2026-06-03 21:11:32', NULL, '9a06d177b18f27eaf0f1b6dfd935db42', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM46ANXPW02XGD3D3NG', 'status', 'done', 'done', NULL, '2026-06-03 21:29:47', '2026-06-03 21:29:47', '2026-06-03 21:29:47', NULL, '5c6c0567db81e42c9272a491d615aee6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM46ANXPW02XGD3D3NG', 'description', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists.', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists. -Done (2026-06-03): popover collapses -hc into a ''High contrast'' toggle, lists base themes sorted by display name, widened to 280 with ellipsis, palette icon on the trigger, lowercased bar label; status bar split so the global right group (tool status + theme switcher) hugs the window''s right edge past the context rail (643417e), keeping the 3-rail structure. Pure theme_families helpers unit-tested + popover widget tests. DEFERRED: applying the same -hc-toggle + sort to the modal picker_view (ctrl+k) for consistency — minor, follow-up.', NULL, '2026-06-03 21:29:47', '2026-06-03 21:29:47', '2026-06-03 21:29:47', NULL, '39ad09e473bac0df8704ac0421189bfb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-237', 'status', 'done', 'done', NULL, '2026-06-03 21:29:47', '2026-06-03 21:29:47', '2026-06-03 21:29:47', NULL, '296404eba14bc4889f54f64a94582d28', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-191', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, '2a7d74e3b64a6a5838de675c6a0fe0f7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, '86f701615e971118c73924cf8660f4db', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, 'a44a224173d86595593f35dc2febe995', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-189', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, 'c32ba63842836ecf413a656882c5d8c7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-190', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, 'ff43e51884d62a66ad7c6d4590cacb05', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'description', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work.', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. +Done (2026-06-03): popover collapses -hc into a ''High contrast'' toggle, lists base themes sorted by display name, widened to 280 with ellipsis, palette icon on the trigger, lowercased bar label; status bar split so the global right group (tool status + theme switcher) hugs the window''s right edge past the context rail (643417e), keeping the 3-rail structure. Pure theme_families helpers unit-tested + popover widget tests. DEFERRED: applying the same -hc-toggle + sort to the modal picker_view (ctrl+k) for consistency — minor, follow-up.', NULL, '2026-06-03 21:29:47', '2026-06-03 21:29:47', '2026-06-03 21:29:47', NULL, 'e92a06a4035a0eb5984ea80b84a9b106', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BWXW61EVYGMZD47G', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, '0b5b1c74b5a43d83d7a7109695f6b267', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W624VD4JW1GHDHAR', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, '875e34a0516f4797988ecb4152e797ff', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5ABRPK2SPW8VY7RP4', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, '8813d96e9fdde24a1107a2788f7b0995', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V7GHH42RHV04DPTR', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, '9122fa1a1ad891b12102022c38203b25', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM56FKPV4WSPWA0KMB4', 'status', 'in_progress', 'done', NULL, '2026-06-04 12:00:09', '2026-06-04 12:00:09', '2026-06-04 12:00:09', NULL, '989cb032dc8c8f5a2fd85de7e0927b1e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QJC039TMDF5JGMXC', 'description', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work.', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. -Refinement (2026-06-05, user): match the Claude Code CLI''s actual behavior for the text-field interaction. The number key triggers the button/option shortcut when it would be the FIRST character typed -- i.e. focus is NOT in a text field, OR focus IS in the note/Other field but that field is currently EMPTY. Once the field has any content, digits type normally (no shortcut). This supersedes the earlier ''never capture digits while a text field has focus'' gate: it''s better because focus often defaults into the (empty) note field, so the shortcut still fires there (muscle-memory case) while a digit mid-note still types. Implementation: intercept the digit at the prompt focus scope; consume+activate only when the focused editable (if any) is empty, else let it through to type. The CLI exhibits this exact ''footgun'' (digit-as-first-char in an empty field acts as the choice) and we intentionally mirror it.', NULL, '2026-06-05 09:40:57', '2026-06-05 09:40:57', '2026-06-05 09:40:57', NULL, 'e97171b1dae994c36d96bf86c5814434', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'status', 'backlog', 'in_progress', NULL, '2026-06-05 09:45:51', '2026-06-05 09:45:51', '2026-06-05 09:45:51', NULL, '855d8ce24a7cf884e8115cbf4a8b425d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'description', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column).', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column). +Refinement (2026-06-05, user): match the Claude Code CLI''s actual behavior for the text-field interaction. The number key triggers the button/option shortcut when it would be the FIRST character typed -- i.e. focus is NOT in a text field, OR focus IS in the note/Other field but that field is currently EMPTY. Once the field has any content, digits type normally (no shortcut). This supersedes the earlier ''never capture digits while a text field has focus'' gate: it''s better because focus often defaults into the (empty) note field, so the shortcut still fires there (muscle-memory case) while a digit mid-note still types. Implementation: intercept the digit at the prompt focus scope; consume+activate only when the focused editable (if any) is empty, else let it through to type. The CLI exhibits this exact ''footgun'' (digit-as-first-char in an empty field acts as the choice) and we intentionally mirror it.', NULL, '2026-06-05 09:40:57', '2026-06-05 09:40:57', '2026-06-05 09:40:57', NULL, '14ab9c3fceabd48bab7c461051ca8ab5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F4D2Q363QF7V40K8', 'status', 'backlog', 'in_progress', NULL, '2026-06-05 09:45:51', '2026-06-05 09:45:51', '2026-06-05 09:45:51', NULL, '10b4a203cccc9b0fed0804704eb857ac', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F4D2Q363QF7V40K8', 'description', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column).', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column). -FIXED (2026-06-05): root cause confirmed by probe at 3440px width — the bottom bar used Row[ ...left (incl. a Flexible flex:1 loose item, the Claude status marquee), Spacer(), ...right ]. The Spacer (Expanded, flex:1) and the left flex:1 item SPLIT the free space 50/50, so the Spacer only pushed the right group by HALF the free space. That drift is proportional to width: tiny at 1200px (why probes/normal screens looked fine), ~1500px at 3440px (the right group floated to mid-bar) — hence ''only on ultrawide''. Probe: OLD R.right=1934 at a 3440 edge; NEW R.right=3440. FIX (app.dart StatusbarHost): explicit two-column layout — left group wrapped in Expanded(Row[...]) so it absorbs ALL free space (flex item flexes within it), right group trails at intrinsic width → hugs the workspace block''s right edge by construction, width-independent. Regression test test/app_statusbar_test.dart asserts the right group at width-8 for BOTH 600px and 3440px. Full fast suite green.', NULL, '2026-06-05 10:02:12', '2026-06-05 10:02:12', '2026-06-05 10:02:12', NULL, 'a3934166a2af5b191cbf0670c93468f2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'status', 'in_progress', 'done', NULL, '2026-06-05 10:02:12', '2026-06-05 10:02:12', '2026-06-05 10:02:12', NULL, '00b3cc01164f91ebb5017ca4bc778d3e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'status', 'backlog', 'ready', NULL, '2026-06-05 11:04:12', '2026-06-05 11:04:12', '2026-06-05 11:04:12', NULL, '31ea36a8d7de4a25e9ed0f988f1b0437', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'status', 'backlog', 'ready', NULL, '2026-06-05 11:04:25', '2026-06-05 11:04:25', '2026-06-05 11:04:25', NULL, '0b3392508bac401f96d1b2803f378855', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'status', 'backlog', 'ready', NULL, '2026-06-05 12:15:59', '2026-06-05 12:15:59', '2026-06-05 12:15:59', NULL, 'bdd6d1b4412ae4b7056adedd6282734a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'status', 'ready', 'in_progress', NULL, '2026-06-05 13:07:20', '2026-06-05 13:07:20', '2026-06-05 13:07:20', NULL, 'fe442bfcab62c754cfa229bfde19b026', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'description', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle. +FIXED (2026-06-05): root cause confirmed by probe at 3440px width — the bottom bar used Row[ ...left (incl. a Flexible flex:1 loose item, the Claude status marquee), Spacer(), ...right ]. The Spacer (Expanded, flex:1) and the left flex:1 item SPLIT the free space 50/50, so the Spacer only pushed the right group by HALF the free space. That drift is proportional to width: tiny at 1200px (why probes/normal screens looked fine), ~1500px at 3440px (the right group floated to mid-bar) — hence ''only on ultrawide''. Probe: OLD R.right=1934 at a 3440 edge; NEW R.right=3440. FIX (app.dart StatusbarHost): explicit two-column layout — left group wrapped in Expanded(Row[...]) so it absorbs ALL free space (flex item flexes within it), right group trails at intrinsic width → hugs the workspace block''s right edge by construction, width-independent. Regression test test/app_statusbar_test.dart asserts the right group at width-8 for BOTH 600px and 3440px. Full fast suite green.', NULL, '2026-06-05 10:02:12', '2026-06-05 10:02:12', '2026-06-05 10:02:12', NULL, '1c7d83b62aa242a2cc67715225a7cf1b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F4D2Q363QF7V40K8', 'status', 'in_progress', 'done', NULL, '2026-06-05 10:02:12', '2026-06-05 10:02:12', '2026-06-05 10:02:12', NULL, 'bdd320e30b5741955925e68a87a6c705', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66TK8TQ1X5T9GK6V0', 'status', 'backlog', 'ready', NULL, '2026-06-05 11:04:12', '2026-06-05 11:04:12', '2026-06-05 11:04:12', NULL, 'bc94db15c95b740f2ca3f0c0d3921542', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYY4FVPX9KRVHCMC', 'status', 'backlog', 'ready', NULL, '2026-06-05 11:04:25', '2026-06-05 11:04:25', '2026-06-05 11:04:25', NULL, '7012c8f3ef5f83bc114211666e6763dd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM41S6RRTMTY9TYZA50', 'status', 'backlog', 'ready', NULL, '2026-06-05 12:15:59', '2026-06-05 12:15:59', '2026-06-05 12:15:59', NULL, '8e598b3ac94c0759219816735f773964', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM41S6RRTMTY9TYZA50', 'status', 'ready', 'in_progress', NULL, '2026-06-05 13:07:20', '2026-06-05 13:07:20', '2026-06-05 13:07:20', NULL, '97e0697ae51d47c730e6636b87693a58', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM41S6RRTMTY9TYZA50', 'description', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle. Adding a tap-driven (or even render-only) test that pumps WelcomeView with a non-empty recents list hangs the Flutter test runner until timeout. @@ -231,14 +231,14 @@ Next steps: ProjectManager sticky-startup logic is already covered by test/kernel/src/project_test.dart (14 cases). -ROOT CAUSE FOUND + FIXED (2026-06-05). The strand is NOT nested ClideTappable / intrinsic sizing (that prior hypothesis is disproven: seeding an EMPTY recents list — no rows, no nested tappables — also hangs, while rendering real recent rows seeded correctly passes). Real cause: SettingsStore.set does real file I/O (writeAsString); awaiting settings.set + project.loadRecents INSIDE the testWidgets body runs that I/O in fake-async, which traps the completion callback so the await never returns (a +0 strand only SIGKILL clears). FIX: seed recents via tester.runAsync(() async {...}) (real event loop), then pump in a tight bounded tree (not the shared harness, whose unbounded width is a separate WelcomeView layout hazard). test/builtin/welcome/widget_test.dart now has working recents render + sticky-toggle + open-recent tests; the skip is removed. Harness lesson recorded.', NULL, '2026-06-05 13:08:02', '2026-06-05 13:08:02', '2026-06-05 13:08:02', NULL, 'cc247a7da845b921a5e1864a0cb5c9cc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'status', 'in_progress', 'done', NULL, '2026-06-05 13:08:05', '2026-06-05 13:08:05', '2026-06-05 13:08:05', NULL, '18577271ba7a5602ab1229cc1c97281b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', '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.', '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. +ROOT CAUSE FOUND + FIXED (2026-06-05). The strand is NOT nested ClideTappable / intrinsic sizing (that prior hypothesis is disproven: seeding an EMPTY recents list — no rows, no nested tappables — also hangs, while rendering real recent rows seeded correctly passes). Real cause: SettingsStore.set does real file I/O (writeAsString); awaiting settings.set + project.loadRecents INSIDE the testWidgets body runs that I/O in fake-async, which traps the completion callback so the await never returns (a +0 strand only SIGKILL clears). FIX: seed recents via tester.runAsync(() async {...}) (real event loop), then pump in a tight bounded tree (not the shared harness, whose unbounded width is a separate WelcomeView layout hazard). test/builtin/welcome/widget_test.dart now has working recents render + sticky-toggle + open-recent tests; the skip is removed. Harness lesson recorded.', NULL, '2026-06-05 13:08:02', '2026-06-05 13:08:02', '2026-06-05 13:08:02', NULL, '8459f860f51ab035507cd3a99e869989', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM41S6RRTMTY9TYZA50', 'status', 'in_progress', 'done', NULL, '2026-06-05 13:08:05', '2026-06-05 13:08:05', '2026-06-05 13:08:05', NULL, '9102b9fb80d116f4443fe4b478003a08', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66TK8TQ1X5T9GK6V0', '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.', '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. -DECIDED 2026-06-05 (D-84): diff view = editor-mode surface, inline above Claude (NOT a workspace tab, NOT a context-panel viewer), spawned from the git sidebar. Rationale: a diff is a review+intervention surface needing room for hunk stage/discard and conflict-resolution widgets — too cramped for the ~420px context panel; the middle column above Claude gives space while keeping Claude''s prompt bar fixed (D-47/D-49). This decision ticket is satisfied; the actual re-placement (generalize the editor-mode slot to host a diff, wire git-sidebar spawn, build resolution widgets) is a follow-up implementation story.', NULL, '2026-06-05 15:14:18', '2026-06-05 15:14:18', '2026-06-05 15:14:18', NULL, '03769a838d7ed3117bd939bb365d2588', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-243', 'description', NULL, 'Implements D-84. Today the diff view is a workspace TAB (lib/builtin/diff/src/extension.dart, slot: Slots.workspace) — remove that. Instead: (1) generalize the editor-mode surface (currently hosts the single inline editor in _WorkspaceSlot, app.dart, gated by arrangement.editorOpen/editorRatio) so it can host EITHER the editor or a diff (mutually exclusive in that slot, like the viewer<->editor swap in D-49); (2) selecting a changed file in the git sidebar opens its diff in that above-Claude surface; (3) the diff keeps Claude''s prompt bar fixed (D-47) and gets a draggable divider like the editor. Acceptance: diff no longer appears as a workspace tab; clicking a file in the git panel opens its diff above Claude; closing it (Ctrl/Cmd+W) returns to Claude full-height; coexists with the editor by swap, not both at once. Hunk stage/discard + conflict-resolution widgets can be a further follow-up. Tests: arrangement state for the diff surface; git-sidebar -> diff open wiring; widget render.', NULL, '2026-06-05 15:17:09', '2026-06-05 15:17:09', '2026-06-05 15:17:09', NULL, '855f8b3cb0ef90acd37ef432e4860431', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'status', 'ready', 'done', NULL, '2026-06-05 15:17:09', '2026-06-05 15:17:09', '2026-06-05 15:17:09', NULL, 'e23ca4c4328443cd81fce0a9abad3d9b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', '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).', '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). +DECIDED 2026-06-05 (D-84): diff view = editor-mode surface, inline above Claude (NOT a workspace tab, NOT a context-panel viewer), spawned from the git sidebar. Rationale: a diff is a review+intervention surface needing room for hunk stage/discard and conflict-resolution widgets — too cramped for the ~420px context panel; the middle column above Claude gives space while keeping Claude''s prompt bar fixed (D-47/D-49). This decision ticket is satisfied; the actual re-placement (generalize the editor-mode slot to host a diff, wire git-sidebar spawn, build resolution widgets) is a follow-up implementation story.', NULL, '2026-06-05 15:14:18', '2026-06-05 15:14:18', '2026-06-05 15:14:18', NULL, 'f963b14dc689b03e3e6bf53bfd5f7d73', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66TK8TQ1X5T9GK6V0', 'status', 'ready', 'done', NULL, '2026-06-05 15:17:09', '2026-06-05 15:17:09', '2026-06-05 15:17:09', NULL, '71c38038b7804fb5afd636c27de1a53c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WT3BHB8JZY77G5AW', 'description', NULL, 'Implements D-84. Today the diff view is a workspace TAB (lib/builtin/diff/src/extension.dart, slot: Slots.workspace) — remove that. Instead: (1) generalize the editor-mode surface (currently hosts the single inline editor in _WorkspaceSlot, app.dart, gated by arrangement.editorOpen/editorRatio) so it can host EITHER the editor or a diff (mutually exclusive in that slot, like the viewer<->editor swap in D-49); (2) selecting a changed file in the git sidebar opens its diff in that above-Claude surface; (3) the diff keeps Claude''s prompt bar fixed (D-47) and gets a draggable divider like the editor. Acceptance: diff no longer appears as a workspace tab; clicking a file in the git panel opens its diff above Claude; closing it (Ctrl/Cmd+W) returns to Claude full-height; coexists with the editor by swap, not both at once. Hunk stage/discard + conflict-resolution widgets can be a further follow-up. Tests: arrangement state for the diff surface; git-sidebar -> diff open wiring; widget render.', NULL, '2026-06-05 15:17:09', '2026-06-05 15:17:09', '2026-06-05 15:17:09', NULL, 'fe174b3a1ef5357f0c527d6209f4c671', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYY4FVPX9KRVHCMC', '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).', '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). REFINED 2026-06-05. Build (bottom-right, system + proof emitter): - kernel ToastService (ChangeNotifier): show(message, {severity, duration}) -> id, dismiss(id), queue of active toasts, per-toast auto-dismiss Timer (default ~4s; errors longer/sticky), cancel on manual dismiss. ToastSeverity {success, warning, error, info}. @@ -246,10 +246,10 @@ REFINED 2026-06-05. Build (bottom-right, system + proof emitter): - ToastOverlay: mounted in the app-root Stack (app.dart) alongside ClidePalette/QuickOpenOverlay, anchored bottom-right; stacks multiple toasts vertically; animates enter/exit. - a11y: Semantics liveRegion for the message + labelled dismiss; keyboard-dismissable. - Proof emitter: git push success/failure raises a toast (wire in the git command path). Broad wiring (extension activated, update available, generic errors) = follow-up tickets. -- Tests: ToastService queue + auto-dismiss (fakeAsync timers) + severity mapping; ClideToast render per severity; a11y contract; overlay mount.', NULL, '2026-06-05 15:17:36', '2026-06-05 15:17:36', '2026-06-05 15:17:36', NULL, '800e0d1a19a42a0080643bde37283fd0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'status', 'ready', 'in_progress', NULL, '2026-06-05 15:17:36', '2026-06-05 15:17:36', '2026-06-05 15:17:36', NULL, 'ca87b010b519f2afaf004d0f448b0063', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'status', 'backlog', 'ready', NULL, '2026-06-05 15:21:04', '2026-06-05 15:21:04', '2026-06-05 15:21:04', NULL, 'a1236549768cc8ce72e0a7fe3d092883', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-244', 'description', NULL, 'When a Claude Code session hits context compaction, the Claude pane gives no signal that anything is happening: the conversation surface shows no in-pane indicator and the status bar shows no progress. From the user''s side the pane just appears to stall until compaction finishes and the next response streams in. +- Tests: ToastService queue + auto-dismiss (fakeAsync timers) + severity mapping; ClideToast render per severity; a11y contract; overlay mount.', NULL, '2026-06-05 15:17:36', '2026-06-05 15:17:36', '2026-06-05 15:17:36', NULL, '210739d5d63388a41de15b8e3bc6dfc7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYY4FVPX9KRVHCMC', 'status', 'ready', 'in_progress', NULL, '2026-06-05 15:17:36', '2026-06-05 15:17:36', '2026-06-05 15:17:36', NULL, 'aed11ee397b37d20662eabf1741fc4db', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', 'status', 'backlog', 'ready', NULL, '2026-06-05 15:21:04', '2026-06-05 15:21:04', '2026-06-05 15:21:04', NULL, 'f7966535989e601c2d724e65701f8191', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KPN4VK4EZP013NNG', 'description', NULL, 'When a Claude Code session hits context compaction, the Claude pane gives no signal that anything is happening: the conversation surface shows no in-pane indicator and the status bar shows no progress. From the user''s side the pane just appears to stall until compaction finishes and the next response streams in. Expected: compaction is a visible, first-class state. - In-pane: a clear "Compacting context…" affordance on the conversation surface (e.g. a transient status card / banner in the live-status line from T-168) so it''s obvious the session is busy, not wedged. @@ -259,18 +259,18 @@ Implementation notes: - Source the state from the stream-json event stream (T-164/T-165), not transcript tailing — identify the compaction signal (system/compact_boundary or equivalent event) and thread it through the conversation controller into both the in-pane live-status line and the status-bar slot. - Clear the indicator when compaction completes and normal streaming resumes. -Acceptance: triggering /compact (or an auto-compaction) shows an in-pane "compacting" indicator and a status-bar progress affordance for the duration, both of which clear when it finishes; covered by a unit test against a canned event fixture containing the compaction event.', NULL, '2026-06-05 15:24:35', '2026-06-05 15:24:35', '2026-06-05 15:24:35', NULL, '35417512fd3c04ba9fd74dce0d573d16', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'status', 'in_progress', 'done', NULL, '2026-06-05 21:12:23', '2026-06-05 21:12:23', '2026-06-05 21:12:23', NULL, '3104824ca77d25cde2da8ccfc2f56196', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-245', 'description', NULL, 'Implements the agent/CLI drive-half of T-50 (parity, like ui.open/T-231): clide ui toast "message" [--severity success|warning|error|info] [--duration MS]. Registered as ui.toast in lib/src/daemon/ui_command.dart; publishes {message,severity,durationMs?} on the kernel MessageBus ''toast'' channel (literal kept Flutter-free), which the ToastService consumes. Lets a hosted Claude session (or any script) surface ''done/failed'' on the user''s screen. Validates message-required, severity, integer duration; toolError when no live GUI. Tested in test/daemon/ui_command_test.dart. NOTE: a running GUI must be rebuilt to pick this up.', NULL, '2026-06-05 21:24:52', '2026-06-05 21:24:52', '2026-06-05 21:24:52', NULL, '5fe4ba2137f9581a161476aa422d6449', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-245', 'status', 'backlog', 'done', NULL, '2026-06-05 21:24:52', '2026-06-05 21:24:52', '2026-06-05 21:24:52', NULL, '120067f5227da971c2d8e865f44caed8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-246', 'description', NULL, '`clide pane list` reports every GUI tab (T-219, D-83) but only its generic chrome: `id`, `title`, `active`, `visible`. For the context-slot detail panes the title is a static word — `tickets.detail` → "Ticket", `decisions.detail` → "Decision", `editor.active` → "Editor" — so the CLI cannot tell *which* ticket / decision / file the pane currently shows. The loaded subject lives in the view''s reader-nav state and never reaches the wire. +Acceptance: triggering /compact (or an auto-compaction) shows an in-pane "compacting" indicator and a status-bar progress affordance for the duration, both of which clear when it finishes; covered by a unit test against a canned event fixture containing the compaction event.', NULL, '2026-06-05 15:24:35', '2026-06-05 15:24:35', '2026-06-05 15:24:35', NULL, 'd752e15e3e771292aa0084dc91181318', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYY4FVPX9KRVHCMC', 'status', 'in_progress', 'done', NULL, '2026-06-05 21:12:23', '2026-06-05 21:12:23', '2026-06-05 21:12:23', NULL, 'd9a116530e54d9766adb050be6db160d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66DNEK5FS9C0T4J5R', 'status', 'backlog', 'done', NULL, '2026-06-05 21:24:52', '2026-06-05 21:24:52', '2026-06-05 21:24:52', NULL, 'c089018f0a5131bc60a2c4b467dac861', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66DNEK5FS9C0T4J5R', 'description', NULL, 'Implements the agent/CLI drive-half of T-50 (parity, like ui.open/T-231): clide ui toast "message" [--severity success|warning|error|info] [--duration MS]. Registered as ui.toast in lib/src/daemon/ui_command.dart; publishes {message,severity,durationMs?} on the kernel MessageBus ''toast'' channel (literal kept Flutter-free), which the ToastService consumes. Lets a hosted Claude session (or any script) surface ''done/failed'' on the user''s screen. Validates message-required, severity, integer duration; toolError when no live GUI. Tested in test/daemon/ui_command_test.dart. NOTE: a running GUI must be rebuilt to pick this up.', NULL, '2026-06-05 21:24:52', '2026-06-05 21:24:52', '2026-06-05 21:24:52', NULL, 'cb7834de6d3591d77a66d288e125097f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6DH7C6GQN9WNKQNXR', 'description', NULL, '`clide pane list` reports every GUI tab (T-219, D-83) but only its generic chrome: `id`, `title`, `active`, `visible`. For the context-slot detail panes the title is a static word — `tickets.detail` → "Ticket", `decisions.detail` → "Decision", `editor.active` → "Editor" — so the CLI cannot tell *which* ticket / decision / file the pane currently shows. The loaded subject lives in the view''s reader-nav state and never reaches the wire. Concrete failure: with a ticket open in the right-hand context pane, there is no `clide` verb that answers "what ticket am I looking at". `pane list` shows `{"id":"tickets.detail","title":"Ticket","active":true}` and stops there. This breaks D-6 parity — the UI surfaces the open ticket; the CLI can''t observe it. Fix: give `ViewPane` (lib/src/panes/view_pane.dart) an optional `subject` (and/or `subtitle`) field and have `snapshotViewPanes` (lib/kernel/src/panels/view_pane_snapshot.dart) populate it from the active reader-nav selection for detail panes — `tickets.detail` → `T-NNN`, `decisions.detail` → `D-NNN`, `editor.active` → the file path. Serialise it in `ViewPane.toJson` so `pane list` carries it. Keep ViewPane Flutter-free (it must stay usable under `dart test`); read the selection in the kernel-side snapshot, not in the value object. -Acceptance: with a ticket open in the context pane, `clide pane list` returns that pane with its loaded id (e.g. `"subject":"T-244"`); same for an open decision and an open editor file; panes with no subject omit the field; covered by a unit test over a snapshot with a populated reader-nav selection.', NULL, '2026-06-05 21:29:50', '2026-06-05 21:29:50', '2026-06-05 21:29:50', NULL, 'b0a30f6b4779868ddd11a5ebefe77d07', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-247', 'description', NULL, 'The `clide` CLI does not honor the `CLIDE_SOCK` env var — it auto-discovers a running instance instead. Proof: with `CLIDE_SOCK=/run/user/1000/clide/DOES_NOT_EXIST.sock`, `clide version` still returns `{"version":"2.1.0"}` from the live app. Separately, the runtime socket dir accumulates orphaned socket files: `/run/user/1000/clide/` held two `.sock` entries while only one GUI process (the `build/linux/x64/debug/bundle/clide` bundle) was running, so a previous run''s socket was never cleaned up. +Acceptance: with a ticket open in the context pane, `clide pane list` returns that pane with its loaded id (e.g. `"subject":"T-244"`); same for an open decision and an open editor file; panes with no subject omit the field; covered by a unit test over a snapshot with a populated reader-nav selection.', NULL, '2026-06-05 21:29:50', '2026-06-05 21:29:50', '2026-06-05 21:29:50', NULL, 'bd7e5281457bb9cd90e25fb210313296', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6QCYT7JBF352YVJ48', 'description', NULL, 'The `clide` CLI does not honor the `CLIDE_SOCK` env var — it auto-discovers a running instance instead. Proof: with `CLIDE_SOCK=/run/user/1000/clide/DOES_NOT_EXIST.sock`, `clide version` still returns `{"version":"2.1.0"}` from the live app. Separately, the runtime socket dir accumulates orphaned socket files: `/run/user/1000/clide/` held two `.sock` entries while only one GUI process (the `build/linux/x64/debug/bundle/clide` bundle) was running, so a previous run''s socket was never cleaned up. Why it matters: today (single instance) it''s harmless, but it''s a latent split-brain + observability hole. If two clide instances are ever live on the same machine, the CLI attaches to whichever discovery resolves first, with (a) no way to target a specific instance and (b) no way to find out which one you''re talking to. Combined with the stale-socket litter, `clide` could silently drive the wrong window. This is a D-6 surface gap — the CLI must be able to address the same instance the user is in. @@ -279,10 +279,10 @@ Fix (scope to confirm): - Clean up orphaned/stale socket files on app startup (and on clean shutdown) — detect a dead listener and unlink before binding a new hash. - Add a way to enumerate/identify live instances (e.g. a `clide instances` verb, or include the instance id/socket path + pid in `clide version`) so a human or agent can pick the right one. -Acceptance: a bogus `CLIDE_SOCK` fails loudly instead of returning data from a different instance; a valid `CLIDE_SOCK` pins the CLI to that instance; startup leaves exactly one live socket for one running app (no orphan accumulation); there is a CLI affordance to list/identify running instances.', NULL, '2026-06-05 21:30:04', '2026-06-05 21:30:04', '2026-06-05 21:30:04', NULL, '80c539f71840c0b6b07455d4fe936e45', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'status', 'in_progress', 'done', NULL, '2026-06-06 06:44:40', '2026-06-06 06:44:40', '2026-06-06 06:44:40', NULL, '4038822bf1bb104a931939ce1a84b572', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-238', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 06:51:47', '2026-06-06 06:51:47', '2026-06-06 06:51:47', NULL, '3d2d4639b374be7a0e3e81f5e403045a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'description', NULL, 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. +Acceptance: a bogus `CLIDE_SOCK` fails loudly instead of returning data from a different instance; a valid `CLIDE_SOCK` pins the CLI to that instance; startup leaves exactly one live socket for one running app (no orphan accumulation); there is a CLI affordance to list/identify running instances.', NULL, '2026-06-05 21:30:04', '2026-06-05 21:30:04', '2026-06-05 21:30:04', NULL, '453237303b4ea9c6a396f9f0ad23280a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F4D2Q363QF7V40K8', 'status', 'in_progress', 'done', NULL, '2026-06-06 06:44:40', '2026-06-06 06:44:40', '2026-06-06 06:44:40', NULL, '4b87229500c5bf1514d4eb4835f9da82', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F2GC0ABFFVAH34EC', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 06:51:47', '2026-06-06 06:51:47', '2026-06-06 06:51:47', NULL, '9c1ed7e465d7c41078928c3fce667668', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70TW38E78FFXDHYAC', 'description', NULL, 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. Problem: my knowledge of the clide surface comes from a hand-curated session blurb. There is no runtime discovery — `clide help` is a stub, `clide ` with no verb just prints `usage:`, and the dispatcher''s registered command table (e.g. pane_commands.dart) is never exposed. D-6 guarantees a verb EXISTS for every UI action, but parity != discoverability: a correctly-registered verb is still unreachable if nothing tells me it''s there. @@ -292,8 +292,8 @@ Scope: This is what makes T-249 (image viewer) and future panels reachable by Claude the moment they register — no skill edit per panel. -Refs: D-6 (CLI/event-surface parity). Related: T-249.', NULL, '2026-06-06 07:21:30', '2026-06-06 07:21:30', '2026-06-06 07:21:30', NULL, '29a73bfc03d14ff7764f614d3c07aa3a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-249', 'description', NULL, 'Add an image-viewer card to the Claude conversation log, with the CLI verb that drives it (D-6 parity). +Refs: D-6 (CLI/event-surface parity). Related: T-249.', NULL, '2026-06-06 07:21:30', '2026-06-06 07:21:30', '2026-06-06 07:21:30', NULL, 'b08b5ff7848bfe37cb507d261ed5fa63', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7JXD3FKW91934D0W0', 'description', NULL, 'Add an image-viewer card to the Claude conversation log, with the CLI verb that drives it (D-6 parity). Scope: - Conversation card: render an image inline in the Claude conversation widget — clide-owned rendering (no opinionated package), display-only per D-78 (no inline interactive controls; any controls belong in the interaction zone). Respect theme tokens / ui-design. @@ -302,14 +302,14 @@ Scope: Dependency: pairs with T-248 — without the /clide skill + discovery verb, Claude won''t know this card/verb exists even once shipped. Build the plumbing here; T-248 makes it discoverable. -Refs: D-6 (parity), D-78 (interaction zone, display-only conversation widgets). Related: T-248.', NULL, '2026-06-06 07:21:36', '2026-06-06 07:21:36', '2026-06-06 07:21:36', NULL, '2fc1ece427f44c640340b0ad7fbdb1ca', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-249', 'status', 'backlog', 'ready', NULL, '2026-06-06 07:22:48', '2026-06-06 07:22:48', '2026-06-06 07:22:48', NULL, '4a539286dd5caa33660c37c9d5ebf385', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'status', 'backlog', 'ready', NULL, '2026-06-06 07:22:48', '2026-06-06 07:22:48', '2026-06-06 07:22:48', NULL, 'e54319c011c21c6304b3fbbaab1f0d2b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-238', 'description', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish.', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish. +Refs: D-6 (parity), D-78 (interaction zone, display-only conversation widgets). Related: T-248.', NULL, '2026-06-06 07:21:36', '2026-06-06 07:21:36', '2026-06-06 07:21:36', NULL, '63ed4e687ffb9dd923ba3fe0e4a428ec', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70TW38E78FFXDHYAC', 'status', 'backlog', 'ready', NULL, '2026-06-06 07:22:48', '2026-06-06 07:22:48', '2026-06-06 07:22:48', NULL, '74e5e7966bc30bb36c2c657c04ba0e6a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7JXD3FKW91934D0W0', 'status', 'backlog', 'ready', NULL, '2026-06-06 07:22:48', '2026-06-06 07:22:48', '2026-06-06 07:22:48', NULL, '9799cee0cac8cb51d8aa0bf66f76351c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F2GC0ABFFVAH34EC', 'description', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish.', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish. -DONE (2026-06-06). Re-scoped per user: instead of just applying the -hc-checkbox+sort polish to the theme modal, the modal was PROMOTED to a general Settings modal (picker_view.dart -> settings_view.dart, ThemePickerView -> SettingsView). ctrl+k / theme.pick now opens Settings, whose only section today is Appearance: base themes (sorted) + a High contrast toggle, reusing the theme_families helpers (shared with the status-bar popover, T-237). Command id kept as theme.pick (welcome link + tests reference it); retitled ''Settings...''. Extensible: more sections later (or split to a builtin/settings extension when it grows). Tests in test/builtin/theme_picker/widget_test.dart (base-only list, hc toggle applies sibling). NOTE: status-bar popover (T-237) was already done — that is what the user saw ''done in the ui''.', NULL, '2026-06-06 07:24:00', '2026-06-06 07:24:00', '2026-06-06 07:24:00', NULL, '550fc661464648f8da23dbe52acdd134', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-238', 'status', 'in_progress', 'done', NULL, '2026-06-06 07:24:00', '2026-06-06 07:24:00', '2026-06-06 07:24:00', NULL, '18cdaac49c11fa6606f456690d3cbc18', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-250', 'description', NULL, 'Symptom: the permission-mode badge in the Claude status line does nothing visible when clicked, and Ctrl/Cmd+M (cycle mode, T-226) likewise appears to do nothing. Either the mode isn''t actually changing, or — more likely — it changes in the Claude process but the status-bar tracker is never updated. +DONE (2026-06-06). Re-scoped per user: instead of just applying the -hc-checkbox+sort polish to the theme modal, the modal was PROMOTED to a general Settings modal (picker_view.dart -> settings_view.dart, ThemePickerView -> SettingsView). ctrl+k / theme.pick now opens Settings, whose only section today is Appearance: base themes (sorted) + a High contrast toggle, reusing the theme_families helpers (shared with the status-bar popover, T-237). Command id kept as theme.pick (welcome link + tests reference it); retitled ''Settings...''. Extensible: more sections later (or split to a builtin/settings extension when it grows). Tests in test/builtin/theme_picker/widget_test.dart (base-only list, hc toggle applies sibling). NOTE: status-bar popover (T-237) was already done — that is what the user saw ''done in the ui''.', NULL, '2026-06-06 07:24:00', '2026-06-06 07:24:00', '2026-06-06 07:24:00', NULL, '0bb9be512fd59f90b2d893f4241cb096', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F2GC0ABFFVAH34EC', 'status', 'in_progress', 'done', NULL, '2026-06-06 07:24:00', '2026-06-06 07:24:00', '2026-06-06 07:24:00', NULL, '95c12acd291bb53b28c0669a04c894f0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BVE5EYM92CWGSXA4', 'description', NULL, 'Symptom: the permission-mode badge in the Claude status line does nothing visible when clicked, and Ctrl/Cmd+M (cycle mode, T-226) likewise appears to do nothing. Either the mode isn''t actually changing, or — more likely — it changes in the Claude process but the status-bar tracker is never updated. Likely root cause (confirmed by reading the code): the cycle is fire-and-forget with no status feedback. - Composer Ctrl/Cmd+M -> widget.onCycleMode -> _ClaudePaneState._cycleMode (lib/builtin/claude/src/claude_pane.dart:302) -> s.setPermissionMode(nextSafePermissionMode(...)). @@ -328,9 +328,9 @@ Acceptance: - The displayed mode reflects the session''s actual mode (reconciled from the response / next init), not just an optimistic guess. - Regression test at the session level: setPermissionMode emits an updated SessionStatus on statusStream. -Refs: T-226 (interactive mode badge + Ctrl/Cmd+M), T-181 (bypassPermissions behind confirmed path — keep excluded from the safe cycle), D-78 (interaction-zone / display-only conventions).', NULL, '2026-06-06 07:31:06', '2026-06-06 07:31:06', '2026-06-06 07:31:06', NULL, '1aa3d2ea3fd0501d699c5a26d87ac4eb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-250', 'status', 'backlog', 'ready', NULL, '2026-06-06 07:32:57', '2026-06-06 07:32:57', '2026-06-06 07:32:57', NULL, 'ab80a8820d40ede74f9eeed3eda250d6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', '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. +Refs: T-226 (interactive mode badge + Ctrl/Cmd+M), T-181 (bypassPermissions behind confirmed path — keep excluded from the safe cycle), D-78 (interaction-zone / display-only conventions).', NULL, '2026-06-06 07:31:06', '2026-06-06 07:31:06', '2026-06-06 07:31:06', NULL, '5f293364d4b8cc2ea694e594007b1bc5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BVE5EYM92CWGSXA4', 'status', 'backlog', 'ready', NULL, '2026-06-06 07:32:57', '2026-06-06 07:32:57', '2026-06-06 07:32:57', NULL, '9622e6e1c057e278f1f355fddd7c22d9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', '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. **Progress (2026-05-17, T-117):** The keymap layer now binds `ctrl+shift+p` / `meta+shift+p` to `PaletteOpenIntent` in `assets/keymaps/default.yaml`. The intent resolves end-to-end through `KeymapService.resolveEvent` → `Actions.maybeInvoke`. **Still pending**: an `Actions` provider somewhere in the tree that handles `PaletteOpenIntent` by calling `kernel.palette.open()`, plus the arrow-key / Escape / Enter handlers on `ClidePalette` itself. Those land as part of T-100 (palette keyboard nav). @@ -358,14 +358,14 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Bindings live in the keymap (T-117) — not in `lib/kernel/src/commands/keybindings.dart` (that file is legacy). - The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open). -DONE (2026-06-06). Keybinding + nav were already wired (T-117 binds ctrl/meta+shift+p -> PaletteOpenIntent; _RootShell handles it -> palette.open(); T-100 added arrow/Enter/Esc nav + selected-index). Remaining acceptance implemented now: (1) FUZZY match — PaletteController.filtered() uses a shared subsequence matcher (lib/kernel/src/fuzzy.dart, extracted from quick_open so both share one source of truth), ranked best-score-first; (2) RECENCY — invoked commands float to the top on empty filter and break fuzzy-score ties (in-session MRU). DEFERRED: ''pinned'' commands + cross-session recency persistence need a pin affordance + settings storage — filed as a follow-up. Tests: test/kernel/src/commands/palette_test.dart + test/kernel/src/fuzzy_test.dart.', NULL, '2026-06-06 07:40:43', '2026-06-06 07:40:43', '2026-06-06 07:40:43', NULL, '725ba044d75722c460c7869a2249c386', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'status', 'ready', 'done', NULL, '2026-06-06 07:40:43', '2026-06-06 07:40:43', '2026-06-06 07:40:43', NULL, '121337ddae4598659f07bbe16c1ddcc3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-250', 'status', 'ready', 'done', NULL, '2026-06-06 07:49:08', '2026-06-06 07:49:08', '2026-06-06 07:49:08', NULL, '0765a2ccbb37deedb402d06709511928', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'status', 'ready', 'done', NULL, '2026-06-06 07:57:16', '2026-06-06 07:57:16', '2026-06-06 07:57:16', NULL, 'b36bb78451373dae70a8b5f1f0e16ace', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-208', 'status', 'backlog', 'ready', NULL, '2026-06-06 08:06:55', '2026-06-06 08:06:55', '2026-06-06 08:06:55', NULL, 'c6035deb44dfefa54618b264a5bb4cad', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-208', 'status', 'ready', 'in_progress', NULL, '2026-06-06 08:12:45', '2026-06-06 08:12:45', '2026-06-06 08:12:45', NULL, '1a99ebcbec47b3377fd43373f9c01876', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-209', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 08:12:45', '2026-06-06 08:12:45', '2026-06-06 08:12:45', NULL, '9a84a354ef89306ae8b3f0835c380a4b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'description', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. +DONE (2026-06-06). Keybinding + nav were already wired (T-117 binds ctrl/meta+shift+p -> PaletteOpenIntent; _RootShell handles it -> palette.open(); T-100 added arrow/Enter/Esc nav + selected-index). Remaining acceptance implemented now: (1) FUZZY match — PaletteController.filtered() uses a shared subsequence matcher (lib/kernel/src/fuzzy.dart, extracted from quick_open so both share one source of truth), ranked best-score-first; (2) RECENCY — invoked commands float to the top on empty filter and break fuzzy-score ties (in-session MRU). DEFERRED: ''pinned'' commands + cross-session recency persistence need a pin affordance + settings storage — filed as a follow-up. Tests: test/kernel/src/commands/palette_test.dart + test/kernel/src/fuzzy_test.dart.', NULL, '2026-06-06 07:40:43', '2026-06-06 07:40:43', '2026-06-06 07:40:43', NULL, '3169ee6fdb1c4c2c0243b6b5708166aa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', 'status', 'ready', 'done', NULL, '2026-06-06 07:40:43', '2026-06-06 07:40:43', '2026-06-06 07:40:43', NULL, 'ad3e8ce905d36af8e853d9097f3d1609', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BVE5EYM92CWGSXA4', 'status', 'ready', 'done', NULL, '2026-06-06 07:49:08', '2026-06-06 07:49:08', '2026-06-06 07:49:08', NULL, '2f7a63272c39b62e02adb8355ac73d36', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70TW38E78FFXDHYAC', 'status', 'ready', 'done', NULL, '2026-06-06 07:57:16', '2026-06-06 07:57:16', '2026-06-06 07:57:16', NULL, '2929c0012d12a978d20507b6289d4347', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XMQ44AVCAARQZX14', 'status', 'backlog', 'ready', NULL, '2026-06-06 08:06:55', '2026-06-06 08:06:55', '2026-06-06 08:06:55', NULL, '2154d682e253c8d82a5baee32ccf8c20', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QYYKHPQ03H3QD61W', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 08:12:45', '2026-06-06 08:12:45', '2026-06-06 08:12:45', NULL, '043bc847ebe807e4a470d21b2daaaa7e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XMQ44AVCAARQZX14', 'status', 'ready', 'in_progress', NULL, '2026-06-06 08:12:45', '2026-06-06 08:12:45', '2026-06-06 08:12:45', NULL, '783dc9628035b4a7b15b1d7518447bf6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70TW38E78FFXDHYAC', 'description', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. Problem: my knowledge of the clide surface comes from a hand-curated session blurb. There is no runtime discovery — `clide help` is a stub, `clide ` with no verb just prints `usage:`, and the dispatcher''s registered command table (e.g. pane_commands.dart) is never exposed. D-6 guarantees a verb EXISTS for every UI action, but parity != discoverability: a correctly-registered verb is still unreachable if nothing tells me it''s there. @@ -395,15 +395,15 @@ RE-SCOPE (2026-06-06, from T-208 completeness review): partly overtaken by reali New scope for this ticket: 1. Verify/fix the live `clide capabilities` path end-to-end (rebuild/restart vs real wiring gap in the C client / server umbrella handling). Acceptance: `clide capabilities` returns the JSON command table against a running instance. 2. Reconcile with the already-shipped `/clide` skill rather than authoring anew — confirm it points at the working verb and covers the conventions. -Net: this is now a verify-and-reconcile task, not a build-from-scratch one. Related: T-249, T-208.', NULL, '2026-06-06 08:12:53', '2026-06-06 08:12:53', '2026-06-06 08:12:53', NULL, '44685b2fe0265e9e8b110d4306de92be', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'status', 'done', 'review', NULL, '2026-06-06 08:14:01', '2026-06-06 08:14:01', '2026-06-06 08:14:01', NULL, '1cfdde30e08e7e18f8b9eb39df3abcbc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-249', 'status', 'ready', 'done', NULL, '2026-06-06 08:21:40', '2026-06-06 08:21:40', '2026-06-06 08:21:40', NULL, '996d52afaf650a7d5021184a673d09a3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'status', 'review', 'done', NULL, '2026-06-06 08:31:25', '2026-06-06 08:31:25', '2026-06-06 08:31:25', NULL, '231d1bb74f6f154f7e888760f1c4e3a6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 08:33:42', '2026-06-06 08:33:42', '2026-06-06 08:33:42', NULL, '1ce52a9aabbedd3f4164095e1a0a291e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'in_progress', 'done', NULL, '2026-06-06 08:50:12', '2026-06-06 08:50:12', '2026-06-06 08:50:12', NULL, 'ec94ca747fc852db6bf514b4d5079085', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'done', 'in_progress', NULL, '2026-06-06 09:04:03', '2026-06-06 09:04:03', '2026-06-06 09:04:03', NULL, '9dd345ff2fad6f79a7a1ba7f872dc761', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'status', 'in_progress', 'done', NULL, '2026-06-06 09:20:02', '2026-06-06 09:20:02', '2026-06-06 09:20:02', NULL, 'bf55d4f34a22b9d6e69563bc3e9fd95a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'description', NULL, 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. +Net: this is now a verify-and-reconcile task, not a build-from-scratch one. Related: T-249, T-208.', NULL, '2026-06-06 08:12:53', '2026-06-06 08:12:53', '2026-06-06 08:12:53', NULL, 'a8e6b6c8c4b66c575bf0fa125406dbab', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70TW38E78FFXDHYAC', 'status', 'done', 'review', NULL, '2026-06-06 08:14:01', '2026-06-06 08:14:01', '2026-06-06 08:14:01', NULL, 'f7a8ffee6531ed6f6646855335cb1631', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7JXD3FKW91934D0W0', 'status', 'ready', 'done', NULL, '2026-06-06 08:21:40', '2026-06-06 08:21:40', '2026-06-06 08:21:40', NULL, '2731b543315820500f0456dec9144c0d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70TW38E78FFXDHYAC', 'status', 'review', 'done', NULL, '2026-06-06 08:31:25', '2026-06-06 08:31:25', '2026-06-06 08:31:25', NULL, 'd44a8900af4cb745252d71d996e80db4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NEXC0P46NJHACNM0', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 08:33:42', '2026-06-06 08:33:42', '2026-06-06 08:33:42', NULL, '1ba6fe86835b5c6a53c17e111d70fab6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NEXC0P46NJHACNM0', 'status', 'in_progress', 'done', NULL, '2026-06-06 08:50:12', '2026-06-06 08:50:12', '2026-06-06 08:50:12', NULL, '932999018f2d5f258a734fb521c9ea3f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NEXC0P46NJHACNM0', 'status', 'done', 'in_progress', NULL, '2026-06-06 09:04:03', '2026-06-06 09:04:03', '2026-06-06 09:04:03', NULL, 'f3c5361ca5ea98f01575790cb9eefbeb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NEXC0P46NJHACNM0', 'status', 'in_progress', 'done', NULL, '2026-06-06 09:20:02', '2026-06-06 09:20:02', '2026-06-06 09:20:02', NULL, '1c96df771c2b4ee65728028de006085f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42VPM50VKAWXBCAM8', 'description', NULL, 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. ## Behaviour - Click (or ctrl/cmd-click) on a colorized link opens it via the OS default handler. @@ -413,8 +413,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, ## Notes - Honour user/Claude parity (D-6) where relevant. - Use the platform URL launcher; avoid pulling in an opinionated package if a thin native/url_launcher shim already exists in the tree. -- Guard against non-http schemes / malformed URLs.', NULL, '2026-06-06 09:33:01', '2026-06-06 09:33:01', '2026-06-06 09:33:01', NULL, '5c3a0892cc5e3fe4c5005573ed1611df', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'description', NULL, 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. +- Guard against non-http schemes / malformed URLs.', NULL, '2026-06-06 09:33:01', '2026-06-06 09:33:01', '2026-06-06 09:33:01', NULL, 'e9ae63ee723e46643f5b2c0456e12900', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'description', NULL, 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. ## Behaviour - Detect a pasted-image path in the conversation stream and render the image viewer card. @@ -423,9 +423,9 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, ## Notes - Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images.', NULL, '2026-06-06 09:33:04', '2026-06-06 09:33:04', '2026-06-06 09:33:04', NULL, '294299335de181628d5e4974e3111f4e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-212', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 09:43:49', '2026-06-06 09:43:49', '2026-06-06 09:43:49', NULL, '10094351742e1721be36aae585089bdb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'description', NULL, '## Problem +- Consider failure cases: missing/deleted file, non-image paste, very large images.', NULL, '2026-06-06 09:33:04', '2026-06-06 09:33:04', '2026-06-06 09:33:04', NULL, 'ce15a7fb32f8d4cd997d179bb7d58319', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78Q3VYWSE830CTVB4', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 09:43:49', '2026-06-06 09:43:49', '2026-06-06 09:43:49', NULL, '2453ca10f223362581a2205ea46ed5d0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63WP564EZRERJHVCM', 'description', NULL, '## Problem While a Claude turn is in flight, the composer swaps in a static, muted `running…` label next to the Stop button (`lib/builtin/claude/src/claude_composer.dart:486`, @@ -459,16 +459,16 @@ list, ship our own curated, on-brand list instead. Document the decision. - Use theme tokens for color; keep it muted/tasteful, not distracting. - Touch point today is the `widget.busy` branch in `claude_composer.dart`; consider whether the rotating-word state belongs there or in the session/orchestrator layer. -- Add a widget/golden test for the animated states (bounded pumps — no real timers).', NULL, '2026-06-06 09:57:12', '2026-06-06 09:57:12', '2026-06-06 09:57:12', NULL, '586f397ed5d0f14a0e7d510237d54ea2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-212', 'status', 'in_progress', 'done', NULL, '2026-06-06 10:02:03', '2026-06-06 10:02:03', '2026-06-06 10:02:03', NULL, 'c0174026e93366bda336a58bfdaec999', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-209', 'status', 'in_progress', 'done', NULL, '2026-06-06 10:02:06', '2026-06-06 10:02:06', '2026-06-06 10:02:06', NULL, 'b66cdca1e3acedc775ec2e25e9509cd6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-223', 'description', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both.', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both. +- Add a widget/golden test for the animated states (bounded pumps — no real timers).', NULL, '2026-06-06 09:57:12', '2026-06-06 09:57:12', '2026-06-06 09:57:12', NULL, 'ba353c0ca89d203805c4b72dfe2ba359', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78Q3VYWSE830CTVB4', 'status', 'in_progress', 'done', NULL, '2026-06-06 10:02:03', '2026-06-06 10:02:03', '2026-06-06 10:02:03', NULL, 'fda523ee07c6aec223b8b9f3f4a5048a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QYYKHPQ03H3QD61W', 'status', 'in_progress', 'done', NULL, '2026-06-06 10:02:06', '2026-06-06 10:02:06', '2026-06-06 10:02:06', NULL, 'b53bbacef5897da88d1327c0c381d3fa', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72ACQSDBJTKRBC58W', 'description', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both.', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both. -Refinement (2026-06-06): blockers resolved. Q-2 + Q-3 closed by D-85 (event bus delivery). Design is now fixed: serve --since from a bounded in-memory ring keyed by a monotonic cursor; return events-after-cursor + next-cursor, and a gap marker (per-subscriber dropped-count) when the requested cursor has aged out so callers detect loss rather than silently miss events. Back-pressure is drop-oldest (producer never blocks, subscribers never killed). No on-disk persistence in v1. Ready to implement.', NULL, '2026-06-06 10:24:57', '2026-06-06 10:24:57', '2026-06-06 10:24:57', NULL, 'a343da4fb69f6cfa6dd6c01193a94a8c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-225', 'description', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface).', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface). +Refinement (2026-06-06): blockers resolved. Q-2 + Q-3 closed by D-85 (event bus delivery). Design is now fixed: serve --since from a bounded in-memory ring keyed by a monotonic cursor; return events-after-cursor + next-cursor, and a gap marker (per-subscriber dropped-count) when the requested cursor has aged out so callers detect loss rather than silently miss events. Back-pressure is drop-oldest (producer never blocks, subscribers never killed). No on-disk persistence in v1. Ready to implement.', NULL, '2026-06-06 10:24:57', '2026-06-06 10:24:57', '2026-06-06 10:24:57', NULL, '7df5c81270c33469ed7eafc5d49f496a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5DYC7CRP8TMHADW8W', 'description', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface).', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface). -Refinement (2026-06-06): tool-surface question resolved. Q-32 closed by D-86 — expose the full mcp__clide__* namespace, but GENERATE tools/list from the co-registered command registry (D-74) that already feeds the CLI + palette, so there is no hand-maintained second surface; add a per-command MCP opt-out for poor-fit verbs (long-lived streams, UI-side-effecting). Transport stays SSE-only per D-73 (Q-33 re-confirmed, not reopened). Remaining stubs to make real: getDiagnostics + executeCode. Scope is now: registry->MCP tool-definition adapter (arg-schema -> JSON-Schema), served over the existing SSE transport.', NULL, '2026-06-06 10:25:02', '2026-06-06 10:25:02', '2026-06-06 10:25:02', NULL, '00d54d4febb98792806e520c9921c790', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-257', 'description', NULL, '**Symptom:** In the editor with the Vim preset active, pressing `Esc` to leave insert/visual mode and return to normal mode closes the current file/pane instead of switching modes. +Refinement (2026-06-06): tool-surface question resolved. Q-32 closed by D-86 — expose the full mcp__clide__* namespace, but GENERATE tools/list from the co-registered command registry (D-74) that already feeds the CLI + palette, so there is no hand-maintained second surface; add a per-command MCP opt-out for poor-fit verbs (long-lived streams, UI-side-effecting). Transport stays SSE-only per D-73 (Q-33 re-confirmed, not reopened). Remaining stubs to make real: getDiagnostics + executeCode. Scope is now: registry->MCP tool-definition adapter (arg-schema -> JSON-Schema), served over the existing SSE transport.', NULL, '2026-06-06 10:25:02', '2026-06-06 10:25:02', '2026-06-06 10:25:02', NULL, 'cd82bcfcee3505798a084a326fc2cd3c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4K2AAPK52Y8BEJ3YR', 'description', NULL, '**Symptom:** In the editor with the Vim preset active, pressing `Esc` to leave insert/visual mode and return to normal mode closes the current file/pane instead of switching modes. **Repro:** 1. Select the Vim keybinding preset (T-65). @@ -486,38 +486,38 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Sanity-check that `dismiss` should close a pane/file at all here — closing the active file on a bare Esc is surprising even outside Vim. **Related:** epic T-65 (Vim preset), T-207 (Vim mode service owns Esc -> normal), T-205 / T-117 (key-sequence resolution + when-clauses). -**Files:** `assets/keymaps/default.yaml`, `assets/keymaps/vim.yaml`, `lib/builtin/editor/src/editor_view.dart`, `lib/kernel/src/keymap/`.', NULL, '2026-06-06 14:01:03', '2026-06-06 14:01:03', '2026-06-06 14:01:03', NULL, '6cd03ce2694808401bc66fb0c9267284', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-257', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 18:44:58', '2026-06-06 18:44:58', '2026-06-06 18:44:58', NULL, '00d866fbb270421aa9e7103ac2a9b720', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-257', 'status', 'in_progress', 'done', NULL, '2026-06-06 18:58:25', '2026-06-06 18:58:25', '2026-06-06 18:58:25', NULL, '8239fd399aa8e8b11784e72667ef532b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-223', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 19:06:30', '2026-06-06 19:06:30', '2026-06-06 19:06:30', NULL, '3dd66949d8dfe668b86e3b54164cc12a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-225', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 19:06:30', '2026-06-06 19:06:30', '2026-06-06 19:06:30', NULL, 'feaa438b0076073c9dd39b319e321253', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'status', 'ready', 'done', NULL, '2026-06-06 19:06:30', '2026-06-06 19:06:30', '2026-06-06 19:06:30', NULL, 'a4d2cc99c6f4c2de46320bff2d2f2270', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-222', 'status', 'backlog', 'done', NULL, '2026-06-06 19:24:44', '2026-06-06 19:24:44', '2026-06-06 19:24:44', NULL, '7ba18fee0a7abf0f43ee86b5f8601a2e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-223', 'status', 'in_progress', 'done', NULL, '2026-06-06 19:24:44', '2026-06-06 19:24:44', '2026-06-06 19:24:44', NULL, 'c4edb71439a13551d1ec27b2e8bcf2d3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-225', 'status', 'in_progress', 'done', NULL, '2026-06-06 19:37:47', '2026-06-06 19:37:47', '2026-06-06 19:37:47', NULL, '5622a2bf33c28bc00aab480172366698', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-256', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 19:45:03', '2026-06-06 19:45:03', '2026-06-06 19:45:03', NULL, 'f9997e23005cbb1966f5e132d20524a5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-256', 'status', 'in_progress', 'done', NULL, '2026-06-06 19:53:07', '2026-06-06 19:53:07', '2026-06-06 19:53:07', NULL, '2c8e4bc4e093620c9313dbf09b6f48f0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-208', 'status', 'in_progress', 'done', NULL, '2026-06-06 19:53:07', '2026-06-06 19:53:07', '2026-06-06 19:53:07', NULL, '83aab9acdaff92bd1018b0591291abb7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'status', 'backlog', 'ready', NULL, '2026-06-06 20:00:45', '2026-06-06 20:00:45', '2026-06-06 20:00:45', NULL, '0634bad8a7ba2f618e9c27c6b4014544', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', '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.', '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. +**Files:** `assets/keymaps/default.yaml`, `assets/keymaps/vim.yaml`, `lib/builtin/editor/src/editor_view.dart`, `lib/kernel/src/keymap/`.', NULL, '2026-06-06 14:01:03', '2026-06-06 14:01:03', '2026-06-06 14:01:03', NULL, '01f136ca149c821a56a37d36821fd959', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4K2AAPK52Y8BEJ3YR', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 18:44:58', '2026-06-06 18:44:58', '2026-06-06 18:44:58', NULL, 'd1ce20399e57562703a5f061d9a8e012', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4K2AAPK52Y8BEJ3YR', 'status', 'in_progress', 'done', NULL, '2026-06-06 18:58:25', '2026-06-06 18:58:25', '2026-06-06 18:58:25', NULL, '968e8833c027634439b39dc6c6f57ff1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5DYC7CRP8TMHADW8W', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 19:06:30', '2026-06-06 19:06:30', '2026-06-06 19:06:30', NULL, '6fd4f5a99810ca96fa7db1de1c1511ed', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72ACQSDBJTKRBC58W', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 19:06:30', '2026-06-06 19:06:30', '2026-06-06 19:06:30', NULL, '873de3e88de3b6d9bd74c96753af3568', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', 'status', 'ready', 'done', NULL, '2026-06-06 19:06:30', '2026-06-06 19:06:30', '2026-06-06 19:06:30', NULL, 'e3c10829d6afbf5d2682ef9e0df257a8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72ACQSDBJTKRBC58W', 'status', 'in_progress', 'done', NULL, '2026-06-06 19:24:44', '2026-06-06 19:24:44', '2026-06-06 19:24:44', NULL, '3138697fe1501716a729fd70ce390099', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM620F5ENRYNT9S0JDW', 'status', 'backlog', 'done', NULL, '2026-06-06 19:24:44', '2026-06-06 19:24:44', '2026-06-06 19:24:44', NULL, '5abe1c20278eb488c1e743769be37542', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5DYC7CRP8TMHADW8W', 'status', 'in_progress', 'done', NULL, '2026-06-06 19:37:47', '2026-06-06 19:37:47', '2026-06-06 19:37:47', NULL, '1e98081ef3b9dca916bf47fcef83aa9e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6S7KHYPE9W7S1JHZ8', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 19:45:03', '2026-06-06 19:45:03', '2026-06-06 19:45:03', NULL, '2aa119d66370d885457a7687ade45723', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6S7KHYPE9W7S1JHZ8', 'status', 'in_progress', 'done', NULL, '2026-06-06 19:53:07', '2026-06-06 19:53:07', '2026-06-06 19:53:07', NULL, '0f6ef4536136a8b715e1a0f06e999ae0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XMQ44AVCAARQZX14', 'status', 'in_progress', 'done', NULL, '2026-06-06 19:53:07', '2026-06-06 19:53:07', '2026-06-06 19:53:07', NULL, 'c8767f309187c2af7dcbc185ff808356', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7GCVGCH9RMGYHENCC', 'status', 'backlog', 'ready', NULL, '2026-06-06 20:00:45', '2026-06-06 20:00:45', '2026-06-06 20:00:45', NULL, 'b0c8f6f0883b0737db525a3d08010afd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7GCVGCH9RMGYHENCC', '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.', '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. -UX design (2026-06-06, D-87): the panel is a bottom OUTPUT DOCK, read-only, two tabs — Output (the Logger stream, filter by source/level/text, auto-scroll) + Problems (moved out of the sidebar; no duplication). Toggled by a single status-bar widget that REPLACES the app-status indicator (merged health+log: green check when clean, warn/error counts when not, chevron for open state) — opens with click or Cmd/Ctrl+J. Needs a bounded in-memory ring sink on the Logger (no history today). Layout amends D-47 (dock pushes Claude up, capped so Claude stays >=50%). Terminal is NOT in the dock — kept first-class in the editor pane, tracked by T-258. Resolves Q-28. Wireframe: docs/design/wireframes/output-dock/.', NULL, '2026-06-06 20:56:04', '2026-06-06 20:56:04', '2026-06-06 20:56:04', NULL, 'c1df82ae7d048d460fa63288d00ad78b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'status', 'backlog', 'review', NULL, '2026-06-06 20:56:20', '2026-06-06 20:56:20', '2026-06-06 20:56:20', NULL, 'bcdc7e0811d809ddd533e12867c111d5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-252', 'status', 'backlog', 'ready', NULL, '2026-06-06 21:00:34', '2026-06-06 21:00:34', '2026-06-06 21:00:34', NULL, '2a079e0a52e8f4072918349942e6089e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'status', 'ready', 'in_progress', NULL, '2026-06-06 21:07:50', '2026-06-06 21:07:50', '2026-06-06 21:07:50', NULL, 'f76cd5804a0dfe4dd8b8a6581c9c3387', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', '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.', 'When an editor is open in edit mode on a renderable doc (.md), the right-hand context panel auto-opens a read-only view that mirrors the buffer and updates live as the user types (D-50 behavior 4). If the editor is on a non-renderable file, no auto-viewer (D-50 behavior 5, already holds). +UX design (2026-06-06, D-87): the panel is a bottom OUTPUT DOCK, read-only, two tabs — Output (the Logger stream, filter by source/level/text, auto-scroll) + Problems (moved out of the sidebar; no duplication). Toggled by a single status-bar widget that REPLACES the app-status indicator (merged health+log: green check when clean, warn/error counts when not, chevron for open state) — opens with click or Cmd/Ctrl+J. Needs a bounded in-memory ring sink on the Logger (no history today). Layout amends D-47 (dock pushes Claude up, capped so Claude stays >=50%). Terminal is NOT in the dock — kept first-class in the editor pane, tracked by T-258. Resolves Q-28. Wireframe: docs/design/wireframes/output-dock/.', NULL, '2026-06-06 20:56:04', '2026-06-06 20:56:04', '2026-06-06 20:56:04', NULL, '8e9ea74ba5a360b0211ae1b3898d60df', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JK0RHMRAH47K8ND4', 'status', 'backlog', 'review', NULL, '2026-06-06 20:56:20', '2026-06-06 20:56:20', '2026-06-06 20:56:20', NULL, 'c4bb10f75b8ac6ab0bb472fe4e5bff74', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QW8VH454J8V3E174', 'status', 'backlog', 'ready', NULL, '2026-06-06 21:00:34', '2026-06-06 21:00:34', '2026-06-06 21:00:34', NULL, '6f96d3cc06ca1d904ae02910499d1e36', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7GCVGCH9RMGYHENCC', 'status', 'ready', 'in_progress', NULL, '2026-06-06 21:07:50', '2026-06-06 21:07:50', '2026-06-06 21:07:50', NULL, '15e3a73b9493513a9e2e3eadb1ec414e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JK0RHMRAH47K8ND4', '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.', 'When an editor is open in edit mode on a renderable doc (.md), the right-hand context panel auto-opens a read-only view that mirrors the buffer and updates live as the user types (D-50 behavior 4). If the editor is on a non-renderable file, no auto-viewer (D-50 behavior 5, already holds). Wiring sketch: EditorRegistry already emits editor.opened / editor.active-changed / editor.edited (lib/src/editor/registry.dart:85). The markdown reader (lib/builtin/markdown/src/markdown_viewer.dart) currently pulls content once via files.read with no subscription. The live-sync work is: (1) on editor.opened for a renderable file, auto-activate the markdown reader in the context panel; (2) subscribe the reader to editor.edited and re-render from the in-memory buffer rather than re-reading disk; (3) read-only — no edit affordances in the mirror. -HISTORY: T-36 originally bundled four D-50 clauses (parse Claude''s output for file references, swap the open viewer, badge the spine when collapsed, live-sync). The give-clide-hands push (T-208) superseded the first three: instead of clide scraping the terminal for references, the agent explicitly drives the reader via `clide ui open markdown ` (T-231) and ui.open -> diff (T-233). The spine badge was dropped (the open is now an intentional agent act, not a passive notification). Re-scoped 2026-06-06 to the one UI-owned piece that give-clide-hands did not deliver: the live-sync read-mirror. Re-homed from T-7 (Tier 5 canvas/graph, a mis-parent) to T-259 (interaction model). See the D-50 amendment.', NULL, '2026-06-06 21:14:16', '2026-06-06 21:14:16', '2026-06-06 21:14:16', NULL, '336ac966d853631c4e2af1ada62c42aa', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'title', 'Context auto-behavior: right panel reacts to Claude references', 'Live-sync read-mirror: context panel tracks the open editor buffer', NULL, '2026-06-06 21:14:16', '2026-06-06 21:14:16', '2026-06-06 21:14:16', NULL, '67abd3f0d89ecc36bb4bdc8da09b6214', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'parent_id', 'T-7', 'T-259', NULL, '2026-06-06 21:14:19', '2026-06-06 21:14:19', '2026-06-06 21:14:19', NULL, '5b9e4be14eebf718400754752107b84c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'status', 'review', 'backlog', NULL, '2026-06-06 21:14:22', '2026-06-06 21:14:22', '2026-06-06 21:14:22', NULL, 'cbecd31c7d0a269f97351da4c1a6e691', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'status', 'backlog', 'ready', NULL, '2026-06-06 21:43:40', '2026-06-06 21:43:40', '2026-06-06 21:43:40', NULL, 'bc4878fd6f920def39986ab0c9b2c34e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'status', 'in_progress', 'done', NULL, '2026-06-07 08:03:17', '2026-06-07 08:03:17', '2026-06-07 08:03:17', NULL, 'e166c11b3839c37954e7e3c21194994a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-252', 'status', 'ready', 'in_progress', NULL, '2026-06-07 08:07:42', '2026-06-07 08:07:42', '2026-06-07 08:07:42', NULL, 'ecff1f56ea618c88ac4f2dd9e5f53e41', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-252', 'status', 'in_progress', 'done', NULL, '2026-06-07 08:32:36', '2026-06-07 08:32:36', '2026-06-07 08:32:36', NULL, '7f66c2872874836d984b5e7f516ba3ff', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'description', 'Today a Claude tool call renders as TWO stacked cards in the conversation log: the tool-use card (e.g. "Write " with a collapsible call body) and a separate success/result card ("Write · result / File created successfully..."). Collapse the successful pair into ONE card; failure keeps the current two-card interaction. +HISTORY: T-36 originally bundled four D-50 clauses (parse Claude''s output for file references, swap the open viewer, badge the spine when collapsed, live-sync). The give-clide-hands push (T-208) superseded the first three: instead of clide scraping the terminal for references, the agent explicitly drives the reader via `clide ui open markdown ` (T-231) and ui.open -> diff (T-233). The spine badge was dropped (the open is now an intentional agent act, not a passive notification). Re-scoped 2026-06-06 to the one UI-owned piece that give-clide-hands did not deliver: the live-sync read-mirror. Re-homed from T-7 (Tier 5 canvas/graph, a mis-parent) to T-259 (interaction model). See the D-50 amendment.', NULL, '2026-06-06 21:14:16', '2026-06-06 21:14:16', '2026-06-06 21:14:16', NULL, '94a5b6a263c0d1ba8922a753db4434c5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JK0RHMRAH47K8ND4', 'title', 'Context auto-behavior: right panel reacts to Claude references', 'Live-sync read-mirror: context panel tracks the open editor buffer', NULL, '2026-06-06 21:14:16', '2026-06-06 21:14:16', '2026-06-06 21:14:16', NULL, 'ed09e0656722f18cfa69461f3ed2af9d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JK0RHMRAH47K8ND4', 'parent_id', 'T-7', 'T-259', NULL, '2026-06-06 21:14:19', '2026-06-06 21:14:19', '2026-06-06 21:14:19', NULL, '828d07396d886d58d6cf09902d9b6e9a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JK0RHMRAH47K8ND4', 'status', 'review', 'backlog', NULL, '2026-06-06 21:14:22', '2026-06-06 21:14:22', '2026-06-06 21:14:22', NULL, '38410b8cab1366094cac896ff6ff1257', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63WP564EZRERJHVCM', 'status', 'backlog', 'ready', NULL, '2026-06-06 21:43:40', '2026-06-06 21:43:40', '2026-06-06 21:43:40', NULL, '51798182bc568f87d420859e4389a4f0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7GCVGCH9RMGYHENCC', 'status', 'in_progress', 'done', NULL, '2026-06-07 08:03:17', '2026-06-07 08:03:17', '2026-06-07 08:03:17', NULL, '6b1cfe3dd2263ea586eee797748dfc0a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QW8VH454J8V3E174', 'status', 'ready', 'in_progress', NULL, '2026-06-07 08:07:42', '2026-06-07 08:07:42', '2026-06-07 08:07:42', NULL, '54492c75e5f9415a70de079767bc00fe', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QW8VH454J8V3E174', 'status', 'in_progress', 'done', NULL, '2026-06-07 08:32:36', '2026-06-07 08:32:36', '2026-06-07 08:32:36', NULL, '96c3f6af3d4274629ec0d2b1809b3cb8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71P14PAF7K4QE61G0', 'description', 'Today a Claude tool call renders as TWO stacked cards in the conversation log: the tool-use card (e.g. "Write " with a collapsible call body) and a separate success/result card ("Write · result / File created successfully..."). Collapse the successful pair into ONE card; failure keeps the current two-card interaction. Current code: lib/builtin/claude/src/conversation_view.dart — _toolUse (~L294) renders AssistantToolUse, _toolResult (~L329) renders ToolResultMessage; the two ConversationItems are linked by toolUseId (ToolResultMessage.isError marks failure). ConversationCard header is built in lib/builtin/claude/src/conversation_card.dart _header (~L200). Builds on T-168 (per-tool body rendering). @@ -577,8 +577,8 @@ IMPLEMENTATION NOTES (from streamlining analysis): C — Error-card symmetry decision: this ticket merges only SUCCESS into the tool card; the error path intentionally stays a separate prominent red "· error" card (conversation_view.dart ~L337) for failure visibility. Decide explicitly: keep that asymmetry (success folds, failure stays two-card) OR also stamp a red status mark on the tool-use header for visual symmetry while still keeping the separate red card. Default leaning: keep the separate red card; optionally add the red header mark. -D — Activity-cluster coupling: activity_cluster.groupConversation currently folds a tool CALL and its RESULT as TWO separate foldable items into a cluster (activity_cluster.dart ~L100-109, classifying each by toolUseId). Once this ticket makes call+result a single self-contained card, the grouping pass must treat the tool call as ONE unit (its result is part of the card, no longer a separate foldable item) or the result will double-render (once folded into the card, once as a cluster item). Update _isFoldable / the pairing logic accordingly and add a test that a merged tool card is not double-counted.', NULL, '2026-06-07 08:40:45', '2026-06-07 08:40:45', '2026-06-07 08:40:45', NULL, '4e43d330678ad9b5c090755cfcd892b2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'description', 'When Claude launches a sub-agent (the Agent/Task tool), the sub-agent conversation runs as a SIDECHAIN. The prompt Claude wrote for that sub-agent comes through the transcript as a UserMessage with isSidechain=true but injected=false. The conversation view does not look at isSidechain, so it renders that prompt with the blue "you" label exactly like real user input — FALSELY implying the user sent it. It sits as a standalone block below the Agent tool-use card. This is misleading: the user did not write that prompt. +D — Activity-cluster coupling: activity_cluster.groupConversation currently folds a tool CALL and its RESULT as TWO separate foldable items into a cluster (activity_cluster.dart ~L100-109, classifying each by toolUseId). Once this ticket makes call+result a single self-contained card, the grouping pass must treat the tool call as ONE unit (its result is part of the card, no longer a separate foldable item) or the result will double-render (once folded into the card, once as a cluster item). Update _isFoldable / the pairing logic accordingly and add a test that a merged tool card is not double-counted.', NULL, '2026-06-07 08:40:45', '2026-06-07 08:40:45', '2026-06-07 08:40:45', NULL, 'ce5df7afb04b516724c06e891f9ca0a4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XXSERDHKR0EEH3N4', 'description', 'When Claude launches a sub-agent (the Agent/Task tool), the sub-agent conversation runs as a SIDECHAIN. The prompt Claude wrote for that sub-agent comes through the transcript as a UserMessage with isSidechain=true but injected=false. The conversation view does not look at isSidechain, so it renders that prompt with the blue "you" label exactly like real user input — FALSELY implying the user sent it. It sits as a standalone block below the Agent tool-use card. This is misleading: the user did not write that prompt. Confirmed facts from the code: every transcript envelope carries BOTH isSidechain AND parentUuid (transcript_reader.dart ~L547 parses isSidechain but never USES it; parentUuid is dropped entirely). The Agent/Task tool-use is rendered by _toolUse in lib/builtin/claude/src/conversation_view.dart (only AskUserQuestion is special-cased today). The "you" vs "context" label is chosen in conversation_view.dart (~L200): UserMessage with injected=true → "context" (muted); else → "you" (blue, tokens.globalFocus). isSidechain is currently never read by any rendering/grouping logic. @@ -626,13 +626,13 @@ Refs: D-78 (interaction zone / display-only conversation widgets; injected/conte IMPLEMENTATION NOTE (E — Agent-card layered ordering, from streamlining analysis): -With this ticket (fold prompt) and T-262 (merge success result) both landing, the Agent/Task card ends up owning multiple segments: the tool INPUT, the folded PROMPT, and — via T-262 — the sub-agents final returned RESULT (the Task ToolResultMessage). Define a deliberate layered order when expanded (e.g. call/input → prompt → returned result) with clear sub-labels/dividers so the Agent card stays readable and does not become a kitchen sink. Coordinate with T-262 (result merge) and T-264 (nesting the whole run): the nested run region vs the returned-result segment must not duplicate the sub-agent output.', NULL, '2026-06-07 08:40:49', '2026-06-07 08:40:49', '2026-06-07 08:40:49', NULL, '32da92ac8d6ccc26be240641d1d15ceb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, '181e31ff6821ae0b9cc8a5909505957c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, '787e0ecb6f59a6849f6bdaa2595ceaaf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, 'c04317821855808ac734b6775b419d33', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, 'cff6c265aa33fb5f60cfe075c3cfaa8a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, 'fb42cd74598846125107209d7c0ba4a6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'description', '## Problem +With this ticket (fold prompt) and T-262 (merge success result) both landing, the Agent/Task card ends up owning multiple segments: the tool INPUT, the folded PROMPT, and — via T-262 — the sub-agents final returned RESULT (the Task ToolResultMessage). Define a deliberate layered order when expanded (e.g. call/input → prompt → returned result) with clear sub-labels/dividers so the Agent card stays readable and does not become a kitchen sink. Coordinate with T-262 (result merge) and T-264 (nesting the whole run): the nested run region vs the returned-result segment must not duplicate the sub-agent output.', NULL, '2026-06-07 08:40:49', '2026-06-07 08:40:49', '2026-06-07 08:40:49', NULL, '6c5fa64d1114b050708456bd650e2213', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XXSERDHKR0EEH3N4', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, '0d2ee5d7b3d6623a90e36ccaca03916d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYMMKFD5Z8WG9F60', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, '223065799d6e12580c20492ea0233057', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45HHRBCRMV2166Y6M', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, '517313811d3de674226344c51953e787', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71P14PAF7K4QE61G0', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, '694c1016bf75a0104bef6ab9e152a9d6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q1Q97CHA0XNK2G4W', 'parent_id', NULL, 'T-267', NULL, '2026-06-07 08:43:00', '2026-06-07 08:43:00', '2026-06-07 08:43:00', NULL, 'b94daf10a59ddbb4bacec73eb5ccb716', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63WP564EZRERJHVCM', 'description', '## Problem While a Claude turn is in flight, the composer swaps in a static, muted `running…` label next to the Stop button (`lib/builtin/claude/src/claude_composer.dart:486`, @@ -702,9 +702,9 @@ list, ship our own curated, on-brand list instead. Document the decision. whether the rotating-word state belongs there or in the session/orchestrator layer. - Add a widget/golden test for the animated states (bounded pumps — no real timers). -Refinement (2026-06-07): open question resolved — ship OUR OWN curated verb list, do not reuse the CLI''s. Rationale: the spinner words are a TUI cosmetic not surfaced by the stream-json control protocol (so there''s nothing to read live), and extracting Anthropic''s bundled list is a licensing gray area. A clide-owned list aligns with ''own the rendering stack'' and D-75 (isolate/version-pin CC coupling). State lives in the WIDGET layer (a RunningIndicator in lib/builtin/claude/), not the orchestrator — it''s ephemeral UI. Animation via AnimationController (no Timers, so tests use bounded pumps); reduced-motion (MediaQuery.disableAnimations) shows a static verb. Ready to implement.', NULL, '2026-06-07 08:44:32', '2026-06-07 08:44:32', '2026-06-07 08:44:32', NULL, 'e9f7c48071d4669d6efdf4ab6238220f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'status', 'ready', 'in_progress', NULL, '2026-06-07 08:44:32', '2026-06-07 08:44:32', '2026-06-07 08:44:32', NULL, 'a97e70d290c60f9055cb3d254ba3cd1f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'description', 'DESIGN DISCUSSION PENDING — do not build until T-262/T-263/T-264/T-265 settle. Captured from user feedback; the interaction model needs a design pass first. +Refinement (2026-06-07): open question resolved — ship OUR OWN curated verb list, do not reuse the CLI''s. Rationale: the spinner words are a TUI cosmetic not surfaced by the stream-json control protocol (so there''s nothing to read live), and extracting Anthropic''s bundled list is a licensing gray area. A clide-owned list aligns with ''own the rendering stack'' and D-75 (isolate/version-pin CC coupling). State lives in the WIDGET layer (a RunningIndicator in lib/builtin/claude/), not the orchestrator — it''s ephemeral UI. Animation via AnimationController (no Timers, so tests use bounded pumps); reduced-motion (MediaQuery.disableAnimations) shows a static verb. Ready to implement.', NULL, '2026-06-07 08:44:32', '2026-06-07 08:44:32', '2026-06-07 08:44:32', NULL, '420aecd96d2137e5ed9c09ebae4c6c38', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63WP564EZRERJHVCM', 'status', 'ready', 'in_progress', NULL, '2026-06-07 08:44:32', '2026-06-07 08:44:32', '2026-06-07 08:44:32', NULL, 'fc3d71b83ffcc1a213a2f0a74219bffc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45HHRBCRMV2166Y6M', 'description', 'DESIGN DISCUSSION PENDING — do not build until T-262/T-263/T-264/T-265 settle. Captured from user feedback; the interaction model needs a design pass first. Problem: the current activity card (lib/builtin/claude/src/conversation_view.dart _ActivityCard ~L427, grouping in activity_cluster.dart) is a COLLAPSER HEADER that, when expanded, reveals the subsequent cards rendered below it. Two issues: 1. Model: it reads as a header-plus-loose-cards rather than a single container. Proposal: make it a HOLDER CARD that visually CONTAINS its sub-cards, where clicking anywhere in the card space (the holder) toggles collapse/expand — the whole card is the affordance, not just a small caret/header at the top. @@ -746,8 +746,8 @@ Edge cases: Tests: holder background tap toggles; tap on a child sub-card does NOT toggle the holder; copy button still copies (not swallowed); keyboard/AT path toggles via the explicit control; collapsed ticker + step count preserved. Add a golden for the holder container (collapsed + expanded). -Refs: D-78. Provides the shared container consumed by T-264. Related: T-230 (activity card), T-262 / T-263 / T-265. Parent: T-267.', NULL, '2026-06-07 08:48:47', '2026-06-07 08:48:47', '2026-06-07 08:48:47', NULL, 'f837575dbd551fad13a57e3089171395', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'description', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). +Refs: D-78. Provides the shared container consumed by T-264. Related: T-230 (activity card), T-262 / T-263 / T-265. Parent: T-267.', NULL, '2026-06-07 08:48:47', '2026-06-07 08:48:47', '2026-06-07 08:48:47', NULL, '55264f40c2a84b70cfc8b2d006644423', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'description', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). Common substrate across the children: the flat ConversationItem list and its renderers in lib/builtin/claude/src/conversation_view.dart, the pure grouping pass in activity_cluster.dart, the ConversationCard template in conversation_card.dart, and the so-far-unused ConversationItem.isSidechain / parentUuid transcript fields (transcript_reader.dart). @@ -761,13 +761,13 @@ Children: T-262 (merge tool-call + success result into one card with header stat Refs: D-78 (interaction zone / display-only conversation widgets). Built on T-168 (per-tool body rendering) and T-230 (activity card). -SEQUENCING UPDATE (after T-266 refinement): the shared container/holder primitive now lives in T-266 and is CONSUMED by T-264 (nested agent run), so T-266 is no longer "last" — its primitive lands before/with T-264. T-264 is now blocked by T-266. Revised order: T-262 / T-263 (fold success result, fold agent prompt) → T-266 (shared holder/container primitive + activity-card restyle) → T-264 (nest the whole agent run on that primitive) → T-265 (relabel sidechain prose) can land anytime alongside.', NULL, '2026-06-07 08:49:16', '2026-06-07 08:49:16', '2026-06-07 08:49:16', NULL, '7795b980e57c7bf096b495b3f61c8289', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'status', 'in_progress', 'done', NULL, '2026-06-07 08:51:08', '2026-06-07 08:51:08', '2026-06-07 08:51:08', NULL, '68cf2ebb539ca57c1c8165acecf3c111', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-268', 'status', 'backlog', 'done', NULL, '2026-06-07 09:36:59', '2026-06-07 09:36:59', '2026-06-07 09:36:59', NULL, 'dcce34ebf831a8836fe31bcad6bab92c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-269', 'status', 'backlog', 'done', NULL, '2026-06-07 09:51:32', '2026-06-07 09:51:32', '2026-06-07 09:51:32', NULL, '3fec60e8de272979ad791c313eac76f8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'status', 'backlog', 'ready', NULL, '2026-06-07 10:03:36', '2026-06-07 10:03:36', '2026-06-07 10:03:36', NULL, '643716825d71326f6066fb04cbb67077', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-270', 'status', 'backlog', 'done', NULL, '2026-06-07 10:43:18', '2026-06-07 10:43:18', '2026-06-07 10:43:18', NULL, '5bae170fdf2314837253e366ae06812e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', '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.', '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. +SEQUENCING UPDATE (after T-266 refinement): the shared container/holder primitive now lives in T-266 and is CONSUMED by T-264 (nested agent run), so T-266 is no longer "last" — its primitive lands before/with T-264. T-264 is now blocked by T-266. Revised order: T-262 / T-263 (fold success result, fold agent prompt) → T-266 (shared holder/container primitive + activity-card restyle) → T-264 (nest the whole agent run on that primitive) → T-265 (relabel sidechain prose) can land anytime alongside.', NULL, '2026-06-07 08:49:16', '2026-06-07 08:49:16', '2026-06-07 08:49:16', NULL, 'c298fbc83059f3d273cef241597df2b1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63WP564EZRERJHVCM', 'status', 'in_progress', 'done', NULL, '2026-06-07 08:51:08', '2026-06-07 08:51:08', '2026-06-07 08:51:08', NULL, '1c60d10586ea686fabb448e72e0a2d79', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74JAV0MJMB4VQNTRG', 'status', 'backlog', 'done', NULL, '2026-06-07 09:36:59', '2026-06-07 09:36:59', '2026-06-07 09:36:59', NULL, '698f4b77d20b3c6d7a7df7741ec054d3', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P9YQ5V7WS6RFJNHW', 'status', 'backlog', 'done', NULL, '2026-06-07 09:51:32', '2026-06-07 09:51:32', '2026-06-07 09:51:32', NULL, '16c545fabac310e2c03c13575fd785f0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM551VZ2NYV6PD0XQRC', 'status', 'backlog', 'ready', NULL, '2026-06-07 10:03:36', '2026-06-07 10:03:36', '2026-06-07 10:03:36', NULL, 'bac535db2b0406292ac10ba1c90d91af', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40JE9MZZWCXC91THG', 'status', 'backlog', 'done', NULL, '2026-06-07 10:43:18', '2026-06-07 10:43:18', '2026-06-07 10:43:18', NULL, '5330cc7897890a6032fc222f792dc259', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM551VZ2NYV6PD0XQRC', '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.', '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. ## Refinement (decisions, 2026-06-07) @@ -779,20 +779,20 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, **Context behavior:** Items reflect the focused surface; inapplicable items render disabled (greyed), not hidden. Mainly exercised once Edit/Selection land, but View/File items honor it too where relevant. -**Keyboard / a11y:** Full keyboard support — Alt+mnemonic opens a menu, arrow-key navigation within, Enter activates, Esc / click-away closes. Must meet the a11y contract (keyboard nav + semantics).', NULL, '2026-06-07 10:43:22', '2026-06-07 10:43:22', '2026-06-07 10:43:22', NULL, '24d0f8f4ae04e75128f681498c24374f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'status', 'ready', 'done', NULL, '2026-06-07 15:40:06', '2026-06-07 15:40:06', '2026-06-07 15:40:06', NULL, '82f49dda57f70efe965735d4207d1d2c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'status', 'backlog', 'ready', NULL, '2026-06-07 16:25:28', '2026-06-07 16:25:28', '2026-06-07 16:25:28', NULL, 'f89b4bd8614ca3f405f7f888e789d56a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'status', 'ready', 'in_progress', NULL, '2026-06-07 17:22:06', '2026-06-07 17:22:06', '2026-06-07 17:22:06', NULL, 'bd4730367ab0af0d33d145a56748b2ab', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'status', 'backlog', 'in_progress', NULL, '2026-06-07 17:23:39', '2026-06-07 17:23:39', '2026-06-07 17:23:39', NULL, 'dd562ebdd3cb176d4d58bdf07190924a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'status', 'in_progress', 'done', NULL, '2026-06-07 17:41:11', '2026-06-07 17:41:11', '2026-06-07 17:41:11', NULL, '2e581770ff1d438497379beaa530b9c6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 06:53:38', '2026-06-08 06:53:38', '2026-06-08 06:53:38', NULL, 'f182186b0196ff0c7970cef5330efef1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'parent_id', NULL, 'T-276', NULL, '2026-06-08 07:46:46', '2026-06-08 07:46:46', '2026-06-08 07:46:46', NULL, '7a455ce70f5891c8c2ace7896a2eb454', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-274', 'parent_id', NULL, 'T-276', NULL, '2026-06-08 07:46:46', '2026-06-08 07:46:46', '2026-06-08 07:46:46', NULL, 'adcee5482a4c2ba3c669c997890d53a2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-273', 'parent_id', NULL, 'T-276', NULL, '2026-06-08 07:46:46', '2026-06-08 07:46:46', '2026-06-08 07:46:46', NULL, 'f3d6d28f63d78294615a4d28a83e0553', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-276', 'description', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work.', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work. +**Keyboard / a11y:** Full keyboard support — Alt+mnemonic opens a menu, arrow-key navigation within, Enter activates, Esc / click-away closes. Must meet the a11y contract (keyboard nav + semantics).', NULL, '2026-06-07 10:43:22', '2026-06-07 10:43:22', '2026-06-07 10:43:22', NULL, '5c601de01c18e6a2173c503d0e0fb4c4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM551VZ2NYV6PD0XQRC', 'status', 'ready', 'done', NULL, '2026-06-07 15:40:06', '2026-06-07 15:40:06', '2026-06-07 15:40:06', NULL, 'a1ffa40ddf5216c02291ee63fb968969', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'status', 'backlog', 'ready', NULL, '2026-06-07 16:25:28', '2026-06-07 16:25:28', '2026-06-07 16:25:28', NULL, '0e2661a8a87c59ee3518bc5dc05bc934', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'status', 'ready', 'in_progress', NULL, '2026-06-07 17:22:06', '2026-06-07 17:22:06', '2026-06-07 17:22:06', NULL, 'f1faae99292a583bfc9024fd301f583b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71P14PAF7K4QE61G0', 'status', 'backlog', 'in_progress', NULL, '2026-06-07 17:23:39', '2026-06-07 17:23:39', '2026-06-07 17:23:39', NULL, '16d80211bbafa711b80b4cc2330ff3d2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71P14PAF7K4QE61G0', 'status', 'in_progress', 'done', NULL, '2026-06-07 17:41:11', '2026-06-07 17:41:11', '2026-06-07 17:41:11', NULL, 'b4cd35786f4d22bc1020dd0b02c2d372', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XXSERDHKR0EEH3N4', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 06:53:38', '2026-06-08 06:53:38', '2026-06-08 06:53:38', NULL, 'cff556a456f675bd41eb127ce02eb108', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5W6VN98RQM6S22X28', 'parent_id', NULL, 'T-276', NULL, '2026-06-08 07:46:46', '2026-06-08 07:46:46', '2026-06-08 07:46:46', NULL, '66e1bb30391f9c6224bd944301fb844d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55CV3VDYS88VBJZ60', 'parent_id', NULL, 'T-276', NULL, '2026-06-08 07:46:46', '2026-06-08 07:46:46', '2026-06-08 07:46:46', NULL, '7fc1b8a8ef580b47e634e6ee2b710ddd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AAA9WW54JGP83HGW', 'parent_id', NULL, 'T-276', NULL, '2026-06-08 07:46:46', '2026-06-08 07:46:46', '2026-06-08 07:46:46', NULL, 'be1e98024ab2f03928aec37d266040f1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'description', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work.', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work. -**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', NULL, '2026-06-08 07:53:32', '2026-06-08 07:53:32', '2026-06-08 07:53:32', NULL, '24bbdcbf9e0699528a3ace4220dd73c5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-277', 'description', 'clide doesn''t surface pql ticket labels, so a rolling ''UI tweaks'' tracker has to be modeled as an epic (T-276) instead of a label — which is the cleaner primitive. Make labels first-class in the tickets UI so a label can BE the tracker. +**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', NULL, '2026-06-08 07:53:32', '2026-06-08 07:53:32', '2026-06-08 07:53:32', NULL, '6a31f771212b13b5e38a8d3df74dc8a1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM60PAS5RJ55CZGPM1G', 'description', 'clide doesn''t surface pql ticket labels, so a rolling ''UI tweaks'' tracker has to be modeled as an epic (T-276) instead of a label — which is the cleaner primitive. Make labels first-class in the tickets UI so a label can BE the tracker. Scope: - Render a ticket''s labels on its card and detail view in the tickets panel (builtin/tickets) — small chips/pills, ui-design tokens, no hardcoded hex. @@ -812,29 +812,29 @@ Scope: Once this lands, loose UI/UX work + bugs can move from the T-276 tracker-epic to a label (e.g. ''ui-tweak''); file that migration as a follow-up — do NOT auto-close T-276 (it''s marked never-close). -Relevant: lib/builtin/tickets/, lib/src/pql/ (pql wrapper), and pql''s ''ticket label'' / ''ticket list'' label fields.', NULL, '2026-06-08 08:34:24', '2026-06-08 08:34:24', '2026-06-08 08:34:24', NULL, '499355d06a5e7618a1c32e63a6cd1bcd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-277', 'parent_id', NULL, 'T-276', NULL, '2026-06-08 08:34:47', '2026-06-08 08:34:47', '2026-06-08 08:34:47', NULL, '67d78cbf1e92166d60e45b3f51ed440e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'status', 'in_progress', 'done', NULL, '2026-06-08 08:45:23', '2026-06-08 08:45:23', '2026-06-08 08:45:23', NULL, '7e18bb85a36603663881ebd55d77d308', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 08:47:43', '2026-06-08 08:47:43', '2026-06-08 08:47:43', NULL, '0eebe9e270a6a7a4b9cccfb9ffbe5dc4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'status', 'in_progress', 'done', NULL, '2026-06-08 09:04:12', '2026-06-08 09:04:12', '2026-06-08 09:04:12', NULL, 'fecc7bc78e18d5dd3c613d6583371fc3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 09:07:02', '2026-06-08 09:07:02', '2026-06-08 09:07:02', NULL, 'd82d792cf5ef8e77c581bea27f2cd8e3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'status', 'in_progress', 'done', NULL, '2026-06-08 09:25:50', '2026-06-08 09:25:50', '2026-06-08 09:25:50', NULL, '274e5d99e4db192a6b4cf6902f12df45', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 09:27:49', '2026-06-08 09:27:49', '2026-06-08 09:27:49', NULL, '60970c3937a5ecf1c99960aa0f3f40d8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'status', 'in_progress', 'done', NULL, '2026-06-08 10:09:14', '2026-06-08 10:09:14', '2026-06-08 10:09:14', NULL, 'e737c0595ca3bb11cd6dbf3f1b4a914c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'status', 'in_progress', 'done', NULL, '2026-06-08 10:09:21', '2026-06-08 10:09:21', '2026-06-08 10:09:21', NULL, '4e89e3629e224a95f2a3edf0ed4317c4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-273', 'status', 'backlog', 'ready', NULL, '2026-06-08 10:26:11', '2026-06-08 10:26:11', '2026-06-08 10:26:11', NULL, 'bbb51bea942de11f60d0a6d3da74c322', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-280', 'description', NULL, 'Pre-existing (reproduces at base commit 3a78dfa, predates the T-267 conversation-rendering epic). The widget test `test/app_test.dart` › "Open Folder on a non-repo path surfaces the ''no git repo'' dialog" times out after 10 minutes; teardown is wedged on `_RawReceivePort._handleMessage`. +Relevant: lib/builtin/tickets/, lib/src/pql/ (pql wrapper), and pql''s ''ticket label'' / ''ticket list'' label fields.', NULL, '2026-06-08 08:34:24', '2026-06-08 08:34:24', '2026-06-08 08:34:24', NULL, '6dec8b27eed6dc7ed86047c39a363912', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM60PAS5RJ55CZGPM1G', 'parent_id', NULL, 'T-276', NULL, '2026-06-08 08:34:47', '2026-06-08 08:34:47', '2026-06-08 08:34:47', NULL, '3d746f1dbcc965d98a7d04b7398c90c5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XXSERDHKR0EEH3N4', 'status', 'in_progress', 'done', NULL, '2026-06-08 08:45:23', '2026-06-08 08:45:23', '2026-06-08 08:45:23', NULL, '9a57241efcf29530ce0fb548b73b476c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45HHRBCRMV2166Y6M', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 08:47:43', '2026-06-08 08:47:43', '2026-06-08 08:47:43', NULL, '6c4bf58a2d2ac4e46737b9e6cd5d5a9d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45HHRBCRMV2166Y6M', 'status', 'in_progress', 'done', NULL, '2026-06-08 09:04:12', '2026-06-08 09:04:12', '2026-06-08 09:04:12', NULL, 'd5dded000a54cf6db5418b3525f967db', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYMMKFD5Z8WG9F60', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 09:07:02', '2026-06-08 09:07:02', '2026-06-08 09:07:02', NULL, 'e944f09acd2bd2b6d4ccb01b09e63f8b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYMMKFD5Z8WG9F60', 'status', 'in_progress', 'done', NULL, '2026-06-08 09:25:50', '2026-06-08 09:25:50', '2026-06-08 09:25:50', NULL, 'ec67ba8741c18305514d9e81f40fbbfb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q1Q97CHA0XNK2G4W', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 09:27:49', '2026-06-08 09:27:49', '2026-06-08 09:27:49', NULL, 'd29fe012fff5678f99fe0917363fdfad', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q1Q97CHA0XNK2G4W', 'status', 'in_progress', 'done', NULL, '2026-06-08 10:09:14', '2026-06-08 10:09:14', '2026-06-08 10:09:14', NULL, '80ca7819edb9316a2f01c39249905376', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'status', 'in_progress', 'done', NULL, '2026-06-08 10:09:21', '2026-06-08 10:09:21', '2026-06-08 10:09:21', NULL, '751ea8ae1f3b8faef968556fb089d5c9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55CV3VDYS88VBJZ60', 'status', 'backlog', 'ready', NULL, '2026-06-08 10:26:11', '2026-06-08 10:26:11', '2026-06-08 10:26:11', NULL, '2be4726345e4db30d17f7285f08bd59d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YNEQM1WY25BV7TD8', 'description', NULL, 'Pre-existing (reproduces at base commit 3a78dfa, predates the T-267 conversation-rendering epic). The widget test `test/app_test.dart` › "Open Folder on a non-repo path surfaces the ''no git repo'' dialog" times out after 10 minutes; teardown is wedged on `_RawReceivePort._handleMessage`. Bisected (each a 45s-timeout repro, all on this box): - A bare `Process.run` inside `tester.runAsync` (no app, no extensions) hangs → `Process.run`-in-`runAsync` leaks its exit ReceivePort here. - `pumpApp` + empty `runAsync`, and `pumpApp` + a 1.5s real delay → both PASS (boot + runAsync alone is fine). - The full openFolder tap flow hangs even when project validation is stubbed to a synchronous, pure-Dart `.git` walk (no subprocess) AND `runAsync` is removed — so the wedge is not solely the git subprocess; something in the booted-app + extensions + open-folder command path holds a native port that teardown waits on forever. -Quarantined with `skip:` so the suite/gate stays green. Real fix: find the leaked native async resource (likely a Process/Isolate/FakeDaemonClient port reachable from the open-folder command or app boot under the test harness) and ensure it''s drained/cancelled before teardown — or drive the "no git repo" assertion without booting the resource. Then remove the skip.', NULL, '2026-06-08 11:29:33', '2026-06-08 11:29:33', '2026-06-08 11:29:33', NULL, '3a03d00854f80ca2f8320875fe37e558', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-273', 'status', 'ready', 'done', NULL, '2026-06-08 11:44:27', '2026-06-08 11:44:27', '2026-06-08 11:44:27', NULL, 'd4dc7a1144ebf85630815e55f59ff99b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-281', 'status', 'backlog', 'ready', NULL, '2026-06-08 11:47:56', '2026-06-08 11:47:56', '2026-06-08 11:47:56', NULL, '46cc72de9659468d07dc9eed045367cb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-281', 'status', 'ready', 'done', NULL, '2026-06-08 11:49:36', '2026-06-08 11:49:36', '2026-06-08 11:49:36', NULL, 'e302cadedc3e13cf232001de156ef1ec', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-283', 'description', 'Surfaced 2026-06-08 while chasing a --resume hang in the Claude pane (T-274 diagnostic line). The specific corrupted-transcript repro may turn out to be a one-off, but the code trace found two real latent gaps that make a resume hang unrecoverable regardless of root cause. +Quarantined with `skip:` so the suite/gate stays green. Real fix: find the leaked native async resource (likely a Process/Isolate/FakeDaemonClient port reachable from the open-folder command or app boot under the test harness) and ensure it''s drained/cancelled before teardown — or drive the "no git repo" assertion without booting the resource. Then remove the skip.', NULL, '2026-06-08 11:29:33', '2026-06-08 11:29:33', '2026-06-08 11:29:33', NULL, '1f99f25c756004f2d368ef61b00c0da6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55CV3VDYS88VBJZ60', 'status', 'ready', 'done', NULL, '2026-06-08 11:44:27', '2026-06-08 11:44:27', '2026-06-08 11:44:27', NULL, '45834ee4b421914db5fe8d7bfa72568d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59DG2WADJ9A0JFD1W', 'status', 'backlog', 'ready', NULL, '2026-06-08 11:47:56', '2026-06-08 11:47:56', '2026-06-08 11:47:56', NULL, '6602a5dca91f8ef4e762f326032767fc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59DG2WADJ9A0JFD1W', 'status', 'ready', 'done', NULL, '2026-06-08 11:49:36', '2026-06-08 11:49:36', '2026-06-08 11:49:36', NULL, '08fa6a8e6fffcb665c51cbb7a8c93604', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6K1QRC911JMQ9C960', 'description', 'Surfaced 2026-06-08 while chasing a --resume hang in the Claude pane (T-274 diagnostic line). The specific corrupted-transcript repro may turn out to be a one-off, but the code trace found two real latent gaps that make a resume hang unrecoverable regardless of root cause. (a) No timeout / no fallback on the init-event wait. On spawn the orchestrator listens for the session_id from claude''s init event (StreamJsonSession sessionIdResolved -> session_orchestrator.dart ~257) with NO timeout. If ''claude --resume '' never emits an init event (hangs), the listener never fires: the pane shows ''resumed · '' + running indicator forever, the process is never killed, and there is no fallback to a fresh --session-id spawn. Unrecoverable without killing the process / restarting clide. @@ -864,25 +864,25 @@ Acceptance: Cross-refs: T-274 (resumed-session status bar empty), T-167/T-185 (resume/fork session id capture), D-77, claude_pane.dart:279/300-308, session_orchestrator.dart:240/257. -UPDATE 2026-06-08: the active hang did NOT reproduce — clide is running fine inside the 31b214bd primary session (this very session resumes cleanly). So the original break was a one-off (likely the single corrupted transcript), not a live resume bug. This ticket stands as defensive hardening only: the two gaps (no init-event timeout/fallback; resume keyed off file-exists not content) are real but latent — they''d only bite again if a resume genuinely stalls or a metadata-only transcript appears. Lowering to low priority.', NULL, '2026-06-08 13:44:57', '2026-06-08 13:44:57', '2026-06-08 13:44:57', NULL, 'd8eb4299c87c4111a5bb904846e39655', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-282', 'status', 'backlog', 'ready', NULL, '2026-06-08 13:48:31', '2026-06-08 13:48:31', '2026-06-08 13:48:31', NULL, 'fe941aceb2e27ca50d1be47bbc33dd8d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-279', 'status', 'backlog', 'ready', NULL, '2026-06-08 13:48:42', '2026-06-08 13:48:42', '2026-06-08 13:48:42', NULL, '705850ce44bbd34f3b1e441ef00891cc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-282', 'status', 'ready', 'in_progress', NULL, '2026-06-08 13:49:32', '2026-06-08 13:49:32', '2026-06-08 13:49:32', NULL, '1b49ae9cbea9a98aa6eb93ecd6329436', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-279', 'status', 'ready', 'in_progress', NULL, '2026-06-08 13:49:32', '2026-06-08 13:49:32', '2026-06-08 13:49:32', NULL, 'e6593b168ebb31f66ca19fe3e36557cb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'status', 'backlog', 'ready', NULL, '2026-06-08 14:12:26', '2026-06-08 14:12:26', '2026-06-08 14:12:26', NULL, 'a6cea52d7e3e1d0881e6330f1d9799bd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-279', 'status', 'in_progress', 'done', NULL, '2026-06-08 14:12:46', '2026-06-08 14:12:46', '2026-06-08 14:12:46', NULL, '86c50eb88cf56b7e9e9914dcab61b9c3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-284', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 14:55:51', '2026-06-08 14:55:51', '2026-06-08 14:55:51', NULL, '088102bf2d6d9337db66c82fd7b17922', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-284', 'status', 'in_progress', 'done', NULL, '2026-06-08 14:57:15', '2026-06-08 14:57:15', '2026-06-08 14:57:15', NULL, '7f2c5be1e2bb330dac14007350d6dec1', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-285', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 15:22:35', '2026-06-08 15:22:35', '2026-06-08 15:22:35', NULL, 'd4ed6a44830ac6e7cac90547d9a027a9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-285', 'status', 'in_progress', 'done', NULL, '2026-06-08 15:37:29', '2026-06-08 15:37:29', '2026-06-08 15:37:29', NULL, 'b8289629265f19b4f41318863e744804', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-282', 'status', 'in_progress', 'done', NULL, '2026-06-08 15:42:01', '2026-06-08 15:42:01', '2026-06-08 15:42:01', NULL, '3ebc8c4a480cb16b403e562a4829912a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'status', 'ready', 'in_progress', NULL, '2026-06-08 15:45:13', '2026-06-08 15:45:13', '2026-06-08 15:45:13', NULL, '2745334e049523152af749102abe31e5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'parent_id', 'T-276', 'T-286', NULL, '2026-06-08 16:30:47', '2026-06-08 16:30:47', '2026-06-08 16:30:47', NULL, 'e0d912feca8121cdeb7a6e39b17e5eb0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'status', 'in_progress', 'done', NULL, '2026-06-08 16:52:13', '2026-06-08 16:52:13', '2026-06-08 16:52:13', NULL, '6ac483536876a274c0f76a4f4bb5fc69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'status', 'backlog', 'ready', NULL, '2026-06-08 17:34:29', '2026-06-08 17:34:29', '2026-06-08 17:34:29', NULL, '01a873c4191650aa6faa3e7f63926439', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'status', 'ready', 'in_progress', NULL, '2026-06-08 17:34:31', '2026-06-08 17:34:31', '2026-06-08 17:34:31', NULL, 'b2ff3d5dbf5d72be72ba59b2d7e7c4d8', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'status', 'backlog', 'ready', NULL, '2026-06-08 17:37:13', '2026-06-08 17:37:13', '2026-06-08 17:37:13', NULL, '296037989ed5819a589d12dd46f555da', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-288', 'description', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. +UPDATE 2026-06-08: the active hang did NOT reproduce — clide is running fine inside the 31b214bd primary session (this very session resumes cleanly). So the original break was a one-off (likely the single corrupted transcript), not a live resume bug. This ticket stands as defensive hardening only: the two gaps (no init-event timeout/fallback; resume keyed off file-exists not content) are real but latent — they''d only bite again if a resume genuinely stalls or a metadata-only transcript appears. Lowering to low priority.', NULL, '2026-06-08 13:44:57', '2026-06-08 13:44:57', '2026-06-08 13:44:57', NULL, '53d61af5561611d655fcee6eeb0ce208', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W2MWZ45YNK7TJGC4', 'status', 'backlog', 'ready', NULL, '2026-06-08 13:48:31', '2026-06-08 13:48:31', '2026-06-08 13:48:31', NULL, '2bf8f6c9f94877217f737d3354d4dbff', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6A6A56VFSVRRBKMP0', 'status', 'backlog', 'ready', NULL, '2026-06-08 13:48:42', '2026-06-08 13:48:42', '2026-06-08 13:48:42', NULL, 'c297bea78881a8738f24fb3313453294', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6A6A56VFSVRRBKMP0', 'status', 'ready', 'in_progress', NULL, '2026-06-08 13:49:32', '2026-06-08 13:49:32', '2026-06-08 13:49:32', NULL, '0d0c6f8d18e9d59100387427018c5f49', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W2MWZ45YNK7TJGC4', 'status', 'ready', 'in_progress', NULL, '2026-06-08 13:49:32', '2026-06-08 13:49:32', '2026-06-08 13:49:32', NULL, '440309f1ae2191f822fbcb79bd6dc9b2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AAA9WW54JGP83HGW', 'status', 'backlog', 'ready', NULL, '2026-06-08 14:12:26', '2026-06-08 14:12:26', '2026-06-08 14:12:26', NULL, '137d4e3cae7c30bc903e4eb059712c77', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6A6A56VFSVRRBKMP0', 'status', 'in_progress', 'done', NULL, '2026-06-08 14:12:46', '2026-06-08 14:12:46', '2026-06-08 14:12:46', NULL, '7880cf5180b7600fc4ca7673ceb6865d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KFHAS1WZFGBJ6JN4', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 14:55:51', '2026-06-08 14:55:51', '2026-06-08 14:55:51', NULL, '367a474aebf7e34a1cb5f2fad404113a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KFHAS1WZFGBJ6JN4', 'status', 'in_progress', 'done', NULL, '2026-06-08 14:57:15', '2026-06-08 14:57:15', '2026-06-08 14:57:15', NULL, '8a3454fab8ee7165408de4d35ea45880', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GZ55VD4SAZZ6XXP4', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 15:22:35', '2026-06-08 15:22:35', '2026-06-08 15:22:35', NULL, '648f507b573971c2ce0dffab325f219b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GZ55VD4SAZZ6XXP4', 'status', 'in_progress', 'done', NULL, '2026-06-08 15:37:29', '2026-06-08 15:37:29', '2026-06-08 15:37:29', NULL, '9d9a58f785a610aa8bd4ce1850328f04', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W2MWZ45YNK7TJGC4', 'status', 'in_progress', 'done', NULL, '2026-06-08 15:42:01', '2026-06-08 15:42:01', '2026-06-08 15:42:01', NULL, '39d714d44f8a897420d248a9b245c868', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AAA9WW54JGP83HGW', 'status', 'ready', 'in_progress', NULL, '2026-06-08 15:45:13', '2026-06-08 15:45:13', '2026-06-08 15:45:13', NULL, '6e6b49a52ee640d74a171dcc8524bdcd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AAA9WW54JGP83HGW', 'parent_id', 'T-276', 'T-286', NULL, '2026-06-08 16:30:47', '2026-06-08 16:30:47', '2026-06-08 16:30:47', NULL, '6750d8892dd9b437fb7cafbed811fa90', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AAA9WW54JGP83HGW', 'status', 'in_progress', 'done', NULL, '2026-06-08 16:52:13', '2026-06-08 16:52:13', '2026-06-08 16:52:13', NULL, '2e092a257808514fe2b6e43888748024', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R93N5J6RNVTBRB28', 'status', 'backlog', 'ready', NULL, '2026-06-08 17:34:29', '2026-06-08 17:34:29', '2026-06-08 17:34:29', NULL, '55ba9ff47275418cf9238495c02d1dd0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R93N5J6RNVTBRB28', 'status', 'ready', 'in_progress', NULL, '2026-06-08 17:34:31', '2026-06-08 17:34:31', '2026-06-08 17:34:31', NULL, 'b568ce91cc9623a1ffc58c1874a36109', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5E41Q8TX8X55X4MZ0', 'status', 'backlog', 'ready', NULL, '2026-06-08 17:37:13', '2026-06-08 17:37:13', '2026-06-08 17:37:13', NULL, 'f561a5bc9500193fbf0f74b89b4aa95f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4501EBPDFDH84RJ5G', 'description', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. Blockers found (theme picker attempt): 1. Focus-capture race. ClideAnchoredOverlay.captureFocus does a post-frame _scope.requestFocus(); ClideMenu also autofocuses its own node. Depending on mount timing (e.g. an extra ListenableBuilder wrapper) the scope wins and STEALS focus from ClideMenu''s node, so arrow/enter never reach the menu (Esc still works only via the ancestor fallback). Menu bar happens to win the race; the theme popover loses. Fix the primitive''s focus model so content''s own focus node reliably ends up focused (e.g. don''t force scope focus; or make the scope delegate to the autofocus child deterministically), then re-verify menu bar + anchored tests. @@ -905,8 +905,8 @@ RE-SCOPE (2026-06-08, per D-88 amendment): the goal is NOT ''migrate everything - Add a NEW shared content component ClideTypeahead for the slash + @ typeaheads (near-duplicate caret-anchored completion surfaces) — they share this, not ClideMenu. This dedups two hand-rolls. - Quick-open keeps bespoke content (centred filter + fuzzy + two-column rows); uses ClideAnchoredOverlay base only. Weakest fit; last or skip. - Theme picker: its own small toggle+list content on the base (or ClideMenu if it cleanly fits). -Order: (1) fix the base blockers — focus capture racing content autofocus, and follower content untappable in the canSizeOverlay/zero-MediaQuery test harness (use View size for autoFlip; add a test path for anchored content) — with tests; THEN (2) ClideTypeahead + migrate slash + @; (3) theme picker; (4) quick-open last.', NULL, '2026-06-08 20:12:31', '2026-06-08 20:12:31', '2026-06-08 20:12:31', NULL, '4b5c423678c8c672ead79427b78e6448', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-288', 'description', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. +Order: (1) fix the base blockers — focus capture racing content autofocus, and follower content untappable in the canSizeOverlay/zero-MediaQuery test harness (use View size for autoFlip; add a test path for anchored content) — with tests; THEN (2) ClideTypeahead + migrate slash + @; (3) theme picker; (4) quick-open last.', NULL, '2026-06-08 20:12:31', '2026-06-08 20:12:31', '2026-06-08 20:12:31', NULL, 'cd71f1fce9071ab191adc7f2c573fb21', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4501EBPDFDH84RJ5G', 'description', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. Blockers found (theme picker attempt): 1. Focus-capture race. ClideAnchoredOverlay.captureFocus does a post-frame _scope.requestFocus(); ClideMenu also autofocuses its own node. Depending on mount timing (e.g. an extra ListenableBuilder wrapper) the scope wins and STEALS focus from ClideMenu''s node, so arrow/enter never reach the menu (Esc still works only via the ancestor fallback). Menu bar happens to win the race; the theme popover loses. Fix the primitive''s focus model so content''s own focus node reliably ends up focused (e.g. don''t force scope focus; or make the scope delegate to the autofocus child deterministically), then re-verify menu bar + anchored tests. @@ -938,20 +938,21 @@ RE-SCOPE (2026-06-08, per D-88 amendment): the goal is NOT ''migrate everything - Theme picker: its own small toggle+list content on the base (or ClideMenu if it cleanly fits). Order: (1) fix the base blockers — focus capture racing content autofocus, and follower content untappable in the canSizeOverlay/zero-MediaQuery test harness (use View size for autoFlip; add a test path for anchored content) — with tests; THEN (2) ClideTypeahead + migrate slash + @; (3) theme picker; (4) quick-open last. -DONE: Sweep complete. Base blockers fixed (View-size autoFlip; follower drops Align so it hit-tests; tests use anchoredHarness). Migrated: ClideTypeahead + slash + @-mention typeaheads (live ValueNotifier-bridged suggestions); theme picker onto ClideMenu (HC toggle = keepOpenOnSelect item). Quick-open deliberately NOT migrated — persistent centred widget sharing neither ClideMenu nor anchoring; rationale in D-88 closing amendment.', NULL, '2026-06-09 06:18:55', '2026-06-09 06:18:55', '2026-06-09 06:18:55', NULL, '6b0c790d72d53190497b0c0ab3edd001', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-288', 'status', 'backlog', 'done', NULL, '2026-06-09 06:19:01', '2026-06-09 06:19:01', '2026-06-09 06:19:01', NULL, '4ff1094b849e94375249a22e5395acf9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'description', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration.', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration. +DONE: Sweep complete. Base blockers fixed (View-size autoFlip; follower drops Align so it hit-tests; tests use anchoredHarness). Migrated: ClideTypeahead + slash + @-mention typeaheads (live ValueNotifier-bridged suggestions); theme picker onto ClideMenu (HC toggle = keepOpenOnSelect item). Quick-open deliberately NOT migrated — persistent centred widget sharing neither ClideMenu nor anchoring; rationale in D-88 closing amendment.', NULL, '2026-06-09 06:18:55', '2026-06-09 06:18:55', '2026-06-09 06:18:55', NULL, '820fe7f1d764b7707f531d4216578b3a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4501EBPDFDH84RJ5G', 'status', 'backlog', 'done', NULL, '2026-06-09 06:19:01', '2026-06-09 06:19:01', '2026-06-09 06:19:01', NULL, 'b9a0ef09f741bddcb1cfb52c17645f56', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R93N5J6RNVTBRB28', 'description', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration.', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration. -DONE: Primitive (ClideAnchoredOverlay + ClideMenu + ClideMenuListController + ClideTypeahead) shipped and exported. Migrated: T-275 permission picker, menu bar, theme picker (ClideMenu), @-mention + slash typeaheads (ClideTypeahead). Quick-open intentionally left bespoke (centred persistent widget, no shared shape) — see D-88 closing amendment. Modal session/project/branch pickers stay on DialogRouter by design.', NULL, '2026-06-09 06:19:33', '2026-06-09 06:19:33', '2026-06-09 06:19:33', NULL, 'b06e0da4e5e9034b278ce6ee038d9735', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'status', 'in_progress', 'done', NULL, '2026-06-09 06:19:36', '2026-06-09 06:19:36', '2026-06-09 06:19:36', NULL, '758a6290901bf0cea2725db0269c6a68', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'status', 'backlog', 'ready', NULL, '2026-06-09 06:33:31', '2026-06-09 06:33:31', '2026-06-09 06:33:31', NULL, 'a2ce57ac3dd7c550d5bd3ef6de579e31', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'status', 'ready', 'in_progress', NULL, '2026-06-09 06:34:31', '2026-06-09 06:34:31', '2026-06-09 06:34:31', NULL, '58f6b717659a5cdba9de5af300add3be', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', '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.', '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. +DONE: Primitive (ClideAnchoredOverlay + ClideMenu + ClideMenuListController + ClideTypeahead) shipped and exported. Migrated: T-275 permission picker, menu bar, theme picker (ClideMenu), @-mention + slash typeaheads (ClideTypeahead). Quick-open intentionally left bespoke (centred persistent widget, no shared shape) — see D-88 closing amendment. Modal session/project/branch pickers stay on DialogRouter by design.', NULL, '2026-06-09 06:19:33', '2026-06-09 06:19:33', '2026-06-09 06:19:33', NULL, 'c919a01c49bf8be33ba2e1cf5c3a7111', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R93N5J6RNVTBRB28', 'status', 'in_progress', 'done', NULL, '2026-06-09 06:19:36', '2026-06-09 06:19:36', '2026-06-09 06:19:36', NULL, '7f0dc7f8b224b31b585d5d2a49f88df6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50BPNDT50PM24ZVA4', 'status', 'backlog', 'ready', NULL, '2026-06-09 06:33:31', '2026-06-09 06:33:31', '2026-06-09 06:33:31', NULL, 'a9788029757cd116218c5a4f1f47c927', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5E41Q8TX8X55X4MZ0', 'status', 'ready', 'in_progress', NULL, '2026-06-09 06:34:31', '2026-06-09 06:34:31', '2026-06-09 06:34:31', NULL, 'd179dce4eb30e804056b7a49596134c2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5E41Q8TX8X55X4MZ0', '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.', '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. -DONE: The editor honours .editorconfig end-to-end. New source-agnostic EditorSettings model (editor_settings.dart) + composition seam (editor_settings_resolver.dart); .editorconfig demoted to a source (editorconfig.dart, own INI parser + glob matcher, root/nearest-wins precedence, no deps). Registry resolves on open and re-resolves open buffers when a .editorconfig is saved in-app (editor.settings-changed; a save hook, not an fs-watch). Editor: Tab/Shift+Tab indent + max_line_length ruler; save applies end_of_line/trim_trailing_whitespace/insert_final_newline. Follow-ups: T-290 (edit from settings panel), T-291 (external fs-watch). 100% coverage on new model/resolver/parser.', NULL, '2026-06-09 10:46:41', '2026-06-09 10:46:41', '2026-06-09 10:46:41', NULL, '96304f51da31f3ea356faa207ea1873e', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'status', 'in_progress', 'done', NULL, '2026-06-09 10:46:41', '2026-06-09 10:46:41', '2026-06-09 10:46:41', NULL, '9419d87a1b9d0cbb938399638d6a1453', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'status', 'ready', 'in_progress', NULL, '2026-06-09 14:59:45', '2026-06-09 14:59:45', '2026-06-09 14:59:45', NULL, '57787b04f8886096911590b3e6357d90', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'description', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart): +DONE: The editor honours .editorconfig end-to-end. New source-agnostic EditorSettings model (editor_settings.dart) + composition seam (editor_settings_resolver.dart); .editorconfig demoted to a source (editorconfig.dart, own INI parser + glob matcher, root/nearest-wins precedence, no deps). Registry resolves on open and re-resolves open buffers when a .editorconfig is saved in-app (editor.settings-changed; a save hook, not an fs-watch). Editor: Tab/Shift+Tab indent + max_line_length ruler; save applies end_of_line/trim_trailing_whitespace/insert_final_newline. Follow-ups: T-290 (edit from settings panel), T-291 (external fs-watch). 100% coverage on new model/resolver/parser.', NULL, '2026-06-09 10:46:41', '2026-06-09 10:46:41', '2026-06-09 10:46:41', NULL, '88ea41d16ef29fe640e57f05dca084c2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5E41Q8TX8X55X4MZ0', 'status', 'in_progress', 'done', NULL, '2026-06-09 10:46:41', '2026-06-09 10:46:41', '2026-06-09 10:46:41', NULL, '96346570173938ba2b7112497db1210e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50BPNDT50PM24ZVA4', 'status', 'ready', 'in_progress', NULL, '2026-06-09 14:59:45', '2026-06-09 14:59:45', '2026-06-09 14:59:45', NULL, 'af89fd8ae447a5052eaeb7b9375590cf', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50BPNDT50PM24ZVA4', 'status', 'in_progress', 'done', NULL, '2026-06-09 15:03:23', '2026-06-09 15:03:23', '2026-06-09 15:03:23', NULL, '60faf709c9ae2ac315fb9a5331fdcca7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50BPNDT50PM24ZVA4', 'description', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart): 1. Typing a ''-'' character breaks the typeahead. Many command names contain hyphens (e.g. clear-context-style names, custom commands), but typing ''-'' appears to drop/empty the suggestion list or mis-parse the active query. Suspect activeSlashQuery''s token run or filterSlashCommands prefix matching not handling ''-'' (or the composer treating ''-'' as a boundary). @@ -969,10 +970,10 @@ Repro: open the composer, type ''/'' then a command fragment; (a) include a ''-' Acceptance: ''-'' is treated as a normal command-name character (suggestions keep filtering through hyphens); Tab reliably completes the highlighted suggestion every time (insert via completeSlash). Add/extend unit tests in slash_commands_test.dart for hyphenated queries and a composer widget test for Tab-accept. -RESOLVED by the D-88 ClideTypeahead migration (T-286), verified 2026-06-09. Root cause of both symptoms was the OLD hand-rolled overlay, not the pure helpers (activeSlashQuery/filterSlashCommands always handled ''-''): (1) the old overlay didn''t narrow live as you typed, so a ''-'' looked like it emptied/broke the list; (2) the old focus model raced Tab. Post-migration the popover narrows live (ValueNotifier) and the field keeps focus (captureFocus:false), so ''-'' filters normally and Tab reliably completes. Confirmed bare ''-'' is NOT a keybinding (zoom is ctrl/meta+minus). Added regression tests: hyphen cases in slash_commands_test; composer widget test typing through a hyphen + Tab-accept. Commit d8b9a41.', NULL, '2026-06-09 15:03:23', '2026-06-09 15:03:23', '2026-06-09 15:03:23', NULL, '5f790b8fdba01f358c12abc80f916a7d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'status', 'in_progress', 'done', NULL, '2026-06-09 15:03:23', '2026-06-09 15:03:23', '2026-06-09 15:03:23', NULL, '8561bd722293a0e24b1ed4a51d4d1885', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'status', 'backlog', 'in_progress', NULL, '2026-06-09 15:03:37', '2026-06-09 15:03:37', '2026-06-09 15:03:37', NULL, '95d2f6712b0fddd8c5d33e93b971dbfd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'description', 'test/pty/session_test.dart:47 ''write sends keystrokes to child'' (and its sibling pty integration tests) intermittently fail when run in a large parallel pool — observed twice during T-29: once in a bare ''flutter test --coverage'' (whole suite, max parallelism + coverage instrumentation) and once mid pre-push gate (''+570 -1''). Passes reliably in isolation and via the gate''s SERIAL test-core pass (''dart test test/pty'', +571 ok), so it is not a logic bug. +RESOLVED by the D-88 ClideTypeahead migration (T-286), verified 2026-06-09. Root cause of both symptoms was the OLD hand-rolled overlay, not the pure helpers (activeSlashQuery/filterSlashCommands always handled ''-''): (1) the old overlay didn''t narrow live as you typed, so a ''-'' looked like it emptied/broke the list; (2) the old focus model raced Tab. Post-migration the popover narrows live (ValueNotifier) and the field keeps focus (captureFocus:false), so ''-'' filters normally and Tab reliably completes. Confirmed bare ''-'' is NOT a keybinding (zoom is ctrl/meta+minus). Added regression tests: hyphen cases in slash_commands_test; composer widget test typing through a hyphen + Tab-accept. Commit d8b9a41.', NULL, '2026-06-09 15:03:23', '2026-06-09 15:03:23', '2026-06-09 15:03:23', NULL, 'bce323a83d11cce9aa109b64b723a609', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BD8PXDRHBF001200', 'status', 'backlog', 'in_progress', NULL, '2026-06-09 15:03:37', '2026-06-09 15:03:37', '2026-06-09 15:03:37', NULL, '6e3a604a8718416923865ef53bec19bc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BD8PXDRHBF001200', 'status', 'in_progress', 'done', NULL, '2026-06-09 15:09:05', '2026-06-09 15:09:05', '2026-06-09 15:09:05', NULL, '38a4f5c2f60bd3bcfabb99869c0deba0', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BD8PXDRHBF001200', 'description', 'test/pty/session_test.dart:47 ''write sends keystrokes to child'' (and its sibling pty integration tests) intermittently fail when run in a large parallel pool — observed twice during T-29: once in a bare ''flutter test --coverage'' (whole suite, max parallelism + coverage instrumentation) and once mid pre-push gate (''+570 -1''). Passes reliably in isolation and via the gate''s SERIAL test-core pass (''dart test test/pty'', +571 ok), so it is not a logic bug. Root cause: these tests spawn a real /bin/sh via NativePty and wait on the reader ISOLATE to deliver bytes. Under heavy CPU contention the reader isolate is starved and the 20s ioTimeout (test/helpers/timeouts.dart) elapses before the shell''s first byte / echo response is delivered. Already hardened once (T-108 swapped wall-clock sleeps for event-driven waits; first-byte Completer + 25ms-poll _waitForBuffer), but the isolate-scheduling assumption still breaks under load. @@ -982,12 +983,32 @@ Root cause: these tests spawn a real /bin/sh via NativePty and wait on the reade These tests are tagged ''pty'' and are meant to run in the gate''s serial pass. Fix options: (a) ensure the parallel coverage pass EXCLUDES tag:pty (run pty only in the serial pass) so they never compete for isolate scheduling — verify ci/test.sh / dart_test.yaml tag routing; (b) harden against isolate starvation (e.g. give the reader isolate priority, or assert on delivery via a more robust signal); (c) document that ''flutter test --coverage'' over the whole tree is not a supported invocation (use make test + the gate). Prefer (a). Refs: T-108, T-96 (reader-isolate hangs), T-192 (gate parallel/serial passes), D-23 (test pyramid). -FIXED 2026-06-09 (commit 0231cb4). Root cause: ci/test_core.sh ran ''dart test test/ipc test/pty ... test/pql'' in the DEFAULT parallel pool, so test/pty''s real-PTY tests contended for fds+CPU with the other core suites and the reader isolate was starved. (ci/test.sh already isolated pty via --concurrency=1 --tags pty; test_core.sh was the gap.) My manual ''flutter test --coverage'' repro was a separate wrong-invocation artifact — the flutter runner can''t reliably deliver the PTY master fd, which is why the gate excludes pty from the flutter pool entirely.) Fix: test_core.sh now runs a serial ''--concurrency=1 --tags pty'' pass + a parallel ''--exclude-tags pty'' pass, mirroring ci/test.sh. Verified: pty pass +5 stable across repeated runs; full core +571 unchanged. Option (a) from the ticket.', NULL, '2026-06-09 15:09:05', '2026-06-09 15:09:05', '2026-06-09 15:09:05', NULL, 'c06cbcb21ad470e62af4beba4b2e8f57', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'status', 'in_progress', 'done', NULL, '2026-06-09 15:09:05', '2026-06-09 15:09:05', '2026-06-09 15:09:05', NULL, '6e5ddf473f6094246098d13c65515825', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'status', 'backlog', 'ready', NULL, '2026-06-09 15:10:57', '2026-06-09 15:10:57', '2026-06-09 15:10:57', NULL, '004f3513ae78eb18ddde5ebdba44c518', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-293', 'status', 'backlog', 'ready', NULL, '2026-06-09 15:13:06', '2026-06-09 15:13:06', '2026-06-09 15:13:06', NULL, '2dc7c5580e1bb26d741b5c6f08dbbd83', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'status', 'backlog', 'ready', NULL, '2026-06-09 15:13:25', '2026-06-09 15:13:25', '2026-06-09 15:13:25', NULL, '48cc5a8a417428ba0b42373bd7306d47', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'description', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. +FIXED 2026-06-09 (commit 0231cb4). Root cause: ci/test_core.sh ran ''dart test test/ipc test/pty ... test/pql'' in the DEFAULT parallel pool, so test/pty''s real-PTY tests contended for fds+CPU with the other core suites and the reader isolate was starved. (ci/test.sh already isolated pty via --concurrency=1 --tags pty; test_core.sh was the gap.) My manual ''flutter test --coverage'' repro was a separate wrong-invocation artifact — the flutter runner can''t reliably deliver the PTY master fd, which is why the gate excludes pty from the flutter pool entirely.) Fix: test_core.sh now runs a serial ''--concurrency=1 --tags pty'' pass + a parallel ''--exclude-tags pty'' pass, mirroring ci/test.sh. Verified: pty pass +5 stable across repeated runs; full core +571 unchanged. Option (a) from the ticket.', NULL, '2026-06-09 15:09:05', '2026-06-09 15:09:05', '2026-06-09 15:09:05', NULL, '9182c7efd54567ce5dc158eb0d01d137', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GN07FN8GAXZ20YAR', 'status', 'backlog', 'ready', NULL, '2026-06-09 15:10:57', '2026-06-09 15:10:57', '2026-06-09 15:10:57', NULL, 'c7f6e34ce3fd1a90a22d758f573f2676', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42WW0SY3XEXKE8PK8', 'status', 'backlog', 'ready', NULL, '2026-06-09 15:13:06', '2026-06-09 15:13:06', '2026-06-09 15:13:06', NULL, '0c5e9a67ba3131bef2e761a8db3cb753', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'status', 'backlog', 'ready', NULL, '2026-06-09 15:13:25', '2026-06-09 15:13:25', '2026-06-09 15:13:25', NULL, 'f4fbca5fcde6b9589ae5aad230b08a47', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'description', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. + +## Behaviour +- Detect a pasted-image path in the conversation stream and render the image viewer card. +- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. +- Reuse the existing image viewer card component rather than building a new one. + +## Notes +- Applies to the Claude conversation panel rendering path. +- Consider failure cases: missing/deleted file, non-image paste, very large images.', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. + +## Behaviour +- Detect a pasted-image path in the conversation stream and render the image viewer card. +- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. +- Reuse the existing image viewer card component rather than building a new one. + +## Notes +- Applies to the Claude conversation panel rendering path. +- Consider failure cases: missing/deleted file, non-image paste, very large images. + +CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236.', NULL, '2026-06-09 15:14:46', '2026-06-09 15:14:46', '2026-06-09 15:14:46', NULL, '6faa9d5cb777b2aa1d0a996c8f542c26', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GN07FN8GAXZ20YAR', 'description', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). @@ -1023,56 +1044,10 @@ Acceptance: 4. Prose and any non-image @path tokens around it still render readably; multiple tokens in one message all resolve. 5. The message content delivered to Claude is unchanged (render-only). Relates to T-142 (paste attachments). -MERGED T-254 (duplicate) into this ticket. Extra nuance carried over from T-254: keep the file path available (e.g. as a small caption/subtitle under the thumbnail, or via the lightbox) so it can still be copied/referenced. Reuse the existing inline image card + lightbox (T-252) rather than a new component.', NULL, '2026-06-09 15:14:46', '2026-06-09 15:14:46', '2026-06-09 15:14:46', NULL, 'f1a57da564d471e11c8f0d8ea223af31', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'status', 'ready', 'cancelled', NULL, '2026-06-09 15:14:46', '2026-06-09 15:14:46', '2026-06-09 15:14:46', NULL, 'a99bb80020cf6bb97e3b663c6f634f70', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'description', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images.', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. - -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236.', NULL, '2026-06-09 15:14:46', '2026-06-09 15:14:46', '2026-06-09 15:14:46', NULL, 'cde407f81386ea83768640cabdc122f3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'status', 'cancelled', 'ready', NULL, '2026-06-09 15:15:18', '2026-06-09 15:15:18', '2026-06-09 15:15:18', NULL, '4f7cbc7120c4793c6f1393d3550fd675', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'description', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. - -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236.', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. - -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. - -RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket.', NULL, '2026-06-09 15:15:18', '2026-06-09 15:15:18', '2026-06-09 15:15:18', NULL, '7bb96830192e99e75aae7f151673ee41', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'description', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. +MERGED T-254 (duplicate) into this ticket. Extra nuance carried over from T-254: keep the file path available (e.g. as a small caption/subtitle under the thumbnail, or via the lightbox) so it can still be copied/referenced. Reuse the existing inline image card + lightbox (T-252) rather than a new component.', NULL, '2026-06-09 15:14:46', '2026-06-09 15:14:46', '2026-06-09 15:14:46', NULL, '76b992a5d460e81ec313ae6154dea1f6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'status', 'ready', 'cancelled', NULL, '2026-06-09 15:14:46', '2026-06-09 15:14:46', '2026-06-09 15:14:46', NULL, 'de7af22be9beaa7c28d65a5fdde3ca8e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'status', 'cancelled', 'ready', NULL, '2026-06-09 15:15:18', '2026-06-09 15:15:18', '2026-06-09 15:15:18', NULL, '3478f18c0380519c915fa463bc12289f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GN07FN8GAXZ20YAR', 'description', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). @@ -1112,8 +1087,33 @@ Acceptance: MERGED T-254 (duplicate) into this ticket. Extra nuance carried over from T-254: keep the file path available (e.g. as a small caption/subtitle under the thumbnail, or via the lightbox) so it can still be copied/referenced. Reuse the existing inline image card + lightbox (T-252) rather than a new component. -RETRACTED the earlier ''merged T-254'' note — T-236 and T-254 are distinct tickets (per user); disregard that note. T-236 stays scoped to its own description.', NULL, '2026-06-09 15:15:18', '2026-06-09 15:15:18', '2026-06-09 15:15:18', NULL, '78ef3c0cad670d9dc912aaa4fd3938ca', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +RETRACTED the earlier ''merged T-254'' note — T-236 and T-254 are distinct tickets (per user); disregard that note. T-236 stays scoped to its own description.', NULL, '2026-06-09 15:15:18', '2026-06-09 15:15:18', '2026-06-09 15:15:18', NULL, '5047d91e71c748a7a9c2e1ad29c636c4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'description', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. + +## Behaviour +- Detect a pasted-image path in the conversation stream and render the image viewer card. +- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. +- Reuse the existing image viewer card component rather than building a new one. + +## Notes +- Applies to the Claude conversation panel rendering path. +- Consider failure cases: missing/deleted file, non-image paste, very large images. + +CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236.', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. + +## Behaviour +- Detect a pasted-image path in the conversation stream and render the image viewer card. +- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. +- Reuse the existing image viewer card component rather than building a new one. + +## Notes +- Applies to the Claude conversation panel rendering path. +- Consider failure cases: missing/deleted file, non-image paste, very large images. + +CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. + +RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket.', NULL, '2026-06-09 15:15:18', '2026-06-09 15:15:18', '2026-06-09 15:15:18', NULL, '51620d0b6f97ab50c29fc12ad0a9632d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1142,8 +1142,8 @@ DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center Implications for the mock: - Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. - Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse.', NULL, '2026-06-09 15:21:37', '2026-06-09 15:21:37', '2026-06-09 15:21:37', NULL, '123270ca44c9cfc48192cfc2412410f7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +- Chevron direction reflects state: points outward to expand, inward to collapse.', NULL, '2026-06-09 15:21:37', '2026-06-09 15:21:37', '2026-06-09 15:21:37', NULL, '674ce50eb5d3471f5c024942badb8c1a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1189,8 +1189,8 @@ So this ticket is scoped to the VISUAL AFFORDANCE only: - Read arrangement.isCollapsed(...) to flip the chevron direction per state. - D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed).', NULL, '2026-06-09 15:24:00', '2026-06-09 15:24:00', '2026-06-09 15:24:00', NULL, '216c6fef2135525d9a901d01975f9f74', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed).', NULL, '2026-06-09 15:24:00', '2026-06-09 15:24:00', '2026-06-09 15:24:00', NULL, 'bf8f7751e6379b8a3fc57cdab29566fc', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1250,8 +1250,8 @@ Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (o PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}.', NULL, '2026-06-09 15:28:16', '2026-06-09 15:28:16', '2026-06-09 15:28:16', NULL, 'a41172c3ac1fa78bbfdbf3773467d681', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}.', NULL, '2026-06-09 15:28:16', '2026-06-09 15:28:16', '2026-06-09 15:28:16', NULL, 'f415ce161a4fe67f8c24f4f6573a762a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1317,9 +1317,9 @@ PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. T Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. -APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation.', NULL, '2026-06-09 15:29:08', '2026-06-09 15:29:08', '2026-06-09 15:29:08', NULL, '92fdba74a32b217f7345d5095bd4fa02', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'status', 'backlog', 'ready', NULL, '2026-06-09 15:29:11', '2026-06-09 15:29:11', '2026-06-09 15:29:11', NULL, '2e8f7c70314543a83510796173c2463b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation.', NULL, '2026-06-09 15:29:08', '2026-06-09 15:29:08', '2026-06-09 15:29:08', NULL, '25d8d4bf9893db066dd16e5d06a10968', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'status', 'backlog', 'ready', NULL, '2026-06-09 15:29:11', '2026-06-09 15:29:11', '2026-06-09 15:29:11', NULL, 'a60a2682884dc0534ff386a4f029c97c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1391,8 +1391,8 @@ APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. De PLACEMENT FINAL (supersedes "pinned-to-edge"): toggles are FIXED at the far-left and far-right ends of the bottom status bar in every state. They do not slide with the pane edge. Position is constant (muscle memory; always where the reference-screenshot arrows pointed); only the chevron flips per isCollapsed. Left end = sidebar toggle, right end = context toggle. Mirrored for parity. Buttons invoke the existing sidebar.collapse / context.collapse commands. -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}.', NULL, '2026-06-09 15:30:16', '2026-06-09 15:30:16', '2026-06-09 15:30:16', NULL, 'ff37bad7bec3122bb8d10c1eb1c961f4', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}.', NULL, '2026-06-09 15:30:16', '2026-06-09 15:30:16', '2026-06-09 15:30:16', NULL, '240bef9edb3bf7d0d695673085f9498a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1470,8 +1470,8 @@ PLACEMENT FINAL (supersedes "pinned-to-edge"): toggles are FIXED at the far-left Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. -SPACE NOTE: the status bar already hosts content at both ends (left: branch / skills count; right: Output / terminal). Fixed-end toggles therefore share that space. Resolution: reserve the OUTERMOST ~24px cell at each end for the toggle and shift the existing status items inward by that width — a constant reservation, not a dynamic fight (the toggle never moves or grows). The mock reflects this: status text begins after the left toggle and ends before the right toggle.', NULL, '2026-06-09 15:30:41', '2026-06-09 15:30:41', '2026-06-09 15:30:41', NULL, '718d30dbfcb581743c06e94a4a3c91f5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +SPACE NOTE: the status bar already hosts content at both ends (left: branch / skills count; right: Output / terminal). Fixed-end toggles therefore share that space. Resolution: reserve the OUTERMOST ~24px cell at each end for the toggle and shift the existing status items inward by that width — a constant reservation, not a dynamic fight (the toggle never moves or grows). The mock reflects this: status text begins after the left toggle and ends before the right toggle.', NULL, '2026-06-09 15:30:41', '2026-06-09 15:30:41', '2026-06-09 15:30:41', NULL, '817cb40d9b8f5d92a00e453d1747b7a2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1556,8 +1556,8 @@ SPACE NOTE: the status bar already hosts content at both ends (left: branch / sk GLYPH DECISION: use Phosphor caret-line icons (chevron + edge line), which read as "collapse to the edge": - caret-line-left -> 0xe132 (CaretLineLeft) - caret-line-right -> 0xe130 (CaretLineRight) -The glyphs already ship in assets/fonts/phosphor (codepoints.csv lines 150-151) — no new dependency. Add two consts to lib/widgets/src/icons/phosphor.dart (PhosphorIcons.caretLineLeft / caretLineRight) during implementation. Chevron-line direction flips per isCollapsed.', NULL, '2026-06-09 15:33:14', '2026-06-09 15:33:14', '2026-06-09 15:33:14', NULL, '3638f5f2ae709db7081273a413fe528a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +The glyphs already ship in assets/fonts/phosphor (codepoints.csv lines 150-151) — no new dependency. Add two consts to lib/widgets/src/icons/phosphor.dart (PhosphorIcons.caretLineLeft / caretLineRight) during implementation. Chevron-line direction flips per isCollapsed.', NULL, '2026-06-09 15:33:14', '2026-06-09 15:33:14', '2026-06-09 15:33:14', NULL, 'bef1363660f958fbda57d1a1ab2c3600', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1649,8 +1649,9 @@ GLYPH DECISION: use Phosphor caret-line icons (chevron + edge line), which read - caret-line-right -> 0xe130 (CaretLineRight) The glyphs already ship in assets/fonts/phosphor (codepoints.csv lines 150-151) — no new dependency. Add two consts to lib/widgets/src/icons/phosphor.dart (PhosphorIcons.caretLineLeft / caretLineRight) during implementation. Chevron-line direction flips per isCollapsed. -RESOLUTION (2026-06-09): considered moving the re-open control into the center (pane edge or center status segment) to dodge the end-of-bar space contention; user chose to KEEP TOGGLES FIXED AT THE STATUS-BAR ENDS (best muscle memory). Final design = v3 mock: caret-line glyphs at fixed far-left/far-right status-bar cells (~24px reserved, side items shifted in), chevron-line flips per isCollapsed, firing existing sidebar.collapse / context.collapse. Design fully settled; ready to implement.', NULL, '2026-06-09 15:34:11', '2026-06-09 15:34:11', '2026-06-09 15:34:11', NULL, '7126eddc9c30025df3f4a73de45edf40', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-293', 'description', 'Picking a theme (status-bar switcher T-234, or the Settings modal) applies live but is never saved, so every reload resets to the first bundled theme. The choice should persist PER REPO. +RESOLUTION (2026-06-09): considered moving the re-open control into the center (pane edge or center status segment) to dodge the end-of-bar space contention; user chose to KEEP TOGGLES FIXED AT THE STATUS-BAR ENDS (best muscle memory). Final design = v3 mock: caret-line glyphs at fixed far-left/far-right status-bar cells (~24px reserved, side items shifted in), chevron-line flips per isCollapsed, firing existing sidebar.collapse / context.collapse. Design fully settled; ready to implement.', NULL, '2026-06-09 15:34:11', '2026-06-09 15:34:11', '2026-06-09 15:34:11', NULL, '194a6e362e707647b03610226991cdf1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42WW0SY3XEXKE8PK8', 'status', 'ready', 'done', NULL, '2026-06-09 15:36:15', '2026-06-09 15:36:15', '2026-06-09 15:36:15', NULL, '4daeb008d8c9b1f137995c7eef7d133d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42WW0SY3XEXKE8PK8', 'description', 'Picking a theme (status-bar switcher T-234, or the Settings modal) applies live but is never saved, so every reload resets to the first bundled theme. The choice should persist PER REPO. Root cause: ThemeController.select() (lib/kernel/src/theme/controller.dart:43) only mutates _currentName + notifies — no persistence. It is constructed in facade.dart:160 as ThemeController(bundled: bundledThemes) with no initialName, so on boot it always falls back to bundled.first. @@ -1680,9 +1681,8 @@ Acceptance: pick a theme (incl. the High-contrast toggle) -> restart -> same the Tests: SettingsStore round-trips ''project.theme''; opening a project applies the saved theme to the controller; select() persists; unknown saved theme falls back. Refs: T-234 (theme switcher), T-288/D-88 (picker), SettingsStore, ProjectManager. -FIXED 2026-06-09 (this commit). Added lib/kernel/src/theme/theme_persistence.dart + wireThemePersistence() in facade. Decision on the open question: persist BOTH project.theme (per-repo, in .clide/settings.yaml) AND app.theme (global default) — so a themed repo keeps its choice, an unthemed/new repo inherits the last global choice, and the HC variant persists (name encodes -hc). Restore prefers project over app; unknown theme is ignored (no throw). Tests: test/kernel/src/theme_persistence_test.dart (6 cases — persist app/project, HC, restore-on-open, boot restore, unknown fallback). All green.', NULL, '2026-06-09 15:36:15', '2026-06-09 15:36:15', '2026-06-09 15:36:15', NULL, '7bfa444c00cfab00bb8bba19e3677547', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-293', 'status', 'ready', 'done', NULL, '2026-06-09 15:36:15', '2026-06-09 15:36:15', '2026-06-09 15:36:15', NULL, '4b398e8cbe8244580bb10b5306f73140', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'description', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. +FIXED 2026-06-09 (this commit). Added lib/kernel/src/theme/theme_persistence.dart + wireThemePersistence() in facade. Decision on the open question: persist BOTH project.theme (per-repo, in .clide/settings.yaml) AND app.theme (global default) — so a themed repo keeps its choice, an unthemed/new repo inherits the last global choice, and the HC variant persists (name encodes -hc). Restore prefers project over app; unknown theme is ignored (no throw). Tests: test/kernel/src/theme_persistence_test.dart (6 cases — persist app/project, HC, restore-on-open, boot restore, unknown fallback). All green.', NULL, '2026-06-09 15:36:15', '2026-06-09 15:36:15', '2026-06-09 15:36:15', NULL, '767b65a07e73e6a0299fe19d7851b8c2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63TRGBFRWK9W2WA98', 'description', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. Behaviour: - Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). @@ -1710,8 +1710,8 @@ WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stre - [3 edits] (the first edit run) - [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) - [7 edits] (the second edit run) -Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card.', NULL, '2026-06-09 15:45:12', '2026-06-09 15:45:12', '2026-06-09 15:45:12', NULL, '0878699228e77f6fd0226b05c6f0d6f0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'description', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. +Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card.', NULL, '2026-06-09 15:45:12', '2026-06-09 15:45:12', '2026-06-09 15:45:12', NULL, 'b7535e024ee171cb46ae7bad08d23092', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63TRGBFRWK9W2WA98', 'description', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. Behaviour: - Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). @@ -1752,8 +1752,38 @@ STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header st - edit completed -> success check (the current green tick) - next edit starts -> back to spinner - ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). -So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this.', NULL, '2026-06-09 15:46:02', '2026-06-09 15:46:02', '2026-06-09 15:46:02', NULL, '3967ce1c50019ad946ae4484f58209d3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'description', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. +So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this.', NULL, '2026-06-09 15:46:02', '2026-06-09 15:46:02', '2026-06-09 15:46:02', NULL, 'ac4da1997b2c2cba25b78ed97ba9e9be', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GN07FN8GAXZ20YAR', 'status', 'ready', 'done', NULL, '2026-06-09 15:55:06', '2026-06-09 15:55:06', '2026-06-09 15:55:06', NULL, '2453f53a89cda92cea0734929c9d1a81', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'description', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. + +## Behaviour +- Detect a pasted-image path in the conversation stream and render the image viewer card. +- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. +- Reuse the existing image viewer card component rather than building a new one. + +## Notes +- Applies to the Claude conversation panel rendering path. +- Consider failure cases: missing/deleted file, non-image paste, very large images. + +CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. + +RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket.', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. + +## Behaviour +- Detect a pasted-image path in the conversation stream and render the image viewer card. +- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. +- Reuse the existing image viewer card component rather than building a new one. + +## Notes +- Applies to the Claude conversation panel rendering path. +- Consider failure cases: missing/deleted file, non-image paste, very large images. + +CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. + +RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket. + +DONE 2026-06-09 — satisfied by D-89 together with T-236. The lightbox-expansion + path-retention (filename a11y label / unchanged copyText) intent from T-254 is delivered by the shared ImageThumbnail → lightbox; the chosen presentation is inline (not a separate card) per the user. See T-236 / D-89 / commit.', NULL, '2026-06-09 15:55:06', '2026-06-09 15:55:06', '2026-06-09 15:55:06', NULL, '592144661bfd762b61789f05d958cdd4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GN07FN8GAXZ20YAR', 'description', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). @@ -1797,41 +1827,96 @@ MERGED T-254 (duplicate) into this ticket. Extra nuance carried over from T-254: RETRACTED the earlier ''merged T-254'' note — T-236 and T-254 are distinct tickets (per user); disregard that note. T-236 stays scoped to its own description. -DONE 2026-06-09 — implemented as the hybrid (D-89): inline thumbnail in the message prose that opens the lightbox on click/Enter. New ImageThumbnail + openImageLightbox (lib/builtin/claude/src/image_thumbnail.dart); ClideMarkdown gained an onImageToken WidgetSpan seam. Missing file → placeholder; multiple tokens; non-image @path stays text; render-only (copyText unchanged). Composer chips reuse the same 44px thumbnail. Tests: image_thumbnail_test + conversation_view_test cases. Closes alongside T-254.', NULL, '2026-06-09 15:55:06', '2026-06-09 15:55:06', '2026-06-09 15:55:06', NULL, '14703b7b9644d40d524089077725d751', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'status', 'ready', 'done', NULL, '2026-06-09 15:55:06', '2026-06-09 15:55:06', '2026-06-09 15:55:06', NULL, 'c0cf2740ab59fe5b3401c8a86d8a9f61', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'description', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. +DONE 2026-06-09 — implemented as the hybrid (D-89): inline thumbnail in the message prose that opens the lightbox on click/Enter. New ImageThumbnail + openImageLightbox (lib/builtin/claude/src/image_thumbnail.dart); ClideMarkdown gained an onImageToken WidgetSpan seam. Missing file → placeholder; multiple tokens; non-image @path stays text; render-only (copyText unchanged). Composer chips reuse the same 44px thumbnail. Tests: image_thumbnail_test + conversation_view_test cases. Closes alongside T-254.', NULL, '2026-06-09 15:55:06', '2026-06-09 15:55:06', '2026-06-09 15:55:06', NULL, '8585bb4d9f6890c70777706d4225cef1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'status', 'ready', 'done', NULL, '2026-06-09 15:55:06', '2026-06-09 15:55:06', '2026-06-09 15:55:06', NULL, '862cc2b139b96265c8d8d693f581e943', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RXN6HW6XTEHKYQDG', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:01:58', '2026-06-09 16:01:58', '2026-06-09 16:01:58', NULL, '1caefc65216b108ebf53b512c21997a8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63TRGBFRWK9W2WA98', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:02:14', '2026-06-09 16:02:14', '2026-06-09 16:02:14', NULL, '1ccbef8e24e7315c9f907940a2a6db1c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63TRGBFRWK9W2WA98', 'status', 'ready', 'done', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, '1ff5b1a09d01bbc6bf265c33dbdb8025', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'status', 'ready', 'done', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, '550373b90ef92ee9bf9b298f303f3bef', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RXN6HW6XTEHKYQDG', 'description', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. +Trigger: anything that resizes the bottom interaction zone (D-78) — e.g. opening a permission dialog or the AskUserQuestion UI, which replace/expand the composer. The taller box shrinks the conversation viewport from the bottom, but the scroll offset isn''t adjusted, so content that was at the bottom edge is now occluded. -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. +Expected: when the interaction zone grows, the conversation re-anchors so the previously-visible bottom content stays visible above the box (preserve bottom-anchoring / keep the tail in view). Symmetric on shrink — no leftover gap when the box collapses back. -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. +Repro: +1. Scroll the Claude conversation to the bottom (tail in view). +2. Trigger a permission prompt or AskUserQuestion (interaction zone expands). +3. Observed: a strip of the last message/card is hidden behind the enlarged input box. +4. Expected: view scrolls so that content remains fully visible above the box. -RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket.', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. +Notes: +- Likely the scroll controller doesn''t react to the composer/interaction-zone height change (no re-scroll on viewport-inset/size change). Audit the conversation view''s scroll handling around interaction-zone show/hide (D-78) and on keyboard/box resize. +- Affects the Claude conversation panel.', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. +Trigger: anything that resizes the bottom interaction zone (D-78) — e.g. opening a permission dialog or the AskUserQuestion UI, which replace/expand the composer. The taller box shrinks the conversation viewport from the bottom, but the scroll offset isn''t adjusted, so content that was at the bottom edge is now occluded. -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. +Expected: when the interaction zone grows, the conversation re-anchors so the previously-visible bottom content stays visible above the box (preserve bottom-anchoring / keep the tail in view). Symmetric on shrink — no leftover gap when the box collapses back. -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. +Repro: +1. Scroll the Claude conversation to the bottom (tail in view). +2. Trigger a permission prompt or AskUserQuestion (interaction zone expands). +3. Observed: a strip of the last message/card is hidden behind the enlarged input box. +4. Expected: view scrolls so that content remains fully visible above the box. -RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket. +Notes: +- Likely the scroll controller doesn''t react to the composer/interaction-zone height change (no re-scroll on viewport-inset/size change). Audit the conversation view''s scroll handling around interaction-zone show/hide (D-78) and on keyboard/box resize. +- Affects the Claude conversation panel. -DONE 2026-06-09 — satisfied by D-89 together with T-236. The lightbox-expansion + path-retention (filename a11y label / unchanged copyText) intent from T-254 is delivered by the shared ImageThumbnail → lightbox; the chosen presentation is inline (not a separate card) per the user. See T-236 / D-89 / commit.', NULL, '2026-06-09 15:55:06', '2026-06-09 15:55:06', '2026-06-09 15:55:06', NULL, 'c6090b1f5fdbdee4b56e6fd1ac39a9e2', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'status', 'ready', 'done', NULL, '2026-06-09 15:55:06', '2026-06-09 15:55:06', '2026-06-09 15:55:06', NULL, 'e07372e7e9662712183808199ac27210', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-297', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:01:58', '2026-06-09 16:01:58', '2026-06-09 16:01:58', NULL, '12474d426c882ddf33efdd4fa6f86f95', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:02:14', '2026-06-09 16:02:14', '2026-06-09 16:02:14', NULL, '8f3930428725856dcfd8042261852398', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +DONE 2026-06-09 (commit). conversation_view tracks at-bottom + a LayoutBuilder re-jumps to the tail when the viewport height changes (interaction-zone resize, D-78), only when pinned. Tests: conversation_scroll_test.', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, '8c8361c593bce055c595ee3d6eca594c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63TRGBFRWK9W2WA98', 'description', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. + +Behaviour: +- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). +- Collapsed (default): one-line ticker of the latest edit + a count label. +- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. +- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. + +Notes: +- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. +- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. +- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run. + +WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stream renders 11 stacked cards — 3x "Edit clide_markdown.dart", then a folded "Read … 2 steps" holder, then 7x "Edit clide_markdown.dart". With this feature it collapses to THREE cards: +- [3 edits] (the first edit run) +- [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) +- [7 edits] (the second edit run) +Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card. + +STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header status indicator that reuses the existing per-step success tick, driven by the latest edit''s state: +- edit in flight -> spinner +- edit completed -> success check (the current green tick) +- next edit starts -> back to spinner +- ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). +So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this.', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. + +Behaviour: +- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). +- Collapsed (default): one-line ticker of the latest edit + a count label. +- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. +- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. + +Notes: +- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. +- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. +- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run. + +WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stream renders 11 stacked cards — 3x "Edit clide_markdown.dart", then a folded "Read … 2 steps" holder, then 7x "Edit clide_markdown.dart". With this feature it collapses to THREE cards: +- [3 edits] (the first edit run) +- [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) +- [7 edits] (the second edit run) +Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card. + +STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header status indicator that reuses the existing per-step success tick, driven by the latest edit''s state: +- edit in flight -> spinner +- edit completed -> success check (the current green tick) +- next edit starts -> back to spinner +- ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). +So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this. + +DONE 2026-06-09 (commit). coalesceEditRuns folds consecutive same-file edits into one ''# edits'' ClideHolderCard; aggregate live status via new ClideStatusIndicator (running/success/error) + ClideSpinner (logo-mark, 3D Y rotation, reduced-motion aware), shared with the activity card. Per user: spinner is a self-contained component (not built on ConversationCard''s mark) with an AnimatedSwitcher seam for a richer spinner→check transition later. Tests: activity_cluster_test (coalesce), conversation_view_test (edits card), clide_status_indicator_test.', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, 'd4aed0a994dbdc503811e7c309e96f7c', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RXN6HW6XTEHKYQDG', 'status', 'ready', 'done', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, 'ed7b38da5bfea63147029d903cc57728', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'description', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -1927,100 +2012,16 @@ The glyphs already ship in assets/fonts/phosphor (codepoints.csv lines 150-151) RESOLUTION (2026-06-09): considered moving the re-open control into the center (pane edge or center status segment) to dodge the end-of-bar space contention; user chose to KEEP TOGGLES FIXED AT THE STATUS-BAR ENDS (best muscle memory). Final design = v3 mock: caret-line glyphs at fixed far-left/far-right status-bar cells (~24px reserved, side items shifted in), chevron-line flips per isCollapsed, firing existing sidebar.collapse / context.collapse. Design fully settled; ready to implement. -DONE 2026-06-09 (commit). Fixed ~24px caret-line toggles bookend the status bar (StatusbarHost), flip chevron per arrangement.isCollapsed, fire existing sidebar.collapse/context.collapse. Visual affordance only. Tests: app_collapse_toggle_test + updated app_statusbar_test.', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, '96263b16de5104d6579d5b010e105b16', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'status', 'ready', 'done', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, 'd0ac2758ff994f261a6b774c25ff61aa', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-297', 'description', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. - -Trigger: anything that resizes the bottom interaction zone (D-78) — e.g. opening a permission dialog or the AskUserQuestion UI, which replace/expand the composer. The taller box shrinks the conversation viewport from the bottom, but the scroll offset isn''t adjusted, so content that was at the bottom edge is now occluded. - -Expected: when the interaction zone grows, the conversation re-anchors so the previously-visible bottom content stays visible above the box (preserve bottom-anchoring / keep the tail in view). Symmetric on shrink — no leftover gap when the box collapses back. - -Repro: -1. Scroll the Claude conversation to the bottom (tail in view). -2. Trigger a permission prompt or AskUserQuestion (interaction zone expands). -3. Observed: a strip of the last message/card is hidden behind the enlarged input box. -4. Expected: view scrolls so that content remains fully visible above the box. - -Notes: -- Likely the scroll controller doesn''t react to the composer/interaction-zone height change (no re-scroll on viewport-inset/size change). Audit the conversation view''s scroll handling around interaction-zone show/hide (D-78) and on keyboard/box resize. -- Affects the Claude conversation panel.', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. - -Trigger: anything that resizes the bottom interaction zone (D-78) — e.g. opening a permission dialog or the AskUserQuestion UI, which replace/expand the composer. The taller box shrinks the conversation viewport from the bottom, but the scroll offset isn''t adjusted, so content that was at the bottom edge is now occluded. - -Expected: when the interaction zone grows, the conversation re-anchors so the previously-visible bottom content stays visible above the box (preserve bottom-anchoring / keep the tail in view). Symmetric on shrink — no leftover gap when the box collapses back. - -Repro: -1. Scroll the Claude conversation to the bottom (tail in view). -2. Trigger a permission prompt or AskUserQuestion (interaction zone expands). -3. Observed: a strip of the last message/card is hidden behind the enlarged input box. -4. Expected: view scrolls so that content remains fully visible above the box. - -Notes: -- Likely the scroll controller doesn''t react to the composer/interaction-zone height change (no re-scroll on viewport-inset/size change). Audit the conversation view''s scroll handling around interaction-zone show/hide (D-78) and on keyboard/box resize. -- Affects the Claude conversation panel. - -DONE 2026-06-09 (commit). conversation_view tracks at-bottom + a LayoutBuilder re-jumps to the tail when the viewport height changes (interaction-zone resize, D-78), only when pinned. Tests: conversation_scroll_test.', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, '4a480f458656a560dbf8ec4dc37b7f06', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-297', 'status', 'ready', 'done', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, '5e057b905d14d1bddbb58ad4e72dc6cd', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'description', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. - -Behaviour: -- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). -- Collapsed (default): one-line ticker of the latest edit + a count label. -- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. -- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. - -Notes: -- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. -- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. -- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run. - -WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stream renders 11 stacked cards — 3x "Edit clide_markdown.dart", then a folded "Read … 2 steps" holder, then 7x "Edit clide_markdown.dart". With this feature it collapses to THREE cards: -- [3 edits] (the first edit run) -- [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) -- [7 edits] (the second edit run) -Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card. - -STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header status indicator that reuses the existing per-step success tick, driven by the latest edit''s state: -- edit in flight -> spinner -- edit completed -> success check (the current green tick) -- next edit starts -> back to spinner -- ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). -So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this.', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. - -Behaviour: -- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). -- Collapsed (default): one-line ticker of the latest edit + a count label. -- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. -- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. - -Notes: -- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. -- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. -- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run. - -WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stream renders 11 stacked cards — 3x "Edit clide_markdown.dart", then a folded "Read … 2 steps" holder, then 7x "Edit clide_markdown.dart". With this feature it collapses to THREE cards: -- [3 edits] (the first edit run) -- [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) -- [7 edits] (the second edit run) -Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card. - -STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header status indicator that reuses the existing per-step success tick, driven by the latest edit''s state: -- edit in flight -> spinner -- edit completed -> success check (the current green tick) -- next edit starts -> back to spinner -- ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). -So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this. - -DONE 2026-06-09 (commit). coalesceEditRuns folds consecutive same-file edits into one ''# edits'' ClideHolderCard; aggregate live status via new ClideStatusIndicator (running/success/error) + ClideSpinner (logo-mark, 3D Y rotation, reduced-motion aware), shared with the activity card. Per user: spinner is a self-contained component (not built on ConversationCard''s mark) with an AnimatedSwitcher seam for a richer spinner→check transition later. Tests: activity_cluster_test (coalesce), conversation_view_test (edits card), clide_status_indicator_test.', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, '899916f67fe3c12a2ac4d231630bd34d', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'status', 'ready', 'done', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, '9c76b18710ed6e17c878dea8f92ad881', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:54:28', '2026-06-09 16:54:28', '2026-06-09 16:54:28', NULL, '992ed635baad8cd22fbc9a7d1fee52be', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-241', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:55:49', '2026-06-09 16:55:49', '2026-06-09 16:55:49', NULL, '03b3031c85c7266d0b2309bf25a9ce46', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-44', 'status', 'backlog', 'done', NULL, '2026-06-09 16:56:55', '2026-06-09 16:56:55', '2026-06-09 16:56:55', NULL, '987700c439d6728dfbb6451c2d9ed816', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-242', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:57:16', '2026-06-09 16:57:16', '2026-06-09 16:57:16', NULL, '3c162ac53229d8aa097d6b986807cc69', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:57:23', '2026-06-09 16:57:23', '2026-06-09 16:57:23', NULL, '50e18e6e1213791071dd78f766b91af6', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:57:35', '2026-06-09 16:57:35', '2026-06-09 16:57:35', NULL, '5241fed0ca5bed83588583f4f5aab228', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:58:12', '2026-06-09 16:58:12', '2026-06-09 16:58:12', NULL, 'f88e3d53c9a681fb33293ad51e9116f9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'description', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. +DONE 2026-06-09 (commit). Fixed ~24px caret-line toggles bookend the status bar (StatusbarHost), flip chevron per arrangement.isCollapsed, fire existing sidebar.collapse/context.collapse. Visual affordance only. Tests: app_collapse_toggle_test + updated app_statusbar_test.', NULL, '2026-06-09 16:34:46', '2026-06-09 16:34:46', '2026-06-09 16:34:46', NULL, 'ef47660ba615407e22a179e30866d85a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W0NPAEG3KWNFY9DR', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:54:28', '2026-06-09 16:54:28', '2026-06-09 16:54:28', NULL, '7cfb52c0de285d0cf5230a72b27dfd35', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM531VE0C6N36AT12SR', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:55:49', '2026-06-09 16:55:49', '2026-06-09 16:55:49', NULL, 'f9fadc88a5e4ee925e7972f3389bcd16', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53HQD6YV754GKBA60', 'status', 'backlog', 'done', NULL, '2026-06-09 16:56:55', '2026-06-09 16:56:55', '2026-06-09 16:56:55', NULL, 'fb4a56615b5e1e64c5cf6d12d9a3ee60', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AE2GEF6BXSGKRMGC', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:57:16', '2026-06-09 16:57:16', '2026-06-09 16:57:16', NULL, '8bba7559aeefd3fcdccacc7f7fa7a09b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BRWWPB4R06Q31XV0', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:57:23', '2026-06-09 16:57:23', '2026-06-09 16:57:23', NULL, 'ddb373332db2254e04fdda13cca96f94', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QJC039TMDF5JGMXC', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:57:35', '2026-06-09 16:57:35', '2026-06-09 16:57:35', NULL, '5501d1a65c21a3448d0ce1a1522cc90d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42VPM50VKAWXBCAM8', 'status', 'backlog', 'ready', NULL, '2026-06-09 16:58:12', '2026-06-09 16:58:12', '2026-06-09 16:58:12', NULL, '6277911a72c6774b5692c9af3a2402c5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W0NPAEG3KWNFY9DR', 'status', 'ready', 'in_progress', NULL, '2026-06-09 17:06:47', '2026-06-09 17:06:47', '2026-06-09 17:06:47', NULL, '03f03880fba3cfc2ddb335cde60b2dfb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W0NPAEG3KWNFY9DR', 'description', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. Why: 1. No cross-widget text selection/copy in clide''s native widgets today (regression vs a terminal). @@ -2048,17 +2049,16 @@ Acceptance: all child tickets done; D-record landed; Claude pane renders nativel Source: plan iridescent-tinkering-umbrella (2026-05-22), grounded in an-idea.md + read-only validation of the transcript schema this session. -STATUS CORRECTED (2026-06-09): not superseded — substantially DELIVERED. 32/34 children done; the native Claude pane (transcript rendering, select/copy, no terminal), tmux team tiles + meta sidebar, composer, and D-75 all shipped. Re-flagged from ''ready'' (misleading — no startable work) to in_progress. Remaining, both non-blocking, kept under this epic: T-158 (team/account token budget — blocked on upstream /usage exposure) and T-235 (persist activity-card fold level, T-230 follow-up). Close when those land.', NULL, '2026-06-09 17:06:47', '2026-06-09 17:06:47', '2026-06-09 17:06:47', NULL, 'a11411cd79f8fa9b93a74693676d02f0', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'status', 'ready', 'in_progress', NULL, '2026-06-09 17:06:47', '2026-06-09 17:06:47', '2026-06-09 17:06:47', NULL, '8f39c5ef2d84d66fd67b8b0bce0c0fe3', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-235', 'description', 'Follow-up to T-230. The activity-card fold level is implemented as a switchable ConversationView.foldLevel parameter (FoldLevel none/tools/thinking/everything; groupConversation re-groups per level, unit-tested), but the live Claude pane currently uses the L1 default with no way for the user to change it. Finish acceptance #4: (1) read the fold level from a persisted user setting (via ctx.settings / the kernel settings service — see how other builtins read settings), defaulting to L1; (2) add a control to change it (e.g. a command ''claude.activity.fold-level'' cycling none->L1->L2->L3, and/or an entry in the Claude/settings UI); (3) thread it into the ConversationView built in claude_pane.dart (and team_panel_host.dart). The grouping + card + a11y are already done in activity_cluster.dart + conversation_view.dart.', 'Follow-up to T-230. The activity-card fold level is implemented as a switchable ConversationView.foldLevel parameter (FoldLevel none/tools/thinking/everything; groupConversation re-groups per level, unit-tested), but the live Claude pane currently uses the L1 default with no way for the user to change it. Finish acceptance #4: (1) read the fold level from a persisted user setting (via ctx.settings / the kernel settings service — see how other builtins read settings), defaulting to L1; (2) add a control to change it (e.g. a command ''claude.activity.fold-level'' cycling none->L1->L2->L3, and/or an entry in the Claude/settings UI); (3) thread it into the ConversationView built in claude_pane.dart (and team_panel_host.dart). The grouping + card + a11y are already done in activity_cluster.dart + conversation_view.dart. +STATUS CORRECTED (2026-06-09): not superseded — substantially DELIVERED. 32/34 children done; the native Claude pane (transcript rendering, select/copy, no terminal), tmux team tiles + meta sidebar, composer, and D-75 all shipped. Re-flagged from ''ready'' (misleading — no startable work) to in_progress. Remaining, both non-blocking, kept under this epic: T-158 (team/account token budget — blocked on upstream /usage exposure) and T-235 (persist activity-card fold level, T-230 follow-up). Close when those land.', NULL, '2026-06-09 17:06:47', '2026-06-09 17:06:47', '2026-06-09 17:06:47', NULL, '1d1c8e343c2faf4f4ec24bd8abdf91c7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7D5DMYF5X92Q55CBW', 'description', 'Follow-up to T-230. The activity-card fold level is implemented as a switchable ConversationView.foldLevel parameter (FoldLevel none/tools/thinking/everything; groupConversation re-groups per level, unit-tested), but the live Claude pane currently uses the L1 default with no way for the user to change it. Finish acceptance #4: (1) read the fold level from a persisted user setting (via ctx.settings / the kernel settings service — see how other builtins read settings), defaulting to L1; (2) add a control to change it (e.g. a command ''claude.activity.fold-level'' cycling none->L1->L2->L3, and/or an entry in the Claude/settings UI); (3) thread it into the ConversationView built in claude_pane.dart (and team_panel_host.dart). The grouping + card + a11y are already done in activity_cluster.dart + conversation_view.dart.', 'Follow-up to T-230. The activity-card fold level is implemented as a switchable ConversationView.foldLevel parameter (FoldLevel none/tools/thinking/everything; groupConversation re-groups per level, unit-tested), but the live Claude pane currently uses the L1 default with no way for the user to change it. Finish acceptance #4: (1) read the fold level from a persisted user setting (via ctx.settings / the kernel settings service — see how other builtins read settings), defaulting to L1; (2) add a control to change it (e.g. a command ''claude.activity.fold-level'' cycling none->L1->L2->L3, and/or an entry in the Claude/settings UI); (3) thread it into the ConversationView built in claude_pane.dart (and team_panel_host.dart). The grouping + card + a11y are already done in activity_cluster.dart + conversation_view.dart. -DONE 2026-06-09. Persisted app-scoped setting kActivityFoldLevelKey (app.claude.activityFoldLevel); command claude.activity.fold-level cycles none→tools→thinking→everything (foldLevelFromName/nextFoldLevel in activity_cluster.dart); ClaudePane + team_panel_host read the setting and re-fold live via the settings notifier. Tests: foldLevelFromName/nextFoldLevel in activity_cluster_test.', NULL, '2026-06-09 17:16:33', '2026-06-09 17:16:33', '2026-06-09 17:16:33', NULL, 'd405dc094770c506152a1c1733e8d311', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-235', 'status', 'backlog', 'done', NULL, '2026-06-09 17:16:34', '2026-06-09 17:16:34', '2026-06-09 17:16:34', NULL, '0f918f8f93f76772c2eb987acdbc349c', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-158', 'parent_id', 'T-132', NULL, NULL, '2026-06-09 17:16:34', '2026-06-09 17:16:34', '2026-06-09 17:16:34', NULL, 'cbc56070c7311b51c3709c7958d71d4f', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-158', 'description', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''.', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''. +DONE 2026-06-09. Persisted app-scoped setting kActivityFoldLevelKey (app.claude.activityFoldLevel); command claude.activity.fold-level cycles none→tools→thinking→everything (foldLevelFromName/nextFoldLevel in activity_cluster.dart); ClaudePane + team_panel_host read the setting and re-fold live via the settings notifier. Tests: foldLevelFromName/nextFoldLevel in activity_cluster_test.', NULL, '2026-06-09 17:16:33', '2026-06-09 17:16:33', '2026-06-09 17:16:33', NULL, 'b9f41312692ede5ad66e99200c785b91', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62FKQQD0B9B80PFY4', 'parent_id', 'T-132', NULL, NULL, '2026-06-09 17:16:34', '2026-06-09 17:16:34', '2026-06-09 17:16:34', NULL, '1077ce81ca0eb879dcd1a6445d3de6e6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62FKQQD0B9B80PFY4', 'description', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''.', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''. -2026-06-09: detached from T-132 (which is otherwise complete) and made the RESOLVER ticket for Q-34 (how + when to surface the budget given upstream doesn''t expose it). Stays in the backlog; revisit when a viable data path lands (upstream claude usage --json / endpoint per anthropics/claude-code#44328, or an API-key usage path).', NULL, '2026-06-09 17:16:34', '2026-06-09 17:16:34', '2026-06-09 17:16:34', NULL, 'cdaf44fb19cf2dce450fee6e35b7c8cf', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'description', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. +2026-06-09: detached from T-132 (which is otherwise complete) and made the RESOLVER ticket for Q-34 (how + when to surface the budget given upstream doesn''t expose it). Stays in the backlog; revisit when a viable data path lands (upstream claude usage --json / endpoint per anthropics/claude-code#44328, or an API-key usage path).', NULL, '2026-06-09 17:16:34', '2026-06-09 17:16:34', '2026-06-09 17:16:34', NULL, '504b382920e7b07cafc1da32738625c6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7D5DMYF5X92Q55CBW', 'status', 'backlog', 'done', NULL, '2026-06-09 17:16:34', '2026-06-09 17:16:34', '2026-06-09 17:16:34', NULL, 'cbab9b537913971dba5e335d55928673', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W0NPAEG3KWNFY9DR', 'description', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. Why: 1. No cross-widget text selection/copy in clide''s native widgets today (regression vs a terminal). @@ -2090,10 +2090,10 @@ Source: plan iridescent-tinkering-umbrella (2026-05-22), grounded in an-idea.md STATUS CORRECTED (2026-06-09): not superseded — substantially DELIVERED. 32/34 children done; the native Claude pane (transcript rendering, select/copy, no terminal), tmux team tiles + meta sidebar, composer, and D-75 all shipped. Re-flagged from ''ready'' (misleading — no startable work) to in_progress. Remaining, both non-blocking, kept under this epic: T-158 (team/account token budget — blocked on upstream /usage exposure) and T-235 (persist activity-card fold level, T-230 follow-up). Close when those land. -CLOSED 2026-06-09: all doable work delivered (native Claude pane, transcript rendering, select/copy, no terminal, tmux team tiles + meta sidebar, composer, activity-card folding incl. the persisted fold level T-235, D-75). The one blocked item — account/team token budget — was detached (T-158) and reframed as Q-34 (how + when to surface it given upstream doesn''t expose the data); T-158 is the resolver, parked in the backlog. Nothing actionable remains under this epic.', NULL, '2026-06-09 17:23:25', '2026-06-09 17:23:25', '2026-06-09 17:23:25', NULL, '19f585ff8564f76c5d823a3455831636', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'status', 'in_progress', 'done', NULL, '2026-06-09 17:23:25', '2026-06-09 17:23:25', '2026-06-09 17:23:25', NULL, '6ad60f480bedf1a382cfb9af3d415060', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-300', 'parent_id', NULL, 'T-276', NULL, '2026-06-09 20:02:53', '2026-06-09 20:02:53', '2026-06-09 20:02:53', NULL, '260fe1f17b71bdc89c40df70fba3aab9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-300', 'description', NULL, 'Make file-path references rendered in the Claude conversation pane clickable: clicking a path opens that file in the editor. +CLOSED 2026-06-09: all doable work delivered (native Claude pane, transcript rendering, select/copy, no terminal, tmux team tiles + meta sidebar, composer, activity-card folding incl. the persisted fold level T-235, D-75). The one blocked item — account/team token budget — was detached (T-158) and reframed as Q-34 (how + when to surface it given upstream doesn''t expose the data); T-158 is the resolver, parked in the backlog. Nothing actionable remains under this epic.', NULL, '2026-06-09 17:23:25', '2026-06-09 17:23:25', '2026-06-09 17:23:25', NULL, '749ec9a3224d1ae1c9661a37c05da4da', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W0NPAEG3KWNFY9DR', 'status', 'in_progress', 'done', NULL, '2026-06-09 17:23:25', '2026-06-09 17:23:25', '2026-06-09 17:23:25', NULL, 'eadab6091961f410df29809647ee6574', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F2TBJJV1F2KP7XR8', 'parent_id', NULL, 'T-276', NULL, '2026-06-09 20:02:53', '2026-06-09 20:02:53', '2026-06-09 20:02:53', NULL, '9a44220eef05245a83e53df6d32f9a13', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F2TBJJV1F2KP7XR8', 'description', NULL, 'Make file-path references rendered in the Claude conversation pane clickable: clicking a path opens that file in the editor. **Behavior** - Detect references to files in the workspace as the conversation markdown is rendered — bare paths (`lib/app.dart`), `file:line` forms (`lib/app.dart:42`, clickable per CLAUDE.md), and likely inline-code spans / markdown links pointing at repo paths. @@ -2103,9 +2103,9 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, **Notes / constraints** - Markdown rendering is clide-owned custom CustomPaint/widgets (not a package), so detection + hit-testing lands in the conversation/markdown render path. - Honors User/Claude parity (D-6): the click maps to the existing `clide editor open` verb. -- Open questions to settle during design: how aggressive path detection should be (existence check vs. heuristic), handling of non-existent / external paths, and visual affordance (underline/hover) for a linkified ref.', NULL, '2026-06-09 20:03:08', '2026-06-09 20:03:08', '2026-06-09 20:03:08', NULL, 'b958458ae0528f8c5a90e2222c1b4447', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-301', 'parent_id', NULL, 'T-276', NULL, '2026-06-09 20:04:31', '2026-06-09 20:04:31', '2026-06-09 20:04:31', NULL, 'b8ab10d6e8cf7cef0fc3ee4e26295469', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-301', 'description', NULL, 'When file paths to files inside the current repo appear in the Claude conversation, show them relative to the repo root rather than as absolute paths. +- Open questions to settle during design: how aggressive path detection should be (existence check vs. heuristic), handling of non-existent / external paths, and visual affordance (underline/hover) for a linkified ref.', NULL, '2026-06-09 20:03:08', '2026-06-09 20:03:08', '2026-06-09 20:03:08', NULL, '5e7b2e6008889f0624ed204c97fc718b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6048VEGFDNEBHFMM4', 'parent_id', NULL, 'T-276', NULL, '2026-06-09 20:04:31', '2026-06-09 20:04:31', '2026-06-09 20:04:31', NULL, '818820d6c1dc1b3b8d0c60096d0025f4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6048VEGFDNEBHFMM4', 'description', NULL, 'When file paths to files inside the current repo appear in the Claude conversation, show them relative to the repo root rather than as absolute paths. **DESIGN OPEN — needs discussion before implementation.** The exact presentation is undecided. @@ -2121,10 +2121,11 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Does this happen at render time only, or is the underlying text also normalized? **Related** -- Pairs with T-300 (clickable file references) — same conversation/markdown render path; likely share path-detection + workspace-root resolution.', NULL, '2026-06-09 20:04:54', '2026-06-09 20:04:54', '2026-06-09 20:04:54', NULL, '05fb96e25be6d666d0e259f45ada35a5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-242', 'description', NULL, 'VERIFIED DONE 2026-06-09: failures-only is the default reporter across all gate scripts — ci/test.sh (REPORTER var, used in every flutter/dart test invocation), ci/test_core.sh, ci/test_a11y.sh — each with a TEST_REPORTER=expanded escape hatch for debugging. No work remaining.', NULL, '2026-06-09 20:07:29', '2026-06-09 20:07:29', '2026-06-09 20:07:29', NULL, '5a4b91ffd41fde8bca92457d539e2bd5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-242', 'status', 'ready', 'done', NULL, '2026-06-09 20:07:30', '2026-06-09 20:07:30', '2026-06-09 20:07:30', NULL, '98793f24ffa8ce23f574b1ddd702a476', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'description', 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. +- Pairs with T-300 (clickable file references) — same conversation/markdown render path; likely share path-detection + workspace-root resolution.', NULL, '2026-06-09 20:04:54', '2026-06-09 20:04:54', '2026-06-09 20:04:54', NULL, '8ecbd1bca77bd64222112c780fa216b4', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AE2GEF6BXSGKRMGC', 'description', NULL, 'VERIFIED DONE 2026-06-09: failures-only is the default reporter across all gate scripts — ci/test.sh (REPORTER var, used in every flutter/dart test invocation), ci/test_core.sh, ci/test_a11y.sh — each with a TEST_REPORTER=expanded escape hatch for debugging. No work remaining.', NULL, '2026-06-09 20:07:29', '2026-06-09 20:07:29', '2026-06-09 20:07:29', NULL, '29dd32588e4378ba0d0f4ba0ac104bfd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AE2GEF6BXSGKRMGC', 'status', 'ready', 'done', NULL, '2026-06-09 20:07:30', '2026-06-09 20:07:30', '2026-06-09 20:07:30', NULL, '23b91495413b8ba1b00291c6f22541d8', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42VPM50VKAWXBCAM8', 'status', 'ready', 'done', NULL, '2026-06-09 20:17:04', '2026-06-09 20:17:04', '2026-06-09 20:17:04', NULL, '3c82a7b65e8d1c68ea241aa9a0a4e6dd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42VPM50VKAWXBCAM8', 'description', 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. ## Behaviour - Click (or ctrl/cmd-click) on a colorized link opens it via the OS default handler. @@ -2146,10 +2147,9 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Use the platform URL launcher; avoid pulling in an opinionated package if a thin native/url_launcher shim already exists in the tree. - Guard against non-http schemes / malformed URLs. -DONE 2026-06-09. http(s) links in the conversation open via OsBridge.openURL on click (hover underline + pointer); non-http inert. ClideMarkdown gained an onLinkTap hook + _urlLinkSpan; the 3 inline-interaction callbacks were bundled into ClideMarkdownHooks (single threaded param) — also fixed links/images only working in some markdown contexts. Tests: clide_markdown_test.', NULL, '2026-06-09 20:17:04', '2026-06-09 20:17:04', '2026-06-09 20:17:04', NULL, '52aeaf560871a3f62a3790d3ad0946dc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'status', 'ready', 'done', NULL, '2026-06-09 20:17:04', '2026-06-09 20:17:04', '2026-06-09 20:17:04', NULL, '316ad39ac68ed299afeaed9e74c46387', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-302', 'parent_id', NULL, 'T-8', NULL, '2026-06-09 20:24:57', '2026-06-09 20:24:57', '2026-06-09 20:24:57', NULL, 'd2dbf772252ce008d7c7e59bdd5f06d7', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-302', 'description', NULL, 'Design the app''s settings screen as a Frame0 wireframe before building `builtin.settings-ui` (see Tier 6 epic T-8). Output is a wireframe to align on layout/IA, not implementation. +DONE 2026-06-09. http(s) links in the conversation open via OsBridge.openURL on click (hover underline + pointer); non-http inert. ClideMarkdown gained an onLinkTap hook + _urlLinkSpan; the 3 inline-interaction callbacks were bundled into ClideMarkdownHooks (single threaded param) — also fixed links/images only working in some markdown contexts. Tests: clide_markdown_test.', NULL, '2026-06-09 20:17:04', '2026-06-09 20:17:04', '2026-06-09 20:17:04', NULL, 'bcf049444a57e41061b1b9adeeb7b058', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7H8JWCP59WQVD0FW4', 'parent_id', NULL, 'T-8', NULL, '2026-06-09 20:24:57', '2026-06-09 20:24:57', '2026-06-09 20:24:57', NULL, 'd7dfe7ec18b8657bf2ca96f2ca0d75f2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7H8JWCP59WQVD0FW4', 'description', NULL, 'Design the app''s settings screen as a Frame0 wireframe before building `builtin.settings-ui` (see Tier 6 epic T-8). Output is a wireframe to align on layout/IA, not implementation. **Deliverable** - Frame0 wireframe authored via the frame0-wireframe skill: local JSON source-of-truth synced to Frame0, exported for review. @@ -2170,8 +2170,8 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, **Constraints** - Follow clide visual language — pull theme tokens / control geometry from the ui-design skill so the wireframe maps cleanly to real widgets (no Material/Cupertino). -This is the design step; implementation of the actual settings UI is separate child work under T-8.', NULL, '2026-06-09 20:25:49', '2026-06-09 20:25:49', '2026-06-09 20:25:49', NULL, '4b1fefa0723073ca71fdb99cb319f424', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'description', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: +This is the design step; implementation of the actual settings UI is separate child work under T-8.', NULL, '2026-06-09 20:25:49', '2026-06-09 20:25:49', '2026-06-09 20:25:49', NULL, '1d0b97bd0ad807733f995ee05eb9d2f2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SK3D1QYACBPHHBZ0', 'description', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: GLib-GIO-CRITICAL: GFileInfo created without standard::size file ../gio/gfileinfo.c: line 1865 (g_file_info_get_size): should not be reached @@ -2199,33 +2199,33 @@ Investigation: run with G_DEBUG=fatal-warnings (or gdb break on g_log/g_logv) to Env: Fedora, GTK Linux embedder, flutter run. -UPDATE (2026-06-09): also fires MID-SESSION, not only at boot — contradicts the "fires early in boot, on every launch" framing above. Observed log: app booted 16:47:01, but the GLib-GIO-CRITICAL pair fired at 18:51:07.841 / 18:51:08.190 (~2h into the session), near pane/session activity. So the trigger is more likely a file-info code path tied to a user action or background file enumeration than pure startup. Re-scope the investigation to capture the stack when it fires mid-session (G_DEBUG=fatal-warnings / gdb break on g_log) rather than only at boot.', NULL, '2026-06-09 20:26:44', '2026-06-09 20:26:44', '2026-06-09 20:26:44', NULL, '0686bc37e43fe2adb5e54d6628abbd24', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'description', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. +UPDATE (2026-06-09): also fires MID-SESSION, not only at boot — contradicts the "fires early in boot, on every launch" framing above. Observed log: app booted 16:47:01, but the GLib-GIO-CRITICAL pair fired at 18:51:07.841 / 18:51:08.190 (~2h into the session), near pane/session activity. So the trigger is more likely a file-info code path tied to a user action or background file enumeration than pure startup. Re-scope the investigation to capture the stack when it fires mid-session (G_DEBUG=fatal-warnings / gdb break on g_log) rather than only at boot.', NULL, '2026-06-09 20:26:44', '2026-06-09 20:26:44', '2026-06-09 20:26:44', NULL, '4cdfa1f8240ffeba7dd710712405f482', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QJC039TMDF5JGMXC', 'status', 'ready', 'done', NULL, '2026-06-09 20:27:17', '2026-06-09 20:27:17', '2026-06-09 20:27:17', NULL, '894a8c8c0863f2abe160f610a2f85215', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QJC039TMDF5JGMXC', 'description', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. Refinement (2026-06-05, user): match the Claude Code CLI''s actual behavior for the text-field interaction. The number key triggers the button/option shortcut when it would be the FIRST character typed -- i.e. focus is NOT in a text field, OR focus IS in the note/Other field but that field is currently EMPTY. Once the field has any content, digits type normally (no shortcut). This supersedes the earlier ''never capture digits while a text field has focus'' gate: it''s better because focus often defaults into the (empty) note field, so the shortcut still fires there (muscle-memory case) while a digit mid-note still types. Implementation: intercept the digit at the prompt focus scope; consume+activate only when the focused editable (if any) is empty, else let it through to type. The CLI exhibits this exact ''footgun'' (digit-as-first-char in an empty field acts as the choice) and we intentionally mirror it.', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. Refinement (2026-06-05, user): match the Claude Code CLI''s actual behavior for the text-field interaction. The number key triggers the button/option shortcut when it would be the FIRST character typed -- i.e. focus is NOT in a text field, OR focus IS in the note/Other field but that field is currently EMPTY. Once the field has any content, digits type normally (no shortcut). This supersedes the earlier ''never capture digits while a text field has focus'' gate: it''s better because focus often defaults into the (empty) note field, so the shortcut still fires there (muscle-memory case) while a digit mid-note still types. Implementation: intercept the digit at the prompt focus scope; consume+activate only when the focused editable (if any) is empty, else let it through to type. The CLI exhibits this exact ''footgun'' (digit-as-first-char in an empty field acts as the choice) and we intentionally mirror it. -DONE 2026-06-09. ToolPromptCard: number keys pick the matching button/option (labels prefixed 1./2./3.), Enter confirms the primary action. Permission 1=Allow / 2=Allow&remember(if offered) else Deny / 3=Deny; AskUserQuestion 1..N select+toggle options + Other. Card autofocuses; _onKey self-guards on hasPrimaryFocus so a focused note field types digits normally. Permission/option actions shared between buttons + keys. Tests: prompt_card_test number-key group + updated label finders.', NULL, '2026-06-09 20:27:17', '2026-06-09 20:27:17', '2026-06-09 20:27:17', NULL, '875ba4de0af2b7b07d18393084808ab9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'status', 'ready', 'done', NULL, '2026-06-09 20:27:17', '2026-06-09 20:27:17', '2026-06-09 20:27:17', NULL, 'e954b9bb742ce4959998d25daf0d9026', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-241', 'description', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end.', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end. +DONE 2026-06-09. ToolPromptCard: number keys pick the matching button/option (labels prefixed 1./2./3.), Enter confirms the primary action. Permission 1=Allow / 2=Allow&remember(if offered) else Deny / 3=Deny; AskUserQuestion 1..N select+toggle options + Other. Card autofocuses; _onKey self-guards on hasPrimaryFocus so a focused note field types digits normally. Permission/option actions shared between buttons + keys. Tests: prompt_card_test number-key group + updated label finders.', NULL, '2026-06-09 20:27:17', '2026-06-09 20:27:17', '2026-06-09 20:27:17', NULL, 'c05ca703a5a99d070e1eca42d853370f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM531VE0C6N36AT12SR', 'status', 'ready', 'done', NULL, '2026-06-09 20:34:30', '2026-06-09 20:34:30', '2026-06-09 20:34:30', NULL, '6dd8b5ff2ee01ab1eac6aa74c08d4186', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM531VE0C6N36AT12SR', 'description', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end.', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end. -DONE 2026-06-09. Added the reusable setSurfaceSize(tester, width) helper to test/helpers/widget_harness.dart (the foundation the ticket asked for). Ultrawide (3440) cases added on the flagged width-sensitive surfaces: ClideMarquee (was narrow-only — now verified static when a line fits a wide slot) and the quick-open palette (verified width-capped, not stretched). Status bar already covers 600+3440 (T-239). AUDIT NOTES on the rest: overlays/pickers use fixed-width panels (ClideMenu/quick-open 480, anchored popovers) so they''re inherently capped/centered; tab strips (MultitabPane) and status items overflow only when NARROW (a small-screen concern, Q-26), not wide; RootLayout slots are explicit fixed/flex widths with drag-resize. The helper is now in place for any future width-sensitive widget. Not exhaustive by design (audit + reusable tooling + the high-risk surfaces).', NULL, '2026-06-09 20:34:30', '2026-06-09 20:34:30', '2026-06-09 20:34:30', NULL, '38aecf162a111228f3e2b3d3caa45806', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-241', 'status', 'ready', 'done', NULL, '2026-06-09 20:34:30', '2026-06-09 20:34:30', '2026-06-09 20:34:30', NULL, '3214df9c993271536a2e2226ba6065fc', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'description', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration.', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. +DONE 2026-06-09. Added the reusable setSurfaceSize(tester, width) helper to test/helpers/widget_harness.dart (the foundation the ticket asked for). Ultrawide (3440) cases added on the flagged width-sensitive surfaces: ClideMarquee (was narrow-only — now verified static when a line fits a wide slot) and the quick-open palette (verified width-capped, not stretched). Status bar already covers 600+3440 (T-239). AUDIT NOTES on the rest: overlays/pickers use fixed-width panels (ClideMenu/quick-open 480, anchored popovers) so they''re inherently capped/centered; tab strips (MultitabPane) and status items overflow only when NARROW (a small-screen concern, Q-26), not wide; RootLayout slots are explicit fixed/flex widths with drag-resize. The helper is now in place for any future width-sensitive widget. Not exhaustive by design (audit + reusable tooling + the high-risk surfaces).', NULL, '2026-06-09 20:34:30', '2026-06-09 20:34:30', '2026-06-09 20:34:30', NULL, 'f81c453978dfa298dfb66a17a39a6264', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BRWWPB4R06Q31XV0', 'status', 'ready', 'done', NULL, '2026-06-09 20:41:22', '2026-06-09 20:41:22', '2026-06-09 20:41:22', NULL, '9a195bdb92b08e8a05c5ea8ab0792278', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BRWWPB4R06Q31XV0', 'description', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration.', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. -DONE (Linux) 2026-06-09. clide://open?path=&line= → editor.open via parseArgv, routed through the existing CLI→IPC path into the running window (single-instance for free). Parser validates action/path/line; tests in argv_to_request_test. Scheme registered: linux/clide.desktop (x-scheme-handler/clide, Exec already %U) + macOS Info.plist CFBundleURLTypes. Linux works end-to-end. macOS URL DELIVERY (AppDelegate openURLs → method channel → Dart) deferred to T-303 — needs a macOS machine to verify, not shipped blind.', NULL, '2026-06-09 20:41:22', '2026-06-09 20:41:22', '2026-06-09 20:41:22', NULL, '8b44d2146d801f6a6d652f435333ddac', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'status', 'ready', 'done', NULL, '2026-06-09 20:41:22', '2026-06-09 20:41:22', '2026-06-09 20:41:22', NULL, '9cfad50d09be7e0946f985b33b47ddbb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'description', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. +DONE (Linux) 2026-06-09. clide://open?path=&line= → editor.open via parseArgv, routed through the existing CLI→IPC path into the running window (single-instance for free). Parser validates action/path/line; tests in argv_to_request_test. Scheme registered: linux/clide.desktop (x-scheme-handler/clide, Exec already %U) + macOS Info.plist CFBundleURLTypes. Linux works end-to-end. macOS URL DELIVERY (AppDelegate openURLs → method channel → Dart) deferred to T-303 — needs a macOS machine to verify, not shipped blind.', NULL, '2026-06-09 20:41:22', '2026-06-09 20:41:22', '2026-06-09 20:41:22', NULL, 'ca04430461aeac7583a044f10aa57469', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BRWWPB4R06Q31XV0', 'description', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. DONE (Linux) 2026-06-09. clide://open?path=&line= → editor.open via parseArgv, routed through the existing CLI→IPC path into the running window (single-instance for free). Parser validates action/path/line; tests in argv_to_request_test. Scheme registered: linux/clide.desktop (x-scheme-handler/clide, Exec already %U) + macOS Info.plist CFBundleURLTypes. Linux works end-to-end. macOS URL DELIVERY (AppDelegate openURLs → method channel → Dart) deferred to T-303 — needs a macOS machine to verify, not shipped blind.', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. DONE (Linux) 2026-06-09. clide://open?path=&line= → editor.open via parseArgv, routed through the existing CLI→IPC path into the running window (single-instance for free). Parser validates action/path/line; tests in argv_to_request_test. Scheme registered: linux/clide.desktop (x-scheme-handler/clide, Exec already %U) + macOS Info.plist CFBundleURLTypes. Linux works end-to-end. macOS URL DELIVERY (AppDelegate openURLs → method channel → Dart) deferred to T-303 — needs a macOS machine to verify, not shipped blind. -SECURITY REDESIGN 2026-06-09 (D-90): per user, the clide:// surface is now paranoid. The link no longer translates to a command in parseArgv — it routes the raw URL to a new builtin.deeplink handler gated by a DEFAULT-DENY allowlist (kDeepLinkSafeActions = {open} only — run/git/write/passthrough rejected) AND a mandatory confirmation prompt (''an external link wants to: … allow?'') before any action. The generic CLI-passthrough idea is deliberately NOT shipped (it would make a webpage a remote control); each new action is an explicit allowlist + handler addition. Tests: deep_link_test (allowlist boundary), extension_test (gating).', NULL, '2026-06-09 20:57:53', '2026-06-09 20:57:53', '2026-06-09 20:57:53', NULL, 'c11fcdd9e2477b0ee81e85c298e08869', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'status', 'backlog', 'ready', NULL, '2026-06-09 21:04:10', '2026-06-09 21:04:10', '2026-06-09 21:04:10', NULL, '5f05b0b47d4a2883449d9ec21e0a0a3a', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'status', 'ready', 'in_progress', NULL, '2026-06-09 21:04:33', '2026-06-09 21:04:33', '2026-06-09 21:04:33', NULL, '6d9856a6c2288f192d7cba5722ae3e2b', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'description', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: +SECURITY REDESIGN 2026-06-09 (D-90): per user, the clide:// surface is now paranoid. The link no longer translates to a command in parseArgv — it routes the raw URL to a new builtin.deeplink handler gated by a DEFAULT-DENY allowlist (kDeepLinkSafeActions = {open} only — run/git/write/passthrough rejected) AND a mandatory confirmation prompt (''an external link wants to: … allow?'') before any action. The generic CLI-passthrough idea is deliberately NOT shipped (it would make a webpage a remote control); each new action is an explicit allowlist + handler addition. Tests: deep_link_test (allowlist boundary), extension_test (gating).', NULL, '2026-06-09 20:57:53', '2026-06-09 20:57:53', '2026-06-09 20:57:53', NULL, 'aee84cbde5ade207d02379c497eed4ed', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SK3D1QYACBPHHBZ0', 'status', 'backlog', 'ready', NULL, '2026-06-09 21:04:10', '2026-06-09 21:04:10', '2026-06-09 21:04:10', NULL, '9004651246035582806f204f61b20e91', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SK3D1QYACBPHHBZ0', 'status', 'ready', 'in_progress', NULL, '2026-06-09 21:04:33', '2026-06-09 21:04:33', '2026-06-09 21:04:33', NULL, 'bff9123dd5b8658d12afdf3d87bfc71f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SK3D1QYACBPHHBZ0', 'description', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: GLib-GIO-CRITICAL: GFileInfo created without standard::size file ../gio/gfileinfo.c: line 1865 (g_file_info_get_size): should not be reached @@ -2267,12 +2267,12 @@ Fix (both, per user direction): 1. Switched pickDirectory to GtkFileChooserNative — portal-backed, runs out-of-process in sandboxed/Flatpak builds so the noise never enters our process; nicer Wayland picker. (Verified: outside a sandbox GtkFileChooserNative falls back to the in-process chooser and STILL warns on this box — portal is only used under Flatpak/snap or GTK_USE_PORTAL=1.) 2. Added clide_gio_log_filter in clide_app.cc, installed via g_log_set_handler("GLib-GIO", CRITICAL, ...) in clide_app_startup. Drops ONLY messages containing "g_file_info_get_size" or "without standard::size"; forwards every other GLib-GIO log to the default handler. This is the universal fix (works in every run mode). -Verification: standalone reproducer with both the native chooser AND the filter — size warnings gone, an unrelated GLib-GIO CRITICAL still passes through (filter is scoped, not a blanket mute). `make build-linux` green. CHANGELOG updated under Fixed.', NULL, '2026-06-09 21:21:04', '2026-06-09 21:21:04', '2026-06-09 21:21:04', NULL, '872947bf48bd74c3fb6942ec90149ac5', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'status', 'in_progress', 'done', NULL, '2026-06-09 21:21:08', '2026-06-09 21:21:08', '2026-06-09 21:21:08', NULL, '931bae221c6780445f00b547da62c8cb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-298', 'status', 'backlog', 'ready', NULL, '2026-06-09 21:21:30', '2026-06-09 21:21:30', '2026-06-09 21:21:30', NULL, '9aeac5d0a8d6dfeb6adb96ed63dda4eb', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-298', 'status', 'ready', 'in_progress', NULL, '2026-06-09 21:22:26', '2026-06-09 21:22:26', '2026-06-09 21:22:26', NULL, 'b14a5b998bd76cda5bb558fc0faa4be9', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-304', 'parent_id', NULL, 'T-276', NULL, '2026-06-09 21:22:31', '2026-06-09 21:22:31', '2026-06-09 21:22:31', NULL, '0b04f300702a4b9a46d5b591f15e82af', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-304', 'description', NULL, 'The logo-mark spinner shown on in-progress activity/holder cards in the Claude conversation is too small to read as a spinner — it reads as a static speck. Enlarge it so the running state is legible at a glance. +Verification: standalone reproducer with both the native chooser AND the filter — size warnings gone, an unrelated GLib-GIO CRITICAL still passes through (filter is scoped, not a blanket mute). `make build-linux` green. CHANGELOG updated under Fixed.', NULL, '2026-06-09 21:21:04', '2026-06-09 21:21:04', '2026-06-09 21:21:04', NULL, '0b6444210390483f0e12a042efd5e3f1', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SK3D1QYACBPHHBZ0', 'status', 'in_progress', 'done', NULL, '2026-06-09 21:21:08', '2026-06-09 21:21:08', '2026-06-09 21:21:08', NULL, 'd22822fc67f1dc3e1d63c924b6fe91fd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50S6WRD81V3Z0NH1M', 'status', 'backlog', 'ready', NULL, '2026-06-09 21:21:30', '2026-06-09 21:21:30', '2026-06-09 21:21:30', NULL, '80c5f5e2ca71bc6146c769b93cfdc8e9', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50S6WRD81V3Z0NH1M', 'status', 'ready', 'in_progress', NULL, '2026-06-09 21:22:26', '2026-06-09 21:22:26', '2026-06-09 21:22:26', NULL, '8c699e741fba562a14ec7fe5ebd4695e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KS233FGZE9H7ABWR', 'parent_id', NULL, 'T-276', NULL, '2026-06-09 21:22:31', '2026-06-09 21:22:31', '2026-06-09 21:22:31', NULL, 'b70cdfeb0ac73677be0a6d190ee88376', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KS233FGZE9H7ABWR', 'description', NULL, 'The logo-mark spinner shown on in-progress activity/holder cards in the Claude conversation is too small to read as a spinner — it reads as a static speck. Enlarge it so the running state is legible at a glance. **Where** - `ClideSpinner` (lib/widgets/src/clide_spinner.dart) — defaults to size 14; renders the logo SVG at width/height = size. @@ -2285,7 +2285,7 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, - Check the other ClideSpinner/StatusIndicator consumers (status surfaces) so the bump doesn''t bloat unrelated spots — may warrant sizing the cards explicitly rather than changing the shared default. **Acceptance** -- The in-progress spinner on conversation activity cards is comfortably distinguishable as a spinning indicator; success/error glyphs stay aligned at the same footprint.', NULL, '2026-06-09 21:22:44', '2026-06-09 21:22:44', '2026-06-09 21:22:44', NULL, '1aec6212db45140ee906592f9ce125ef', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-280', 'status', 'backlog', 'ready', NULL, '2026-06-09 21:32:29', '2026-06-09 21:32:29', '2026-06-09 21:32:29', NULL, '0fcac3a23b0303186fefb36827374616', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-298', 'status', 'in_progress', 'done', NULL, '2026-06-09 21:36:05', '2026-06-09 21:36:05', '2026-06-09 21:36:05', NULL, 'ecf870d28771c1368654195d93ec4dec', 1) ON CONFLICT(hash) DO NOTHING; -INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-280', 'status', 'ready', 'done', NULL, '2026-06-09 21:42:59', '2026-06-09 21:42:59', '2026-06-09 21:42:59', NULL, '11d1dc9865ac563878cd40691248240c', 1) ON CONFLICT(hash) DO NOTHING; +- The in-progress spinner on conversation activity cards is comfortably distinguishable as a spinning indicator; success/error glyphs stay aligned at the same footprint.', NULL, '2026-06-09 21:22:44', '2026-06-09 21:22:44', '2026-06-09 21:22:44', NULL, '71233a6425b5734391c4d315579d2c64', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YNEQM1WY25BV7TD8', 'status', 'backlog', 'ready', NULL, '2026-06-09 21:32:29', '2026-06-09 21:32:29', '2026-06-09 21:32:29', NULL, 'b6690849ecbdddf638c67dfa82192a3f', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50S6WRD81V3Z0NH1M', 'status', 'in_progress', 'done', NULL, '2026-06-09 21:36:05', '2026-06-09 21:36:05', '2026-06-09 21:36:05', NULL, 'c494cd6008aa55db47c5f372ab52c5b2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YNEQM1WY25BV7TD8', 'status', 'ready', 'done', NULL, '2026-06-09 21:42:59', '2026-06-09 21:42:59', '2026-06-09 21:42:59', NULL, '73d904ce53c8f0d5adbb6e724d47f885', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/ticket_idmap/0000-schema.sql b/.pql/changelog/ticket_idmap/0000-schema.sql new file mode 100644 index 00000000..f13a0107 --- /dev/null +++ b/.pql/changelog/ticket_idmap/0000-schema.sql @@ -0,0 +1,132 @@ +-- Auto-generated by migrate-ids (D-26). CREATE TABLE statements +-- for the planning schema; per-table dir keeps the changelog +-- self-describing per D-15. +-- pql:created_by: migrate-ids +-- pql:canonical_version: 2 + + +CREATE TABLE IF NOT EXISTS decisions ( + id TEXT PRIMARY KEY, + type TEXT NOT NULL CHECK(type IN ('confirmed','question','rejected')), + domain TEXT NOT NULL, + title TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'active' + CHECK(status IN ('active','superseded','resolved','open')), + date TEXT, + file_path TEXT NOT NULL, + synced_at TEXT NOT NULL DEFAULT (datetime('now')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER +); + +CREATE TABLE IF NOT EXISTS decision_refs ( + source_id TEXT NOT NULL REFERENCES decisions(id) ON DELETE CASCADE, + target_id TEXT NOT NULL REFERENCES decisions(id) ON DELETE CASCADE, + ref_type TEXT NOT NULL + CHECK(ref_type IN ('supersedes','references','resolves','depends_on','amends')), + note TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER, + PRIMARY KEY (source_id, target_id, ref_type) +); + +-- Identity split (D-26): a ticket's stable, collision-proof identity is its +-- record_id (a locally-generated ULID, planning.NewRecordID); the friendly +-- T-NNN label lives in ticket_idmap and may be reconciled. Every structural +-- reference (parent, deps, history, labels) targets record_id, so a label +-- clash never corrupts the graph — only ticket_idmap needs a relabel. +CREATE TABLE IF NOT EXISTS tickets ( + record_id TEXT PRIMARY KEY, + type TEXT NOT NULL CHECK(type IN ('initiative','epic','story','task','bug')), + parent_record_id TEXT REFERENCES tickets(record_id), + title TEXT NOT NULL, + description TEXT, + -- No CHECK enumeration: the ticket status vocabulary is per-vault + -- configurable (ticket_statuses in .pql/config.yaml). Validation lives + -- in Go (planning.StatusSet), so adding/renaming statuses needs no + -- schema change. The DEFAULT is a harmless fallback — CreateTicket + -- always inserts the configured default explicitly. + status TEXT NOT NULL DEFAULT 'backlog', + priority TEXT DEFAULT 'medium' + CHECK(priority IN ('critical','high','medium','low')), + assigned_to TEXT, + team TEXT, + decision_ref TEXT REFERENCES decisions(id), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER +); + +-- ticket_idmap maps a record_id to its current friendly label (T-NNN). +-- ticket_id is intentionally NOT globally unique: two uncoordinated clones +-- can mint the same label, which surfaces as a duplicate-label collision +-- (detected at replay) and is fixed with "pql ticket relabel". +CREATE TABLE IF NOT EXISTS ticket_idmap ( + record_id TEXT PRIMARY KEY REFERENCES tickets(record_id), + ticket_id TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER +); + +CREATE TABLE IF NOT EXISTS ticket_deps ( + blocker_record_id TEXT NOT NULL REFERENCES tickets(record_id), + blocked_record_id TEXT NOT NULL REFERENCES tickets(record_id), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER, + PRIMARY KEY (blocker_record_id, blocked_record_id) +); + +CREATE TABLE IF NOT EXISTS ticket_history ( + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), + field TEXT NOT NULL, + old_value TEXT, + new_value TEXT, + changed_by TEXT, + changed_at TEXT NOT NULL DEFAULT (datetime('now')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT UNIQUE, + canonical_version INTEGER +); + +CREATE TABLE IF NOT EXISTS ticket_labels ( + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), + label TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER, + PRIMARY KEY (ticket_record_id, label) +); + +CREATE TABLE IF NOT EXISTS meta ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status); +CREATE INDEX IF NOT EXISTS idx_tickets_team ON tickets(team); +CREATE INDEX IF NOT EXISTS idx_tickets_decision_ref ON tickets(decision_ref); +CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON tickets(assigned_to); +CREATE INDEX IF NOT EXISTS idx_tickets_parent ON tickets(parent_record_id); +CREATE INDEX IF NOT EXISTS idx_ticket_idmap_label ON ticket_idmap(ticket_id); +CREATE INDEX IF NOT EXISTS idx_decisions_domain ON decisions(domain); +CREATE INDEX IF NOT EXISTS idx_decisions_type ON decisions(type); +CREATE INDEX IF NOT EXISTS idx_decision_refs_target ON decision_refs(target_id); diff --git a/.pql/changelog/ticket_idmap/2026-05.sql b/.pql/changelog/ticket_idmap/2026-05.sql new file mode 100644 index 00000000..fb6e9788 --- /dev/null +++ b/.pql/changelog/ticket_idmap/2026-05.sql @@ -0,0 +1,173 @@ +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ZXPWX71ND4C699C', 'T-43', '2026-04-22 22:01:13', '2026-05-07 19:04:55', NULL, '069521bcf068ff6889cee5251511446c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SS2V5Z1M1XJGQX0M', 'T-86', '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, '07e51e80b6df55240c465d17a9430700', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7P6Q0DG4RG4CBHFJG', 'T-67', '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '0b64bd846488ccf4fce74a6e14629edb', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM460GRBMZMV8E5339M', 'T-77', '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '0cdd07e0992ab8eb76d6945ae1079855', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79D19C0ZTXAVPKCCG', 'T-12', '2026-04-22 11:47:03', '2026-05-07 19:04:55', NULL, '10055ea4ae6c926286eb81eb35b8ef8f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'T-63', '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '178d61b166cf7e8f17efc83cdf60c062', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7DV4ZS010XWZJ9A84', 'T-6', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '17e7bcab28adfbab47be2ad9aa4e4c56', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W194B2421P2SF83R', 'T-25', '2026-04-22 20:14:24', '2026-05-07 19:04:55', NULL, '1b888a483ec795737d7a1cf71fdbd699', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77477EN2R5872G95M', 'T-93', '2026-05-07 06:33:47', '2026-05-07 19:04:55', NULL, '1f5f4e360943d2a903cdd7c819bfeaa3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7D8AG5AY1CW58HCB4', 'T-58', '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '253f8fb1458c5394eea856c885f94fff', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4M63G18VAQFG6R3XW', 'T-28', '2026-04-22 20:14:52', '2026-05-07 19:04:55', NULL, '280276cb372707c0fcdbc7de5c68f568', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7CEP425DNV3W7RFPM', 'T-72', '2026-05-05 12:53:05', '2026-05-07 19:04:55', NULL, '2c7cd24dee2e7d21a31f476af233284d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XZA5399D2EHYCVFC', 'T-5', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '31b2ad13bb8bcc41e4d21825a232f086', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79CNBXJ2S3CFQR7VM', 'T-81', '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '32698f648acf611d10fc719f6be28bad', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM76XDA3SZMPWR8WQSC', 'T-62', '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '3439d016919d87e8b4860735a23de1a8', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GSR5WPC2T7CJM8XW', 'T-4', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '407e3921ca9e6d5d13e11b7f577ad591', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PMYC2E1X99HX4MDM', 'T-84', '2026-05-06 10:16:02', '2026-05-07 19:04:55', NULL, '40ce052d36bd0270aa01f9e25c1a944a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5A8DYZ3BCJN1DEY7C', 'T-71', '2026-05-05 12:53:01', '2026-05-07 19:04:55', NULL, '43cfd9109375b0fb6a8f176a0df764a5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48MDE8ZZ82VWNY994', 'T-27', '2026-04-22 20:14:45', '2026-05-07 19:04:55', NULL, '440474d801f1b9bd05e8fbb4254c3817', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'T-3', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '44d2e24e49ca8555a6b3327f7e954ede', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AVABFH067XGTTCVM', 'T-76', '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '4a3e92a591238f629d18f9b10087c7f6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WXV1RKPDK810C3FR', 'T-31', '2026-04-22 20:33:56', '2026-05-07 19:04:55', NULL, '4c423ea86e8173f7fa6edddea9adbadb', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62E3MNW8TBTKSR9GM', 'T-1', '2026-04-22 09:41:51', '2026-05-07 19:04:55', NULL, '55239329122e1f84e9aeac9cee14a21a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KH7VR0BMZV5SSTXC', 'T-37', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '55c80e1ce022b96aab6e97e31579c302', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42RQZVEWTF9CJSRF8', 'T-85', '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, '6133918ee124e5502286fa0de06b5bd9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7QREFG3SBG2QPYY7R', 'T-59', '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '64c389eab795606b9d8b5b65877a67bc', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JH7WV2RH5DGEA2AC', 'T-19', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '65ba2e99100be29287532b3390054401', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YG22EV7BFTX5RTPR', 'T-26', '2026-04-22 20:14:33', '2026-05-07 19:04:55', NULL, '685c38f471288372eab298a106a06d66', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6T6580D8ABDVTMNZW', 'T-8', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '6aa0a08f23546d11b37f80d54920c3be', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VVHNKNXPYD4XWRYW', 'T-30', '2026-04-22 20:33:53', '2026-05-07 19:04:55', NULL, '6d929d2dcc64310a43b21be1a8314258', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TQEYESK545T8F164', 'T-57', '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '6ff368c440149011af6cc3e2bdfe243f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79H4MPEWW2TWQ89D0', 'T-46', '2026-04-23 20:27:58', '2026-05-07 19:04:55', NULL, '71f030d09b588292801ae8853accd90c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70QNVQY1MQGP3A33G', 'T-78', '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '73ecd48d2d9f1cdf8e1bf6e88a8dc812', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM52JT96V5067SM0DMR', 'T-87', '2026-05-06 13:46:07', '2026-05-07 19:04:55', NULL, '740c1dcab17dbdc339750aa8e32a1281', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QKBQY7FPCNX6N28R', 'T-45', '2026-04-23 20:27:28', '2026-05-07 19:04:55', NULL, '7c312190b902337039b21f4743be8908', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5Q6AWSTKGT0P64TRR', 'T-39', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '825042c21d032181c1fe6d55a29a94f9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59BAJSJ1JESDTPNER', 'T-15', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, '836e6112c6e1dff23772619499fad245', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7TW8EJNJ2VP306A20', 'T-14', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, '8496d923db0e1fa76e7b787a273e974e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QHMB46NSHHPVQRF0', 'T-24', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '8a892f3717f8da2c93f67d3170af014f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AACHWPFWZDCJ3CPW', 'T-73', '2026-05-05 12:53:21', '2026-05-07 19:04:55', NULL, '8e532488915724b80881c7b2dc227de9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'T-18', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '900d00f28a29207a464017a0bd750f33', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM734YZ060Q63H40EYG', 'T-80', '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '90da3a2c5bd03b314d661362a3919506', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6JXWYYQ6MT6N8STYM', 'T-38', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '91c3ef42d94e16789915ee6366f7015d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM75XRV8AQG2HEQZEJ0', 'T-16', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, '98bf61f1693e8b8877c2cc03c9fb120d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67JSC5RKS6M9182KG', 'T-41', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '9ab8f6ffa933c87dd3153eaea452e902', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66FTCTWHH9AQTNFKR', 'T-55', '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'a7b7dc2af0426c8bcbfea6a3023a0aff', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7YHYTWJGWPFJNHEAR', 'T-10', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'a8fa7598a624ad83878db046ffa8986f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TS48DVFDVJNZ5WGC', 'T-11', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'af42494291be5910520becc10285e157', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R0WRHSJV1MVCW31M', 'T-9', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'b8d2fca6e4b3882e4cd1ec486924265b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4005W4QV0ZPBMQ02C', 'T-35', '2026-04-22 20:34:06', '2026-05-07 19:04:55', NULL, 'bbbe9cce785ce60416cba671cea2985a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50DVPFP4WY74RQBB8', 'T-61', '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, 'bbf53c20ed761296c7bd54d418b67218', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50X0DX8XTVZEA5GN8', 'T-60', '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'bc1502a8c8fe4e44c004f56e9e15b03e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6PMW57C70KBJVAV10', 'T-22', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'be0ae61f02897da33e8a9b2d6d1b002e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GHHRANK5532VJVJR', 'T-34', '2026-04-22 20:34:03', '2026-05-07 19:04:55', NULL, 'c042cabf5814a596e1582a8a85138543', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM606BMYDDGRSJBVPG8', 'T-70', '2026-05-05 12:52:56', '2026-05-07 19:04:55', NULL, 'c04d59ecdcc1ade801ed7f0866a3f00c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74S58G8XZV1766VD0', 'T-90', '2026-05-06 20:38:14', '2026-05-07 19:04:55', NULL, 'c36ec6ce6ac423fa9983758304139d2b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7CEKQCMZAV751402G', 'T-47', '2026-04-23 20:28:43', '2026-05-07 19:04:55', NULL, 'c5abd99aac954b783b84f3b77d003691', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5EP8B65T0AR52TQFW', 'T-92', '2026-05-07 05:51:48', '2026-05-07 19:04:55', NULL, 'c62020a1cf82eb7ed6bb5635d5f8f0fa', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QHBTA4VE6TWQT43R', 'T-13', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'd08af226653f0822b332b640dfcedde5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KX7HEQJ65HZ566DG', 'T-2', '2026-04-22 11:45:26', '2026-05-07 19:04:55', NULL, 'd1b1abb9046e230f581009daf61e7bfd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7Y0DFP66HSP073XHC', 'T-88', '2026-05-06 14:34:35', '2026-05-07 19:04:55', NULL, 'd1ea1e789ff487787076d0ca66670e35', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JZ2AKN74FKMK3MR8', 'T-69', '2026-05-05 12:52:52', '2026-05-07 19:04:55', NULL, 'd2622779cfbab03758ed02949f12fa72', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7MKCHC37G69SJGQ04', 'T-32', '2026-04-22 20:33:58', '2026-05-07 19:04:55', NULL, 'd4125c549e70d4f4d37bb43a4f3ccbd1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'T-17', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, 'd732affde22ee738f13bacb127c094e7', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5X2K66A0MQY2VZ7H4', 'T-83', '2026-05-06 09:17:39', '2026-05-07 19:04:55', NULL, 'da553a2c61adb1795c0ab33867dc6ba3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q4XW93E3WA1SD9KW', 'T-75', '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'db0b7c934e54d06fcf33aaf168a723e4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YBQP75NQQ4Y9DN9C', 'T-68', '2026-05-05 12:52:48', '2026-05-07 19:04:55', NULL, 'e14345ea70ff466ed3d9b8de161ea556', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6VMWWSP4BV8Q11W0M', 'T-20', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'ebf54efaf812fb838959a80b4cdcc527', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM699HAWC76SHRA7AM4', 'T-21', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'f217b0ca72240fbbe463d296f250f3b6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM64DC28XV20JJ8KW00', 'T-33', '2026-04-22 20:34:01', '2026-05-07 19:04:55', NULL, 'f7924a89ac74721c9145a511b7a1b4e9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM454GCK8ENRKRA44NW', 'T-49', '2026-04-23 20:31:35', '2026-05-07 19:04:55', NULL, 'f9fb75eb241d8edd62688d7ac3fdd25a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM57AEVK5E0GWVPGAY8', 'T-79', '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'fe4c71ca925995617dcd4409caaae9d0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RDM2EKZX132GPV7M', 'T-40', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, 'fe57a623da7c3fda11455f1d78ba7f4c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7162Z26FDWXG35VH0', 'T-7', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, 'ffa3d6a93f30550da97a3357a0b40c3c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48X0763Z9DZSM0HFR', 'T-94', '2026-05-07 19:08:29', '2026-05-07 19:08:52', NULL, '8087c73fd647b2156a0275ec7ec2ffcd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WCDTR41VN9P1EGMC', 'T-96', '2026-05-17 17:16:01', '2026-05-17 18:11:18', NULL, 'c44d6bedd77f53875b4fb7f92e8c9895', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F3V130BQXQH6HDVC', 'T-98', '2026-05-17 18:47:13', '2026-05-17 18:55:57', NULL, 'b2543702670de76b92f98bcb3690901e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7NRKR9J1QTDSP9650', 'T-101', '2026-05-17 18:47:29', '2026-05-17 19:01:39', NULL, '8acbd73697594b2f0686c7e1f01f0b8e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QNZM15V1ZK32YS6G', 'T-102', '2026-05-17 18:47:33', '2026-05-17 19:01:39', NULL, 'c77e3fe23ababa8854065860aaaf7645', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM493QAZNJTJQHYFGCC', 'T-103', '2026-05-17 18:47:39', '2026-05-17 19:18:43', NULL, '34fae6a3452e2f3a6fc76605fb82cd85', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6MP9JD93ZE0CDGFFW', 'T-106', '2026-05-17 18:47:51', '2026-05-17 19:18:43', NULL, '52a5c071f2c90428eba13aaa8f785831', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WW3008NZRJWJ88HG', 'T-113', '2026-05-17 18:48:24', '2026-05-17 19:18:43', NULL, 'fabd5c2006ae0e13714b1aa12ecd2e41', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6MWAKNB2NE3ZZ0XKW', 'T-110', '2026-05-17 18:48:09', '2026-05-17 19:39:12', NULL, '22d75188f931b18801e8e9015e9863a5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM60QRRNEEWG84VWKXC', 'T-66', '2026-04-24 06:34:16', '2026-05-17 19:39:37', NULL, '8b24910aba2e49638668b03f64006a5a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42M9RK4399B4F4WSG', 'T-64', '2026-04-24 06:34:16', '2026-05-17 19:39:37', NULL, '9d3e8f627b3095e1db58e047618be710', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65SSVGN5M36T4QNDC', 'T-117', '2026-05-17 19:28:28', '2026-05-17 19:39:40', NULL, '852007cdda3b405757c088e160798b40', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4CWANNSKNF2X5JEJ4', 'T-100', '2026-05-17 18:47:23', '2026-05-17 19:47:50', NULL, '3edd0e73ac89116392d4aab4e8180c6a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63H3CXCKB13E3RNHW', 'T-105', '2026-05-17 18:47:47', '2026-05-17 19:56:28', NULL, '0f673c39dc8c450947f34c86e743f8cc', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40PV7G53JMHHNYPYG', 'T-108', '2026-05-17 18:47:59', '2026-05-17 20:05:38', NULL, 'f023344206a267eefd5d2c453d3483c9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4ZNR15BSANVX07N1R', 'T-112', '2026-05-17 18:48:20', '2026-05-17 20:11:16', NULL, '874a9bbe234acb55dfc20d72a2c5a361', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W20PVMHX1BBJ4TVW', 'T-118', '2026-05-17 20:51:47', '2026-05-17 20:53:41', NULL, 'b8fbc53c7092e7b29a869f0247cd0e38', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7FG92QKNFJSCAP888', 'T-114', '2026-05-17 18:48:28', '2026-05-18 07:04:38', NULL, '2c16b22992624d00be14801c105350c9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7Y9KHNVRZ41VYW1YM', 'T-111', '2026-05-17 18:48:14', '2026-05-18 07:43:45', NULL, '1c12b7ea3d82ff2bc1e5680690ee0746', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5W67PPRVA8SAJXHFG', 'T-104', '2026-05-17 18:47:42', '2026-05-18 07:53:10', NULL, 'ec883c703bad46f757d38057c71336d3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TTAC53CQYWDQ91H0', 'T-109', '2026-05-17 18:48:03', '2026-05-18 08:07:23', NULL, 'ed187720e362af1ff3f8cae3fd0ac828', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45DS16DX0NKMN9Q3G', 'T-121', '2026-05-18 08:07:23', '2026-05-18 08:22:19', NULL, 'ec1cbea6674c6fb5d365510587c082ec', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM614M7ES54V3SETAQM', 'T-115', '2026-05-17 19:10:21', '2026-05-18 09:30:59', NULL, '640c8f8e5a67cdb44eaabd65bf21fc04', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4RJ9K19P5AX14RHRR', 'T-123', '2026-05-18 10:29:02', '2026-05-18 10:29:02', NULL, '255468e265f300a234e8456d3a0abf51', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7FXS713CKYDZ284NC', 'T-107', '2026-05-17 18:47:55', '2026-05-18 10:30:30', NULL, '4bc77250919cd4cef2fb1bfd3438a7c6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4H5C2Z6GCBCQ85QGR', 'T-116', '2026-05-17 19:14:31', '2026-05-18 11:22:46', NULL, '972bf9cfde6de9ff37cb5cb1dcc5a2dd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53N56Z60B4SQ5VTRG', 'T-74', '2026-05-05 12:53:22', '2026-05-18 11:51:40', NULL, '7b71c20018634be865ab843900b93eff', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', 'T-124', '2026-05-18 11:58:47', '2026-05-18 12:51:08', NULL, 'b272f7b62db06897d71bcee86b0aef6e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KY9JVRKF22GGFMXR', 'T-125', '2026-05-18 11:58:52', '2026-05-18 15:51:09', NULL, '801eba2049b8dc046ab81f731d0caef1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78E2NAM4YK3A2AV6R', 'T-126', '2026-05-18 11:58:57', '2026-05-18 16:04:29', NULL, '6bc6a6b1f009225521ace82a6616244c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7J40S0A8Q8PC0PDY8', 'T-127', '2026-05-18 11:59:02', '2026-05-19 10:03:26', NULL, '0e2e8e90772e11cfad16ee7805a58f7e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KEY6593FMW8Y40VC', 'T-128', '2026-05-18 11:59:06', '2026-05-19 10:06:46', NULL, '9cf26209ff598d91824d59f791b34938', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40XWGSN99DBDQDHJR', 'T-129', '2026-05-18 11:59:11', '2026-05-19 12:19:51', NULL, '618bff52db127ed5655c78c4e09f3078', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5MW2V3QCMWKMY4VQM', 'T-130', '2026-05-18 11:59:17', '2026-05-19 12:33:52', NULL, 'ab53ef037d31eb5a977cf6fe3280e126', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5ZSRZARG7SXCC71NM', 'T-131', '2026-05-18 11:59:23', '2026-05-19 13:14:41', NULL, '7339bc2840f028e45e798e8bd8c9c337', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM76Q97D8R01CRTTA9R', 'T-99', '2026-05-17 18:47:18', '2026-05-19 13:14:41', NULL, 'ff7b3f5bbb802d081c305d32b3330163', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54DENVAR46RXCCQQM', 'T-119', '2026-05-18 07:43:50', '2026-05-20 07:00:06', NULL, 'd2732a32ffbc29ddca7f9eacad2d5570', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47ARMYK2H24KJ0DAG', 'T-120', '2026-05-18 07:53:08', '2026-05-20 16:02:35', NULL, 'fdab02033af4c3f6df800ddf3bc78198', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM56CF9W94FSFDSBKGW', 'T-97', '2026-05-17 18:47:08', '2026-05-20 16:02:46', NULL, '84c961d85f80b8ba21fc227f7d191948', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TNXTJCQBH9VADC4M', 'T-89', '2026-05-06 20:36:42', '2026-05-22 13:37:46', NULL, '7197034866b3116490b8633fd18ff01f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4HGK6KR0314P2M32M', 'T-91', '2026-05-06 20:49:47', '2026-05-22 13:37:46', NULL, 'c1d096207b720c77ac05b1950dce1bcd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54N0ZDJRS7G6MP1XC', 'T-136', '2026-05-22 15:59:34', '2026-05-22 17:37:23', NULL, '24eae70edc48f10e227710ee77e94fb6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54G017Y29APWJTCN0', 'T-133', '2026-05-22 15:50:06', '2026-05-22 17:37:23', NULL, 'a4886b596be68f26bcf01315f2eba159', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SRMD12DMXCX6N7DM', 'T-135', '2026-05-22 15:59:34', '2026-05-22 17:37:23', NULL, 'e18b6b13a86b6202afb93b4bae32c824', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4EQAERS4J9X9RAETR', 'T-134', '2026-05-22 15:59:34', '2026-05-22 17:55:11', NULL, '99fdef2c143698cd8dee33e5903015f5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6E3X5WWRS09GCDGK8', 'T-137', '2026-05-22 15:59:34', '2026-05-22 18:14:32', NULL, '0ae6c36aab9db692afc7b9de9d83b70b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4V1PZ0EK6G60699EM', 'T-138', '2026-05-22 15:59:34', '2026-05-22 21:49:09', NULL, '006e8c34ed9058294d4144ed2fd853ba', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427ZFSTWJ21EBK5FG', 'T-142', '2026-05-22 22:06:59', '2026-05-22 22:18:02', NULL, '93352b90d5b8532dca21609a5e6dee2c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YWAC8SSGQBK31T98', 'T-143', '2026-05-22 22:31:22', '2026-05-22 22:36:09', NULL, 'd585cc1215d301735c9e207e08193e6e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BV0CXWCT04Z81EG0', 'T-144', '2026-05-22 22:40:42', '2026-05-22 22:43:45', NULL, 'b8e257b9abde9da0c38fc15cb7d4ee41', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM653J6604KYDK6RT8R', 'T-139', '2026-05-22 15:59:34', '2026-05-22 23:16:28', NULL, 'e43a27d4464ce09179016a65dc702a57', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AVSMKD15KBJYX48W', 'T-140', '2026-05-22 15:59:34', '2026-05-22 23:31:46', NULL, '2e962cad8956dff339a9c3028ff8c83a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM558PCHET90BBSHGTC', 'T-146', '2026-05-23 06:19:51', '2026-05-23 06:30:47', NULL, 'd575cf325d7b347a88ca11f516b1b715', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47FEEMHPE8J5VVBKM', 'T-147', '2026-05-23 07:03:18', '2026-05-23 07:06:54', NULL, '2b5e6b5f67000bea0097ea86986124b6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GDS1R8H2TW12WX7G', 'T-149', '2026-05-23 07:14:36', '2026-05-23 07:27:32', NULL, '1ffa4dc9299a04293bfa473036ed8e08', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KN50C7A723SEKZXG', 'T-145', '2026-05-23 06:07:59', '2026-05-23 08:43:19', NULL, 'e688de6397028cc53baef88f4af4db5c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6F9XA7F2BX9P18BR4', 'T-150', '2026-05-23 09:04:40', '2026-05-23 09:33:03', NULL, '6103463406709c57456e06226486a739', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77GWREHKA9K45P020', 'T-151', '2026-05-23 09:52:14', '2026-05-23 10:20:12', NULL, '1ee4e62532962562e7a57316eedf0d04', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7BJS5E1JJGSJ9XHG0', 'T-153', '2026-05-23 09:52:27', '2026-05-23 10:35:56', NULL, '872e6c5e27120a76b24f7c033286071e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YSQYCE096R2SFRXG', 'T-152', '2026-05-23 09:52:20', '2026-05-23 10:43:52', NULL, '9ba6f625fd14bb95a31902b699e821e6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5B47G9GG22N9KBNEG', 'T-156', '2026-05-23 11:15:08', '2026-05-23 12:05:19', NULL, '35180e547e94880bbe7c9f73b8b28e2c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XM8EY708NAM1JK00', 'T-154', '2026-05-23 09:52:32', '2026-05-23 14:50:17', NULL, '45a2e199fca3eb06e286f98d5c8c2228', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R9Q3T7CBMTHM2GFR', 'T-155', '2026-05-23 11:06:50', '2026-05-23 14:50:17', NULL, '578a38cb9982309cac40cd0a214d7839', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6CYT7NW4WE8ZP1TXR', 'T-148', '2026-05-23 07:03:18', '2026-05-23 15:05:29', NULL, 'edd29e766c59c65f2ca3c83700ed96f0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V9RNW2BGT5GHRTRG', 'T-141', '2026-05-22 15:59:34', '2026-05-23 20:49:44', NULL, 'c95d953ec43d43dde07d94192342c3a9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QZ3JJF1704AV5JCW', 'T-157', '2026-05-23 20:48:31', '2026-05-23 20:57:07', NULL, '96ab8af2d5b2835a37c23b1af6c25be1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KD2DKTGHC6CF1MJ4', 'T-159', '2026-05-24 08:53:13', '2026-05-24 08:54:30', NULL, '6d88f03a4c6268aefb7ab51411e6970f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SPRJZ519RZK1ZPAW', 'T-161', '2026-05-24 09:24:02', '2026-05-24 09:45:07', NULL, 'e5ad416566fa75f3caf1ccf085aa2506', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5932T486Z1F1M6X6W', 'T-173', '2026-05-24 16:45:40', '2026-05-24 17:10:07', NULL, '0d564a0dc8adc327e3f9460c254499fa', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AHSN3BXZFFWS0EPM', 'T-165', '2026-05-24 16:26:15', '2026-05-25 06:29:25', NULL, '879dcc70eadbc3d29a1e6f81ee0e86d4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7KA37YDBJ89GJ27H4', 'T-166', '2026-05-24 16:26:23', '2026-05-25 06:31:08', NULL, '6a6a0ba97d6331ac3441060ec28197db', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7K30Y9WAYZ3D330YR', 'T-175', '2026-05-25 07:11:09', '2026-05-25 07:25:02', NULL, '1d7a94fb62083c7c4390103f5bf29786', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RP0Q312AZJ8S1Y6R', 'T-177', '2026-05-25 07:11:22', '2026-05-25 07:25:02', NULL, '59a180929bd8894b53a761b2dda7b4f1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AXEWDKD4VQNQCACM', 'T-176', '2026-05-25 07:11:16', '2026-05-25 07:27:57', NULL, '231d303368c603a6e01a6da02802fbc4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VNVEAEYBX3S9Z8Q8', 'T-178', '2026-05-25 08:10:10', '2026-05-25 08:21:46', NULL, '3f7aae66c9e487bef74fd4d93b304d12', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4DTGYPAB29MRDN000', 'T-179', '2026-05-25 08:10:18', '2026-05-25 08:26:21', NULL, '826c15b06fe0aaaf660e31042fe371bd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48P8T23ZPR0BRK6PR', 'T-169', '2026-05-24 16:26:49', '2026-05-25 12:12:29', NULL, '99d40776974ced8e57efaa135df3ea9f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM654XV2TG4K0RVV1CW', 'T-170', '2026-05-24 16:27:01', '2026-05-25 19:38:16', NULL, 'd4a19feb40528dd6d22186c442c46890', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R8YY69YAARCVE4AC', 'T-160', '2026-05-24 09:13:42', '2026-05-30 11:05:56', NULL, '87a62aa99c4c3e3be11c031c5667ff68', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7G7MK8MP3BGQC36HG', 'T-167', '2026-05-24 16:26:30', '2026-05-30 11:05:56', NULL, 'a7343c529fc0c766f4d7c2a9530614fe', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78W48QX3JZP0QYAVW', 'T-168', '2026-05-24 16:26:39', '2026-05-30 11:44:28', NULL, '65bd7ae87632b0d65777a26af0bb31f2', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6FMAHXM1XAHHCQC60', 'T-171', '2026-05-24 16:27:09', '2026-05-30 21:11:26', NULL, '556df973d835841f7e8ae0c8cd9dd310', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZB63YYNFXM9DQNRR', 'T-174', '2026-05-24 17:11:17', '2026-05-30 21:11:26', NULL, 'db290b95db28db6b5a90d880d8a36fdb', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71N3YMS82BAYD3M9R', 'T-172', '2026-05-24 16:27:16', '2026-05-30 21:34:56', NULL, '8fca1700a4bf028e071cf30e845ee8f1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6V6TKRPA106D5SMMM', 'T-180', '2026-05-25 09:15:58', '2026-05-31 08:49:07', NULL, '412af567b67756da2e6b438c6a4bfd45', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WE0SD9N4HCP9BB64', 'T-183', '2026-05-25 09:26:40', '2026-05-31 09:30:11', NULL, 'f09d4e58d36e3049bbcf5d92df301530', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55DZN2RFEVP6CG48G', 'T-184', '2026-05-30 11:42:53', '2026-05-31 10:17:09', NULL, '7f2e958614fa540d1087d3f1c84568df', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM52PRNJM86A1DZTQ1C', 'T-185', '2026-05-30 21:34:26', '2026-05-31 10:22:02', NULL, 'd7d14700bc8fb0164e6e2852b7a358cd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5HM3N07VB3RPFJCFM', 'T-164', '2026-05-24 16:26:05', '2026-05-31 10:22:03', NULL, '8bc4ad2dbf63176d9674cd014c6cce7b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TDSA2PDCV814E3YW', 'T-95', '2026-05-08 10:48:59', '2026-05-31 11:20:21', NULL, '628386f31fcfb4e96eac7b503ab2eb4b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5316HVGDYD2W0DC50', 'T-162', '2026-05-24 09:29:07', '2026-05-31 14:17:22', NULL, '46d071266f94b404760832f3c2d88927', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM514YJBF8AY8Q4N7Q4', 'T-188', '2026-05-31 11:38:28', '2026-05-31 14:17:22', NULL, '7328143d32f30fc412c22b5d76de92a9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GVWHMRTMQYRTRKD4', 'T-187', '2026-05-31 11:38:20', '2026-05-31 14:17:22', NULL, 'ce42be5c4b71e4c155b26e22506045f4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4DAQ2CEFESG9JGMZ8', 'T-192', '2026-05-31 12:08:42', '2026-05-31 14:46:39', NULL, '6bd69db546e3d3aadb77b5c289db817a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6S7FDAXQX32JNCSFG', 'T-186', '2026-05-31 11:37:34', '2026-05-31 16:40:19', NULL, 'eed0473280a7c582289f0fcaa6bf6dd5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM709YPWECH1E66XXD4', 'T-193', '2026-05-31 16:12:42', '2026-05-31 17:26:25', NULL, 'ffbdde83a1cd6bbe9046f686705b2e15', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YYG4X5R1GX97TJB4', 'T-52', '2026-04-23 20:32:06', '2026-05-31 18:54:02', NULL, '1e34fa00e15b0ada663e117c7493ca69', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QPGVP54EKJPCNQQM', 'T-51', '2026-04-23 20:32:06', '2026-05-31 18:54:02', NULL, 'cc6cf69d394f87a29c685b27ec13155e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM452XPSG99PA60ZZ48', 'T-53', '2026-04-23 20:32:06', '2026-05-31 19:41:13', NULL, '22e98aa4226d3e9979abd7b7cdf7b68a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); diff --git a/.pql/changelog/ticket_idmap/2026-06.sql b/.pql/changelog/ticket_idmap/2026-06.sql new file mode 100644 index 00000000..cb99d805 --- /dev/null +++ b/.pql/changelog/ticket_idmap/2026-06.sql @@ -0,0 +1,131 @@ +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7MFNNCC0MMY2TCNRM', 'T-194', '2026-06-01 06:49:25', '2026-06-01 06:49:47', NULL, 'c4ed6eb84383fb6052a61f94e37c98c9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4M2Z9YZ0SA81E5Q08', 'T-195', '2026-06-01 06:56:05', '2026-06-01 07:08:16', NULL, '6d199187494f2f942598092f9743285f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6NTYAB8K6T50F8W00', 'T-196', '2026-06-01 08:58:10', '2026-06-01 10:45:14', NULL, '57fadff50bee964e4d821507d0e6e79b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6HQXNNF05GXWK7BWM', 'T-198', '2026-06-01 11:11:32', '2026-06-01 11:31:39', NULL, 'f203d6089bcb5f23d81e4c3cf67b3d33', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM68APBCQTMGJAZ2B9C', 'T-199', '2026-06-01 11:38:32', '2026-06-01 12:35:04', NULL, '6fda1ce6b9f1ea55f001f1840fef1f7a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4RAKSX59W1HJCZ1M8', 'T-200', '2026-06-01 14:04:00', '2026-06-01 14:04:24', NULL, 'b2c666f841d2546d0e6f150dec9e901b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM57GY6PTG78SD3961M', 'T-201', '2026-06-01 14:25:59', '2026-06-01 14:45:39', NULL, '289f9f811f27bad8b3f01acda2651b4b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7G8QBJ2PNQ0VREN24', 'T-197', '2026-06-01 08:58:19', '2026-06-01 16:36:38', NULL, 'a29f2ef4fa8bc86eceff40c324d613d0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5A3984KFPD4NKRG6M', 'T-202', '2026-06-01 16:48:31', '2026-06-01 16:48:56', NULL, '726f989a61da913e9f41f3353e453541', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM643F7Z648KNFPT7GR', 'T-203', '2026-06-01 16:48:31', '2026-06-01 16:48:56', NULL, '736af2dbfdf6cfbc8d2683c8e0feb063', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65R8CNFNE1MA5GGR0', 'T-204', '2026-06-01 18:48:19', '2026-06-01 18:51:08', NULL, '7685b5bd09a25bb7094a7458f292e81b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M83TTXHRXESFZY14', 'T-207', '2026-06-01 18:48:47', '2026-06-01 18:59:19', NULL, '0fd6c0e9537080a0b8ab85f351161542', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PH1J6ENYT4PJZRHG', 'T-205', '2026-06-01 18:48:27', '2026-06-01 19:12:01', NULL, 'e874c6bf6c97dae25404d6f1044f9c53', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5REFZXMY8ESWN0Z90', 'T-206', '2026-06-01 18:48:40', '2026-06-01 19:27:18', NULL, 'cdf5b34234372c3de0ef0c3caecabed2', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4C2PSN2GVJY9JDSZG', 'T-210', '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, '1281485b320f6c146f8af2519c0c02f3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48QJV4N7J62436GC0', 'T-213', '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, 'ac6cf78658e8a9ddfee7573e125377ed', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7171NRB1T82TGP5RG', 'T-211', '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, 'bf74e45672e065ef2aa21ce85c341bb0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54WTNQDMYT0AVR4WM', 'T-224', '2026-06-02 18:13:52', '2026-06-03 09:05:50', NULL, '16525f2ae7035719b319a6f579d5e316', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TP6KXTB1GHKBW1G4', 'T-215', '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, '4fcb599f4b489ac8050770416737295d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WYF8ESBSGAQF1S48', 'T-217', '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, '682a13f3d6e396600a5d51d38e9508d7', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM51CNEJP52P79RDVJW', 'T-216', '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, 'e89cf8d8a6c65f6c47ef73aa0f1a8d7d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78J2H2S0R79SSSPW8', 'T-214', '2026-06-02 18:13:52', '2026-06-03 09:18:38', NULL, '32ca757e5489e190af0dae4297b200d7', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XZQJAK31WRVQ0HGW', 'T-228', '2026-06-03 09:49:35', '2026-06-03 10:02:02', NULL, 'ce3d4e957ec8cf656f8f022e518bf8d0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SA9TEX5CNMTG67MW', 'T-163', '2026-05-24 09:42:44', '2026-06-03 10:05:55', NULL, 'abed11805979ce7f3d996679ad7007a4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5PFAYE4XH7WV6VDW8', 'T-227', '2026-06-03 09:48:23', '2026-06-03 10:09:26', NULL, '99368eee1c22ea3cf782b52159c04dc9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM68T660K07YH3X5DH0', 'T-229', '2026-06-03 09:51:28', '2026-06-03 10:09:26', NULL, '9d77e30754feb77e0a64a4aa5d47f668', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6Y5SET5WGSS2W78F8', 'T-219', '2026-06-02 18:13:52', '2026-06-03 10:31:10', NULL, 'fae2d0c2950c6f4050c70e7c3c07a525', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6EGSD0SB7NYNSP1BM', 'T-220', '2026-06-02 18:13:52', '2026-06-03 10:59:25', NULL, '3c812d20f5da80c0b936cb59edd2704f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ET3VQP90WRXVSMG', 'T-181', '2026-05-25 09:16:05', '2026-06-03 11:41:17', NULL, '045b971616418029b63a850050eef8a5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM758PXMJH16PA3JHFW', 'T-221', '2026-06-02 18:13:52', '2026-06-03 11:50:02', NULL, '9e906306ef56b2c22724d0b72f14ce2d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AMEYEBTF8YGJ4BVG', 'T-218', '2026-06-02 18:13:52', '2026-06-03 11:50:08', NULL, '9c2a9480a37df7681fe4388e27345573', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GJRWZVHZF24TPPS4', 'T-226', '2026-06-03 09:37:54', '2026-06-03 11:50:23', NULL, '6bf4765285f29d4c8fce8d71472bae0c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427BVZYN6MD7ZZX74', 'T-231', '2026-06-03 13:17:32', '2026-06-03 13:30:05', NULL, 'd6eb4f7458f0d1c72432b87a23aa60e3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BJCK5YG3R0NEEF70', 'T-232', '2026-06-03 13:29:49', '2026-06-03 13:41:42', NULL, 'f255d56c3872bf6a210734e127a4a4bd', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JR6QZDMKE167C4MR', 'T-82', '2026-05-06 08:30:15', '2026-06-03 14:56:07', NULL, 'd2669b112125f4f76041885296f5d315', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM492DTRZJK9CGQPGDC', 'T-234', '2026-06-03 13:49:44', '2026-06-03 15:04:07', NULL, '92a7cabc855b76f429217cc65fcfa308', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GH6VP8TZA95NMYK0', 'T-230', '2026-06-03 11:15:39', '2026-06-03 15:17:33', NULL, 'eec8da81c7180e8f301196514ba54e3b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM46ANXPW02XGD3D3NG', 'T-237', '2026-06-03 15:43:45', '2026-06-03 21:29:47', NULL, 'f9b1b279c9292d892ac07b7f4ea7cef8', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W624VD4JW1GHDHAR', 'T-182', '2026-05-25 09:26:31', '2026-06-04 12:00:09', NULL, '35ccb60111678fbc43644460a31f1d61', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM56FKPV4WSPWA0KMB4', 'T-189', '2026-05-31 11:38:36', '2026-06-04 12:00:09', NULL, '3759a2e27d13bddbcc19ed9a4fffef9a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V7GHH42RHV04DPTR', 'T-65', '2026-04-24 06:34:16', '2026-06-04 12:00:09', NULL, '544a078f04d5c926a47cbfdacb4e3e2f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BWXW61EVYGMZD47G', 'T-191', '2026-05-31 11:58:19', '2026-06-04 12:00:09', NULL, 'd186513cc0783306b2c4e1b2e789dff7', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5ABRPK2SPW8VY7RP4', 'T-190', '2026-05-31 11:38:42', '2026-06-04 12:00:09', NULL, 'fac1c8383ac95413e8885f8ee64b0d54', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM41S6RRTMTY9TYZA50', 'T-122', '2026-05-18 09:06:59', '2026-06-05 13:08:05', NULL, '600101f09173e61159a24ed44e34dd71', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66TK8TQ1X5T9GK6V0', 'T-42', '2026-04-22 21:03:40', '2026-06-05 15:17:09', NULL, '223a9820685b3da2a6a6e19cf6ad036b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WT3BHB8JZY77G5AW', 'T-243', '2026-06-05 15:14:25', '2026-06-05 15:17:09', NULL, 'd82988ff85cd644376a13f49e406138f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KPN4VK4EZP013NNG', 'T-244', '2026-06-05 15:21:10', '2026-06-05 15:24:35', NULL, 'f375c96bfd96b9cdd89db0cd046318f3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYY4FVPX9KRVHCMC', 'T-50', '2026-04-23 20:31:48', '2026-06-05 21:12:23', NULL, '581605b67cbab29fdcddbac46154308a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66DNEK5FS9C0T4J5R', 'T-245', '2026-06-05 21:20:00', '2026-06-05 21:24:52', NULL, '577902ab47810d0f848f48c8bf9b3196', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6DH7C6GQN9WNKQNXR', 'T-246', '2026-06-05 21:28:44', '2026-06-05 21:29:50', NULL, '4ce06c5b0b8a09d452934ae8ba1e5c0b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6QCYT7JBF352YVJ48', 'T-247', '2026-06-05 21:28:48', '2026-06-05 21:30:04', NULL, 'c2fbcdc7eda67938f8d35f64a92782b4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F4D2Q363QF7V40K8', 'T-239', '2026-06-03 21:41:58', '2026-06-06 06:44:40', NULL, '7312cd1c56d3c8d60625df7712a7deb4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F2GC0ABFFVAH34EC', 'T-238', '2026-06-03 21:11:40', '2026-06-06 07:24:00', NULL, 'a724281049b28271d21c87776313dd72', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZQNDVPAPFSD9KYS4', 'T-251', '2026-06-06 07:40:43', '2026-06-06 07:40:43', NULL, '9f010efd2f1b44840ff0af4d3723a4af', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BVE5EYM92CWGSXA4', 'T-250', '2026-06-06 07:26:50', '2026-06-06 07:49:08', NULL, 'a8c1c3f7d308c4fe005fb998b7ec6730', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7JXD3FKW91934D0W0', 'T-249', '2026-06-06 07:21:17', '2026-06-06 08:21:40', NULL, '9f43580f953cac1a27dab6e0ac2deece', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70TW38E78FFXDHYAC', 'T-248', '2026-06-06 07:21:17', '2026-06-06 08:31:25', NULL, '577fda356a95641dd39c28a716227d1a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NEXC0P46NJHACNM0', 'T-233', '2026-06-03 13:29:59', '2026-06-06 09:20:02', NULL, '4cea7def08ae19b77ecfdd04b11f4b5a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78Q3VYWSE830CTVB4', 'T-212', '2026-06-02 18:13:52', '2026-06-06 10:02:03', NULL, '75bc38afe04d24ff77c2b55d9b764fe1', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QYYKHPQ03H3QD61W', 'T-209', '2026-06-02 18:13:52', '2026-06-06 10:02:06', NULL, '4f782ec2f1fb76eea268247cf4cd75a0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4K2AAPK52Y8BEJ3YR', 'T-257', '2026-06-06 14:01:03', '2026-06-06 18:58:25', NULL, 'd79886881baf9d88ff31ec2970da2712', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', 'T-23', '2026-04-22 14:08:40', '2026-06-06 19:06:30', NULL, '9b456adde02b184d5d46652aac210af9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72ACQSDBJTKRBC58W', 'T-223', '2026-06-02 18:13:52', '2026-06-06 19:24:44', NULL, '7909a9620aa2a05d6a02b36f030f3641', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM620F5ENRYNT9S0JDW', 'T-222', '2026-06-02 18:13:52', '2026-06-06 19:24:44', NULL, 'b58b0643ed78d32df526e765a076b2eb', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5DYC7CRP8TMHADW8W', 'T-225', '2026-06-02 18:13:52', '2026-06-06 19:37:47', NULL, '6d2670282a9f66635c2fccaa20162209', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XMQ44AVCAARQZX14', 'T-208', '2026-06-02 18:13:52', '2026-06-06 19:53:07', NULL, '015f7fe6ec6e3b348a6157401742472b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6S7KHYPE9W7S1JHZ8', 'T-256', '2026-06-06 10:07:35', '2026-06-06 19:53:07', NULL, '2dbd4405f72911a87fd757c819b9f0c5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6WZ7DKVDFAGQ4QDMG', 'T-258', '2026-06-06 20:55:49', '2026-06-06 20:55:49', NULL, '4bcfdcf889c4bf6ca3405fa7b4857520', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7RQMN1GKEZ5JQKJT0', 'T-259', '2026-06-06 21:12:44', '2026-06-06 21:12:44', NULL, '9efbdb558a1358b67827a0814fa5c23e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JK0RHMRAH47K8ND4', 'T-36', '2026-04-22 20:34:09', '2026-06-06 21:14:22', NULL, 'bab3e0865388263d61db6651c3e5a55d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6DSPH569T4458B8JC', 'T-260', '2026-06-06 21:46:42', '2026-06-06 21:46:42', NULL, 'e45747cbef04e469b21ef0b4f2a3122f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7GJW4V3CXMMXXV988', 'T-261', '2026-06-07 08:03:10', '2026-06-07 08:03:10', NULL, '54c9a91b6b4408f05c85ab0afb7daf47', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7GCVGCH9RMGYHENCC', 'T-54', '2026-04-23 20:32:06', '2026-06-07 08:03:17', NULL, 'ed036c636484f9b724fe82dace896d49', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QW8VH454J8V3E174', 'T-252', '2026-06-06 08:28:43', '2026-06-07 08:32:36', NULL, 'cb647a08e949e13f6386fefbb99c8c6e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63WP564EZRERJHVCM', 'T-255', '2026-06-06 09:56:23', '2026-06-07 08:51:08', NULL, 'e2aa43fb1e6a1c9c28ebd1fe2ab6bd79', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74JAV0MJMB4VQNTRG', 'T-268', '2026-06-07 09:35:03', '2026-06-07 09:36:59', NULL, '796bd5eff55061d92fe00b3c4b15d3ad', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P9YQ5V7WS6RFJNHW', 'T-269', '2026-06-07 09:44:17', '2026-06-07 09:51:32', NULL, '6fc792db7c0d2ba8f8c73c1e1960faf6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XBRZJ6DEHFWREWC0', 'T-271', '2026-06-07 10:42:32', '2026-06-07 10:42:32', NULL, 'a7a8244eab996f86026f60edd5969de6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67PDXAFTPPW7B1WDR', 'T-272', '2026-06-07 10:42:38', '2026-06-07 10:42:38', NULL, 'd3fcdcc97810a9b5f6b0624a3507c952', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40JE9MZZWCXC91THG', 'T-270', '2026-06-07 10:23:13', '2026-06-07 10:43:18', NULL, '014c8442d8f19f6cc075ad8fc27ce9ed', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM551VZ2NYV6PD0XQRC', 'T-48', '2026-04-23 20:30:09', '2026-06-07 15:40:06', NULL, '8389d1a6a5d2db863bda0f7405d9496a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71P14PAF7K4QE61G0', 'T-262', '2026-06-07 08:10:42', '2026-06-07 17:41:11', NULL, '561c4e90d842e6ac1a22dcacd7a5e33f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5W6VN98RQM6S22X28', 'T-274', '2026-06-08 07:26:13', '2026-06-08 07:46:46', NULL, 'd9177bbb472fd16a13d4f23e0cd70344', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'T-276', '2026-06-08 07:46:23', '2026-06-08 07:53:32', NULL, '1b77c5f3a8d66247532ead2164b6b37f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM60PAS5RJ55CZGPM1G', 'T-277', '2026-06-08 08:32:30', '2026-06-08 08:34:47', NULL, '70e24eabce743397720db9d6029a3337', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XXSERDHKR0EEH3N4', 'T-263', '2026-06-07 08:34:02', '2026-06-08 08:45:23', NULL, '1cb588e500a902d2b880f3004c5c33b4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45HHRBCRMV2166Y6M', 'T-266', '2026-06-07 08:41:13', '2026-06-08 09:04:12', NULL, '9162b1edab8984a25660f0fdcebcd41d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYMMKFD5Z8WG9F60', 'T-264', '2026-06-07 08:40:25', '2026-06-08 09:25:50', NULL, '4b9c66ff7114ce85456f57f6d1c58d7b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q1Q97CHA0XNK2G4W', 'T-265', '2026-06-07 08:40:33', '2026-06-08 10:09:14', NULL, 'ff24fcb74089501588a4bfcfa62c9aad', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'T-267', '2026-06-07 08:42:53', '2026-06-08 10:09:21', NULL, 'd860955c7a7030d4c1c2ea98e5447fdf', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55CV3VDYS88VBJZ60', 'T-273', '2026-06-07 17:39:10', '2026-06-08 11:44:27', NULL, '02cd1eaf484bd3adef09a71a7408674e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59DG2WADJ9A0JFD1W', 'T-281', '2026-06-08 11:46:11', '2026-06-08 11:49:36', NULL, '1d9df18227de229f12bda189c4081353', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6K1QRC911JMQ9C960', 'T-283', '2026-06-08 13:39:51', '2026-06-08 13:44:57', NULL, '56929fab7dc064046853959720d43b8b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6A6A56VFSVRRBKMP0', 'T-279', '2026-06-08 10:21:20', '2026-06-08 14:12:46', NULL, '1023e2106149f0366bf3896635ec44c4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KFHAS1WZFGBJ6JN4', 'T-284', '2026-06-08 14:55:43', '2026-06-08 14:57:15', NULL, '75d5f69fd00e63d09e707ad1e8338853', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GZ55VD4SAZZ6XXP4', 'T-285', '2026-06-08 15:22:32', '2026-06-08 15:37:29', NULL, '994bf6438e27af887c2f3d247e76d576', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W2MWZ45YNK7TJGC4', 'T-282', '2026-06-08 13:39:15', '2026-06-08 15:42:01', NULL, '9d5f153703ebeae7f011f555f380ef6e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AAA9WW54JGP83HGW', 'T-275', '2026-06-08 07:28:34', '2026-06-08 16:52:13', NULL, '886bd74d7ec01145f8acb5c7d9397307', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6NNRR61E8P6GHXABG', 'T-289', '2026-06-08 20:16:22', '2026-06-08 20:16:22', NULL, '6c2022166a2139a665912c3fe6d4802e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4501EBPDFDH84RJ5G', 'T-288', '2026-06-08 17:52:51', '2026-06-09 06:19:01', NULL, '16b1c85fab4273ebe7c9cfb9b9800e3e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R93N5J6RNVTBRB28', 'T-286', '2026-06-08 16:30:27', '2026-06-09 06:19:36', NULL, '22c1b0f40e3e3344f0c624af618bdaff', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM44CKN5QQGFKP1KWZC', 'T-290', '2026-06-09 07:15:14', '2026-06-09 07:15:14', NULL, '53ca5591666fa7c01d90174bf28ecf4b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45BSF1B0R0JA1Z7FC', 'T-291', '2026-06-09 07:15:22', '2026-06-09 07:15:22', NULL, '79e9f769c9313f0bc63d5c8c8a1ee641', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5E41Q8TX8X55X4MZ0', 'T-29', '2026-04-22 20:17:26', '2026-06-09 10:46:41', NULL, '93ab2ce409ff22e37a756e41553c0a69', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50BPNDT50PM24ZVA4', 'T-278', '2026-06-08 09:20:00', '2026-06-09 15:03:23', NULL, '0c340b4aeb41ddaa858ae18888207b2e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BD8PXDRHBF001200', 'T-292', '2026-06-09 14:57:32', '2026-06-09 15:09:05', NULL, 'b907503e714ddc775257c90330c25d2f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5DM43Q9HX352STN3M', 'T-295', '2026-06-09 15:26:00', '2026-06-09 15:26:00', NULL, 'c93802bd37eb8dfe981c74cfde3f24c4', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42WW0SY3XEXKE8PK8', 'T-293', '2026-06-09 15:12:15', '2026-06-09 15:36:15', NULL, '55da76c0ebfc56f44cb4096a2a17b9c6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GN07FN8GAXZ20YAR', 'T-236', '2026-06-03 15:31:01', '2026-06-09 15:55:06', NULL, '18d3d9bc2bc268d007d27c4cc20fcea8', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'T-254', '2026-06-06 09:32:54', '2026-06-09 15:55:06', NULL, 'ae31cf118f94e93699eed5f0ac0eee7c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63TRGBFRWK9W2WA98', 'T-296', '2026-06-09 15:43:58', '2026-06-09 16:34:46', NULL, '405de86b123fc1aeccf6f444a36bbf36', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'T-294', '2026-06-09 15:18:22', '2026-06-09 16:34:46', NULL, 'baa483c978c2744aaf95035de3809048', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RXN6HW6XTEHKYQDG', 'T-297', '2026-06-09 16:01:09', '2026-06-09 16:34:46', NULL, 'f228757d5b5ed41b7cf90b6035e09dda', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7K1FN7KD8S5ZVX2VM', 'T-299', '2026-06-09 16:42:47', '2026-06-09 16:42:47', NULL, 'a2fdfc4298353c34568d42e71c2786e6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53HQD6YV754GKBA60', 'T-44', '2026-04-23 20:25:26', '2026-06-09 16:56:55', NULL, '75a46ae8c87aa5a9a27e513b4ea65e58', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62FKQQD0B9B80PFY4', 'T-158', '2026-05-23 20:48:38', '2026-06-09 17:16:34', NULL, '1282d8b5cb009f639f7c7a14e69ef2b3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7D5DMYF5X92Q55CBW', 'T-235', '2026-06-03 15:17:29', '2026-06-09 17:16:34', NULL, '1bccb4cf47ba2a1a72e4f65a214e2f78', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W0NPAEG3KWNFY9DR', 'T-132', '2026-05-22 15:45:15', '2026-06-09 17:23:25', NULL, 'cf1709ddff1d13a7b4adb0dc193b319b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F2TBJJV1F2KP7XR8', 'T-300', '2026-06-09 20:02:41', '2026-06-09 20:03:08', NULL, '6f1c204fc4f81403ba50e9e7ecff7241', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6048VEGFDNEBHFMM4', 'T-301', '2026-06-09 20:04:29', '2026-06-09 20:04:54', NULL, '89490f3e4d3e09714b70f0e35c726038', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AE2GEF6BXSGKRMGC', 'T-242', '2026-06-05 10:17:47', '2026-06-09 20:07:30', NULL, 'c570cc4f2808f31192d947affb3392a0', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42VPM50VKAWXBCAM8', 'T-253', '2026-06-06 09:32:53', '2026-06-09 20:17:04', NULL, '47b9d1a167b65553a14ede7a5a53f851', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7H8JWCP59WQVD0FW4', 'T-302', '2026-06-09 20:24:54', '2026-06-09 20:25:49', NULL, '8e04d93f6163a8f54965bc5988dae31c', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QJC039TMDF5JGMXC', 'T-240', '2026-06-05 09:38:46', '2026-06-09 20:27:17', NULL, '4879c042965effced12e608af5739659', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM531VE0C6N36AT12SR', 'T-241', '2026-06-05 10:02:31', '2026-06-09 20:34:30', NULL, 'b7de744d4e5292306b1fa64bcc3ba1aa', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4K1MTP2MHRKKNXBFM', 'T-303', '2026-06-09 20:41:22', '2026-06-09 20:41:22', NULL, '1adc6db2fcc64d6fd36ad0406939a8d7', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BRWWPB4R06Q31XV0', 'T-56', '2026-04-23 20:32:06', '2026-06-09 20:57:53', NULL, '5a394b49d98e119d21b637745798fd8b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SK3D1QYACBPHHBZ0', 'T-287', '2026-06-08 17:12:49', '2026-06-09 21:21:08', NULL, '7d9537b53ade941ecd8a8efadd591c17', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KS233FGZE9H7ABWR', 'T-304', '2026-06-09 21:22:28', '2026-06-09 21:22:44', NULL, '252c572a5954883d0e5e71406b092a7f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50S6WRD81V3Z0NH1M', 'T-298', '2026-06-09 16:06:41', '2026-06-09 21:36:05', NULL, 'f8666523eaf2642b918f6846e2801539', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YNEQM1WY25BV7TD8', 'T-280', '2026-06-08 11:29:22', '2026-06-09 21:42:59', NULL, 'e9859dd425e0ac667ddbce721415572e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); diff --git a/.pql/changelog/ticket_labels/0000-schema.sql b/.pql/changelog/ticket_labels/0000-schema.sql index 3a49ee0f..f13a0107 100644 --- a/.pql/changelog/ticket_labels/0000-schema.sql +++ b/.pql/changelog/ticket_labels/0000-schema.sql @@ -1,15 +1,8 @@ --- Auto-generated by pql init. CREATE TABLE statements +-- Auto-generated by migrate-ids (D-26). CREATE TABLE statements -- for the planning schema; per-table dir keeps the changelog --- self-describing per D-15. CREATE TABLE IF NOT EXISTS is --- idempotent so running schema files from each directory in --- replay order is harmless. --- --- Importer parses the markers below to detect schema drift --- between the producing pql version and the local one — a --- bumped canonical_version means projection rules changed --- and replay must refuse rather than silently corrupt state. --- pql:created_by: 1.4.26 --- pql:canonical_version: 1 +-- self-describing per D-15. +-- pql:created_by: migrate-ids +-- pql:canonical_version: 2 CREATE TABLE IF NOT EXISTS decisions ( @@ -43,14 +36,23 @@ CREATE TABLE IF NOT EXISTS decision_refs ( PRIMARY KEY (source_id, target_id, ref_type) ); +-- Identity split (D-26): a ticket's stable, collision-proof identity is its +-- record_id (a locally-generated ULID, planning.NewRecordID); the friendly +-- T-NNN label lives in ticket_idmap and may be reconciled. Every structural +-- reference (parent, deps, history, labels) targets record_id, so a label +-- clash never corrupts the graph — only ticket_idmap needs a relabel. CREATE TABLE IF NOT EXISTS tickets ( - id TEXT PRIMARY KEY, + record_id TEXT PRIMARY KEY, type TEXT NOT NULL CHECK(type IN ('initiative','epic','story','task','bug')), - parent_id TEXT REFERENCES tickets(id), + parent_record_id TEXT REFERENCES tickets(record_id), title TEXT NOT NULL, description TEXT, - status TEXT NOT NULL DEFAULT 'backlog' - CHECK(status IN ('backlog','ready','in_progress','review','done','cancelled')), + -- No CHECK enumeration: the ticket status vocabulary is per-vault + -- configurable (ticket_statuses in .pql/config.yaml). Validation lives + -- in Go (planning.StatusSet), so adding/renaming statuses needs no + -- schema change. The DEFAULT is a harmless fallback — CreateTicket + -- always inserts the configured default explicitly. + status TEXT NOT NULL DEFAULT 'backlog', priority TEXT DEFAULT 'medium' CHECK(priority IN ('critical','high','medium','low')), assigned_to TEXT, @@ -63,19 +65,33 @@ CREATE TABLE IF NOT EXISTS tickets ( canonical_version INTEGER ); +-- ticket_idmap maps a record_id to its current friendly label (T-NNN). +-- ticket_id is intentionally NOT globally unique: two uncoordinated clones +-- can mint the same label, which surfaces as a duplicate-label collision +-- (detected at replay) and is fixed with "pql ticket relabel". +CREATE TABLE IF NOT EXISTS ticket_idmap ( + record_id TEXT PRIMARY KEY REFERENCES tickets(record_id), + ticket_id TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER +); + CREATE TABLE IF NOT EXISTS ticket_deps ( - blocker_id TEXT NOT NULL REFERENCES tickets(id), - blocked_id TEXT NOT NULL REFERENCES tickets(id), + blocker_record_id TEXT NOT NULL REFERENCES tickets(record_id), + blocked_record_id TEXT NOT NULL REFERENCES tickets(record_id), created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), deleted_at TEXT, hash TEXT, canonical_version INTEGER, - PRIMARY KEY (blocker_id, blocked_id) + PRIMARY KEY (blocker_record_id, blocked_record_id) ); CREATE TABLE IF NOT EXISTS ticket_history ( - ticket_id TEXT NOT NULL REFERENCES tickets(id), + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), field TEXT NOT NULL, old_value TEXT, new_value TEXT, @@ -89,14 +105,14 @@ CREATE TABLE IF NOT EXISTS ticket_history ( ); CREATE TABLE IF NOT EXISTS ticket_labels ( - ticket_id TEXT NOT NULL REFERENCES tickets(id), + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), label TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), deleted_at TEXT, hash TEXT, canonical_version INTEGER, - PRIMARY KEY (ticket_id, label) + PRIMARY KEY (ticket_record_id, label) ); CREATE TABLE IF NOT EXISTS meta ( @@ -109,6 +125,8 @@ CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status); CREATE INDEX IF NOT EXISTS idx_tickets_team ON tickets(team); CREATE INDEX IF NOT EXISTS idx_tickets_decision_ref ON tickets(decision_ref); CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON tickets(assigned_to); +CREATE INDEX IF NOT EXISTS idx_tickets_parent ON tickets(parent_record_id); +CREATE INDEX IF NOT EXISTS idx_ticket_idmap_label ON ticket_idmap(ticket_id); CREATE INDEX IF NOT EXISTS idx_decisions_domain ON decisions(domain); CREATE INDEX IF NOT EXISTS idx_decisions_type ON decisions(type); CREATE INDEX IF NOT EXISTS idx_decision_refs_target ON decision_refs(target_id); diff --git a/.pql/changelog/tickets/0000-schema.sql b/.pql/changelog/tickets/0000-schema.sql index 3a49ee0f..f13a0107 100644 --- a/.pql/changelog/tickets/0000-schema.sql +++ b/.pql/changelog/tickets/0000-schema.sql @@ -1,15 +1,8 @@ --- Auto-generated by pql init. CREATE TABLE statements +-- Auto-generated by migrate-ids (D-26). CREATE TABLE statements -- for the planning schema; per-table dir keeps the changelog --- self-describing per D-15. CREATE TABLE IF NOT EXISTS is --- idempotent so running schema files from each directory in --- replay order is harmless. --- --- Importer parses the markers below to detect schema drift --- between the producing pql version and the local one — a --- bumped canonical_version means projection rules changed --- and replay must refuse rather than silently corrupt state. --- pql:created_by: 1.4.26 --- pql:canonical_version: 1 +-- self-describing per D-15. +-- pql:created_by: migrate-ids +-- pql:canonical_version: 2 CREATE TABLE IF NOT EXISTS decisions ( @@ -43,14 +36,23 @@ CREATE TABLE IF NOT EXISTS decision_refs ( PRIMARY KEY (source_id, target_id, ref_type) ); +-- Identity split (D-26): a ticket's stable, collision-proof identity is its +-- record_id (a locally-generated ULID, planning.NewRecordID); the friendly +-- T-NNN label lives in ticket_idmap and may be reconciled. Every structural +-- reference (parent, deps, history, labels) targets record_id, so a label +-- clash never corrupts the graph — only ticket_idmap needs a relabel. CREATE TABLE IF NOT EXISTS tickets ( - id TEXT PRIMARY KEY, + record_id TEXT PRIMARY KEY, type TEXT NOT NULL CHECK(type IN ('initiative','epic','story','task','bug')), - parent_id TEXT REFERENCES tickets(id), + parent_record_id TEXT REFERENCES tickets(record_id), title TEXT NOT NULL, description TEXT, - status TEXT NOT NULL DEFAULT 'backlog' - CHECK(status IN ('backlog','ready','in_progress','review','done','cancelled')), + -- No CHECK enumeration: the ticket status vocabulary is per-vault + -- configurable (ticket_statuses in .pql/config.yaml). Validation lives + -- in Go (planning.StatusSet), so adding/renaming statuses needs no + -- schema change. The DEFAULT is a harmless fallback — CreateTicket + -- always inserts the configured default explicitly. + status TEXT NOT NULL DEFAULT 'backlog', priority TEXT DEFAULT 'medium' CHECK(priority IN ('critical','high','medium','low')), assigned_to TEXT, @@ -63,19 +65,33 @@ CREATE TABLE IF NOT EXISTS tickets ( canonical_version INTEGER ); +-- ticket_idmap maps a record_id to its current friendly label (T-NNN). +-- ticket_id is intentionally NOT globally unique: two uncoordinated clones +-- can mint the same label, which surfaces as a duplicate-label collision +-- (detected at replay) and is fixed with "pql ticket relabel". +CREATE TABLE IF NOT EXISTS ticket_idmap ( + record_id TEXT PRIMARY KEY REFERENCES tickets(record_id), + ticket_id TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + deleted_at TEXT, + hash TEXT, + canonical_version INTEGER +); + CREATE TABLE IF NOT EXISTS ticket_deps ( - blocker_id TEXT NOT NULL REFERENCES tickets(id), - blocked_id TEXT NOT NULL REFERENCES tickets(id), + blocker_record_id TEXT NOT NULL REFERENCES tickets(record_id), + blocked_record_id TEXT NOT NULL REFERENCES tickets(record_id), created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), deleted_at TEXT, hash TEXT, canonical_version INTEGER, - PRIMARY KEY (blocker_id, blocked_id) + PRIMARY KEY (blocker_record_id, blocked_record_id) ); CREATE TABLE IF NOT EXISTS ticket_history ( - ticket_id TEXT NOT NULL REFERENCES tickets(id), + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), field TEXT NOT NULL, old_value TEXT, new_value TEXT, @@ -89,14 +105,14 @@ CREATE TABLE IF NOT EXISTS ticket_history ( ); CREATE TABLE IF NOT EXISTS ticket_labels ( - ticket_id TEXT NOT NULL REFERENCES tickets(id), + ticket_record_id TEXT NOT NULL REFERENCES tickets(record_id), label TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')), deleted_at TEXT, hash TEXT, canonical_version INTEGER, - PRIMARY KEY (ticket_id, label) + PRIMARY KEY (ticket_record_id, label) ); CREATE TABLE IF NOT EXISTS meta ( @@ -109,6 +125,8 @@ CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status); CREATE INDEX IF NOT EXISTS idx_tickets_team ON tickets(team); CREATE INDEX IF NOT EXISTS idx_tickets_decision_ref ON tickets(decision_ref); CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON tickets(assigned_to); +CREATE INDEX IF NOT EXISTS idx_tickets_parent ON tickets(parent_record_id); +CREATE INDEX IF NOT EXISTS idx_ticket_idmap_label ON ticket_idmap(ticket_id); CREATE INDEX IF NOT EXISTS idx_decisions_domain ON decisions(domain); CREATE INDEX IF NOT EXISTS idx_decisions_type ON decisions(type); CREATE INDEX IF NOT EXISTS idx_decision_refs_target ON decision_refs(target_id); diff --git a/.pql/changelog/tickets/2026-05.sql b/.pql/changelog/tickets/2026-05.sql index 9b3b13d4..0a0b00f0 100644 --- a/.pql/changelog/tickets/2026-05.sql +++ b/.pql/changelog/tickets/2026-05.sql @@ -1,66 +1,44 @@ -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-8', 'epic', NULL, 'Tier 6 — extension API, settings, theming, distributable builds', 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype. +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RDM2EKZX132GPV7M', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'Add PRs tab to sidebar', 'Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '089a32b8ded28139f19eaedafdd54d61', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GSR5WPC2T7CJM8XW', 'epic', NULL, 'Tier 2 — pane model, editor, active-file awareness, CLI', NULL, 'done', 'medium', NULL, NULL, 'D-6', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '0bd0e5fb1f1843eaada9520a7566cd65', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SS2V5Z1M1XJGQX0M', 'task', NULL, 'codify UI spacing constants (gaps, paddings, control sizes)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, '0bf3b9effbd9d99156f941f9e52d6d52', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7P6Q0DG4RG4CBHFJG', 'story', NULL, 'review Semantics widget coverage and UI testability', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '0c0e2af920b290c9375864a421b90b37', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TQEYESK545T8F164', 'task', NULL, 'drag tab reordering in sidebar and context panel', 'Reorder tabs in the sidebar and context panel icon rails by dragging. Persist order to project settings.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '11588b5a5ec97a4a9693f4221b1fc19e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5EP8B65T0AR52TQFW', 'bug', NULL, 'reflow tail-anchor reparenting can dangle on partially-filled lines', '**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`). -**Extension API (third-party Lua):** -- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc. -- Manifest schema, capability gating, sandboxed FS/IPC access. -- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15). -- Marketplace / distribution story is OUT OF SCOPE for Tier 6 — local-install only. +**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. -**Settings UI (`builtin.settings-ui`):** -- Schema-driven settings panel reading from the kernel SettingsStore. -- Render strategy: form fields keyed off the schema each subsystem registers. -- Edits write back to `.clide/settings.yaml`. +**Trigger paths in production code:** -**Theming UI (`builtin.theme-picker` extends):** -- Live preview of the four bundled themes (D-44). -- Custom theme: import YAML, validate against schema, register at runtime. -- Per-component override surface (long horizon). +1. `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. +2. `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. +3. `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. -**Distributable builds:** -- AppImage / Flatpak for Linux, .dmg for macOS — see T-46. -- Self-update mechanism — see T-47. -- License manifest auto-regen as part of the release build. - -Big epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.', 'backlog', 'medium', NULL, NULL, 'D-15', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '00e5aea23a2e04b462064ed9c88ef76e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'bug', NULL, 'files.read path traversal — validate paths stay under workspace root', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '015fefd2472662b86a8603cdca0da3d3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-51', 'story', NULL, 'quick file open (Ctrl+P)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '0a00222925ff90860ea1648f031f7bad', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'story', 'T-3', 'Ship clide tmux.conf for Claude pane sessions', '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.', 'done', 'high', NULL, NULL, 'D-41', '2026-04-22 22:01:13', '2026-05-07 19:04:55', NULL, '0b2b332c0ed4be810999d48c4fa37573', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '11873ec6f6561870ee3c1a0bcbb3d590', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'epic', NULL, 'Tier 2 — pane model, editor, active-file awareness, CLI', NULL, 'done', 'medium', NULL, NULL, 'D-6', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '118a505a19b839ee9822e867ecd654d7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'story', NULL, 'search and replace across files', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '12ef305ad5acc0c02c41fff7d5e4c818', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'task', 'T-5', 'Decide diff view placement', '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.', 'backlog', 'low', NULL, NULL, 'D-48', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '136aeb8ae1566003e0419890b0893fd4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'task', NULL, 'MultitabPane: drag-to-reorder gesture wiring', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 10:16:02', '2026-05-07 19:04:55', NULL, '14debaa8d5242c1a3254c5ed3246afec', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'story', NULL, 'Claude pane: fullscreen mode + mouse-wheel scroll', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:52', '2026-05-07 19:04:55', NULL, '167ffa2ee6408bb98c5b5cb1432e701b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'task', 'T-2', 'translate four design themes into YAML theme files', NULL, 'done', 'medium', NULL, NULL, 'D-44', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '1c0e735f3f72a139497a7d7b5919c383', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'story', NULL, 'Implement focus mode (full-window takeover)', 'Focus mode via double-click header, hover ⛶ glyph, or ⌘. shortcut. Snapshot LayoutArrangement on enter, restore on Esc. Show Esc hint in header.', 'done', 'medium', NULL, NULL, 'D-52', '2026-04-22 20:33:56', '2026-05-07 19:04:55', NULL, '22c320a9a59a0df6755044757ccdd8a9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'story', NULL, 'deep link protocol handler', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '233c670c41fa11428b0db7ff0edd30f2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'task', 'T-6', 'Contribute tickets & decisions tabs to sidebar', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '2a522feed49043a8b0dbb26813bfbfb1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'task', NULL, 'manual refresh button in sidebar panels', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '2a9439475d053cadb7ad8a3e2a9444b8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'task', 'T-6', 'Contribute graph view tab to context panel', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '34a1728538a029a2069ac746cc036aea', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-58', 'task', NULL, 'activity badges on sidebar icons', 'Show unread count dots on sidebar icons: git changes count, new problems, pending notifications. Badges clear when the tab is viewed.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '36d455053e53369d01620f2a7b1e7887', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'task', 'T-6', 'pql query panel — sidebar tab for vault queries', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, '3a0659a695230a21a9a1733c4f998876', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-25', 'task', 'T-8', 'CI reproducible build for libtree-sitter.so', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:24', '2026-05-07 19:04:55', NULL, '3c11cce594486000fff058265e24e35b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-62', 'story', NULL, 'auto-refresh decisions and tickets on file change and scheduler tick', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '400c80914c237a1d825d429dcd787031', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-59', 'task', NULL, 'progress indicators in status bar', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '40aab381f7084b8b1964f21124098f76', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'story', NULL, 'find in files (Ctrl+Shift+F)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '410fd83b1f53de9b1fb86c896b259bdb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'story', NULL, 'Add icon rail to right panel (context section switcher)', 'Right panel currently has no bottom icon rail. Add one matching the left sidebar rail pattern: viewer, pql graph, links, images as section tabs.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 20:34:03', '2026-05-07 19:04:55', NULL, '4326c5c6219cc7a969029ecf0d6cd204', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-44', 'story', NULL, 'image paste in Claude panel prompt bar', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:25:26', '2026-05-07 19:04:55', NULL, '4aa18b64c88cf6f8ac6f10f4cc9e3d05', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-26', 'task', 'T-8', 'web-tree-sitter via JS interop for browser builds', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:33', '2026-05-07 19:04:55', NULL, '4b8d7c04a645b7db110cfba6c2c8ceb6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-61', 'task', NULL, 'kernel scheduler service with tiered timer events', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '4e3a722c923b8efd5551e2a133d8449a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'bug', NULL, 'Bold text breaks terminal cell grid (workaround: suppress bold)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:56', '2026-05-07 19:04:55', NULL, '51caaff8d16702e485a9b5ebce2572f2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'task', NULL, 'consolidate theme-ui into ui-design skill with theme/surface/geometry/icons references', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, '5bcfd76bee81ba55f7aa56e30005c726', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'task', 'T-8', 'add dart doc generation to CI', '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. +**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. **Acceptance:** -- New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact. -- Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run. -- 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). +- New unit test in `test/terminal/core/core_test.dart` (or a sibling) that: + - Constructs a partially-filled source line and an anchor past `trimmedLength`, + - Runs reflow shrink, + - Asserts the anchor''s owning line is *in the reflow output list*, not dangling. +- 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. +- No regression in the existing reflow tests (the tail-clamp cases, wide-char boundaries, multi-line wrapped runs). -**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).', 'done', 'medium', NULL, NULL, 'D-32', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '5c4b5ba5efbb8efe69500c90f4f70e06', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'story', NULL, 'toast notification system', '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).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:48', '2026-05-07 19:04:55', NULL, '5d52c0db2bfa8068bb4e33c9eec0d8bf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'task', NULL, 'audit error handling in PTY and IPC critical paths', NULL, 'done', 'medium', NULL, NULL, 'D-5', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '653fb101b707eb4e8f7000beebbc3063', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'task', NULL, 'pane.spawn / editor.open: map errno to actionable error kinds', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '68fb1a7e2bd0b056be4068dd51d44272', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'bug', NULL, 'TerminalView.onTapUp callback is wired to a dead path', '**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). +**Investigation notes (do not skip):** + +The fix is conceptually small — the reparented tail anchor must land on a line that gets emitted. Two candidate shapes: + +- **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. +- **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). + +Don''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. + +**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. + +**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. +', 'done', 'high', NULL, NULL, NULL, '2026-05-07 05:51:48', '2026-05-07 19:04:55', NULL, '12904b2eafc7660410f6bd0036381d22', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM76XDA3SZMPWR8WQSC', 'story', NULL, 'auto-refresh decisions and tickets on file change and scheduler tick', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '1c44eb72be315595ad2ea9afd494a5ba', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5X2K66A0MQY2VZ7H4', 'task', NULL, 'design reusable sortable tab system for multitab panes', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-06 09:17:39', '2026-05-07 19:04:55', NULL, '1eda66b1427c5679656bf15c2d7e7533', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77477EN2R5872G95M', 'bug', NULL, 'TerminalView.onTapUp callback is wired to a dead path', '**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). **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: @@ -96,131 +74,34 @@ Recommendation: **option B**. The "remove unused public API" pattern matches the **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. **Cross-references:** T-91 (epic parent), T-89 (coverage epic). -', 'done', 'high', NULL, NULL, NULL, '2026-05-07 06:33:47', '2026-05-07 19:04:55', NULL, '69884fb794e98e0715d3f24edbd64887', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'task', 'T-89', 'test sweep: drive lib/src/terminal/ to 95%', 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. - -**Today''s distribution within `lib/src/terminal/src/`:** - -| sub-area | hit / total | % | shape | -|---|---|---|---| -| `core/buffer/` | 0 / 581 | 0.0% | pure data structures | -| `core/escape/` | 1 / 473 | 0.2% | pure parser logic | -| `core/input/` | 4 / 292 | 1.4% | key + keytab tables | -| `core/(root)` | 0 / 188 | 0.0% | callback wiring | -| `core/mouse/` | 3 / 51 | 5.9% | encoder logic | -| `ui/` | 3 / 823 | 0.4% | painters + render objects | -| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` | -| `utils/` | 1 / 251 | 0.4% | circular buffer etc. | - -**Strategy — pure logic first, then UI:** - -1. **`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. -2. **`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. -3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode. -4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast. -5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests. -6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests. -7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive. +', 'done', 'high', NULL, NULL, NULL, '2026-05-07 06:33:47', '2026-05-07 19:04:55', NULL, '2064576379e694666661b7b1a82e995e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NS39QCDHNVVVT1R4', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'add dart doc generation to CI', '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. **Acceptance:** -- `lib/src/terminal/` line coverage ≥ 95%. -- All tests fast (<5 s for the area''s test files combined; no real PTY shell-out). -- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files. -- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage. -- `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). +- New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact. +- Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run. +- 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). -**Strategy on commit cadence:** -Land tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch: -- Adds tests under `test/terminal//`. -- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows). -- Documents in CHANGELOG under "Changed" (test-only changes can also fold into one CHANGELOG entry per area). - -**Out of scope:** -- 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. -- Branch coverage (D-66 explicitly excludes). -- Mouse / keyboard *protocol* round-trips against real TUIs (that''s T-74 and integration work). - -**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing). -', 'in_progress', 'high', NULL, NULL, 'D-66', '2026-05-06 20:49:47', '2026-05-07 19:04:55', NULL, '6a514875e980a44213772eb6a953f203', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-64', 'story', NULL, 'VS Code keybinding preset', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '6c6e7a154bfdae6ebe04cb424db75800', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'task', NULL, 'IPC server: per-request timeout, broadcast logging, stale-socket race', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '6e66b94e7359af2ea5f596b5962b1915', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'task', NULL, 'forward real mouse events to TUI apps (not just PgUp/PgDown)', '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. +**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).', 'done', 'medium', NULL, NULL, 'D-32', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '2128b4a4ae28fcacd71b072868fc2d14', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7Y0DFP66HSP073XHC', 'task', NULL, 'track dugite-native upstream releases for security updates', '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. **Acceptance:** -- 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`). -- Tested with `vim` (mouse mode `a`), `htop`, `less` — all should respond to mouse selection / click correctly. -- Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode. -- The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed. +- A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases. +- When a new release lands with a CVE label or fixes a CVE-tagged git release, schedule a bump; otherwise track the version drift. +- 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/`. -**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.', 'ready', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:22', '2026-05-07 19:04:55', NULL, '701409c8c37bf401eb6da20f622932eb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'task', NULL, 'tmux: isolated -L clide socket so user''s tmux config doesn''t bleed into clide', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:05', '2026-05-07 19:04:55', NULL, '719072755adc0f7f8d004168db345625', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'task', 'T-89', 'install ratcheting coverage gate at current floor', '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. +**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). -**What lands:** - -1. **`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). -2. **`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. -3. **`Makefile` `coverage-gate` target** — wraps `ci/coverage_gate.sh` for local use. -4. **`make push-check`** — adds `coverage-gate` to its dependency list. Pre-push hook (already wired via `.githooks/pre-push`) picks this up automatically. -5. **`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. - -**Acceptance:** -- `make coverage-gate` passes on a clean checkout (floor matches reality). -- `make push-check` includes the gate. -- A test deletion would fail `make coverage-gate` (verify by experiment, not just by reading the script — invariant has to actually hold). -- Pre-push hook runs the gate (verify by attempting a deliberately-failing push and seeing it blocked). -- `coverage/lcov.info` is gitignored (it''s regenerated on every run; only `floor.txt` is committed). - -**Out of scope:** -- Bumping the floor toward 95% — that''s the test-writing children''s job. -- Branch coverage — D-66 explicitly excludes. -- Per-area floors — D-66 chose a single global floor for simplicity. - -**Notes:** -- `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. -- `coverage/lcov.info` is the standard Dart output path of `flutter test --coverage`. Don''t relocate. -- `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). -', 'done', 'high', NULL, NULL, 'D-66', '2026-05-06 20:38:14', '2026-05-07 19:04:55', NULL, '720ba8c20ad1fb2489c4939d852b881e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'bug', NULL, 'reflow tail-anchor reparenting can dangle on partially-filled lines', '**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`). - -**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. - -**Trigger paths in production code:** - -1. `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. -2. `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. -3. `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. - -**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. - -**Acceptance:** -- New unit test in `test/terminal/core/core_test.dart` (or a sibling) that: - - Constructs a partially-filled source line and an anchor past `trimmedLength`, - - Runs reflow shrink, - - Asserts the anchor''s owning line is *in the reflow output list*, not dangling. -- 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. -- No regression in the existing reflow tests (the tail-clamp cases, wide-char boundaries, multi-line wrapped runs). - -**Investigation notes (do not skip):** - -The fix is conceptually small — the reparented tail anchor must land on a line that gets emitted. Two candidate shapes: - -- **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. -- **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). - -Don''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. - -**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. - -**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. -', 'done', 'high', NULL, NULL, NULL, '2026-05-07 05:51:48', '2026-05-07 19:04:55', NULL, '7486c03cb3b93e80803cfb7c51e0a88d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'story', NULL, 'Persist layout state across sessions', '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.', 'done', 'medium', NULL, NULL, 'D-53', '2026-04-22 20:33:58', '2026-05-07 19:04:55', NULL, '75a7ac4f4be58e14a66533335e06fcc1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'task', 'T-2', 'bundled dependency licensing', NULL, 'done', 'medium', NULL, NULL, 'D-42', '2026-04-22 11:47:03', '2026-05-07 19:04:55', NULL, '78348abd9a8bfae520680f5c7467fc6e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'story', NULL, 'Wire canonical keyboard shortcuts from interaction model', '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.', 'done', 'high', NULL, NULL, 'D-54', '2026-04-22 20:34:01', '2026-05-07 19:04:55', NULL, '798149aec3fac37a5ccbd36f9991f084', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-45', 'task', 'T-3', 'detect and install tmux on first launch', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:27:28', '2026-05-07 19:04:55', NULL, '7ab5fdb758b60883702d3bd7463a11d6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'epic', NULL, 'Tier 1 — Claude in xterm pane, PTY, session persistence', NULL, 'done', 'medium', NULL, NULL, 'D-41', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '845578273a6b9c5fde53103d5e385762', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'story', 'T-4', 'editor respects .editorconfig settings', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:17:26', '2026-05-07 19:04:55', NULL, '85f07a3097d9b8fd2d00b0a4884ce188', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-80', 'task', NULL, 'Standardize errno constants + logger in PTY/IPC/daemon', '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: +**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.', 'backlog', 'low', NULL, NULL, 'D-59', '2026-05-06 14:34:35', '2026-05-07 19:04:55', NULL, '2a6bbef9dbb21a40a1dfd90e882f0e62', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4005W4QV0ZPBMQ02C', 'story', '06FB0TNQM5GSR5WPC2T7CJM8XW', 'Implement editor-above-Claude mode in middle column', '⌘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.', 'done', 'medium', NULL, NULL, 'D-49', '2026-04-22 20:34:06', '2026-05-07 19:04:55', NULL, '2fbe8bb6f449c6442b8972a0d2b11dd8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM454GCK8ENRKRA44NW', 'story', NULL, 'native folder picker for Open Workspace', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:35', '2026-05-07 19:04:55', NULL, '30493e1d88497d9e9fd0df5f97c10e19', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7CEKQCMZAV751402G', 'story', '06FB0TNQM79H4MPEWW2TWQ89D0', 'clide self-update mechanism', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:28:43', '2026-05-07 19:04:55', NULL, '31a55dc12bc4f0ee447dc2ab853613c1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50X0DX8XTVZEA5GN8', 'story', NULL, 'workspace trust prompt', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '32fd3d312ab5d4fa379911ea408825b0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P00AT6KGVZ1EF60C', 'task', NULL, 'audit error handling in PTY and IPC critical paths', NULL, 'done', 'medium', NULL, NULL, 'D-5', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '388873359f62ba188715066ed30d88c1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427FX3GTR3VNNKM40', 'task', NULL, 'manual refresh button in sidebar panels', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '3cdf2629c314204365d9ad6a56bf9546', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66FTCTWHH9AQTNFKR', 'story', NULL, 'system tray and dock persistence', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '3e40de19315f626560fb598a06e86cea', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79H4MPEWW2TWQ89D0', 'epic', NULL, 'cross-platform clide installer', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:27:58', '2026-05-07 19:04:55', NULL, '424f000fff97ccd5e2c8a4eb260d7b3e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM734YZ060Q63H40EYG', 'task', NULL, 'Standardize errno constants + logger in PTY/IPC/daemon', '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: **Errno constants (audit item #23):** - 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`. @@ -233,172 +114,11 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - Pick one logger interface (kernel `log` already exists for the app side), wire `DaemonServer` and the daemon-side handlers to use it. - Dispatch error messages should prefix with the request `cmd` so log correlation works (audit item #26). -**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '897daf838313309b8fc9eca392dfb84a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'epic', NULL, 'Tier 3 — git panel, diff tab, clide git CLI', NULL, 'done', 'medium', NULL, NULL, 'D-13', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '897fc0ebb13ecf6d9145ecda008c2592', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-49', 'story', NULL, 'native folder picker for Open Workspace', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:35', '2026-05-07 19:04:55', NULL, '8bf79aec0ae026d06357cce81b037049', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-41', 'task', 'T-7', 'Add images tab to context panel', 'Spec lists images as a right-panel section. No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '8c1c09d8da0527e1b0da424f5928c9a9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-81', 'task', NULL, 'Misc PTY/IPC/git polish (audit medium-priority items)', '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: - -- **#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`. -- **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed. -- **#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. -- **#21** — `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd. -- **#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. -- **#27** — `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure. -- **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade. -- **#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. - -Land each as a small focused commit; ticket closes when all items above are merged.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '8e0895430b2a96168a8a5d14799691ca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'epic', NULL, 'drive line coverage from 35% to 95%', 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: - -| area | hit / total | % | -|---|---|---| -| `lib/src/terminal/` | 12 / 3167 | **0.4%** | -| `lib/widgets/` | 537 / 1184 | 45.4% | -| `lib/src/pql/` | 45 / 94 | 47.9% | -| `lib/builtin/` | 269 / 550 | 48.9% | -| `lib/kernel/` | 889 / 1691 | 52.6% | -| `lib/src/daemon/` | 320 / 584 | 54.8% | -| `lib/src/pty/` | 130 / 216 | 60.2% | -| `lib/src/git/` | 356 / 518 | 68.7% | -| `lib/src/ipc/` | 110 / 156 | 70.5% | -| `lib/src/files/` | 98 / 133 | 73.7% | -| `lib/extension/` | 41 / 54 | 75.9% | -| `lib/src/editor/` | 107 / 129 | 82.9% | -| `lib/src/panes/` | 70 / 73 | 95.9% | - -The 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. - -**End state:** -- Total line coverage ≥ 95% (no per-area carve-outs). -- Pre-push gate hard-fails on coverage drops via a committed floor (`coverage/floor.txt`); floor ratchets up only. -- Per D-66 the gate lands at the *current* floor in the first child ticket; subsequent floor bumps come with the test-writing children. - -**Acceptance criteria for closing this epic:** -1. `coverage/floor.txt` reads `95` (or higher). -2. `flutter test --coverage --exclude-tags forkpty` produces ≥95% line coverage. -3. The pre-push gate computes coverage and rejects pushes below the floor. -4. 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. - -**Strategy / child shape:** -The 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. - -1. **Gate plumbing** (first child) — install the ratchet, lock in current floor, expose `make coverage-gate` and wire it into `make push-check`. -2. **`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.). -3. **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. -4. **Final ratchet** — once total ≥ 95%, set floor to 95 and close the epic. - -**Out of scope:** -- Branch coverage (lcov data is weak for branch on Dart; line coverage is the contract). -- Integration / E2E coverage (only widget + unit feed lcov). -- `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. - -**Cross-references:** D-66 (this epic''s decision record), D-29 (pre-push gate scope), "Own the rendering stack" guardrail (CLAUDE.md). -', 'in_progress', 'high', NULL, NULL, 'D-66', '2026-05-06 20:36:42', '2026-05-07 19:04:55', NULL, '94b4adb9293dfd29683a1cba9e3f6bd6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-88', 'task', NULL, 'track dugite-native upstream releases for security updates', '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. - -**Acceptance:** -- A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases. -- When a new release lands with a CVE label or fixes a CVE-tagged git release, schedule a bump; otherwise track the version drift. -- 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/`. - -**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). - -**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.', 'backlog', 'low', NULL, NULL, 'D-59', '2026-05-06 14:34:35', '2026-05-07 19:04:55', NULL, '9686bc9bcfd644ed795ee167810b4789', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'task', 'T-6', 'pql-backed file tree — replace or augment files.ls with pql files', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'a00962410fab04b0acf17e9cab03f84d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'task', NULL, 'ship matching bold weight to restore semantic bold rendering', '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - ---- -Refinement (2026-05-06): - -Investigation 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. - -**Concrete fix shape:** -1. `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/`. -2. `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). -3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195. -4. 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). - -Workaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.', 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:21', '2026-05-07 19:04:55', NULL, 'a19a81d6e069c736964a569246ea8802', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'task', 'T-6', 'backlinks panel — show inbound links to active file', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'a2b1484710d59baed015b7b1e5e790e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'task', NULL, 'design reusable sortable tab system for multitab panes', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-06 09:17:39', '2026-05-07 19:04:55', NULL, 'aa621bca79bf4dbf06e3032bf0f752f4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'task', NULL, 'inline terminal emulator (drop xterm.dart dependency)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:48', '2026-05-07 19:04:55', NULL, 'ab0d6cfc81e69f0f6a255567246b56dd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-55', 'story', NULL, 'system tray and dock persistence', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'ac775be9989d8a5a54966ea72be3504a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'story', 'T-7', 'Context auto-behavior: right panel reacts to Claude references', '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.', 'backlog', 'low', NULL, NULL, 'D-50', '2026-04-22 20:34:09', '2026-05-07 19:04:55', NULL, 'ade02d3e74f5b5d7433feea49fb018ba', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'task', 'T-4', 'implement welcome screen per hi-fi design', NULL, 'done', 'medium', NULL, NULL, 'D-43', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'af5be385d304e5a84fc566402c8df713', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'task', 'T-4', 'wire command palette keybinding', '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. - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Keybinding goes in `lib/kernel/src/commands/keybindings.dart` per the D-54 keymap. -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b099a8a9c20530741b6f030efa1c83c2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'epic', NULL, 'Tier 4 — pql integration: query panel, file tree, backlinks, problems', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, 'b1b64ec1fe2f96d3ea907d03240ffa3b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'task', 'T-4', 'multi-buffer editor tabs', NULL, 'cancelled', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b61b328a29d4ecb1eef10a64c4286af1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'task', 'T-2', 'Python stopgap tooling', NULL, 'done', 'medium', NULL, NULL, 'D-40', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'b7bc906e04533e2e5ef45e34900c70a2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-47', 'story', 'T-46', 'clide self-update mechanism', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:28:43', '2026-05-07 19:04:55', NULL, 'b8270be27465f4eea880f37fcd17fed4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'task', 'T-2', 'expand SurfaceTokens with syntax highlighting roles', NULL, 'done', 'medium', NULL, NULL, 'D-45', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b82f0ed1b3391151b572028a8b40ae5d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'task', NULL, 'add Catppuccin theme to bundled themes', 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). - -**Acceptance:** -- 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). -- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45). -- WCAG-AA contrast gate (D-22) passes against the theme. -- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution. -- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu. - -**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. - -**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.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-06 08:30:15', '2026-05-07 19:04:55', NULL, 'bae6235c5ee09a042846f05abdc235f4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'task', 'T-6', 'problems panel — surface pql doctor + validate warnings', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'bb7d23a0553f331193f1de93af607587', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'bug', NULL, 'kill secondary Claude tmux sessions on tab close and on app shutdown', '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. - -Neither happens today. - -**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`). - -**Gap 2 — app shutdown leaks secondaries.** Nothing reaps `clide-claude--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. - -**Acceptance:** -- Closing a secondary tab in the Claude pane runs `tmux -L clide kill-session -t ` for that secondary''s session name (use existing `secondarySessionName(repoRoot, n)`). -- App shutdown enumerates `clide-claude--*` sessions on the clide socket and kills the non-primary ones. Primary is left detached (D-41 explicit). -- `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone. -- Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner). - -**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.', 'done', 'high', NULL, NULL, 'D-41', '2026-05-06 13:46:07', '2026-05-07 19:04:55', NULL, 'bce38e567d3858511e04ee37d9615380', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-60', 'story', NULL, 'workspace trust prompt', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'bcf89d2601b747cdaa02526c6390ab30', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'task', NULL, 'codify UI spacing constants (gaps, paddings, control sizes)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, 'bedebecf0079b4fcdfc50fc7094b6c82', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'task', 'T-6', 'Contribute markdown viewer tab to context panel', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, 'c712dbddc0d5e728b2283185ab9bbd44', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'task', NULL, 'PTY read buffer: 4KB → 64KB', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:01', '2026-05-07 19:04:55', NULL, 'c998968e1550f82d34e79a02a0057142', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-46', 'epic', NULL, 'cross-platform clide installer', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:27:58', '2026-05-07 19:04:55', NULL, 'd0cb29fbd147583b985897608f2e7f25', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'task', 'T-6', 'validate pql init installation cycle', NULL, 'done', 'medium', NULL, NULL, 'D-39', '2026-04-22 09:41:51', '2026-05-07 19:04:55', NULL, 'd7115d59dd796b37cdec5eefd8e0912b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'task', 'T-2', 'Q&D record system + ADR migration', NULL, 'done', 'medium', NULL, NULL, 'D-34', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'dac1a56fceb74f39d369da55bfd4e541', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'task', NULL, 'PTY: surface errno from forkpty/execve/write/ioctl failures', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'db1b61acdce93092de83a33f25a31bf8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'epic', NULL, 'Tier 0 — app + daemon handshake, empty IDE shell', NULL, 'done', 'medium', NULL, NULL, 'D-7', '2026-04-22 11:45:26', '2026-05-07 19:04:55', NULL, 'dcf6e157b37cde1402c8737ad4fb2a3f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'story', NULL, 'Implement 12px collapse spine for side panels', '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.', 'done', 'high', NULL, NULL, 'D-51', '2026-04-22 20:33:53', '2026-05-07 19:04:55', NULL, 'ddc451b330dad49f5130c463e457e81a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'task', NULL, 'PTY: fix resource leaks and reader-isolate races', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'e1cf1f93207c098bed93bf74801588eb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'story', NULL, 'output and log panel', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'e58c97f0fb119d7cb26717880d97ff37', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-67', 'story', NULL, 'review Semantics widget coverage and UI testability', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, 'e75388d59ceab20d924268f43198caf3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'task', 'T-8', 'feed POLICY.md rules into D-records', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:52', '2026-05-07 19:04:55', NULL, 'ea0b7a2e6843c7c9fc9af1e184d0edf2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-27', 'task', 'T-8', 'migrate shipped extensions from builtin/ to extensions/ per D-46', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:45', '2026-05-07 19:04:55', NULL, 'ea9ec2549a02d75b00431366f4baab2b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-40', 'task', 'T-8', 'Add PRs tab to sidebar', 'Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, 'ec754ddf900ec54777482abf019806ca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'story', NULL, 'application menu bar (Zed-style)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:30:09', '2026-05-07 19:04:55', NULL, 'ef1017848746cf1ea807d166ed982807', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-7', 'epic', NULL, 'Tier 5 — canvas and graph view', '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. +**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '4c0e40a586fa780532aff53460a6a8dc', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7DV4ZS010XWZJ9A84', 'epic', NULL, 'Tier 4 — pql integration: query panel, file tree, backlinks, problems', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '4c409f6313eca2db64e10bc13dc36a0a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62E3MNW8TBTKSR9GM', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'validate pql init installation cycle', NULL, 'done', 'medium', NULL, NULL, 'D-39', '2026-04-22 09:41:51', '2026-05-07 19:04:55', NULL, '4e2209accbd6cdb2732da96ba06212eb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM460GRBMZMV8E5339M', 'task', NULL, 'IPC server: per-request timeout, broadcast logging, stale-socket race', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '4ebb203c91dfc0cc58c23879a077b409', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7162Z26FDWXG35VH0', 'epic', NULL, 'Tier 5 — canvas and graph view', '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. **Canvas pane (`builtin.canvas`):** - Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. @@ -412,65 +132,32 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - Filter pane: tag include/exclude, file glob, depth-from-active. - pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. -Both 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).', 'backlog', 'medium', NULL, NULL, 'D-17', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, 'ef64435bb8bd3ef2d7ce6d12f9d60e5d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-57', 'task', NULL, 'drag tab reordering in sidebar and context panel', 'Reorder tabs in the sidebar and context panel icon rails by dragging. Persist order to project settings.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'f237fb997fdac8c68d56e0e3be0a595d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'task', 'T-3', 'secondary Claude pane UI wiring', NULL, 'done', 'medium', NULL, NULL, 'D-41', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'f72d103fdc4bc62f33bcfb68d2db97c1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-66', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, 'f74e891c8f32e9e51bb663797b6d6b34', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'story', 'T-4', 'Implement editor-above-Claude mode in middle column', '⌘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.', 'done', 'medium', NULL, NULL, 'D-49', '2026-04-22 20:34:06', '2026-05-07 19:04:55', NULL, 'f8f3da430866059531d7ee59b064639c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'task', 'T-6', 'pql skill install + stopgap removal', NULL, 'done', 'medium', NULL, NULL, 'D-39', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'ff252aebaf78340e33fa0d6d6874b5c0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-94', 'task', NULL, 'wire pql plan merge as git custom merge driver', NULL, 'cancelled', 'medium', NULL, NULL, NULL, '2026-05-07 19:08:29', '2026-05-07 19:08:52', NULL, '88cca86b7b710a161f51ad0578c48afe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-95', 'bug', NULL, 'TerminalGestureHandler tertiary-tap wired to secondary; middle-button mis-typed', '**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). +Both 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).', 'backlog', 'medium', NULL, NULL, 'D-17', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '500ef5c50eda06e7a656cd91f8b5aebc', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50DVPFP4WY74RQBB8', 'task', NULL, 'kernel scheduler service with tiered timer events', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '52ddb1654c819675daad6f00013b97a2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QHBTA4VE6TWQT43R', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'pql query panel — sidebar tab for vault queries', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, '54f291ecabdb2450d339156fbeb74346', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XZA5399D2EHYCVFC', 'epic', NULL, 'Tier 3 — git panel, diff tab, clide git CLI', NULL, 'done', 'medium', NULL, NULL, 'D-13', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '593a5ab99da6ded40b6fc5b6a0563546', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM64DC28XV20JJ8KW00', 'story', NULL, 'Wire canonical keyboard shortcuts from interaction model', '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.', 'done', 'high', NULL, NULL, 'D-54', '2026-04-22 20:34:01', '2026-05-07 19:04:55', NULL, '5b7704d640d42c839175a961c51d6ac8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79CNBXJ2S3CFQR7VM', 'task', NULL, 'Misc PTY/IPC/git polish (audit medium-priority items)', '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: -**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. +- **#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`. +- **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed. +- **#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. +- **#21** — `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd. +- **#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. +- **#27** — `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure. +- **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade. +- **#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. -Trace: - -1. `TerminalGestureHandler.build` (gesture_handler.dart:65–82) wires: - ```dart - onTertiaryTapDown: onSecondaryTapDown, - onTertiaryTapUp: onSecondaryTapUp, - ``` - 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**. -2. The state''s `onTertiaryTapDown` and `onTertiaryTapUp` methods (lines 147–152) are therefore **dead** — they''re declared but never invoked anywhere. -3. `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. -4. 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`. - -**Two layers of "dead" again:** - -- The wiring is wrong, so middle-clicks dispatch as if they were right-clicks. -- 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). - -**User-visible impact:** - -A 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. - -**Resolutions, like T-93:** - -- **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. -- **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. - -Recommendation: **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. - -**Aside:** there''s a related dead branch in `onDragStart` (line 176): -```dart -details.kind == PointerDeviceKind.mouse - ? renderTerminal.selectCharacters(details.localPosition) - : renderTerminal.selectWord(details.localPosition); -``` -The `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. - -**Acceptance:** - -- Either: - - **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. - - **B:** `onTertiaryTapDown` / `onTertiaryTapUp` removed from `TerminalGestureHandler`, `TerminalGestureDetector`, and the state''s tertiary methods. Existing tests still pass. -- The `selectWord` branch in `onDragStart` is either reachable (extend PanGestureRecognizer.supportedDevices) or removed. - -**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. - -**Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic). -', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-08 10:48:59', '2026-05-08 11:01:01', NULL, '91cf3f81c256df191ddfcb3ebc6cefb1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-8', 'epic', NULL, 'Tier 6 — extension API, settings, theming, distributable builds', 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype. +Land each as a small focused commit; ticket closes when all items above are merged.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '5c2549784fb9173868ab84a7812e928e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ZXPWX71ND4C699C', 'story', '06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'Ship clide tmux.conf for Claude pane sessions', '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.', 'done', 'high', NULL, NULL, 'D-41', '2026-04-22 22:01:13', '2026-05-07 19:04:55', NULL, '60497a8d7f7ca4c5dba68187ddc8c594', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QHMB46NSHHPVQRF0', 'task', '06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'secondary Claude pane UI wiring', NULL, 'done', 'medium', NULL, NULL, 'D-41', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '60707a19e9176a9e4b62ce0672bd5f68', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q4XW93E3WA1SD9KW', 'task', NULL, 'PTY: surface errno from forkpty/execve/write/ioctl failures', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '6a8edaab6a388961f032fd4151e7be70', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W194B2421P2SF83R', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'CI reproducible build for libtree-sitter.so', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:24', '2026-05-07 19:04:55', NULL, '6dd3b1f754ee2b4b8177d51384917f61', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6JXWYYQ6MT6N8STYM', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'Contribute markdown viewer tab to context panel', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '6e1744b2365f3fdfac29a67d59491e5e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70QNVQY1MQGP3A33G', 'bug', NULL, 'files.read path traversal — validate paths stay under workspace root', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '7035741737fc86fd87dd406858d53185', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM75XRV8AQG2HEQZEJ0', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'problems panel — surface pql doctor + validate warnings', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, '77cb623b768e6276dafff9eabac364fb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4M63G18VAQFG6R3XW', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'feed POLICY.md rules into D-records', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:52', '2026-05-07 19:04:55', NULL, '7d51899d6c3b30effc9596c8cb26367b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6T6580D8ABDVTMNZW', 'epic', NULL, 'Tier 6 — extension API, settings, theming, distributable builds', 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype. **Extension API (third-party Lua):** - The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc. @@ -493,140 +180,58 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - Self-update mechanism — see T-47. - License manifest auto-regen as part of the release build. -Big epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.', 'backlog', 'medium', NULL, NULL, 'D-15', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '00e5aea23a2e04b462064ed9c88ef76e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'bug', NULL, 'files.read path traversal — validate paths stay under workspace root', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '015fefd2472662b86a8603cdca0da3d3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-51', 'story', NULL, 'quick file open (Ctrl+P)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '0a00222925ff90860ea1648f031f7bad', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'story', 'T-3', 'Ship clide tmux.conf for Claude pane sessions', '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.', 'done', 'high', NULL, NULL, 'D-41', '2026-04-22 22:01:13', '2026-05-07 19:04:55', NULL, '0b2b332c0ed4be810999d48c4fa37573', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '11873ec6f6561870ee3c1a0bcbb3d590', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'epic', NULL, 'Tier 2 — pane model, editor, active-file awareness, CLI', NULL, 'done', 'medium', NULL, NULL, 'D-6', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '118a505a19b839ee9822e867ecd654d7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'story', NULL, 'search and replace across files', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '12ef305ad5acc0c02c41fff7d5e4c818', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'task', 'T-5', 'Decide diff view placement', '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.', 'backlog', 'low', NULL, NULL, 'D-48', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '136aeb8ae1566003e0419890b0893fd4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'task', NULL, 'MultitabPane: drag-to-reorder gesture wiring', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 10:16:02', '2026-05-07 19:04:55', NULL, '14debaa8d5242c1a3254c5ed3246afec', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'story', NULL, 'Claude pane: fullscreen mode + mouse-wheel scroll', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:52', '2026-05-07 19:04:55', NULL, '167ffa2ee6408bb98c5b5cb1432e701b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'task', 'T-2', 'translate four design themes into YAML theme files', NULL, 'done', 'medium', NULL, NULL, 'D-44', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '1c0e735f3f72a139497a7d7b5919c383', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'story', NULL, 'Implement focus mode (full-window takeover)', 'Focus mode via double-click header, hover ⛶ glyph, or ⌘. shortcut. Snapshot LayoutArrangement on enter, restore on Esc. Show Esc hint in header.', 'done', 'medium', NULL, NULL, 'D-52', '2026-04-22 20:33:56', '2026-05-07 19:04:55', NULL, '22c320a9a59a0df6755044757ccdd8a9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'story', NULL, 'deep link protocol handler', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '233c670c41fa11428b0db7ff0edd30f2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'task', 'T-6', 'Contribute tickets & decisions tabs to sidebar', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '2a522feed49043a8b0dbb26813bfbfb1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'task', NULL, 'manual refresh button in sidebar panels', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '2a9439475d053cadb7ad8a3e2a9444b8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'task', 'T-6', 'Contribute graph view tab to context panel', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '34a1728538a029a2069ac746cc036aea', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-58', 'task', NULL, 'activity badges on sidebar icons', 'Show unread count dots on sidebar icons: git changes count, new problems, pending notifications. Badges clear when the tab is viewed.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '36d455053e53369d01620f2a7b1e7887', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'task', 'T-6', 'pql query panel — sidebar tab for vault queries', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, '3a0659a695230a21a9a1733c4f998876', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-25', 'task', 'T-8', 'CI reproducible build for libtree-sitter.so', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:24', '2026-05-07 19:04:55', NULL, '3c11cce594486000fff058265e24e35b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-62', 'story', NULL, 'auto-refresh decisions and tickets on file change and scheduler tick', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '400c80914c237a1d825d429dcd787031', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-59', 'task', NULL, 'progress indicators in status bar', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '40aab381f7084b8b1964f21124098f76', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'story', NULL, 'find in files (Ctrl+Shift+F)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '410fd83b1f53de9b1fb86c896b259bdb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'story', NULL, 'Add icon rail to right panel (context section switcher)', 'Right panel currently has no bottom icon rail. Add one matching the left sidebar rail pattern: viewer, pql graph, links, images as section tabs.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 20:34:03', '2026-05-07 19:04:55', NULL, '4326c5c6219cc7a969029ecf0d6cd204', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-44', 'story', NULL, 'image paste in Claude panel prompt bar', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:25:26', '2026-05-07 19:04:55', NULL, '4aa18b64c88cf6f8ac6f10f4cc9e3d05', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-26', 'task', 'T-8', 'web-tree-sitter via JS interop for browser builds', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:33', '2026-05-07 19:04:55', NULL, '4b8d7c04a645b7db110cfba6c2c8ceb6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-61', 'task', NULL, 'kernel scheduler service with tiered timer events', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '4e3a722c923b8efd5551e2a133d8449a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'bug', NULL, 'Bold text breaks terminal cell grid (workaround: suppress bold)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:56', '2026-05-07 19:04:55', NULL, '51caaff8d16702e485a9b5ebce2572f2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'task', NULL, 'consolidate theme-ui into ui-design skill with theme/surface/geometry/icons references', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, '5bcfd76bee81ba55f7aa56e30005c726', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'task', 'T-8', 'add dart doc generation to CI', '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. +Big epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.', 'backlog', 'medium', NULL, NULL, 'D-15', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '7f30f7e08fb60ff98d8d92a4fba6a60d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM699HAWC76SHRA7AM4', 'task', '06FB0TNQM5GSR5WPC2T7CJM8XW', 'implement welcome screen per hi-fi design', NULL, 'done', 'medium', NULL, NULL, 'D-43', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '84f40649f98dd8e3e944fac492c81561', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6PMW57C70KBJVAV10', 'task', '06FB0TNQM5GSR5WPC2T7CJM8XW', 'multi-buffer editor tabs', NULL, 'cancelled', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '8649e78bfc5229e2409dca4ff3c25732', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM52JT96V5067SM0DMR', 'bug', NULL, 'kill secondary Claude tmux sessions on tab close and on app shutdown', '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. + +Neither happens today. + +**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`). + +**Gap 2 — app shutdown leaks secondaries.** Nothing reaps `clide-claude--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. **Acceptance:** -- New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact. -- Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run. -- 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). +- Closing a secondary tab in the Claude pane runs `tmux -L clide kill-session -t ` for that secondary''s session name (use existing `secondarySessionName(repoRoot, n)`). +- App shutdown enumerates `clide-claude--*` sessions on the clide socket and kills the non-primary ones. Primary is left detached (D-41 explicit). +- `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone. +- Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner). -**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).', 'done', 'medium', NULL, NULL, 'D-32', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '5c4b5ba5efbb8efe69500c90f4f70e06', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'story', NULL, 'toast notification system', '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).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:48', '2026-05-07 19:04:55', NULL, '5d52c0db2bfa8068bb4e33c9eec0d8bf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'task', NULL, 'audit error handling in PTY and IPC critical paths', NULL, 'done', 'medium', NULL, NULL, 'D-5', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '653fb101b707eb4e8f7000beebbc3063', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'task', NULL, 'pane.spawn / editor.open: map errno to actionable error kinds', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '68fb1a7e2bd0b056be4068dd51d44272', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'bug', NULL, 'TerminalView.onTapUp callback is wired to a dead path', '**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). - -**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: - -1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304). -2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67). -3. `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. - -The `_onTapUp` method on `TerminalViewState` itself is also dead — it''s only referenced from the dead-field path above. - -**Two layers of "dead" here:** - -- The **wiring** is dead — nothing inside the terminal renderer calls into `widget.onTapUp` after the field is set. -- 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. - -So 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.) - -**Two clean resolutions:** - -- **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: - - `onTapUp` fires on every tap up (raw); `onSingleTapUp` fires only on confirmed singles. - - Collapse the two into a single hook and document the contract. -- **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. - -Recommendation: **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. +**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.', 'done', 'high', NULL, NULL, 'D-41', '2026-05-06 13:46:07', '2026-05-07 19:04:55', NULL, '897bb28855ae3b87703db5e7fb65dafc', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AVABFH067XGTTCVM', 'task', NULL, 'PTY: fix resource leaks and reader-isolate races', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '8c5f7ff42a9c056c3e742c2b6f53f476', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JZ2AKN74FKMK3MR8', 'story', NULL, 'Claude pane: fullscreen mode + mouse-wheel scroll', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:52', '2026-05-07 19:04:55', NULL, '92e8ece6ef3789c757cf58d960ba64c0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KH7VR0BMZV5SSTXC', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'Contribute tickets & decisions tabs to sidebar', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '9496693b45b87c78298677bf6c85864e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM606BMYDDGRSJBVPG8', 'bug', NULL, 'Bold text breaks terminal cell grid (workaround: suppress bold)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:56', '2026-05-07 19:04:55', NULL, '96d977e955a0642e2abcbfa920811658', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JH7WV2RH5DGEA2AC', 'task', '06FB0TNQM6KX7HEQJ65HZ566DG', 'translate four design themes into YAML theme files', NULL, 'done', 'medium', NULL, NULL, 'D-44', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '9716a4756a628296229d13d1afa96a49', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM79D19C0ZTXAVPKCCG', 'task', '06FB0TNQM6KX7HEQJ65HZ566DG', 'bundled dependency licensing', NULL, 'done', 'medium', NULL, NULL, 'D-42', '2026-04-22 11:47:03', '2026-05-07 19:04:55', NULL, '98a155f2841f4c6916840cec22a94dd0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5Q6AWSTKGT0P64TRR', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'Contribute graph view tab to context panel', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '99c21c45c30875b7928ddd6638665357', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WXV1RKPDK810C3FR', 'story', NULL, 'Implement focus mode (full-window takeover)', 'Focus mode via double-click header, hover ⛶ glyph, or ⌘. shortcut. Snapshot LayoutArrangement on enter, restore on Esc. Show Esc hint in header.', 'done', 'medium', NULL, NULL, 'D-52', '2026-04-22 20:33:56', '2026-05-07 19:04:55', NULL, '9bf21bda23ddae9eead766c10656f8e9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AACHWPFWZDCJ3CPW', 'task', NULL, 'ship matching bold weight to restore semantic bold rendering', '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. **Acceptance:** +- 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). +- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. +- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). -- Either: - - **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. - - **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. -- No regression in secondary-tap behaviour (those parameters are wired correctly via `TapGestureRecognizer.onSecondary*`). +**Options to evaluate:** +- 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.) +- Switch to a variable JetBrainsMono and request a specific weight axis position. +- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). -**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. +--- +Refinement (2026-05-06): -**Cross-references:** T-91 (epic parent), T-89 (coverage epic). -', 'done', 'high', NULL, NULL, NULL, '2026-05-07 06:33:47', '2026-05-07 19:04:55', NULL, '69884fb794e98e0715d3f24edbd64887', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'task', 'T-89', 'test sweep: drive lib/src/terminal/ to 95%', 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. +Investigation 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. -**Today''s distribution within `lib/src/terminal/src/`:** +**Concrete fix shape:** +1. `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/`. +2. `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). +3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195. +4. 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). -| sub-area | hit / total | % | shape | -|---|---|---|---| -| `core/buffer/` | 0 / 581 | 0.0% | pure data structures | -| `core/escape/` | 1 / 473 | 0.2% | pure parser logic | -| `core/input/` | 4 / 292 | 1.4% | key + keytab tables | -| `core/(root)` | 0 / 188 | 0.0% | callback wiring | -| `core/mouse/` | 3 / 51 | 5.9% | encoder logic | -| `ui/` | 3 / 823 | 0.4% | painters + render objects | -| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` | -| `utils/` | 1 / 251 | 0.4% | circular buffer etc. | - -**Strategy — pure logic first, then UI:** - -1. **`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. -2. **`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. -3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode. -4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast. -5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests. -6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests. -7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive. - -**Acceptance:** -- `lib/src/terminal/` line coverage ≥ 95%. -- All tests fast (<5 s for the area''s test files combined; no real PTY shell-out). -- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files. -- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage. -- `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). - -**Strategy on commit cadence:** -Land tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch: -- Adds tests under `test/terminal//`. -- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows). -- Documents in CHANGELOG under "Changed" (test-only changes can also fold into one CHANGELOG entry per area). - -**Out of scope:** -- 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. -- Branch coverage (D-66 explicitly excludes). -- Mouse / keyboard *protocol* round-trips against real TUIs (that''s T-74 and integration work). - -**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing). -', 'in_progress', 'high', NULL, NULL, 'D-66', '2026-05-06 20:49:47', '2026-05-07 19:04:55', NULL, '6a514875e980a44213772eb6a953f203', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-64', 'story', NULL, 'VS Code keybinding preset', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '6c6e7a154bfdae6ebe04cb424db75800', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'task', NULL, 'IPC server: per-request timeout, broadcast logging, stale-socket race', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '6e66b94e7359af2ea5f596b5962b1915', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'task', NULL, 'forward real mouse events to TUI apps (not just PgUp/PgDown)', '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. - -**Acceptance:** -- 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`). -- Tested with `vim` (mouse mode `a`), `htop`, `less` — all should respond to mouse selection / click correctly. -- Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode. -- The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed. - -**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.', 'ready', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:22', '2026-05-07 19:04:55', NULL, '701409c8c37bf401eb6da20f622932eb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'task', NULL, 'tmux: isolated -L clide socket so user''s tmux config doesn''t bleed into clide', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:05', '2026-05-07 19:04:55', NULL, '719072755adc0f7f8d004168db345625', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'task', 'T-89', 'install ratcheting coverage gate at current floor', '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. +Workaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.', 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:21', '2026-05-07 19:04:55', NULL, '9e38408074619d9a46f3382a043d2f7b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6VMWWSP4BV8Q11W0M', 'task', '06FB0TNQM6KX7HEQJ65HZ566DG', 'expand SurfaceTokens with syntax highlighting roles', NULL, 'done', 'medium', NULL, NULL, 'D-45', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'a65f93e3f8bea36f544e9f8358655f7d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74S58G8XZV1766VD0', 'task', '06FB0TNQM6TNXTJCQBH9VADC4M', 'install ratcheting coverage gate at current floor', '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. **What lands:** @@ -652,769 +257,31 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - `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. - `coverage/lcov.info` is the standard Dart output path of `flutter test --coverage`. Don''t relocate. - `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). -', 'done', 'high', NULL, NULL, 'D-66', '2026-05-06 20:38:14', '2026-05-07 19:04:55', NULL, '720ba8c20ad1fb2489c4939d852b881e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'bug', NULL, 'reflow tail-anchor reparenting can dangle on partially-filled lines', '**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`). - -**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. - -**Trigger paths in production code:** - -1. `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. -2. `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. -3. `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. - -**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. - -**Acceptance:** -- New unit test in `test/terminal/core/core_test.dart` (or a sibling) that: - - Constructs a partially-filled source line and an anchor past `trimmedLength`, - - Runs reflow shrink, - - Asserts the anchor''s owning line is *in the reflow output list*, not dangling. -- 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. -- No regression in the existing reflow tests (the tail-clamp cases, wide-char boundaries, multi-line wrapped runs). - -**Investigation notes (do not skip):** - -The fix is conceptually small — the reparented tail anchor must land on a line that gets emitted. Two candidate shapes: - -- **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. -- **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). - -Don''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. - -**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. - -**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. -', 'done', 'high', NULL, NULL, NULL, '2026-05-07 05:51:48', '2026-05-07 19:04:55', NULL, '7486c03cb3b93e80803cfb7c51e0a88d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'story', NULL, 'Persist layout state across sessions', '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.', 'done', 'medium', NULL, NULL, 'D-53', '2026-04-22 20:33:58', '2026-05-07 19:04:55', NULL, '75a7ac4f4be58e14a66533335e06fcc1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'task', 'T-2', 'bundled dependency licensing', NULL, 'done', 'medium', NULL, NULL, 'D-42', '2026-04-22 11:47:03', '2026-05-07 19:04:55', NULL, '78348abd9a8bfae520680f5c7467fc6e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'story', NULL, 'Wire canonical keyboard shortcuts from interaction model', '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.', 'done', 'high', NULL, NULL, 'D-54', '2026-04-22 20:34:01', '2026-05-07 19:04:55', NULL, '798149aec3fac37a5ccbd36f9991f084', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-45', 'task', 'T-3', 'detect and install tmux on first launch', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:27:28', '2026-05-07 19:04:55', NULL, '7ab5fdb758b60883702d3bd7463a11d6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'epic', NULL, 'Tier 1 — Claude in xterm pane, PTY, session persistence', NULL, 'done', 'medium', NULL, NULL, 'D-41', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '845578273a6b9c5fde53103d5e385762', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'story', 'T-4', 'editor respects .editorconfig settings', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:17:26', '2026-05-07 19:04:55', NULL, '85f07a3097d9b8fd2d00b0a4884ce188', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-80', 'task', NULL, 'Standardize errno constants + logger in PTY/IPC/daemon', '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: - -**Errno constants (audit item #23):** -- 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`. -- Centralize them in `lib/src/pty/errors.dart` or a sibling `posix.dart` as named constants. -- Existing `lib/src/ipc/errno_mapping.dart` already has a `PosixErrno` class — extend it or move to a shared location both layers import from. - -**Logger standardization (audit item #22, partial #26):** -- `lib/src/ipc/server.dart` uses `stderr.writeln(...)` directly; the rest of the daemon either uses no logger or a custom one. -- The Flutter-host process often consumes stderr, so log lines disappear silently. -- Pick one logger interface (kernel `log` already exists for the app side), wire `DaemonServer` and the daemon-side handlers to use it. -- Dispatch error messages should prefix with the request `cmd` so log correlation works (audit item #26). - -**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '897daf838313309b8fc9eca392dfb84a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'epic', NULL, 'Tier 3 — git panel, diff tab, clide git CLI', NULL, 'done', 'medium', NULL, NULL, 'D-13', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '897fc0ebb13ecf6d9145ecda008c2592', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-49', 'story', NULL, 'native folder picker for Open Workspace', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:35', '2026-05-07 19:04:55', NULL, '8bf79aec0ae026d06357cce81b037049', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-41', 'task', 'T-7', 'Add images tab to context panel', 'Spec lists images as a right-panel section. No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '8c1c09d8da0527e1b0da424f5928c9a9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-81', 'task', NULL, 'Misc PTY/IPC/git polish (audit medium-priority items)', '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: - -- **#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`. -- **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed. -- **#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. -- **#21** — `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd. -- **#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. -- **#27** — `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure. -- **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade. -- **#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. - -Land each as a small focused commit; ticket closes when all items above are merged.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '8e0895430b2a96168a8a5d14799691ca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'epic', NULL, 'drive line coverage from 35% to 95%', 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: - -| area | hit / total | % | -|---|---|---| -| `lib/src/terminal/` | 12 / 3167 | **0.4%** | -| `lib/widgets/` | 537 / 1184 | 45.4% | -| `lib/src/pql/` | 45 / 94 | 47.9% | -| `lib/builtin/` | 269 / 550 | 48.9% | -| `lib/kernel/` | 889 / 1691 | 52.6% | -| `lib/src/daemon/` | 320 / 584 | 54.8% | -| `lib/src/pty/` | 130 / 216 | 60.2% | -| `lib/src/git/` | 356 / 518 | 68.7% | -| `lib/src/ipc/` | 110 / 156 | 70.5% | -| `lib/src/files/` | 98 / 133 | 73.7% | -| `lib/extension/` | 41 / 54 | 75.9% | -| `lib/src/editor/` | 107 / 129 | 82.9% | -| `lib/src/panes/` | 70 / 73 | 95.9% | - -The 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. - -**End state:** -- Total line coverage ≥ 95% (no per-area carve-outs). -- Pre-push gate hard-fails on coverage drops via a committed floor (`coverage/floor.txt`); floor ratchets up only. -- Per D-66 the gate lands at the *current* floor in the first child ticket; subsequent floor bumps come with the test-writing children. - -**Acceptance criteria for closing this epic:** -1. `coverage/floor.txt` reads `95` (or higher). -2. `flutter test --coverage --exclude-tags forkpty` produces ≥95% line coverage. -3. The pre-push gate computes coverage and rejects pushes below the floor. -4. 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. - -**Strategy / child shape:** -The 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. - -1. **Gate plumbing** (first child) — install the ratchet, lock in current floor, expose `make coverage-gate` and wire it into `make push-check`. -2. **`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.). -3. **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. -4. **Final ratchet** — once total ≥ 95%, set floor to 95 and close the epic. - -**Out of scope:** -- Branch coverage (lcov data is weak for branch on Dart; line coverage is the contract). -- Integration / E2E coverage (only widget + unit feed lcov). -- `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. - -**Cross-references:** D-66 (this epic''s decision record), D-29 (pre-push gate scope), "Own the rendering stack" guardrail (CLAUDE.md). -', 'in_progress', 'high', NULL, NULL, 'D-66', '2026-05-06 20:36:42', '2026-05-07 19:04:55', NULL, '94b4adb9293dfd29683a1cba9e3f6bd6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-88', 'task', NULL, 'track dugite-native upstream releases for security updates', '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. - -**Acceptance:** -- A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases. -- When a new release lands with a CVE label or fixes a CVE-tagged git release, schedule a bump; otherwise track the version drift. -- 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/`. - -**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). - -**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.', 'backlog', 'low', NULL, NULL, 'D-59', '2026-05-06 14:34:35', '2026-05-07 19:04:55', NULL, '9686bc9bcfd644ed795ee167810b4789', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'task', 'T-6', 'pql-backed file tree — replace or augment files.ls with pql files', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'a00962410fab04b0acf17e9cab03f84d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'task', NULL, 'ship matching bold weight to restore semantic bold rendering', '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - ---- -Refinement (2026-05-06): - -Investigation 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. - -**Concrete fix shape:** -1. `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/`. -2. `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). -3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195. -4. 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). - -Workaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.', 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:21', '2026-05-07 19:04:55', NULL, 'a19a81d6e069c736964a569246ea8802', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'task', 'T-6', 'backlinks panel — show inbound links to active file', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'a2b1484710d59baed015b7b1e5e790e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'task', NULL, 'design reusable sortable tab system for multitab panes', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-06 09:17:39', '2026-05-07 19:04:55', NULL, 'aa621bca79bf4dbf06e3032bf0f752f4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'task', NULL, 'inline terminal emulator (drop xterm.dart dependency)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:48', '2026-05-07 19:04:55', NULL, 'ab0d6cfc81e69f0f6a255567246b56dd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-55', 'story', NULL, 'system tray and dock persistence', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'ac775be9989d8a5a54966ea72be3504a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'story', 'T-7', 'Context auto-behavior: right panel reacts to Claude references', '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.', 'backlog', 'low', NULL, NULL, 'D-50', '2026-04-22 20:34:09', '2026-05-07 19:04:55', NULL, 'ade02d3e74f5b5d7433feea49fb018ba', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'task', 'T-4', 'implement welcome screen per hi-fi design', NULL, 'done', 'medium', NULL, NULL, 'D-43', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'af5be385d304e5a84fc566402c8df713', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'task', 'T-4', 'wire command palette keybinding', '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. - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Keybinding goes in `lib/kernel/src/commands/keybindings.dart` per the D-54 keymap. -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b099a8a9c20530741b6f030efa1c83c2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'epic', NULL, 'Tier 4 — pql integration: query panel, file tree, backlinks, problems', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, 'b1b64ec1fe2f96d3ea907d03240ffa3b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'task', 'T-4', 'multi-buffer editor tabs', NULL, 'cancelled', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b61b328a29d4ecb1eef10a64c4286af1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'task', 'T-2', 'Python stopgap tooling', NULL, 'done', 'medium', NULL, NULL, 'D-40', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'b7bc906e04533e2e5ef45e34900c70a2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-47', 'story', 'T-46', 'clide self-update mechanism', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:28:43', '2026-05-07 19:04:55', NULL, 'b8270be27465f4eea880f37fcd17fed4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'task', 'T-2', 'expand SurfaceTokens with syntax highlighting roles', NULL, 'done', 'medium', NULL, NULL, 'D-45', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b82f0ed1b3391151b572028a8b40ae5d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'task', NULL, 'add Catppuccin theme to bundled themes', 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). - -**Acceptance:** -- 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). -- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45). -- WCAG-AA contrast gate (D-22) passes against the theme. -- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution. -- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu. - -**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. - -**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.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-06 08:30:15', '2026-05-07 19:04:55', NULL, 'bae6235c5ee09a042846f05abdc235f4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'task', 'T-6', 'problems panel — surface pql doctor + validate warnings', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'bb7d23a0553f331193f1de93af607587', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'bug', NULL, 'kill secondary Claude tmux sessions on tab close and on app shutdown', '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. - -Neither happens today. - -**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`). - -**Gap 2 — app shutdown leaks secondaries.** Nothing reaps `clide-claude--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. - -**Acceptance:** -- Closing a secondary tab in the Claude pane runs `tmux -L clide kill-session -t ` for that secondary''s session name (use existing `secondarySessionName(repoRoot, n)`). -- App shutdown enumerates `clide-claude--*` sessions on the clide socket and kills the non-primary ones. Primary is left detached (D-41 explicit). -- `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone. -- Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner). - -**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.', 'done', 'high', NULL, NULL, 'D-41', '2026-05-06 13:46:07', '2026-05-07 19:04:55', NULL, 'bce38e567d3858511e04ee37d9615380', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-60', 'story', NULL, 'workspace trust prompt', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'bcf89d2601b747cdaa02526c6390ab30', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'task', NULL, 'codify UI spacing constants (gaps, paddings, control sizes)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, 'bedebecf0079b4fcdfc50fc7094b6c82', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'task', 'T-6', 'Contribute markdown viewer tab to context panel', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, 'c712dbddc0d5e728b2283185ab9bbd44', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'task', NULL, 'PTY read buffer: 4KB → 64KB', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:01', '2026-05-07 19:04:55', NULL, 'c998968e1550f82d34e79a02a0057142', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-46', 'epic', NULL, 'cross-platform clide installer', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:27:58', '2026-05-07 19:04:55', NULL, 'd0cb29fbd147583b985897608f2e7f25', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'task', 'T-6', 'validate pql init installation cycle', NULL, 'done', 'medium', NULL, NULL, 'D-39', '2026-04-22 09:41:51', '2026-05-07 19:04:55', NULL, 'd7115d59dd796b37cdec5eefd8e0912b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'task', 'T-2', 'Q&D record system + ADR migration', NULL, 'done', 'medium', NULL, NULL, 'D-34', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'dac1a56fceb74f39d369da55bfd4e541', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'task', NULL, 'PTY: surface errno from forkpty/execve/write/ioctl failures', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'db1b61acdce93092de83a33f25a31bf8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'epic', NULL, 'Tier 0 — app + daemon handshake, empty IDE shell', NULL, 'done', 'medium', NULL, NULL, 'D-7', '2026-04-22 11:45:26', '2026-05-07 19:04:55', NULL, 'dcf6e157b37cde1402c8737ad4fb2a3f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'story', NULL, 'Implement 12px collapse spine for side panels', '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.', 'done', 'high', NULL, NULL, 'D-51', '2026-04-22 20:33:53', '2026-05-07 19:04:55', NULL, 'ddc451b330dad49f5130c463e457e81a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'task', NULL, 'PTY: fix resource leaks and reader-isolate races', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'e1cf1f93207c098bed93bf74801588eb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'story', NULL, 'output and log panel', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'e58c97f0fb119d7cb26717880d97ff37', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-67', 'story', NULL, 'review Semantics widget coverage and UI testability', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, 'e75388d59ceab20d924268f43198caf3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'task', 'T-8', 'feed POLICY.md rules into D-records', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:52', '2026-05-07 19:04:55', NULL, 'ea0b7a2e6843c7c9fc9af1e184d0edf2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-27', 'task', 'T-8', 'migrate shipped extensions from builtin/ to extensions/ per D-46', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:45', '2026-05-07 19:04:55', NULL, 'ea9ec2549a02d75b00431366f4baab2b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-40', 'task', 'T-8', 'Add PRs tab to sidebar', 'Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, 'ec754ddf900ec54777482abf019806ca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'story', NULL, 'application menu bar (Zed-style)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:30:09', '2026-05-07 19:04:55', NULL, 'ef1017848746cf1ea807d166ed982807', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-7', 'epic', NULL, 'Tier 5 — canvas and graph view', '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. - -**Canvas pane (`builtin.canvas`):** -- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. -- Pan/zoom via `InteractiveViewer`; node selection, drag, resize. -- Edit affordances: add note from file picker, add text node, draw edge between nodes. -- Persist layout back to the `.canvas` file on disk. - -**Graph view (`builtin.graph`):** -- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks. -- Hover highlights connected subgraph; click opens the note in the editor. -- Filter pane: tag include/exclude, file glob, depth-from-active. -- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. - -Both 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).', 'backlog', 'medium', NULL, NULL, 'D-17', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, 'ef64435bb8bd3ef2d7ce6d12f9d60e5d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-57', 'task', NULL, 'drag tab reordering in sidebar and context panel', 'Reorder tabs in the sidebar and context panel icon rails by dragging. Persist order to project settings.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'f237fb997fdac8c68d56e0e3be0a595d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'task', 'T-3', 'secondary Claude pane UI wiring', NULL, 'done', 'medium', NULL, NULL, 'D-41', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'f72d103fdc4bc62f33bcfb68d2db97c1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-66', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, 'f74e891c8f32e9e51bb663797b6d6b34', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'story', 'T-4', 'Implement editor-above-Claude mode in middle column', '⌘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.', 'done', 'medium', NULL, NULL, 'D-49', '2026-04-22 20:34:06', '2026-05-07 19:04:55', NULL, 'f8f3da430866059531d7ee59b064639c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'task', 'T-6', 'pql skill install + stopgap removal', NULL, 'done', 'medium', NULL, NULL, 'D-39', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'ff252aebaf78340e33fa0d6d6874b5c0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-94', 'task', NULL, 'wire pql plan merge as git custom merge driver', NULL, 'cancelled', 'medium', NULL, NULL, NULL, '2026-05-07 19:08:29', '2026-05-07 19:08:52', NULL, '88cca86b7b710a161f51ad0578c48afe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-95', 'bug', NULL, 'TerminalGestureHandler tertiary-tap wired to secondary; middle-button mis-typed', '**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). - -**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. - -Trace: - -1. `TerminalGestureHandler.build` (gesture_handler.dart:65–82) wires: - ```dart - onTertiaryTapDown: onSecondaryTapDown, - onTertiaryTapUp: onSecondaryTapUp, - ``` - 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**. -2. The state''s `onTertiaryTapDown` and `onTertiaryTapUp` methods (lines 147–152) are therefore **dead** — they''re declared but never invoked anywhere. -3. `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. -4. 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`. - -**Two layers of "dead" again:** - -- The wiring is wrong, so middle-clicks dispatch as if they were right-clicks. -- 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). - -**User-visible impact:** - -A 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. - -**Resolutions, like T-93:** - -- **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. -- **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. - -Recommendation: **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. - -**Aside:** there''s a related dead branch in `onDragStart` (line 176): -```dart -details.kind == PointerDeviceKind.mouse - ? renderTerminal.selectCharacters(details.localPosition) - : renderTerminal.selectWord(details.localPosition); -``` -The `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. - -**Acceptance:** - -- Either: - - **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. - - **B:** `onTertiaryTapDown` / `onTertiaryTapUp` removed from `TerminalGestureHandler`, `TerminalGestureDetector`, and the state''s tertiary methods. Existing tests still pass. -- The `selectWord` branch in `onDragStart` is either reachable (extend PanGestureRecognizer.supportedDevices) or removed. - -**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. - -**Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic). -', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-08 10:48:59', '2026-05-08 11:01:01', NULL, '91cf3f81c256df191ddfcb3ebc6cefb1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-8', 'epic', NULL, 'Tier 6 — extension API, settings, theming, distributable builds', 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype. - -**Extension API (third-party Lua):** -- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc. -- Manifest schema, capability gating, sandboxed FS/IPC access. -- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15). -- Marketplace / distribution story is OUT OF SCOPE for Tier 6 — local-install only. - -**Settings UI (`builtin.settings-ui`):** -- Schema-driven settings panel reading from the kernel SettingsStore. -- Render strategy: form fields keyed off the schema each subsystem registers. -- Edits write back to `.clide/settings.yaml`. - -**Theming UI (`builtin.theme-picker` extends):** -- Live preview of the four bundled themes (D-44). -- Custom theme: import YAML, validate against schema, register at runtime. -- Per-component override surface (long horizon). - -**Distributable builds:** -- AppImage / Flatpak for Linux, .dmg for macOS — see T-46. -- Self-update mechanism — see T-47. -- License manifest auto-regen as part of the release build. - -Big epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.', 'backlog', 'medium', NULL, NULL, 'D-15', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '00e5aea23a2e04b462064ed9c88ef76e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-78', 'bug', NULL, 'files.read path traversal — validate paths stay under workspace root', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '015fefd2472662b86a8603cdca0da3d3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-51', 'story', NULL, 'quick file open (Ctrl+P)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '0a00222925ff90860ea1648f031f7bad', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-43', 'story', 'T-3', 'Ship clide tmux.conf for Claude pane sessions', '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.', 'done', 'high', NULL, NULL, 'D-41', '2026-04-22 22:01:13', '2026-05-07 19:04:55', NULL, '0b2b332c0ed4be810999d48c4fa37573', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '11873ec6f6561870ee3c1a0bcbb3d590', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-4', 'epic', NULL, 'Tier 2 — pane model, editor, active-file awareness, CLI', NULL, 'done', 'medium', NULL, NULL, 'D-6', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '118a505a19b839ee9822e867ecd654d7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'story', NULL, 'search and replace across files', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '12ef305ad5acc0c02c41fff7d5e4c818', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'task', 'T-5', 'Decide diff view placement', '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.', 'backlog', 'low', NULL, NULL, 'D-48', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '136aeb8ae1566003e0419890b0893fd4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-84', 'task', NULL, 'MultitabPane: drag-to-reorder gesture wiring', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 10:16:02', '2026-05-07 19:04:55', NULL, '14debaa8d5242c1a3254c5ed3246afec', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-69', 'story', NULL, 'Claude pane: fullscreen mode + mouse-wheel scroll', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:52', '2026-05-07 19:04:55', NULL, '167ffa2ee6408bb98c5b5cb1432e701b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-19', 'task', 'T-2', 'translate four design themes into YAML theme files', NULL, 'done', 'medium', NULL, NULL, 'D-44', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, '1c0e735f3f72a139497a7d7b5919c383', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-31', 'story', NULL, 'Implement focus mode (full-window takeover)', 'Focus mode via double-click header, hover ⛶ glyph, or ⌘. shortcut. Snapshot LayoutArrangement on enter, restore on Esc. Show Esc hint in header.', 'done', 'medium', NULL, NULL, 'D-52', '2026-04-22 20:33:56', '2026-05-07 19:04:55', NULL, '22c320a9a59a0df6755044757ccdd8a9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'story', NULL, 'deep link protocol handler', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '233c670c41fa11428b0db7ff0edd30f2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-37', 'task', 'T-6', 'Contribute tickets & decisions tabs to sidebar', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '2a522feed49043a8b0dbb26813bfbfb1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-63', 'task', NULL, 'manual refresh button in sidebar panels', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '2a9439475d053cadb7ad8a3e2a9444b8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-39', 'task', 'T-6', 'Contribute graph view tab to context panel', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '34a1728538a029a2069ac746cc036aea', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-58', 'task', NULL, 'activity badges on sidebar icons', 'Show unread count dots on sidebar icons: git changes count, new problems, pending notifications. Badges clear when the tab is viewed.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '36d455053e53369d01620f2a7b1e7887', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-13', 'task', 'T-6', 'pql query panel — sidebar tab for vault queries', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, '3a0659a695230a21a9a1733c4f998876', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-25', 'task', 'T-8', 'CI reproducible build for libtree-sitter.so', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:24', '2026-05-07 19:04:55', NULL, '3c11cce594486000fff058265e24e35b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-62', 'story', NULL, 'auto-refresh decisions and tickets on file change and scheduler tick', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '400c80914c237a1d825d429dcd787031', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-59', 'task', NULL, 'progress indicators in status bar', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '40aab381f7084b8b1964f21124098f76', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'story', NULL, 'find in files (Ctrl+Shift+F)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, '410fd83b1f53de9b1fb86c896b259bdb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-34', 'story', NULL, 'Add icon rail to right panel (context section switcher)', 'Right panel currently has no bottom icon rail. Add one matching the left sidebar rail pattern: viewer, pql graph, links, images as section tabs.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 20:34:03', '2026-05-07 19:04:55', NULL, '4326c5c6219cc7a969029ecf0d6cd204', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-44', 'story', NULL, 'image paste in Claude panel prompt bar', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:25:26', '2026-05-07 19:04:55', NULL, '4aa18b64c88cf6f8ac6f10f4cc9e3d05', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-26', 'task', 'T-8', 'web-tree-sitter via JS interop for browser builds', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:33', '2026-05-07 19:04:55', NULL, '4b8d7c04a645b7db110cfba6c2c8ceb6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-61', 'task', NULL, 'kernel scheduler service with tiered timer events', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '4e3a722c923b8efd5551e2a133d8449a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-70', 'bug', NULL, 'Bold text breaks terminal cell grid (workaround: suppress bold)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:56', '2026-05-07 19:04:55', NULL, '51caaff8d16702e485a9b5ebce2572f2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-85', 'task', NULL, 'consolidate theme-ui into ui-design skill with theme/surface/geometry/icons references', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, '5bcfd76bee81ba55f7aa56e30005c726', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-17', 'task', 'T-8', 'add dart doc generation to CI', '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. - -**Acceptance:** -- New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact. -- Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run. -- 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). - -**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).', 'done', 'medium', NULL, NULL, 'D-32', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '5c4b5ba5efbb8efe69500c90f4f70e06', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'story', NULL, 'toast notification system', '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).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:48', '2026-05-07 19:04:55', NULL, '5d52c0db2bfa8068bb4e33c9eec0d8bf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-18', 'task', NULL, 'audit error handling in PTY and IPC critical paths', NULL, 'done', 'medium', NULL, NULL, 'D-5', '2026-04-22 13:26:29', '2026-05-07 19:04:55', NULL, '653fb101b707eb4e8f7000beebbc3063', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-79', 'task', NULL, 'pane.spawn / editor.open: map errno to actionable error kinds', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '68fb1a7e2bd0b056be4068dd51d44272', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-93', 'bug', NULL, 'TerminalView.onTapUp callback is wired to a dead path', '**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). - -**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: - -1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304). -2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67). -3. `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. - -The `_onTapUp` method on `TerminalViewState` itself is also dead — it''s only referenced from the dead-field path above. - -**Two layers of "dead" here:** - -- The **wiring** is dead — nothing inside the terminal renderer calls into `widget.onTapUp` after the field is set. -- 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. - -So 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.) - -**Two clean resolutions:** - -- **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: - - `onTapUp` fires on every tap up (raw); `onSingleTapUp` fires only on confirmed singles. - - Collapse the two into a single hook and document the contract. -- **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. - -Recommendation: **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. - -**Acceptance:** - -- Either: - - **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. - - **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. -- No regression in secondary-tap behaviour (those parameters are wired correctly via `TapGestureRecognizer.onSecondary*`). - -**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. - -**Cross-references:** T-91 (epic parent), T-89 (coverage epic). -', 'done', 'high', NULL, NULL, NULL, '2026-05-07 06:33:47', '2026-05-07 19:04:55', NULL, '69884fb794e98e0715d3f24edbd64887', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'task', 'T-89', 'test sweep: drive lib/src/terminal/ to 95%', 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. - -**Today''s distribution within `lib/src/terminal/src/`:** - -| sub-area | hit / total | % | shape | -|---|---|---|---| -| `core/buffer/` | 0 / 581 | 0.0% | pure data structures | -| `core/escape/` | 1 / 473 | 0.2% | pure parser logic | -| `core/input/` | 4 / 292 | 1.4% | key + keytab tables | -| `core/(root)` | 0 / 188 | 0.0% | callback wiring | -| `core/mouse/` | 3 / 51 | 5.9% | encoder logic | -| `ui/` | 3 / 823 | 0.4% | painters + render objects | -| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` | -| `utils/` | 1 / 251 | 0.4% | circular buffer etc. | - -**Strategy — pure logic first, then UI:** - -1. **`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. -2. **`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. -3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode. -4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast. -5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests. -6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests. -7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive. - -**Acceptance:** -- `lib/src/terminal/` line coverage ≥ 95%. -- All tests fast (<5 s for the area''s test files combined; no real PTY shell-out). -- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files. -- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage. -- `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). - -**Strategy on commit cadence:** -Land tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch: -- Adds tests under `test/terminal//`. -- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows). -- Documents in CHANGELOG under "Changed" (test-only changes can also fold into one CHANGELOG entry per area). - -**Out of scope:** -- 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. -- Branch coverage (D-66 explicitly excludes). -- Mouse / keyboard *protocol* round-trips against real TUIs (that''s T-74 and integration work). - -**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing). -', 'in_progress', 'high', NULL, NULL, 'D-66', '2026-05-06 20:49:47', '2026-05-07 19:04:55', NULL, '6a514875e980a44213772eb6a953f203', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-64', 'story', NULL, 'VS Code keybinding preset', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, '6c6e7a154bfdae6ebe04cb424db75800', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-77', 'task', NULL, 'IPC server: per-request timeout, broadcast logging, stale-socket race', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '6e66b94e7359af2ea5f596b5962b1915', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'task', NULL, 'forward real mouse events to TUI apps (not just PgUp/PgDown)', '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. - -**Acceptance:** -- 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`). -- Tested with `vim` (mouse mode `a`), `htop`, `less` — all should respond to mouse selection / click correctly. -- Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode. -- The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed. - -**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.', 'ready', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:22', '2026-05-07 19:04:55', NULL, '701409c8c37bf401eb6da20f622932eb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-72', 'task', NULL, 'tmux: isolated -L clide socket so user''s tmux config doesn''t bleed into clide', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:05', '2026-05-07 19:04:55', NULL, '719072755adc0f7f8d004168db345625', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-90', 'task', 'T-89', 'install ratcheting coverage gate at current floor', '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. - -**What lands:** - -1. **`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). -2. **`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. -3. **`Makefile` `coverage-gate` target** — wraps `ci/coverage_gate.sh` for local use. -4. **`make push-check`** — adds `coverage-gate` to its dependency list. Pre-push hook (already wired via `.githooks/pre-push`) picks this up automatically. -5. **`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. - -**Acceptance:** -- `make coverage-gate` passes on a clean checkout (floor matches reality). -- `make push-check` includes the gate. -- A test deletion would fail `make coverage-gate` (verify by experiment, not just by reading the script — invariant has to actually hold). -- Pre-push hook runs the gate (verify by attempting a deliberately-failing push and seeing it blocked). -- `coverage/lcov.info` is gitignored (it''s regenerated on every run; only `floor.txt` is committed). - -**Out of scope:** -- Bumping the floor toward 95% — that''s the test-writing children''s job. -- Branch coverage — D-66 explicitly excludes. -- Per-area floors — D-66 chose a single global floor for simplicity. - -**Notes:** -- `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. -- `coverage/lcov.info` is the standard Dart output path of `flutter test --coverage`. Don''t relocate. -- `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). -', 'done', 'high', NULL, NULL, 'D-66', '2026-05-06 20:38:14', '2026-05-07 19:04:55', NULL, '720ba8c20ad1fb2489c4939d852b881e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-92', 'bug', NULL, 'reflow tail-anchor reparenting can dangle on partially-filled lines', '**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`). - -**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. - -**Trigger paths in production code:** - -1. `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. -2. `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. -3. `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. - -**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. - -**Acceptance:** -- New unit test in `test/terminal/core/core_test.dart` (or a sibling) that: - - Constructs a partially-filled source line and an anchor past `trimmedLength`, - - Runs reflow shrink, - - Asserts the anchor''s owning line is *in the reflow output list*, not dangling. -- 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. -- No regression in the existing reflow tests (the tail-clamp cases, wide-char boundaries, multi-line wrapped runs). - -**Investigation notes (do not skip):** - -The fix is conceptually small — the reparented tail anchor must land on a line that gets emitted. Two candidate shapes: - -- **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. -- **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). - -Don''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. - -**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. - -**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. -', 'done', 'high', NULL, NULL, NULL, '2026-05-07 05:51:48', '2026-05-07 19:04:55', NULL, '7486c03cb3b93e80803cfb7c51e0a88d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-32', 'story', NULL, 'Persist layout state across sessions', '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.', 'done', 'medium', NULL, NULL, 'D-53', '2026-04-22 20:33:58', '2026-05-07 19:04:55', NULL, '75a7ac4f4be58e14a66533335e06fcc1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-12', 'task', 'T-2', 'bundled dependency licensing', NULL, 'done', 'medium', NULL, NULL, 'D-42', '2026-04-22 11:47:03', '2026-05-07 19:04:55', NULL, '78348abd9a8bfae520680f5c7467fc6e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-33', 'story', NULL, 'Wire canonical keyboard shortcuts from interaction model', '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.', 'done', 'high', NULL, NULL, 'D-54', '2026-04-22 20:34:01', '2026-05-07 19:04:55', NULL, '798149aec3fac37a5ccbd36f9991f084', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-45', 'task', 'T-3', 'detect and install tmux on first launch', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:27:28', '2026-05-07 19:04:55', NULL, '7ab5fdb758b60883702d3bd7463a11d6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-3', 'epic', NULL, 'Tier 1 — Claude in xterm pane, PTY, session persistence', NULL, 'done', 'medium', NULL, NULL, 'D-41', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '845578273a6b9c5fde53103d5e385762', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'story', 'T-4', 'editor respects .editorconfig settings', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:17:26', '2026-05-07 19:04:55', NULL, '85f07a3097d9b8fd2d00b0a4884ce188', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-80', 'task', NULL, 'Standardize errno constants + logger in PTY/IPC/daemon', '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: - -**Errno constants (audit item #23):** -- 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`. -- Centralize them in `lib/src/pty/errors.dart` or a sibling `posix.dart` as named constants. -- Existing `lib/src/ipc/errno_mapping.dart` already has a `PosixErrno` class — extend it or move to a shared location both layers import from. - -**Logger standardization (audit item #22, partial #26):** -- `lib/src/ipc/server.dart` uses `stderr.writeln(...)` directly; the rest of the daemon either uses no logger or a custom one. -- The Flutter-host process often consumes stderr, so log lines disappear silently. -- Pick one logger interface (kernel `log` already exists for the app side), wire `DaemonServer` and the daemon-side handlers to use it. -- Dispatch error messages should prefix with the request `cmd` so log correlation works (audit item #26). - -**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '897daf838313309b8fc9eca392dfb84a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-5', 'epic', NULL, 'Tier 3 — git panel, diff tab, clide git CLI', NULL, 'done', 'medium', NULL, NULL, 'D-13', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, '897fc0ebb13ecf6d9145ecda008c2592', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-49', 'story', NULL, 'native folder picker for Open Workspace', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:35', '2026-05-07 19:04:55', NULL, '8bf79aec0ae026d06357cce81b037049', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-41', 'task', 'T-7', 'Add images tab to context panel', 'Spec lists images as a right-panel section. No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, '8c1c09d8da0527e1b0da424f5928c9a9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-81', 'task', NULL, 'Misc PTY/IPC/git polish (audit medium-priority items)', '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: - -- **#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`. -- **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed. -- **#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. -- **#21** — `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd. -- **#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. -- **#27** — `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure. -- **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade. -- **#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. - -Land each as a small focused commit; ticket closes when all items above are merged.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, '8e0895430b2a96168a8a5d14799691ca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'epic', NULL, 'drive line coverage from 35% to 95%', 'Today: total line coverage **34.9%** (2984/8549 lines). Per area: - -| area | hit / total | % | -|---|---|---| -| `lib/src/terminal/` | 12 / 3167 | **0.4%** | -| `lib/widgets/` | 537 / 1184 | 45.4% | -| `lib/src/pql/` | 45 / 94 | 47.9% | -| `lib/builtin/` | 269 / 550 | 48.9% | -| `lib/kernel/` | 889 / 1691 | 52.6% | -| `lib/src/daemon/` | 320 / 584 | 54.8% | -| `lib/src/pty/` | 130 / 216 | 60.2% | -| `lib/src/git/` | 356 / 518 | 68.7% | -| `lib/src/ipc/` | 110 / 156 | 70.5% | -| `lib/src/files/` | 98 / 133 | 73.7% | -| `lib/extension/` | 41 / 54 | 75.9% | -| `lib/src/editor/` | 107 / 129 | 82.9% | -| `lib/src/panes/` | 70 / 73 | 95.9% | - -The 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. - -**End state:** -- Total line coverage ≥ 95% (no per-area carve-outs). -- Pre-push gate hard-fails on coverage drops via a committed floor (`coverage/floor.txt`); floor ratchets up only. -- Per D-66 the gate lands at the *current* floor in the first child ticket; subsequent floor bumps come with the test-writing children. - -**Acceptance criteria for closing this epic:** -1. `coverage/floor.txt` reads `95` (or higher). -2. `flutter test --coverage --exclude-tags forkpty` produces ≥95% line coverage. -3. The pre-push gate computes coverage and rejects pushes below the floor. -4. 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. - -**Strategy / child shape:** -The 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. - -1. **Gate plumbing** (first child) — install the ratchet, lock in current floor, expose `make coverage-gate` and wire it into `make push-check`. -2. **`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.). -3. **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. -4. **Final ratchet** — once total ≥ 95%, set floor to 95 and close the epic. - -**Out of scope:** -- Branch coverage (lcov data is weak for branch on Dart; line coverage is the contract). -- Integration / E2E coverage (only widget + unit feed lcov). -- `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. - -**Cross-references:** D-66 (this epic''s decision record), D-29 (pre-push gate scope), "Own the rendering stack" guardrail (CLAUDE.md). -', 'in_progress', 'high', NULL, NULL, 'D-66', '2026-05-06 20:36:42', '2026-05-07 19:04:55', NULL, '94b4adb9293dfd29683a1cba9e3f6bd6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-88', 'task', NULL, 'track dugite-native upstream releases for security updates', '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. - -**Acceptance:** -- A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases. -- When a new release lands with a CVE label or fixes a CVE-tagged git release, schedule a bump; otherwise track the version drift. -- 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/`. - -**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). - -**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.', 'backlog', 'low', NULL, NULL, 'D-59', '2026-05-06 14:34:35', '2026-05-07 19:04:55', NULL, '9686bc9bcfd644ed795ee167810b4789', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-14', 'task', 'T-6', 'pql-backed file tree — replace or augment files.ls with pql files', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'a00962410fab04b0acf17e9cab03f84d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-73', 'task', NULL, 'ship matching bold weight to restore semantic bold rendering', '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. - -**Acceptance:** -- 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). -- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold. -- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0). - -**Options to evaluate:** -- 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.) -- Switch to a variable JetBrainsMono and request a specific weight axis position. -- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono). - ---- -Refinement (2026-05-06): - -Investigation 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. - -**Concrete fix shape:** -1. `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/`. -2. `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). -3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195. -4. 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). - -Workaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.', 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:21', '2026-05-07 19:04:55', NULL, 'a19a81d6e069c736964a569246ea8802', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-15', 'task', 'T-6', 'backlinks panel — show inbound links to active file', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'a2b1484710d59baed015b7b1e5e790e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-83', 'task', NULL, 'design reusable sortable tab system for multitab panes', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-06 09:17:39', '2026-05-07 19:04:55', NULL, 'aa621bca79bf4dbf06e3032bf0f752f4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-68', 'task', NULL, 'inline terminal emulator (drop xterm.dart dependency)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:48', '2026-05-07 19:04:55', NULL, 'ab0d6cfc81e69f0f6a255567246b56dd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-55', 'story', NULL, 'system tray and dock persistence', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'ac775be9989d8a5a54966ea72be3504a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'story', 'T-7', 'Context auto-behavior: right panel reacts to Claude references', '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.', 'backlog', 'low', NULL, NULL, 'D-50', '2026-04-22 20:34:09', '2026-05-07 19:04:55', NULL, 'ade02d3e74f5b5d7433feea49fb018ba', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-21', 'task', 'T-4', 'implement welcome screen per hi-fi design', NULL, 'done', 'medium', NULL, NULL, 'D-43', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'af5be385d304e5a84fc566402c8df713', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'task', 'T-4', 'wire command palette keybinding', '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. - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Keybinding goes in `lib/kernel/src/commands/keybindings.dart` per the D-54 keymap. -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b099a8a9c20530741b6f030efa1c83c2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-6', 'epic', NULL, 'Tier 4 — pql integration: query panel, file tree, backlinks, problems', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, 'b1b64ec1fe2f96d3ea907d03240ffa3b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-22', 'task', 'T-4', 'multi-buffer editor tabs', NULL, 'cancelled', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b61b328a29d4ecb1eef10a64c4286af1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-10', 'task', 'T-2', 'Python stopgap tooling', NULL, 'done', 'medium', NULL, NULL, 'D-40', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'b7bc906e04533e2e5ef45e34900c70a2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-47', 'story', 'T-46', 'clide self-update mechanism', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:28:43', '2026-05-07 19:04:55', NULL, 'b8270be27465f4eea880f37fcd17fed4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-20', 'task', 'T-2', 'expand SurfaceTokens with syntax highlighting roles', NULL, 'done', 'medium', NULL, NULL, 'D-45', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'b82f0ed1b3391151b572028a8b40ae5d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'task', NULL, 'add Catppuccin theme to bundled themes', 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). - -**Acceptance:** -- 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). -- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45). -- WCAG-AA contrast gate (D-22) passes against the theme. -- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution. -- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu. - -**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. - -**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.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-06 08:30:15', '2026-05-07 19:04:55', NULL, 'bae6235c5ee09a042846f05abdc235f4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-16', 'task', 'T-6', 'problems panel — surface pql doctor + validate warnings', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'bb7d23a0553f331193f1de93af607587', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-87', 'bug', NULL, 'kill secondary Claude tmux sessions on tab close and on app shutdown', '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. - -Neither happens today. - -**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`). - -**Gap 2 — app shutdown leaks secondaries.** Nothing reaps `clide-claude--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. - -**Acceptance:** -- Closing a secondary tab in the Claude pane runs `tmux -L clide kill-session -t ` for that secondary''s session name (use existing `secondarySessionName(repoRoot, n)`). -- App shutdown enumerates `clide-claude--*` sessions on the clide socket and kills the non-primary ones. Primary is left detached (D-41 explicit). -- `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone. -- Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner). - -**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.', 'done', 'high', NULL, NULL, 'D-41', '2026-05-06 13:46:07', '2026-05-07 19:04:55', NULL, 'bce38e567d3858511e04ee37d9615380', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-60', 'story', NULL, 'workspace trust prompt', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'bcf89d2601b747cdaa02526c6390ab30', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-86', 'task', NULL, 'codify UI spacing constants (gaps, paddings, control sizes)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, 'bedebecf0079b4fcdfc50fc7094b6c82', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-38', 'task', 'T-6', 'Contribute markdown viewer tab to context panel', '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.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, 'c712dbddc0d5e728b2283185ab9bbd44', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-71', 'task', NULL, 'PTY read buffer: 4KB → 64KB', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:01', '2026-05-07 19:04:55', NULL, 'c998968e1550f82d34e79a02a0057142', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-46', 'epic', NULL, 'cross-platform clide installer', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:27:58', '2026-05-07 19:04:55', NULL, 'd0cb29fbd147583b985897608f2e7f25', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1', 'task', 'T-6', 'validate pql init installation cycle', NULL, 'done', 'medium', NULL, NULL, 'D-39', '2026-04-22 09:41:51', '2026-05-07 19:04:55', NULL, 'd7115d59dd796b37cdec5eefd8e0912b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-9', 'task', 'T-2', 'Q&D record system + ADR migration', NULL, 'done', 'medium', NULL, NULL, 'D-34', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'dac1a56fceb74f39d369da55bfd4e541', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-75', 'task', NULL, 'PTY: surface errno from forkpty/execve/write/ioctl failures', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'db1b61acdce93092de83a33f25a31bf8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-2', 'epic', NULL, 'Tier 0 — app + daemon handshake, empty IDE shell', NULL, 'done', 'medium', NULL, NULL, 'D-7', '2026-04-22 11:45:26', '2026-05-07 19:04:55', NULL, 'dcf6e157b37cde1402c8737ad4fb2a3f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-30', 'story', NULL, 'Implement 12px collapse spine for side panels', '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.', 'done', 'high', NULL, NULL, 'D-51', '2026-04-22 20:33:53', '2026-05-07 19:04:55', NULL, 'ddc451b330dad49f5130c463e457e81a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-76', 'task', NULL, 'PTY: fix resource leaks and reader-isolate races', NULL, 'done', 'high', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'e1cf1f93207c098bed93bf74801588eb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'story', NULL, 'output and log panel', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'e58c97f0fb119d7cb26717880d97ff37', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-67', 'story', NULL, 'review Semantics widget coverage and UI testability', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, 'e75388d59ceab20d924268f43198caf3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-28', 'task', 'T-8', 'feed POLICY.md rules into D-records', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:52', '2026-05-07 19:04:55', NULL, 'ea0b7a2e6843c7c9fc9af1e184d0edf2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-27', 'task', 'T-8', 'migrate shipped extensions from builtin/ to extensions/ per D-46', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:45', '2026-05-07 19:04:55', NULL, 'ea9ec2549a02d75b00431366f4baab2b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-40', 'task', 'T-8', 'Add PRs tab to sidebar', 'Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, 'ec754ddf900ec54777482abf019806ca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'story', NULL, 'application menu bar (Zed-style)', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:30:09', '2026-05-07 19:04:55', NULL, 'ef1017848746cf1ea807d166ed982807', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-7', 'epic', NULL, 'Tier 5 — canvas and graph view', '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. - -**Canvas pane (`builtin.canvas`):** -- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state. -- Pan/zoom via `InteractiveViewer`; node selection, drag, resize. -- Edit affordances: add note from file picker, add text node, draw edge between nodes. -- Persist layout back to the `.canvas` file on disk. - -**Graph view (`builtin.graph`):** -- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks. -- Hover highlights connected subgraph; click opens the note in the editor. -- Filter pane: tag include/exclude, file glob, depth-from-active. -- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app. - -Both 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).', 'backlog', 'medium', NULL, NULL, 'D-17', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, 'ef64435bb8bd3ef2d7ce6d12f9d60e5d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-57', 'task', NULL, 'drag tab reordering in sidebar and context panel', 'Reorder tabs in the sidebar and context panel icon rails by dragging. Persist order to project settings.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'f237fb997fdac8c68d56e0e3be0a595d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-24', 'task', 'T-3', 'secondary Claude pane UI wiring', NULL, 'done', 'medium', NULL, NULL, 'D-41', '2026-04-22 14:08:40', '2026-05-07 19:04:55', NULL, 'f72d103fdc4bc62f33bcfb68d2db97c1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-66', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-07 19:04:55', NULL, 'f74e891c8f32e9e51bb663797b6d6b34', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-35', 'story', 'T-4', 'Implement editor-above-Claude mode in middle column', '⌘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.', 'done', 'medium', NULL, NULL, 'D-49', '2026-04-22 20:34:06', '2026-05-07 19:04:55', NULL, 'f8f3da430866059531d7ee59b064639c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-11', 'task', 'T-6', 'pql skill install + stopgap removal', NULL, 'done', 'medium', NULL, NULL, 'D-39', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'ff252aebaf78340e33fa0d6d6874b5c0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-94', 'task', NULL, 'wire pql plan merge as git custom merge driver', NULL, 'cancelled', 'medium', NULL, NULL, NULL, '2026-05-07 19:08:29', '2026-05-07 19:08:52', NULL, '88cca86b7b710a161f51ad0578c48afe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-95', 'bug', NULL, 'TerminalGestureHandler tertiary-tap wired to secondary; middle-button mis-typed', '**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). - -**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. - -Trace: - -1. `TerminalGestureHandler.build` (gesture_handler.dart:65–82) wires: - ```dart - onTertiaryTapDown: onSecondaryTapDown, - onTertiaryTapUp: onSecondaryTapUp, - ``` - 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**. -2. The state''s `onTertiaryTapDown` and `onTertiaryTapUp` methods (lines 147–152) are therefore **dead** — they''re declared but never invoked anywhere. -3. `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. -4. 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`. - -**Two layers of "dead" again:** - -- The wiring is wrong, so middle-clicks dispatch as if they were right-clicks. -- 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). - -**User-visible impact:** - -A 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. - -**Resolutions, like T-93:** - -- **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. -- **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. - -Recommendation: **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. - -**Aside:** there''s a related dead branch in `onDragStart` (line 176): -```dart -details.kind == PointerDeviceKind.mouse - ? renderTerminal.selectCharacters(details.localPosition) - : renderTerminal.selectWord(details.localPosition); -``` -The `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. - -**Acceptance:** - -- Either: - - **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. - - **B:** `onTertiaryTapDown` / `onTertiaryTapUp` removed from `TerminalGestureHandler`, `TerminalGestureDetector`, and the state''s tertiary methods. Existing tests still pass. -- The `selectWord` branch in `onDragStart` is either reachable (extend PanGestureRecognizer.supportedDevices) or removed. - -**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. - -**Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic). -', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-08 10:48:59', '2026-05-08 11:01:01', NULL, '91cf3f81c256df191ddfcb3ebc6cefb1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-96', 'bug', NULL, 'NativePty reader-isolate hangs ~5% of spawns', 'The reader isolate spawned by `NativePty._spawnReaderAsync` (`lib/src/pty/native_pty.dart`) hangs intermittently — about 5% of fresh spawns — with poll() on the master fd never returning POLLIN or POLLHUP, even when the child has clearly written and exited. Probed: 2 hangs out of 30 sequential `/bin/sh -c "printf hello-pty"` spawns over a 30s/spawn timeout. +', 'done', 'high', NULL, NULL, 'D-66', '2026-05-06 20:38:14', '2026-05-07 19:04:55', NULL, 'a907d54f65ad968399d04613d4a2f10f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QKBQY7FPCNX6N28R', 'task', '06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'detect and install tmux on first launch', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:27:28', '2026-05-07 19:04:55', NULL, 'ac47da5ff5680a148aff82226ce6ca8c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7MKCHC37G69SJGQ04', 'story', NULL, 'Persist layout state across sessions', '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.', 'done', 'medium', NULL, NULL, 'D-53', '2026-04-22 20:33:58', '2026-05-07 19:04:55', NULL, 'b2153c7d4cd67c9589a95f80b6b65bb0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM57AEVK5E0GWVPGAY8', 'task', NULL, 'pane.spawn / editor.open: map errno to actionable error kinds', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-05-07 19:04:55', NULL, 'b323a71a6f615032617486ebc06e724c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42RQZVEWTF9CJSRF8', 'task', NULL, 'consolidate theme-ui into ui-design skill with theme/surface/geometry/icons references', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 13:28:58', '2026-05-07 19:04:55', NULL, 'b5c089cb0bf30ec3fff9c969baa5e41d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67JSC5RKS6M9182KG', 'task', '06FB0TNQM7162Z26FDWXG35VH0', 'Add images tab to context panel', 'Spec lists images as a right-panel section. No extension exists yet.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-05-07 19:04:55', NULL, 'bedeea36964687cded4ad249e7adba1d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YG22EV7BFTX5RTPR', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'web-tree-sitter via JS interop for browser builds', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:33', '2026-05-07 19:04:55', NULL, 'c787dd66b8ed15cc58e69683c0b67a2f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PMYC2E1X99HX4MDM', 'task', NULL, 'MultitabPane: drag-to-reorder gesture wiring', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-06 10:16:02', '2026-05-07 19:04:55', NULL, 'cd9defeed3125d8c7bc52f7c47cdeb81', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VVHNKNXPYD4XWRYW', 'story', NULL, 'Implement 12px collapse spine for side panels', '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.', 'done', 'high', NULL, NULL, 'D-51', '2026-04-22 20:33:53', '2026-05-07 19:04:55', NULL, 'd3952bbf29579fce6721345196d76e52', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5A8DYZ3BCJN1DEY7C', 'task', NULL, 'PTY read buffer: 4KB → 64KB', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:01', '2026-05-07 19:04:55', NULL, 'da519a6d00b8425ae1337a2df5089730', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YBQP75NQQ4Y9DN9C', 'task', NULL, 'inline terminal emulator (drop xterm.dart dependency)', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:52:48', '2026-05-07 19:04:55', NULL, 'dcdcdbe3b1716c317a5f80ce9137a3f3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7CEP425DNV3W7RFPM', 'task', NULL, 'tmux: isolated -L clide socket so user''s tmux config doesn''t bleed into clide', NULL, 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:05', '2026-05-07 19:04:55', NULL, 'e0fdd5b963e1907050b3ce8b758d643c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GHHRANK5532VJVJR', 'story', NULL, 'Add icon rail to right panel (context section switcher)', 'Right panel currently has no bottom icon rail. Add one matching the left sidebar rail pattern: viewer, pql graph, links, images as section tabs.', 'done', 'medium', NULL, NULL, 'D-47', '2026-04-22 20:34:03', '2026-05-07 19:04:55', NULL, 'e52e07c18fcb5ce69807191301bd621b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M1VZ8Z2SZ94F0EQR', 'epic', NULL, 'Tier 1 — Claude in xterm pane, PTY, session persistence', NULL, 'done', 'medium', NULL, NULL, 'D-41', '2026-04-22 11:45:41', '2026-05-07 19:04:55', NULL, 'e5843d5c59e66f047f632e9fc723ea18', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48MDE8ZZ82VWNY994', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'migrate shipped extensions from builtin/ to extensions/ per D-46', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:45', '2026-05-07 19:04:55', NULL, 'e6c38225ea932b419ab114733c3ce219', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7QREFG3SBG2QPYY7R', 'task', NULL, 'progress indicators in status bar', '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.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'e9a76c10008aea830f77eb3a237e3ff0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7D8AG5AY1CW58HCB4', 'task', NULL, 'activity badges on sidebar icons', 'Show unread count dots on sidebar icons: git changes count, new problems, pending notifications. Badges clear when the tab is viewed.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-07 19:04:55', NULL, 'eccccf10f621d9c78f870c08f714056c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TS48DVFDVJNZ5WGC', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'pql skill install + stopgap removal', NULL, 'done', 'medium', NULL, NULL, 'D-39', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'ef56815872eedd7254826d86cbd47b2b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59BAJSJ1JESDTPNER', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'backlinks panel — show inbound links to active file', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'f00d9c2888181b29d120e2daa18692ab', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7YHYTWJGWPFJNHEAR', 'task', '06FB0TNQM6KX7HEQJ65HZ566DG', 'Python stopgap tooling', NULL, 'done', 'medium', NULL, NULL, 'D-40', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'f107f3a9dc0013a26faff6193b4723d8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R0WRHSJV1MVCW31M', 'task', '06FB0TNQM6KX7HEQJ65HZ566DG', 'Q&D record system + ADR migration', NULL, 'done', 'medium', NULL, NULL, 'D-34', '2026-04-22 11:47:02', '2026-05-07 19:04:55', NULL, 'f17f648be8c2860e5caa2f36862b6604', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KX7HEQJ65HZ566DG', 'epic', NULL, 'Tier 0 — app + daemon handshake, empty IDE shell', NULL, 'done', 'medium', NULL, NULL, 'D-7', '2026-04-22 11:45:26', '2026-05-07 19:04:55', NULL, 'f96d937e7cf667c19f1b66e09a91c948', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7TW8EJNJ2VP306A20', 'task', '06FB0TNQM7DV4ZS010XWZJ9A84', 'pql-backed file tree — replace or augment files.ls with pql files', NULL, 'done', 'medium', NULL, NULL, 'D-3', '2026-04-22 11:47:18', '2026-05-07 19:04:55', NULL, 'fe6790873528626dd2713569184e19f0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48X0763Z9DZSM0HFR', 'task', NULL, 'wire pql plan merge as git custom merge driver', NULL, 'cancelled', 'medium', NULL, NULL, NULL, '2026-05-07 19:08:29', '2026-05-07 19:08:52', NULL, 'ea6c396c7136af7c7d3ef40c7a936692', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WCDTR41VN9P1EGMC', 'bug', NULL, 'NativePty reader-isolate hangs ~5% of spawns', 'The reader isolate spawned by `NativePty._spawnReaderAsync` (`lib/src/pty/native_pty.dart`) hangs intermittently — about 5% of fresh spawns — with poll() on the master fd never returning POLLIN or POLLHUP, even when the child has clearly written and exited. Probed: 2 hangs out of 30 sequential `/bin/sh -c "printf hello-pty"` spawns over a 30s/spawn timeout. **Root cause — found 2026-05-17 via forensic probe:** @@ -1443,11 +310,8 @@ Why not always? Lock state at fork() time is timing-dependent. ~95% of the time - `lib/src/pty/native_pty.dart:170-191` (child-side post-fork code that deadlocks) - `lib/src/pty/native_pty.dart:235-262` (reader spawn — not the bug; downstream symptom) - glibc posix_spawn docs: https://www.gnu.org/software/libc/manual/html_node/Process-Creation-Example.html -', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 17:16:01', '2026-05-17 18:11:18', NULL, '0451ff710500ecd4d62d8910a46386d1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-97', 'epic', NULL, 'Address 2026-05-14 consultant review', 'Six-reviewer external assessment found four critical gaps (workspace RCE, IPC server unimplemented, keyboard not operable, onboarding docs describe a dissolved architecture) plus ten major items and a handful of quick wins. Source: consultants.md (committed alongside this epic). Children are filed individually so they can land independently. - -Acceptance: all critical findings resolved or formally rejected with a D-record; all major findings either shipped or moved to a follow-up epic with rationale; quick-win batches closed.', 'backlog', 'high', NULL, NULL, NULL, '2026-05-17 18:47:08', '2026-05-17 18:47:08', NULL, 'b52cffa8155835713fece00ecc86f86c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-98', 'bug', 'T-97', 'fix untrusted-workspace RCE in dugite git resolution', '`toolchain_paths.dart:79` builds `''$workspaceRoot/native/dugite/bin''` and runs `_firstExisting([''$dugite/git''])` — if that file exists in the open workspace it becomes the git binary for all GitClient calls, before falling back to PATH. A malicious repo commits an executable at `native/dugite/bin/git`; clide runs it on the first auto-fired `git.status`. Arbitrary code execution from merely opening a repo. +', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 17:16:01', '2026-05-17 18:11:18', NULL, '3600ef630f2bde152f85de29b981d394', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F3V130BQXQH6HDVC', 'bug', '06FB0TNQM56CF9W94FSFDSBKGW', 'fix untrusted-workspace RCE in dugite git resolution', '`toolchain_paths.dart:79` builds `''$workspaceRoot/native/dugite/bin''` and runs `_firstExisting([''$dugite/git''])` — if that file exists in the open workspace it becomes the git binary for all GitClient calls, before falling back to PATH. A malicious repo commits an executable at `native/dugite/bin/git`; clide runs it on the first auto-fired `git.status`. Arbitrary code execution from merely opening a repo. **Fix:** resolve `native/dugite` against `Platform.resolvedExecutable`''s directory (or a known install root), never `workspaceRoot`. @@ -1456,31 +320,15 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, 2. Test that a planted `native/dugite/bin/git` in the temp-dir workspace is ignored. 3. Existing dugite-in-install-dir behavior preserved. -Source: consultants.md "Security — Findings — [Critical]".', 'backlog', 'critical', NULL, NULL, NULL, '2026-05-17 18:47:13', '2026-05-17 18:47:13', NULL, 'ead632bad678df9637914f52791ea8fe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-99', 'task', 'T-97', 'implement (or amend) the IPC socket server per D-1/D-6/D-56/D-68', 'D-56 specifies the Flutter app hosts an in-process IPC server reachable by a thin C client over a Unix socket; D-1/D-6 commit to CLI-first. Today no `ServerSocket` for unix-domain exists anywhere in `lib/`. Three IPC clients coexist (`DaemonClient` socket, `InProcessClient`, `IsolateClient` + `Backend`/`backend_entry.dart`); two service-wiring sites duplicate registration. The load-bearing CLI-first guardrail has no runtime path. D-68 (filed in this session) commits to the dual-surface plan. +Source: consultants.md "Security — Findings — [Critical]".', 'done', 'critical', NULL, NULL, NULL, '2026-05-17 18:47:13', '2026-05-17 18:55:57', NULL, '29057f361b6c923d9202bf720decc360', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QNZM15V1ZK32YS6G', 'bug', '06FB0TNQM56CF9W94FSFDSBKGW', 'add symlink re-resolution + containment re-check to files.read / files.ls', '`path_safety.dart:35-51` explicitly does not resolve symlinks; the filesystem layer (`files_commands.dart:81-85`) doesn''t either. A repo symlink `config -> /etc/shadow` passes the containment check (the link path is under root) and clide reads the target. -**Pick one path:** -- **(a)** Implement the socket server per D-56 + the C `clide` client. Delete `InProcessClient` and `IsolateClient` + `backend_entry.dart`. Wire MCP server alongside per D-68. -- **(b)** Amend D-56 to declare in-process direct dispatch as the design; delete `DaemonClient`''s socket code, `IsolateClient`, `Backend`, and `backend_entry.dart`. Re-evaluate D-1''s CLI-first claim and D-68''s dual-surface plan. +**Fix:** after `resolveUnderRoot`, call `resolveSymbolicLinksSync()` and re-verify containment against the workspace root. -**Acceptance:** -1. Exactly one IPC model exists in code. -2. `clide ` shells from a terminal can drive a running clide app (or the contract is formally rescinded). -3. Service registration happens in one place. -4. D-records updated to match reality. +**Acceptance:** test that a symlink pointing outside the workspace is rejected by both `files.read` and `files.ls`. -Source: consultants.md "Architecture — Findings — [Critical] No IPC socket server exists".', 'backlog', 'high', NULL, NULL, NULL, '2026-05-17 18:47:18', '2026-05-17 18:47:18', NULL, '0cdad0173d2328b613c390aae0d31b39', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-100', 'story', 'T-97', 'make ClideTappable keyboard-operable; add palette arrow-keys + Escape', '`ClideTappable` (`lib/widgets/src/clide_tappable.dart`) is the base of nearly every interactive widget — `ClideButton`, `_WinBtn`, `_RecentProjectRow`, `_ActionRow`, most builtin list items — and it is `MouseRegion` + `GestureDetector` only. No `Focus`, no Enter/Space handler, no focus ring. None of these widgets can be reached by Tab or activated from the keyboard. The keyboard-traversal test passes only because it externally wraps in a `Focus` node — it tests non-blocking, not operability. - -`ClidePalette` (`lib/widgets/src/clide_palette.dart`) is similarly broken: `onSubmitted` only ever invokes `filtered.first`; no up/down handling, no selected index, no selection highlight, no Escape handler. - -**Fix:** -1. Wrap `ClideTappable`''s child in `Focus` + `Shortcuts`/`Actions` so Tab focuses it and Enter/Space invokes `onTap`. Render a focus ring via the token system. -2. Add arrow-key navigation + selected-index + Escape + Enter-on-selected to `ClidePalette` (model after `_ProjectSwitcherDropdown.onKeyEvent` at `app.dart:446-452`). -3. Extend the a11y test layer to assert operability (Tab + Enter actually invokes), not just Semantics presence. - -Source: consultants.md "UX — Findings — [Critical]".', 'backlog', 'high', NULL, NULL, NULL, '2026-05-17 18:47:23', '2026-05-17 18:47:23', NULL, '9e8f220cb4fd6df1ac349dbe47977e67', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-101', 'task', 'T-97', 'rewrite README + docs/initial-plan to match current architecture', '`README.md` and `docs/initial-plan.md` describe an architecture that no longer exists. README documents `ptyc/` as a live component (`README.md:10`) and `make ptyc-build` (`:36`) — `ptyc/` is gone (D-5 amendment 2026-05-07) and the Makefile has no such target. `README.md:44` links to a `decisions/` directory that has moved to `governance/`. `docs/initial-plan.md` still describes a Go sidecar, separate `clide --daemon` long-running process, `app/` subdirectory, and `project.yaml` — all contradicted by D-5, D-56, and the single-package-at-root reality. A new contributor''s first read builds a wrong mental model. +Source: consultants.md "Security — Findings — [Major] Path-safety does not defend against symlink escape".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:33', '2026-05-17 19:01:39', NULL, '7ea8e511e8a3f5b7407a47170ccc5d12', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7NRKR9J1QTDSP9650', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'rewrite README + docs/initial-plan to match current architecture', '`README.md` and `docs/initial-plan.md` describe an architecture that no longer exists. README documents `ptyc/` as a live component (`README.md:10`) and `make ptyc-build` (`:36`) — `ptyc/` is gone (D-5 amendment 2026-05-07) and the Makefile has no such target. `README.md:44` links to a `decisions/` directory that has moved to `governance/`. `docs/initial-plan.md` still describes a Go sidecar, separate `clide --daemon` long-running process, `app/` subdirectory, and `project.yaml` — all contradicted by D-5, D-56, and the single-package-at-root reality. A new contributor''s first read builds a wrong mental model. **Fix:** 1. Rewrite `README.md`''s `ptyc/` sections; remove `make ptyc-build` references. @@ -1488,99 +336,15 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, 3. Either banner `docs/initial-plan.md` as historical (preserved as a snapshot) or extract a current `docs/architecture.md` that matches today''s code. 4. Spot-check other doc references to dissolved components. -Source: consultants.md "Docs — Findings — [Critical]".', 'backlog', 'high', NULL, NULL, NULL, '2026-05-17 18:47:29', '2026-05-17 18:47:29', NULL, 'b7279d92c6980c0617c66678531eeaa0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-102', 'bug', 'T-97', 'add symlink re-resolution + containment re-check to files.read / files.ls', '`path_safety.dart:35-51` explicitly does not resolve symlinks; the filesystem layer (`files_commands.dart:81-85`) doesn''t either. A repo symlink `config -> /etc/shadow` passes the containment check (the link path is under root) and clide reads the target. - -**Fix:** after `resolveUnderRoot`, call `resolveSymbolicLinksSync()` and re-verify containment against the workspace root. - -**Acceptance:** test that a symlink pointing outside the workspace is rejected by both `files.read` and `files.ls`. - -Source: consultants.md "Security — Findings — [Major] Path-safety does not defend against symlink escape".', 'backlog', 'high', NULL, NULL, NULL, '2026-05-17 18:47:33', '2026-05-17 18:47:33', NULL, '775f4fe49033e20f20acc844d95ecc39', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-103', 'task', 'T-97', 'add test-integration + smoke-bundle to make push-check', '`make push-check` runs `decisions-validate test-core test test-a11y coverage-gate changelog-gate`. `test-integration` and `smoke-bundle` are omitted — a boot-order regression sails through the pre-push gate. - -**Acceptance:** `make push-check` runs the integration test layer + smoke-bundle. Wall-clock budget acceptable for pre-push (target <2 min total). If they''re too slow, gate them behind a separate `make push-check-full` and document. - -Source: consultants.md "Tests — Findings — [Major] make push-check does not run integration tests".', 'backlog', 'high', NULL, NULL, NULL, '2026-05-17 18:47:39', '2026-05-17 18:47:39', NULL, 'a8fd3058339ed8100113ba03afb28b49', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-104', 'task', 'T-97', 'schema-validate IPC argument surface; reject -prefixed values', '`DaemonDispatcher.dispatch` (`dispatcher.dart:26`) and `IpcRequest.fromJson` (`envelope.dart:49`) do no schema validation. No size limit on `files.read`, no count cap on `git.log`. `git diff`/`stage` use `--` separators (good), but `checkout(branch)` and `push(remote, branch)` (`git_commands.dart:240`) do not — argument injection (e.g. `git checkout --upload-pack=...`) is possible. - -**Fix:** -1. Typed schema per IPC command (`branch`, `remote`, `path`, etc. with regex / charset constraints). -2. Size + count bounds on `files.read`, `git.log`, `git.diff`. -3. Either reject `-`-prefixed values for branch/remote/path args, or use `--` everywhere (including `checkout` and `push`). - -**Acceptance:** unit tests cover the rejection path for each injection vector listed. - -Source: consultants.md "Security — Findings — [Major]".', 'backlog', 'high', NULL, NULL, NULL, '2026-05-17 18:47:42', '2026-05-17 18:47:42', NULL, '253bf788aab85afcaad22d5983ecb7be', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-105', 'story', 'T-97', 'establish focus-traversal model; integrate FocusTracker with Flutter focus', 'No `FocusScope`/`FocusTraversalGroup` wiring across panels. `FocusTracker` (`lib/kernel/src/focus.dart`) tracks an active contribution id for the `clide active` CLI but doesn''t plug into Flutter''s focus system — it parallels it. Tab order across sidebar → workspace → context is undefined. - -**Fix:** -1. Per-slot `FocusTraversalGroup` so each panel manages its own traversal. -2. A documented top-level keybinding (e.g. `Ctrl+Shift+Tab`-style) that moves focus to the next panel. -3. Integrate `FocusTracker` with Flutter focus rather than maintaining a parallel registry. - -Depends on T-100 (ClideTappable accepting focus). - -Source: consultants.md "UX — Findings — [Major] No focus-traversal wiring".', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:47', '2026-05-17 18:47:47', NULL, 'eaadb20b91bb06cb6b9b4ef96930b773', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-106', 'bug', 'T-97', 'fix SchedulerService._startTicker isolate-spawn race', '`scheduler.dart:71`: `Isolate.spawn(_isolateEntry, _port!.sendPort).then((iso) => _isolate = iso)`. If `_stopTicker()` runs before the spawn future completes, `_isolate` is still null, nothing is killed, and the just-spawned isolate (with its `Timer.periodic`) leaks forever. +Source: consultants.md "Docs — Findings — [Critical]".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:29', '2026-05-17 19:01:39', NULL, '8cc8901d96c79925cbc0114207ccda4e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6MP9JD93ZE0CDGFFW', 'bug', '06FB0TNQM56CF9W94FSFDSBKGW', 'fix SchedulerService._startTicker isolate-spawn race', '`scheduler.dart:71`: `Isolate.spawn(_isolateEntry, _port!.sendPort).then((iso) => _isolate = iso)`. If `_stopTicker()` runs before the spawn future completes, `_isolate` is still null, nothing is killed, and the just-spawned isolate (with its `Timer.periodic`) leaks forever. Same race shape we just fixed in `NativePty` via `_readerReady` (T-96). **Fix:** track the spawn future as `_isolateReady`; `_stopTicker` awaits it before killing. -Source: consultants.md "Code quality — Findings — [Major]".', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:51', '2026-05-17 18:47:51', NULL, '24ccc6ff379afaa61621764cdc105a46', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-107', 'task', 'T-97', 'decide lib/src/terminal/ status: formally vendor or clean to project bar', '~7k LOC forked from xterm.dart in an undeclared middle state. Carries commented-out `print()` debugging (`custom_text_edit.dart:244-275`), dangling TODOs (`parser.dart:110-113`, `keytab.dart:91`), a 1137-line `parser.dart`, and the only `// ignore: invalid_use_of_protected_member` in the repo (`terminal_view.dart:363`). Memory says "code under `lib/` is owned, not vendored." - -**Decide one path and execute:** -- **(a)** Formally vendor: relocate to `native/` or document as frozen in a D-record + `licenses.yaml`. Coverage carve-out documented. -- **(b)** Clean to the project bar: remove debug prints, resolve TODOs, justify or remove the protected-member suppression, consider splitting `parser.dart`. - -Source: consultants.md "Code quality — Findings — [Major]".', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:55', '2026-05-17 18:47:55', NULL, '0a3cd3ee07488b0d76c648222cec249c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-108', 'task', 'T-97', 'replace wall-clock sleeps in watcher_test / session_test with event-driven waits', '`test/files/watcher_test.dart:67-82` uses fixed `Future.delayed`. `test/pty/session_test.dart` polls 50×100ms and uses a bare `500ms` settle. `session_test.dart`''s `timeout(5s, onTimeout: () {})` swallows the timeout — a never-producing PTY proceeds to a confusing assertion failure rather than a clear timeout. - -**Fix:** -1. Replace fixed sleeps with `expectLater(stream, emits(...))` or equivalent event-driven waits. -2. Replace swallowed `onTimeout: () {}` with `onTimeout: () => fail(''...'')`. - -Source: consultants.md "Tests — Findings — [Major]".', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:59', '2026-05-17 18:47:59', NULL, '5eacdfdce7733026d43070d103e5e67d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-109', 'task', 'T-97', 'write CONTRIBUTING.md; cut interim release to drain Unreleased; merge duplicate changelog headings', 'Three docs items: - -1. **No `CONTRIBUTING.md`.** Build/test story is scattered across `CLAUDE.md` (Claude-addressed), `README.md` (partly wrong — see T-101), and Makefile help. Write a human-addressed contributor guide: clone → `make hooks && flutter pub get` → `make test` → DQR workflow → commit conventions. -2. **CHANGELOG has duplicate subsection headings in `[Unreleased]`** — three `### Changed`, two `### Fixed`, two `### Removed` in the 2.0.0 section. Keep a Changelog 1.1.0 expects one of each per release. Merge. -3. **~80-commit Unreleased backlog** undermines the format. Cut an interim release (`vX.Y.Z`) to drain it before continuing. - -Order matters: 2 → 3 → 1 (clean changelog, release, then doc the workflow). - -Source: consultants.md "Docs — Findings".', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:03', '2026-05-17 18:48:03', NULL, 'e5983cfe63a6f472a76389954c16727c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-110', 'story', 'T-97', 'replace root KeyboardListener with scoped Shortcuts/Actions; move off keyLabel', '`app.dart:90-148` routes all shortcuts through one root `KeyboardListener` — no per-context scoping; will conflict with text-input fields once any pane wants to capture keys. `KeybindingResolver.fromKeyEvent` keys off `logicalKey.keyLabel` which is layout-dependent (US-QWERTY `Ctrl+/` differs from AZERTY `Ctrl+:`). - -**Fix:** -1. Replace the root `KeyboardListener` with scoped `Shortcuts`/`Actions` per slot. -2. Move `KeybindingResolver` to `physicalKey` or a stable mapping layer. - -Coordinate with T-100 (Focus/Shortcuts wrapper for ClideTappable) and T-105 (focus traversal). - -Source: consultants.md "UX — Findings — [Major]".', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:09', '2026-05-17 18:48:09', NULL, '5035c2e9d1c4c043045e5fcd1e7995bb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-111', 'story', 'T-97', 'drag-resize keyboard parity + clide panel resize CLI', '`drag_resize.dart` and `app.dart:870-912` are pure `Listener` pointer handlers — no keyboard equivalent, no Semantics node. Per the user/Claude parity rule (D-6), panel sizing should have a CLI affordance. Neither exists. - -**Fix:** -1. Add Semantics to drag-resize handles + keyboard activation (focus → arrow keys adjust width). -2. Add `clide panel resize ` CLI verb. - -Coordinate with T-99 (IPC implementation) if the CLI verb needs new dispatch wiring. - -Source: consultants.md "UX — Findings — [Major] Drag-resize handles".', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:14', '2026-05-17 18:48:14', NULL, '7d936b1c6561bcd6d2981786a17cc486', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-112', 'task', 'T-97', 'quick-wins: code-quality cleanups', 'Batch of small consultant findings that share a domain (code quality): - -1. Replace silent `catch (_)` in `tree_sitter_ffi.dart:197,206` with a logged last-error — `DynamicLibrary.open` failures are currently discarded. -2. Delete `Cmsghdr` alias in `libc.dart:201-202` ("backward compatibility" — CLAUDE.md forbids in a solo repo). -3. Remove `// ignore: unused_field` in `editor_controller.dart:25` (speculative retention; fix not silence). -4. Consume `libc.dart` constants / `PosixErrno` in `native_pty.dart` instead of inline hex (`0x0001`, `28`, `4`, `9`). -5. `ExtensionManager.activate` (`extensions_manager.dart:141-143`) swallows exceptions — surface a "degraded" state for the UI. -6. Verify `file_tree_view.dart:8` barrel leak (commit `5cad982` may already cover this). -7. Move `test_app.dart` out of production `main.dart` import graph (gate behind separate entrypoint or `kDebugMode`). - -Source: consultants.md (Code Quality + Architecture Findings — Minor).', 'backlog', 'low', NULL, NULL, NULL, '2026-05-17 18:48:20', '2026-05-17 18:48:20', NULL, '22c6b816bbc90c6c535d041e6a5ba3d6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-113', 'task', 'T-97', 'quick-wins: governance + docs reconciliation', 'Batch of small consultant findings that share a domain (governance/docs): +Source: consultants.md "Code quality — Findings — [Major]".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:51', '2026-05-17 19:18:43', NULL, '41a8876d254298e5384507bd7e1ba156', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WW3008NZRJWJ88HG', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'quick-wins: governance + docs reconciliation', 'Batch of small consultant findings that share a domain (governance/docs): 1. **Amend D-66** to match current floor location/value: the floor lives in `pubspec.yaml` `coverage_floor:` (single source of truth as of this session), not `coverage/floor.txt`. Add a dated amendment line. 2. **Reconcile `licenses.yaml` vs `pubspec.yaml`** — `test` version drift (`1.25.8` vs `1.30.0`), phantom `lints 5.0.0` not in pubspec. Two-step-commit rule from D-42 is being violated. @@ -1588,97 +352,13 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, 4. **Drop `--no-fatal-infos` from `ci/test.sh`** now that the import queue is clear (post test-sweep this session). 5. Add a `.claude/skills/README.md` inventory. -Source: consultants.md (Docs + Tests + Security Findings — Minor).', 'backlog', 'low', NULL, NULL, NULL, '2026-05-17 18:48:24', '2026-05-17 18:48:24', NULL, 'a917f3d6c5c83c0f95959c24de0a5d9f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-114', 'task', 'T-97', 'quick-wins: a11y + contrast gate expansion', 'Batch of small consultant findings that share a domain (a11y / visual): - -1. Expand contrast gate `canonicalPairs` (`test/a11y/contrast_test.dart`) to cover `globalTextMuted` (muted text is everywhere), the `status*` foregrounds, syntax tokens on `panelBackground`, and `panelActiveBorder`. -2. Tokenize the ~43 hardcoded-color sites the UX reviewer flagged — at minimum the modal/palette shadow and window-control colors that don''t adapt to the `paper` light theme. -3. Add a focus-ring rendering at the token level so every focusable widget gets it for free (depends on T-100). -4. Surface text-zoom (Ctrl +/-/0) in the palette so it''s discoverable. - -Source: consultants.md "UX — Findings — [Minor]" + Strengths section.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-17 18:48:28', '2026-05-17 18:48:28', NULL, 'e749bba718321bcc202cc51c55a59ee4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-98', 'bug', 'T-97', 'fix untrusted-workspace RCE in dugite git resolution', '`toolchain_paths.dart:79` builds `''$workspaceRoot/native/dugite/bin''` and runs `_firstExisting([''$dugite/git''])` — if that file exists in the open workspace it becomes the git binary for all GitClient calls, before falling back to PATH. A malicious repo commits an executable at `native/dugite/bin/git`; clide runs it on the first auto-fired `git.status`. Arbitrary code execution from merely opening a repo. - -**Fix:** resolve `native/dugite` against `Platform.resolvedExecutable`''s directory (or a known install root), never `workspaceRoot`. - -**Acceptance:** -1. Toolchain resolution does not look at any workspace path. -2. Test that a planted `native/dugite/bin/git` in the temp-dir workspace is ignored. -3. Existing dugite-in-install-dir behavior preserved. - -Source: consultants.md "Security — Findings — [Critical]".', 'done', 'critical', NULL, NULL, NULL, '2026-05-17 18:47:13', '2026-05-17 18:55:57', NULL, '40e1ed24c3cce28e2420150f1fac8fbd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-102', 'bug', 'T-97', 'add symlink re-resolution + containment re-check to files.read / files.ls', '`path_safety.dart:35-51` explicitly does not resolve symlinks; the filesystem layer (`files_commands.dart:81-85`) doesn''t either. A repo symlink `config -> /etc/shadow` passes the containment check (the link path is under root) and clide reads the target. - -**Fix:** after `resolveUnderRoot`, call `resolveSymbolicLinksSync()` and re-verify containment against the workspace root. - -**Acceptance:** test that a symlink pointing outside the workspace is rejected by both `files.read` and `files.ls`. - -Source: consultants.md "Security — Findings — [Major] Path-safety does not defend against symlink escape".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:33', '2026-05-17 19:01:39', NULL, '937111bd0e00620e7faf68859018619a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-101', 'task', 'T-97', 'rewrite README + docs/initial-plan to match current architecture', '`README.md` and `docs/initial-plan.md` describe an architecture that no longer exists. README documents `ptyc/` as a live component (`README.md:10`) and `make ptyc-build` (`:36`) — `ptyc/` is gone (D-5 amendment 2026-05-07) and the Makefile has no such target. `README.md:44` links to a `decisions/` directory that has moved to `governance/`. `docs/initial-plan.md` still describes a Go sidecar, separate `clide --daemon` long-running process, `app/` subdirectory, and `project.yaml` — all contradicted by D-5, D-56, and the single-package-at-root reality. A new contributor''s first read builds a wrong mental model. - -**Fix:** -1. Rewrite `README.md`''s `ptyc/` sections; remove `make ptyc-build` references. -2. Fix the `decisions/` link → `governance/decisions/`. -3. Either banner `docs/initial-plan.md` as historical (preserved as a snapshot) or extract a current `docs/architecture.md` that matches today''s code. -4. Spot-check other doc references to dissolved components. - -Source: consultants.md "Docs — Findings — [Critical]".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:29', '2026-05-17 19:01:39', NULL, 'a7d378267119361f2b2188977881a666', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-115', 'story', NULL, 'startup project picker; "always open this project" override', 'Startup flow change: on launch, always show the project picker screen unless the user has explicitly checked an "always open this project" box for a specific project (sticky default). - -**Acceptance:** -1. Default startup: project picker is the first screen, regardless of last-opened state. -2. Picker has an "always open this project on launch" checkbox per project entry. -3. If exactly one project has that flag set, startup skips the picker and opens that project directly. -4. The flag is per-project, persisted in app settings (probably under `app.startupProject`). -5. The checkbox can be unticked from the welcome view to restore picker-first behavior. - -**Notes:** today the app auto-opens `app.lastProject` on boot (kernel/src/project.dart `openLast`). The new flow inverts the default — picker is the steady state, sticky-open is opt-in per project. - -**Out of scope:** multi-project workspaces, project groups, recent-project ordering changes. - -**Source:** user request 2026-05-17.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 19:10:21', '2026-05-17 19:10:21', NULL, 'ce156fb92fbad8ba9bf860a4eb71d1ac', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-116', 'bug', 'T-97', 'integration_test/theme_picker_test.dart hangs pumpAndSettle on theme.pick', 'When running `flutter test integration_test/theme_picker_test.dart` against the real boot path, calling `services.commands.execute(''theme.pick'')` followed by `await tester.pumpAndSettle()` does not settle — the test hangs for 60+ seconds and never reaches the `Select theme` / `Cancel` assertions. Likely an animation loop or a never-completing Future in the modal mount path. - -Blocks T-103 from adding integration tests to `make push-check` (the other two integration tests pass after viewport + assertion fixes). - -**Acceptance:** -1. `flutter test integration_test/theme_picker_test.dart` completes in <10s. -2. Both assertions (`Select theme` visible after `theme.pick`; gone after `Cancel`) pass. -3. T-103''s `push-check-full` can include integration tests without the theme-picker hanging. - -Source: surfaced 2026-05-17 during T-103 work.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-17 19:14:31', '2026-05-17 19:14:31', NULL, 'df0c6bdd610410d4bd27bfeb2be9acbc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-113', 'task', 'T-97', 'quick-wins: governance + docs reconciliation', 'Batch of small consultant findings that share a domain (governance/docs): - -1. **Amend D-66** to match current floor location/value: the floor lives in `pubspec.yaml` `coverage_floor:` (single source of truth as of this session), not `coverage/floor.txt`. Add a dated amendment line. -2. **Reconcile `licenses.yaml` vs `pubspec.yaml`** — `test` version drift (`1.25.8` vs `1.30.0`), phantom `lints 5.0.0` not in pubspec. Two-step-commit rule from D-42 is being violated. -3. **Triage stale Q-records** — Q-1/Q-2/Q-3 ("defer until Tier 1 is in real use") are due now that Tier 1 has shipped. Q-25 (body text face) is de facto resolved by D-43/D-44. -4. **Drop `--no-fatal-infos` from `ci/test.sh`** now that the import queue is clear (post test-sweep this session). -5. Add a `.claude/skills/README.md` inventory. - -Source: consultants.md (Docs + Tests + Security Findings — Minor).', 'done', 'low', NULL, NULL, NULL, '2026-05-17 18:48:24', '2026-05-17 19:18:43', NULL, '6988f892ed906cb397bea5068fdcd30e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-106', 'bug', 'T-97', 'fix SchedulerService._startTicker isolate-spawn race', '`scheduler.dart:71`: `Isolate.spawn(_isolateEntry, _port!.sendPort).then((iso) => _isolate = iso)`. If `_stopTicker()` runs before the spawn future completes, `_isolate` is still null, nothing is killed, and the just-spawned isolate (with its `Timer.periodic`) leaks forever. - -Same race shape we just fixed in `NativePty` via `_readerReady` (T-96). - -**Fix:** track the spawn future as `_isolateReady`; `_stopTicker` awaits it before killing. - -Source: consultants.md "Code quality — Findings — [Major]".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:51', '2026-05-17 19:18:43', NULL, 'b529416bab7c66fde471f990671f3b25', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-103', 'task', 'T-97', 'add test-integration + smoke-bundle to make push-check', '`make push-check` runs `decisions-validate test-core test test-a11y coverage-gate changelog-gate`. `test-integration` and `smoke-bundle` are omitted — a boot-order regression sails through the pre-push gate. +Source: consultants.md (Docs + Tests + Security Findings — Minor).', 'done', 'low', NULL, NULL, NULL, '2026-05-17 18:48:24', '2026-05-17 19:18:43', NULL, 'aae158db5020241de743cf7541dc78c6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM493QAZNJTJQHYFGCC', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'add test-integration + smoke-bundle to make push-check', '`make push-check` runs `decisions-validate test-core test test-a11y coverage-gate changelog-gate`. `test-integration` and `smoke-bundle` are omitted — a boot-order regression sails through the pre-push gate. **Acceptance:** `make push-check` runs the integration test layer + smoke-bundle. Wall-clock budget acceptable for pre-push (target <2 min total). If they''re too slow, gate them behind a separate `make push-check-full` and document. -Source: consultants.md "Tests — Findings — [Major] make push-check does not run integration tests".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:39', '2026-05-17 19:18:43', NULL, 'cbae1915f1c89a8a2b05da71148d77dc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-100', 'story', 'T-97', 'make ClideTappable keyboard-operable; add palette arrow-keys + Escape', '`ClideTappable` (`lib/widgets/src/clide_tappable.dart`) is the base of nearly every interactive widget — `ClideButton`, `_WinBtn`, `_RecentProjectRow`, `_ActionRow`, most builtin list items — and it is `MouseRegion` + `GestureDetector` only. No `Focus`, no Enter/Space handler, no focus ring. None of these widgets can be reached by Tab or activated from the keyboard. The keyboard-traversal test passes only because it externally wraps in a `Focus` node — it tests non-blocking, not operability. - -`ClidePalette` (`lib/widgets/src/clide_palette.dart`) is similarly broken: `onSubmitted` only ever invokes `filtered.first`; no up/down handling, no selected index, no selection highlight, no Escape handler. - -**Fix:** -1. Wrap `ClideTappable`''s child in `Focus` + `Shortcuts`/`Actions` so Tab focuses it and Enter/Space invokes `onTap`. Render a focus ring via the token system. -2. Add arrow-key navigation + selected-index + Escape + Enter-on-selected to `ClidePalette` (model after `_ProjectSwitcherDropdown.onKeyEvent` at `app.dart:446-452`). -3. Extend the a11y test layer to assert operability (Tab + Enter actually invokes), not just Semantics presence. - -Source: consultants.md "UX — Findings — [Critical]".', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-17 18:47:23', '2026-05-17 19:23:24', NULL, '45607823ef1229d43dcfd0bc327a1031', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-110', 'story', 'T-97', 'replace root KeyboardListener with scoped Shortcuts/Actions; move off keyLabel', '**Superseded by T-117 — Done.** +Source: consultants.md "Tests — Findings — [Major] make push-check does not run integration tests".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:39', '2026-05-17 19:18:43', NULL, 'be329e62f9d6cf0322b67038f08883e5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6MWAKNB2NE3ZZ0XKW', 'story', '06FB0TNQM56CF9W94FSFDSBKGW', 'replace root KeyboardListener with scoped Shortcuts/Actions; move off keyLabel', '**Superseded by T-117 — Done.** The consultant findings here — single root `KeyboardListener` will conflict with text-input fields; `KebindingResolver.fromKeyEvent` keys off layout-dependent `logicalKey.keyLabel` — are both addressed by the keystroke mapper layer (T-117): @@ -1687,30 +367,16 @@ The consultant findings here — single root `KeyboardListener` will conflict wi Remaining cleanup (deletion of the now-vestigial `KeybindingResolver` class + the legacy `KeyboardListener` wrap once Flutter Shortcuts widget integration is on every feature) is small and lands as part of T-100 or its own follow-up. -Original text: Replace root KeyboardListener with scoped Shortcuts/Actions; move KebindingResolver off layout-dependent keyLabel.', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:09', '2026-05-17 19:39:12', NULL, '33850630ffa4bf1f1794fd94db8a62c3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'task', 'T-4', 'wire command palette keybinding', '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. - -**Progress (2026-05-17, T-117):** The keymap layer now binds `ctrl+shift+p` / `meta+shift+p` to `PaletteOpenIntent` in `assets/keymaps/default.yaml`. The intent resolves end-to-end through `KeymapService.resolveEvent` → `Actions.maybeInvoke`. **Still pending**: an `Actions` provider somewhere in the tree that handles `PaletteOpenIntent` by calling `kernel.palette.open()`, plus the arrow-key / Escape / Enter handlers on `ClidePalette` itself. Those land as part of T-100 (palette keyboard nav). - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Bindings live in the keymap (T-117) — not in `lib/kernel/src/commands/keybindings.dart` (that file is legacy). -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-05-17 19:39:23', NULL, 'c355fdc971b62e0aedbfb99ed4134678', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-66', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows. +Original text: Replace root KeyboardListener with scoped Shortcuts/Actions; move KebindingResolver off layout-dependent keyLabel.', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:09', '2026-05-17 19:39:12', NULL, '0b55dbdf9c854b5cde5bde806187d512', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM60QRRNEEWG84VWKXC', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows. **Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.). **Acceptance:** 1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings. 2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve. -3. A regression test exercises a representative subset (e.g. shift+shift → quick-open command — see Q-9 if the search-everywhere overlay needs its own intent).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-17 19:39:37', NULL, '633ba8ffaee17af0b6ca6d2cc2a7549d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-64', 'story', NULL, 'VS Code keybinding preset', '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. +3. A regression test exercises a representative subset (e.g. shift+shift → quick-open command — see Q-9 if the search-everywhere overlay needs its own intent).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-17 19:39:37', NULL, '35d89793092f79d3668bea287b346301', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42M9RK4399B4F4WSG', 'story', NULL, 'VS Code keybinding preset', '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. **Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today. @@ -1720,20 +386,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, 3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, …). 4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p → quick-open command, ctrl+shift+p → palette). -**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-17 19:39:37', NULL, '660a4871f59f940fbc2296b36177e0d8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', '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. - -**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place; modal Vim presets are more involved than the VS Code preset (T-64) because modes need to be expressed as scope flags (`vim.normal`, `vim.insert`, `vim.visual`) that the when-clause grammar can branch on. Implementation work: - -1. Author `assets/keymaps/vim.yaml` using the typed Intents + `command:` bindings. -2. Add a small mode-tracking service that publishes `vim.` scope flags via `KeymapService.setScopeFlag`. -3. Bind `Esc` to mode-reset → normal; `i` (when `vim.normal`) → enter insert; etc. - -**Acceptance:** -1. `assets/keymaps/vim.yaml` ships covering the documented Vim default keybindings for editor / navigation / panes. -2. `KeymapService.setPreset("vim")` + the mode-tracking service together produce correct mode transitions. -3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-17 19:39:37', NULL, '8db44365ff24e322ccc4bf145e31c5c5', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-117', 'task', 'T-97', 'keystroke mapper layer — intents + presets + when-clauses + overlay', 'Build the keymap mechanism that all keyboard-driven work plugs into. Lands before T-100 so widget wiring (ClideTappable, ClidePalette, focus traversal) goes through typed Intents, not raw key handlers. Closes T-110 (consultant: scoped Shortcuts/Actions; move off layout-dependent keyLabel). Unblocks T-64/T-65/T-66 (vim/vscode/jetbrains preset data). +**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-05-17 19:39:37', NULL, 'de6e0f61b152438b5ec6f506020bd707', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65SSVGN5M36T4QNDC', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'keystroke mapper layer — intents + presets + when-clauses + overlay', 'Build the keymap mechanism that all keyboard-driven work plugs into. Lands before T-100 so widget wiring (ClideTappable, ClidePalette, focus traversal) goes through typed Intents, not raw key handlers. Closes T-110 (consultant: scoped Shortcuts/Actions; move off layout-dependent keyLabel). Unblocks T-64/T-65/T-66 (vim/vscode/jetbrains preset data). ## Design (locked 2026-05-17) @@ -1760,8 +414,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - A settings UI for editing bindings (file editing + preset switching is enough for v1). - ClideTappable/Palette widget integration (T-100 — comes immediately after this lands). -Source: 2026-05-17 design conversation; supersedes T-110.', 'done', 'high', NULL, NULL, NULL, '2026-05-17 19:28:28', '2026-05-17 19:39:40', NULL, '92d68025f0ee7c4d133dca65e97c97b8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-100', 'story', 'T-97', 'make ClideTappable keyboard-operable; add palette arrow-keys + Escape', '`ClideTappable` (`lib/widgets/src/clide_tappable.dart`) is the base of nearly every interactive widget — `ClideButton`, `_WinBtn`, `_RecentProjectRow`, `_ActionRow`, most builtin list items — and it is `MouseRegion` + `GestureDetector` only. No `Focus`, no Enter/Space handler, no focus ring. None of these widgets can be reached by Tab or activated from the keyboard. The keyboard-traversal test passes only because it externally wraps in a `Focus` node — it tests non-blocking, not operability. +Source: 2026-05-17 design conversation; supersedes T-110.', 'done', 'high', NULL, NULL, NULL, '2026-05-17 19:28:28', '2026-05-17 19:39:40', NULL, 'a52184b3075848c2f955b46afa3e79d5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4CWANNSKNF2X5JEJ4', 'story', '06FB0TNQM56CF9W94FSFDSBKGW', 'make ClideTappable keyboard-operable; add palette arrow-keys + Escape', '`ClideTappable` (`lib/widgets/src/clide_tappable.dart`) is the base of nearly every interactive widget — `ClideButton`, `_WinBtn`, `_RecentProjectRow`, `_ActionRow`, most builtin list items — and it is `MouseRegion` + `GestureDetector` only. No `Focus`, no Enter/Space handler, no focus ring. None of these widgets can be reached by Tab or activated from the keyboard. The keyboard-traversal test passes only because it externally wraps in a `Focus` node — it tests non-blocking, not operability. `ClidePalette` (`lib/widgets/src/clide_palette.dart`) is similarly broken: `onSubmitted` only ever invokes `filtered.first`; no up/down handling, no selected index, no selection highlight, no Escape handler. @@ -1770,8 +424,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, 2. Add arrow-key navigation + selected-index + Escape + Enter-on-selected to `ClidePalette` (model after `_ProjectSwitcherDropdown.onKeyEvent` at `app.dart:446-452`). 3. Extend the a11y test layer to assert operability (Tab + Enter actually invokes), not just Semantics presence. -Source: consultants.md "UX — Findings — [Critical]".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:23', '2026-05-17 19:47:50', NULL, 'f6f0c34a700b133bc84c6d8d7ea450a3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-105', 'story', 'T-97', 'establish focus-traversal model; integrate FocusTracker with Flutter focus', 'No `FocusScope`/`FocusTraversalGroup` wiring across panels. `FocusTracker` (`lib/kernel/src/focus.dart`) tracks an active contribution id for the `clide active` CLI but doesn''t plug into Flutter''s focus system — it parallels it. Tab order across sidebar → workspace → context is undefined. +Source: consultants.md "UX — Findings — [Critical]".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:23', '2026-05-17 19:47:50', NULL, 'e049271ae2749e4e1557d2e35c14caac', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63H3CXCKB13E3RNHW', 'story', '06FB0TNQM56CF9W94FSFDSBKGW', 'establish focus-traversal model; integrate FocusTracker with Flutter focus', 'No `FocusScope`/`FocusTraversalGroup` wiring across panels. `FocusTracker` (`lib/kernel/src/focus.dart`) tracks an active contribution id for the `clide active` CLI but doesn''t plug into Flutter''s focus system — it parallels it. Tab order across sidebar → workspace → context is undefined. **Fix:** 1. Per-slot `FocusTraversalGroup` so each panel manages its own traversal. @@ -1780,15 +434,15 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, Depends on T-100 (ClideTappable accepting focus). -Source: consultants.md "UX — Findings — [Major] No focus-traversal wiring".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:47', '2026-05-17 19:56:28', NULL, '0715d1ded0d12083622f8a46d570f4d9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-108', 'task', 'T-97', 'replace wall-clock sleeps in watcher_test / session_test with event-driven waits', '`test/files/watcher_test.dart:67-82` uses fixed `Future.delayed`. `test/pty/session_test.dart` polls 50×100ms and uses a bare `500ms` settle. `session_test.dart`''s `timeout(5s, onTimeout: () {})` swallows the timeout — a never-producing PTY proceeds to a confusing assertion failure rather than a clear timeout. +Source: consultants.md "UX — Findings — [Major] No focus-traversal wiring".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:47', '2026-05-17 19:56:28', NULL, 'b5088d1873da937dbe2bd266b361f3a5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40PV7G53JMHHNYPYG', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'replace wall-clock sleeps in watcher_test / session_test with event-driven waits', '`test/files/watcher_test.dart:67-82` uses fixed `Future.delayed`. `test/pty/session_test.dart` polls 50×100ms and uses a bare `500ms` settle. `session_test.dart`''s `timeout(5s, onTimeout: () {})` swallows the timeout — a never-producing PTY proceeds to a confusing assertion failure rather than a clear timeout. **Fix:** 1. Replace fixed sleeps with `expectLater(stream, emits(...))` or equivalent event-driven waits. 2. Replace swallowed `onTimeout: () {}` with `onTimeout: () => fail(''...'')`. -Source: consultants.md "Tests — Findings — [Major]".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:59', '2026-05-17 20:05:38', NULL, '33e15d84caed686e88fc42f1f4a3586c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-112', 'task', 'T-97', 'quick-wins: code-quality cleanups', 'Batch of small consultant findings that share a domain (code quality): +Source: consultants.md "Tests — Findings — [Major]".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:59', '2026-05-17 20:05:38', NULL, 'ed94e5ac754a620ee81220c59ff4386d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4ZNR15BSANVX07N1R', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'quick-wins: code-quality cleanups', 'Batch of small consultant findings that share a domain (code quality): 1. Replace silent `catch (_)` in `tree_sitter_ffi.dart:197,206` with a logged last-error — `DynamicLibrary.open` failures are currently discarded. 2. Delete `Cmsghdr` alias in `libc.dart:201-202` ("backward compatibility" — CLAUDE.md forbids in a solo repo). @@ -1798,16 +452,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, 6. Verify `file_tree_view.dart:8` barrel leak (commit `5cad982` may already cover this). 7. Move `test_app.dart` out of production `main.dart` import graph (gate behind separate entrypoint or `kDebugMode`). -Source: consultants.md (Code Quality + Architecture Findings — Minor).', 'done', 'low', NULL, NULL, NULL, '2026-05-17 18:48:20', '2026-05-17 20:11:16', NULL, 'fccc783ff24be83f6d5a498e936c4af9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-114', 'task', 'T-97', 'quick-wins: a11y + contrast gate expansion', 'Batch of small consultant findings that share a domain (a11y / visual): - -1. Expand contrast gate `canonicalPairs` (`test/a11y/contrast_test.dart`) to cover `globalTextMuted` (muted text is everywhere), the `status*` foregrounds, syntax tokens on `panelBackground`, and `panelActiveBorder`. -2. Tokenize the ~43 hardcoded-color sites the UX reviewer flagged — at minimum the modal/palette shadow and window-control colors that don''t adapt to the `paper` light theme. -3. Add a focus-ring rendering at the token level so every focusable widget gets it for free (depends on T-100). -4. Surface text-zoom (Ctrl +/-/0) in the palette so it''s discoverable. - -Source: consultants.md "UX — Findings — [Minor]" + Strengths section.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-17 18:48:28', '2026-05-17 20:13:36', NULL, '1a2776ce1e3dd335966d738f80784a0a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-118', 'task', 'T-97', 'add high-contrast + colour-blind theme variants', 'Per D-69, the four named themes (clide, midnight, paper, terminal) keep +Source: consultants.md (Code Quality + Architecture Findings — Minor).', 'done', 'low', NULL, NULL, NULL, '2026-05-17 18:48:20', '2026-05-17 20:11:16', NULL, '7f7b369ec7d257859d122dd9bdc31ef7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W20PVMHX1BBJ4TVW', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'add high-contrast + colour-blind theme variants', 'Per D-69, the four named themes (clide, midnight, paper, terminal) keep their published palettes and only pass the baseline canonicalPairs gate. The stricter extendedPairs set in lib/kernel/src/theme/contrast.dart (muted text, status chips, syntax @@ -1828,16 +474,16 @@ Scope: 4. The contrast gate at test/a11y/contrast_test.dart already covers them — adding the files is enough. -Sources: D-22, D-69, consultants.md ("UX — Findings — [Minor]").', 'done', 'low', NULL, NULL, 'D-69', '2026-05-17 20:51:47', '2026-05-17 20:53:41', NULL, 'd163731890022856585e9f33432462f6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-114', 'task', 'T-97', 'quick-wins: a11y + contrast gate expansion', 'Batch of small consultant findings that share a domain (a11y / visual): +Sources: D-22, D-69, consultants.md ("UX — Findings — [Minor]").', 'done', 'low', NULL, NULL, 'D-69', '2026-05-17 20:51:47', '2026-05-17 20:53:41', NULL, '61ba5bea84a89e1f72d013460b81ab45', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7FG92QKNFJSCAP888', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'quick-wins: a11y + contrast gate expansion', 'Batch of small consultant findings that share a domain (a11y / visual): 1. Expand contrast gate `canonicalPairs` (`test/a11y/contrast_test.dart`) to cover `globalTextMuted` (muted text is everywhere), the `status*` foregrounds, syntax tokens on `panelBackground`, and `panelActiveBorder`. 2. Tokenize the ~43 hardcoded-color sites the UX reviewer flagged — at minimum the modal/palette shadow and window-control colors that don''t adapt to the `paper` light theme. 3. Add a focus-ring rendering at the token level so every focusable widget gets it for free (depends on T-100). 4. Surface text-zoom (Ctrl +/-/0) in the palette so it''s discoverable. -Source: consultants.md "UX — Findings — [Minor]" + Strengths section.', 'done', 'low', NULL, NULL, NULL, '2026-05-17 18:48:28', '2026-05-18 07:04:38', NULL, '70c65844ee3e52bfcd6220b77ab8957a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-111', 'story', 'T-97', 'drag-resize keyboard parity + clide panel resize CLI', '`drag_resize.dart` and `app.dart:870-912` are pure `Listener` pointer handlers — no keyboard equivalent, no Semantics node. Per the user/Claude parity rule (D-6), panel sizing should have a CLI affordance. Neither exists. +Source: consultants.md "UX — Findings — [Minor]" + Strengths section.', 'done', 'low', NULL, NULL, NULL, '2026-05-17 18:48:28', '2026-05-18 07:04:38', NULL, 'ab216d9c583bac8285588827a271115d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7Y9KHNVRZ41VYW1YM', 'story', '06FB0TNQM56CF9W94FSFDSBKGW', 'drag-resize keyboard parity + clide panel resize CLI', '`drag_resize.dart` and `app.dart:870-912` are pure `Listener` pointer handlers — no keyboard equivalent, no Semantics node. Per the user/Claude parity rule (D-6), panel sizing should have a CLI affordance. Neither exists. **Fix:** 1. Add Semantics to drag-resize handles + keyboard activation (focus → arrow keys adjust width). @@ -1845,18 +491,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, Coordinate with T-99 (IPC implementation) if the CLI verb needs new dispatch wiring. -Source: consultants.md "UX — Findings — [Major] Drag-resize handles".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:14', '2026-05-18 07:43:45', NULL, '594d1cbcc8daa115246e2398d9ba9506', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'task', 'T-99', 'clide panel resize CLI verb (split from T-111)', 'Once T-99 lands an IPC dispatch path, register a ''panel.resize'' command that takes and args and calls LayoutArrangement.setSize / setEditorRatio. Keyboard parity already landed in T-111; this completes user/Claude parity per D-6.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-18 07:43:50', '2026-05-18 07:43:50', NULL, '29df32cde1a30168a27b763f5e6ce81d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'task', 'T-97', 'typed IPC command schema framework (split from T-104)', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. - -This needs design before code: -- Where the schema lives (per-handler? a central registry?) -- How it composes with the IpcRequest envelope -- Whether it generates the existing handler boilerplate or wraps it -- Coordination with T-99''s IPC architecture decision (if we go socket-server, the schema becomes the wire contract) - -Source: consultants.md "Security — Findings — [Major]" item 1.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-18 07:53:08', '2026-05-18 07:53:08', NULL, 'c2673e06e042bb418de9a99e8ab21c2b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-104', 'task', 'T-97', 'schema-validate IPC argument surface; reject -prefixed values', '`DaemonDispatcher.dispatch` (`dispatcher.dart:26`) and `IpcRequest.fromJson` (`envelope.dart:49`) do no schema validation. No size limit on `files.read`, no count cap on `git.log`. `git diff`/`stage` use `--` separators (good), but `checkout(branch)` and `push(remote, branch)` (`git_commands.dart:240`) do not — argument injection (e.g. `git checkout --upload-pack=...`) is possible. +Source: consultants.md "UX — Findings — [Major] Drag-resize handles".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:14', '2026-05-18 07:43:45', NULL, '711d757f542600cc20738194f5fe47d2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5W67PPRVA8SAJXHFG', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'schema-validate IPC argument surface; reject -prefixed values', '`DaemonDispatcher.dispatch` (`dispatcher.dart:26`) and `IpcRequest.fromJson` (`envelope.dart:49`) do no schema validation. No size limit on `files.read`, no count cap on `git.log`. `git diff`/`stage` use `--` separators (good), but `checkout(branch)` and `push(remote, branch)` (`git_commands.dart:240`) do not — argument injection (e.g. `git checkout --upload-pack=...`) is possible. **Fix:** 1. Typed schema per IPC command (`branch`, `remote`, `path`, etc. with regex / charset constraints). @@ -1865,8 +501,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, **Acceptance:** unit tests cover the rejection path for each injection vector listed. -Source: consultants.md "Security — Findings — [Major]".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:42', '2026-05-18 07:53:10', NULL, '688f888ba89cefe636ece09f32bea8ab', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-109', 'task', 'T-97', 'write CONTRIBUTING.md; cut interim release to drain Unreleased; merge duplicate changelog headings', 'Three docs items: +Source: consultants.md "Security — Findings — [Major]".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:42', '2026-05-18 07:53:10', NULL, '6d17568c12cbcab8832650e45b190213', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TTAC53CQYWDQ91H0', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'write CONTRIBUTING.md; cut interim release to drain Unreleased; merge duplicate changelog headings', 'Three docs items: 1. **No `CONTRIBUTING.md`.** Build/test story is scattered across `CLAUDE.md` (Claude-addressed), `README.md` (partly wrong — see T-101), and Makefile help. Write a human-addressed contributor guide: clone → `make hooks && flutter pub get` → `make test` → DQR workflow → commit conventions. 2. **CHANGELOG has duplicate subsection headings in `[Unreleased]`** — three `### Changed`, two `### Fixed`, two `### Removed` in the 2.0.0 section. Keep a Changelog 1.1.0 expects one of each per release. Merge. @@ -1874,8 +510,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, Order matters: 2 → 3 → 1 (clean changelog, release, then doc the workflow). -Source: consultants.md "Docs — Findings".', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:03', '2026-05-18 08:02:06', NULL, '01396c3a11fb562a859cdf76f933824c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-121', 'task', 'T-97', 'cut interim release to drain Unreleased (split from T-109)', 'Item 3 of T-109. With CONTRIBUTING.md landed and [Unreleased] consolidated, the remaining piece is cutting an actual release so the ~80-commit Unreleased backlog drains into a dated heading. +Source: consultants.md "Docs — Findings".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:03', '2026-05-18 08:07:23', NULL, 'e7e81d29221956cdaa80068e8a406ee6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45DS16DX0NKMN9Q3G', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'cut interim release to drain Unreleased (split from T-109)', 'Item 3 of T-109. With CONTRIBUTING.md landed and [Unreleased] consolidated, the remaining piece is cutting an actual release so the ~80-commit Unreleased backlog drains into a dated heading. Per .claude/skills/git-commit/SKILL.md "Cutting a release": 1. Move every [Unreleased] entry under a new `## [X.Y.Z] — YYYY-MM-DD` heading. @@ -1885,28 +521,8 @@ Per .claude/skills/git-commit/SKILL.md "Cutting a release": Version pick is the user''s call — likely 2.1.0 given the scope since 2.0.0 (D-66 95% coverage gate, T-117 keymap, contrast-gate split, IPC hardening, panel-keyboard parity, etc.). -Source: consultants.md "Docs — Findings" item 3.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-18 08:07:23', '2026-05-18 08:07:23', NULL, '74aa71a8c832231c1debb66c767cf490', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-109', 'task', 'T-97', 'write CONTRIBUTING.md; cut interim release to drain Unreleased; merge duplicate changelog headings', 'Three docs items: - -1. **No `CONTRIBUTING.md`.** Build/test story is scattered across `CLAUDE.md` (Claude-addressed), `README.md` (partly wrong — see T-101), and Makefile help. Write a human-addressed contributor guide: clone → `make hooks && flutter pub get` → `make test` → DQR workflow → commit conventions. -2. **CHANGELOG has duplicate subsection headings in `[Unreleased]`** — three `### Changed`, two `### Fixed`, two `### Removed` in the 2.0.0 section. Keep a Changelog 1.1.0 expects one of each per release. Merge. -3. **~80-commit Unreleased backlog** undermines the format. Cut an interim release (`vX.Y.Z`) to drain it before continuing. - -Order matters: 2 → 3 → 1 (clean changelog, release, then doc the workflow). - -Source: consultants.md "Docs — Findings".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:03', '2026-05-18 08:07:23', NULL, '7c1f64a5aef6e3e92157691a2b192beb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-121', 'task', 'T-97', 'cut interim release to drain Unreleased (split from T-109)', 'Item 3 of T-109. With CONTRIBUTING.md landed and [Unreleased] consolidated, the remaining piece is cutting an actual release so the ~80-commit Unreleased backlog drains into a dated heading. - -Per .claude/skills/git-commit/SKILL.md "Cutting a release": -1. Move every [Unreleased] entry under a new `## [X.Y.Z] — YYYY-MM-DD` heading. -2. Leave an empty [Unreleased] skeleton. -3. Bump pubspec.yaml version: (drop the -dev suffix for the tag). -4. Commit subject: `release vX.Y.Z`. - -Version pick is the user''s call — likely 2.1.0 given the scope since 2.0.0 (D-66 95% coverage gate, T-117 keymap, contrast-gate split, IPC hardening, panel-keyboard parity, etc.). - -Source: consultants.md "Docs — Findings" item 3.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 08:07:23', '2026-05-18 08:22:19', NULL, '6f9f48da5d95074af1abed3df64b109a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-115', 'story', NULL, 'startup project picker; "always open this project" override', 'Startup flow change: on launch, always show the project picker screen unless the user has explicitly checked an "always open this project" box for a specific project (sticky default). +Source: consultants.md "Docs — Findings" item 3.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 08:07:23', '2026-05-18 08:22:19', NULL, '740bb2fcfc0a793f18cb8d6e93e85cae', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM614M7ES54V3SETAQM', 'story', NULL, 'startup project picker; "always open this project" override', 'Startup flow change: on launch, always show the project picker screen unless the user has explicitly checked an "always open this project" box for a specific project (sticky default). **Acceptance:** 1. Default startup: project picker is the first screen, regardless of last-opened state. @@ -1919,39 +535,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, **Out of scope:** multi-project workspaces, project groups, recent-project ordering changes. -**Source:** user request 2026-05-17.', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 19:10:21', '2026-05-18 08:26:05', NULL, 'dc53a7ac8809206c2824d316750ec28a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'task', 'T-115', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle', 'T-115''s render-only widget test at test/builtin/welcome/widget_test.dart ''sticky-startup toggle renders on a seeded recent row'' verifies the toggle is constructed but does not exercise the tap → setStickyStartup path. Adding a tap-driven assertion hung the 10-minute Flutter test timeout consistently (find.byTooltip, find.bySemanticsLabel, and find.byKey + tap variants all reproduced). - -Hypothesis: ClideTooltip wraps the toggle in a MouseRegion whose onEnter awaits Future.delayed(showDelay). Some path during tap simulation (or surrounding pump scheduling) keeps that timer pending, and the test runner waits the full 10-minute idle timeout before declaring it stuck. - -Next steps: -- Reproduce in isolation against a stripped-down harness pumping just the _StickyToggle widget. -- Determine whether the hang is the MouseRegion timer or another future (e.g., the welcome-tab activation, toolchain check). -- Either fix the underlying cause or use fakeAsync.run() to drain the timers explicitly. - -Until then, ProjectManager unit tests in test/kernel/src/project_test.dart cover the sticky-startup logic (14 cases including round-trip, no-op, idempotent flip, ambiguity, and openStickyOrNothing).', 'backlog', 'low', NULL, NULL, NULL, '2026-05-18 09:06:59', '2026-05-18 09:06:59', NULL, '16b6c7735880fe331798c0164b84da00', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-115', 'story', NULL, 'startup project picker; "always open this project" override', 'Startup flow change: on launch, always show the project picker screen unless the user has explicitly checked an "always open this project" box for a specific project (sticky default). - -**Acceptance:** -1. Default startup: project picker is the first screen, regardless of last-opened state. -2. Picker has an "always open this project on launch" checkbox per project entry. -3. If exactly one project has that flag set, startup skips the picker and opens that project directly. -4. The flag is per-project, persisted in app settings (probably under `app.startupProject`). -5. The checkbox can be unticked from the welcome view to restore picker-first behavior. - -**Notes:** today the app auto-opens `app.lastProject` on boot (kernel/src/project.dart `openLast`). The new flow inverts the default — picker is the steady state, sticky-open is opt-in per project. - -**Out of scope:** multi-project workspaces, project groups, recent-project ordering changes. - -**Source:** user request 2026-05-17.', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 19:10:21', '2026-05-18 09:30:59', NULL, 'c88ab1b158beddb3cd74ad9a287bd224', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-107', 'task', 'T-97', 'decide lib/src/terminal/ status: formally vendor or clean to project bar', '~7k LOC forked from xterm.dart in an undeclared middle state. Carries commented-out `print()` debugging (`custom_text_edit.dart:244-275`), dangling TODOs (`parser.dart:110-113`, `keytab.dart:91`), a 1137-line `parser.dart`, and the only `// ignore: invalid_use_of_protected_member` in the repo (`terminal_view.dart:363`). Memory says "code under `lib/` is owned, not vendored." - -**Decide one path and execute:** -- **(a)** Formally vendor: relocate to `native/` or document as frozen in a D-record + `licenses.yaml`. Coverage carve-out documented. -- **(b)** Clean to the project bar: remove debug prints, resolve TODOs, justify or remove the protected-member suppression, consider splitting `parser.dart`. - -Source: consultants.md "Code quality — Findings — [Major]".', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:55', '2026-05-18 10:27:25', NULL, 'c6716898300053ff1a611a5e186c03fa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-123', 'task', 'T-91', 'split lib/src/terminal/.../escape/parser.dart (1139 LOC)', 'parser.dart is a single 1139-line file containing the full ESC/CSI/OSC/DCS handler tree for the terminal emulator. Functional but unwieldy; the consultant flagged it as ''consider splitting'' in the T-107 review. +**Source:** user request 2026-05-17.', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 19:10:21', '2026-05-18 09:30:59', NULL, 'e5895d60605001ae4d69dffdc089b2ff', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4RJ9K19P5AX14RHRR', 'task', '06FB0TNQM4HGK6KR0314P2M32M', 'split lib/src/terminal/.../escape/parser.dart (1139 LOC)', 'parser.dart is a single 1139-line file containing the full ESC/CSI/OSC/DCS handler tree for the terminal emulator. Functional but unwieldy; the consultant flagged it as ''consider splitting'' in the T-107 review. Suggested split (sequenced with the T-91 coverage sweep on lib/src/terminal/, so the split doesn''t fight in-flight test work): @@ -1968,15 +553,15 @@ Done when: - All existing parser tests pass without changes - No new public surface; everything stays library-private -Source: T-107 / consultants.md "Code quality — Findings — [Major]".', 'backlog', 'low', NULL, NULL, NULL, '2026-05-18 10:29:02', '2026-05-18 10:29:02', NULL, '1ac51fb8f92a9e72903c16e31f076955', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-107', 'task', 'T-97', 'decide lib/src/terminal/ status: formally vendor or clean to project bar', '~7k LOC forked from xterm.dart in an undeclared middle state. Carries commented-out `print()` debugging (`custom_text_edit.dart:244-275`), dangling TODOs (`parser.dart:110-113`, `keytab.dart:91`), a 1137-line `parser.dart`, and the only `// ignore: invalid_use_of_protected_member` in the repo (`terminal_view.dart:363`). Memory says "code under `lib/` is owned, not vendored." +Source: T-107 / consultants.md "Code quality — Findings — [Major]".', 'backlog', 'low', NULL, NULL, NULL, '2026-05-18 10:29:02', '2026-05-18 10:29:02', NULL, 'd812c0c9d1401967041981dff920fed8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7FXS713CKYDZ284NC', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'decide lib/src/terminal/ status: formally vendor or clean to project bar', '~7k LOC forked from xterm.dart in an undeclared middle state. Carries commented-out `print()` debugging (`custom_text_edit.dart:244-275`), dangling TODOs (`parser.dart:110-113`, `keytab.dart:91`), a 1137-line `parser.dart`, and the only `// ignore: invalid_use_of_protected_member` in the repo (`terminal_view.dart:363`). Memory says "code under `lib/` is owned, not vendored." **Decide one path and execute:** - **(a)** Formally vendor: relocate to `native/` or document as frozen in a D-record + `licenses.yaml`. Coverage carve-out documented. - **(b)** Clean to the project bar: remove debug prints, resolve TODOs, justify or remove the protected-member suppression, consider splitting `parser.dart`. -Source: consultants.md "Code quality — Findings — [Major]".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:55', '2026-05-18 10:30:30', NULL, '930012b3c62d18d9fe242c7d5879a347', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-116', 'bug', 'T-97', 'integration_test/theme_picker_test.dart hangs pumpAndSettle on theme.pick', 'When running `flutter test integration_test/theme_picker_test.dart` against the real boot path, calling `services.commands.execute(''theme.pick'')` followed by `await tester.pumpAndSettle()` does not settle — the test hangs for 60+ seconds and never reaches the `Select theme` / `Cancel` assertions. Likely an animation loop or a never-completing Future in the modal mount path. +Source: consultants.md "Code quality — Findings — [Major]".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:55', '2026-05-18 10:30:30', NULL, '49a34b2f3d09c36a62fac63a4a70444b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4H5C2Z6GCBCQ85QGR', 'bug', '06FB0TNQM56CF9W94FSFDSBKGW', 'integration_test/theme_picker_test.dart hangs pumpAndSettle on theme.pick', 'When running `flutter test integration_test/theme_picker_test.dart` against the real boot path, calling `services.commands.execute(''theme.pick'')` followed by `await tester.pumpAndSettle()` does not settle — the test hangs for 60+ seconds and never reaches the `Select theme` / `Cancel` assertions. Likely an animation loop or a never-completing Future in the modal mount path. Blocks T-103 from adding integration tests to `make push-check` (the other two integration tests pass after viewport + assertion fixes). @@ -1985,8 +570,8 @@ Blocks T-103 from adding integration tests to `make push-check` (the other two i 2. Both assertions (`Select theme` visible after `theme.pick`; gone after `Cancel`) pass. 3. T-103''s `push-check-full` can include integration tests without the theme-picker hanging. -Source: surfaced 2026-05-17 during T-103 work.', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 19:14:31', '2026-05-18 11:22:46', NULL, 'ff3b728310bd265a57a60778417d026f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-74', 'task', NULL, 'forward real mouse events to TUI apps (not just PgUp/PgDown)', '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. +Source: surfaced 2026-05-17 during T-103 work.', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 19:14:31', '2026-05-18 11:22:46', NULL, '1f5a84168d1d82828c77cbe3f269a0aa', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53N56Z60B4SQ5VTRG', 'task', NULL, 'forward real mouse events to TUI apps (not just PgUp/PgDown)', '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. **Acceptance:** - 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`). @@ -1994,88 +579,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode. - The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed. -**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.', 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:22', '2026-05-18 11:51:40', NULL, '20b66d93b9315ad51170a6f8762d4abe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-125', 'task', 'T-99', 'argv→IpcRequest translator on the Dart side', 'Second slice of T-99(a). Owns the CLI grammar so the C client stays a dumb pipe. - -Add lib/src/cli/argv_to_request.dart: takes a List (the argv after `clide`) and returns an IpcRequest. Handles `clide [positionals...] [--flag value] [-- argv...]` per D-6''s shape. Errors (unknown subsystem/verb, malformed flag) surface as IpcResponse.err with the right exit code per pql''s contract. - -Acceptance: -1. Pure-Dart function with no I/O; unit-testable. -2. Covers every active subsystem from D-6''s list (pane/tab/open/editor/panel/tree/git/pql/canvas/graph/theme/settings/project) at least to the level of "recognise the verb". -3. Round-trip tests against the existing IpcRequest envelope. -4. Sysexit-code parity with pql (0/1/2/3/4 + 64-78 reserved). - -Source: T-99 sketch.', 'backlog', 'high', NULL, NULL, NULL, '2026-05-18 11:58:52', '2026-05-18 11:58:52', NULL, '807086680d24e2394bf13a4f25fb6998', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-126', 'task', 'T-99', 'thin C `clide` client binary in native/clide-cli/', 'Third slice of T-99(a). The user-facing entry point. - -~150 LOC of C in native/clide-cli/. Resolves the socket path (env override + default), connects, sends a single JSON-line request shaped as {"argv": [...]} (parsing happens in Dart per T-125), reads the JSON-line response, writes stdout/stderr per the pql contract, exits with the response''s exit code. - -Acceptance: -1. native/clide-cli/clide compiles on Linux + macOS via the existing native build harness (same shape as dugite / libtree-sitter.so). -2. Binary lands in the install bundle; runs from the user''s PATH after `make build-linux` / `make build-macos`. -3. `clide status` against a running app returns the status JSON and exits 0. -4. `clide nonsense` returns the right exit code (sysexit 64 = usage error per D-6). -5. Documented in assets/licenses.yaml + a one-paragraph note in CONTRIBUTING.md. - -Source: T-99 sketch. Depends on T-124 (server) + T-125 (argv translator).', 'backlog', 'high', NULL, NULL, NULL, '2026-05-18 11:58:57', '2026-05-18 11:58:57', NULL, 'f7f5ca46af98cb7d0aecd8e3472081e9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'task', 'T-99', 'replace InProcessClient call sites with the socket loopback', 'Fourth slice of T-99(a). Convert the in-process callers — KernelServices.boot''s daemonClientFactory and friends — to talk to the local socket instead of the direct in-process dispatch path. Delete InProcessClient afterward. - -Acceptance: -1. lib/kernel/src/ipc/in_process.dart removed; nothing imports it. -2. main.dart''s daemonClientFactory builds a socket-connected DaemonClient pointed at the local server. -3. Test fixtures use a synthetic in-memory socket pair (or short-lived AF_UNIX socket in /tmp) for isolation. -4. Wall-clock perf delta is within reason (no widget rebuild storm; round-trip ~ms) — measure before/after under a representative test. -5. Falls back to in-process direct dispatch if the perf delta is unacceptable; surface the finding in a Q-record before doing so. - -Source: T-99 sketch. Depends on T-124 (server must exist). Blocks T-128 (legacy IPC cleanup).', 'backlog', 'high', NULL, NULL, NULL, '2026-05-18 11:59:02', '2026-05-18 11:59:02', NULL, '69d3791bf8e0f5320243538eaa062fbc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-128', 'task', 'T-99', 'delete IsolateClient + Backend + backend_entry.dart; collapse service registration', 'Fifth slice of T-99(a). Pure cleanup once T-127 lands. - -Remove the third unused IPC path entirely. Today three implementations coexist: DaemonClient (socket — gets a real server in T-124), InProcessClient (deleted in T-127), and IsolateClient + Backend + backend_entry.dart (a backend isolate path that was never wired through). After T-127, none of those should still be referenced. - -Also: consolidate the two duplicate service-wiring sites into one (currently main.dart''s buildDispatcher and the kernel facade both register subsystem handlers; one should own it). - -Acceptance: -1. lib/kernel/src/ipc/isolate_client.dart, lib/src/daemon/backend.dart, lib/src/daemon/backend_entry.dart removed. -2. Single service-registration site; no duplicate registerPaneCommands / registerFilesCommands / etc. across files. -3. flutter analyze + full test suite green. - -Source: T-99 sketch. Depends on T-127.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:06', '2026-05-18 11:59:06', NULL, 'a5ab2120d437f63d7d892e7fe6ae1689', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-129', 'task', 'T-99', 'event streaming over the socket — `clide tail --events`', 'Sixth slice of T-99(a). Long-lived subscription channel — the second half of D-6 (`clide tail --events [--filter [:]]`). - -Client opens a connection, sends {"subscribe": "|*"}, server pushes JSON-line events until the client closes. Per D-6: replay buffer per subsystem (default depth 16) so a late subscriber still sees recent effects. - -Acceptance: -1. `clide tail --events --filter git` streams git.* events from the running app. -2. Replay buffer per subsystem; new subscribers receive the last 16 events. -3. Server doesn''t block writes on a slow client (back-pressure handling per Q-2 — drop with a warning or apply flow control; resolve in this ticket). -4. End-to-end smoke: launch app, run `clide tail --events --filter pane` in another shell, perform a pane action in the UI, observe the event. - -Source: T-99 sketch. Depends on T-124 + T-126.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:11', '2026-05-18 11:59:11', NULL, '732603dbb5bd24ef0d62811223d13d96', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-130', 'task', 'T-99', 'MCP server alongside the CLI, wrapping the same dispatcher (D-68)', 'Seventh slice of T-99(a). Adds an /ide-compatible MCP endpoint per D-68 so Claude Code instances outside clide can connect via /ide. - -Minimum tools (per D-68): mcp__ide__getDiagnostics, mcp__ide__executeCode. Optional mcp__clide__* namespace deferred to Q-32. Transport choice (SSE vs WebSocket vs stdio) — resolve Q-33 at the start of this ticket. - -Both surfaces (CLI socket + MCP) wrap the SAME DaemonDispatcher — there is no second source of truth. - -Acceptance: -1. lib/src/ipc/mcp_server.dart serves the chosen transport. -2. Claude Code''s /ide command discovers and connects to clide. -3. Both minimum tools work end-to-end against a real Claude Code session. -4. Q-32 + Q-33 closed (either as decisions or with resolutions written into the ticket). - -Source: T-99 sketch. Depends on T-124 (server foundation must exist). Can land in parallel with T-127 / T-128 / T-129.', 'backlog', 'medium', NULL, NULL, 'D-68', '2026-05-18 11:59:17', '2026-05-18 11:59:17', NULL, 'bb89c5fdc348dcc9e029296569416876', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-131', 'task', 'T-99', 'T-99 wrap-up: governance + docs + unblock T-119 / T-120', 'Eighth and final slice of T-99(a). Bookkeeping once the substantive work lands. - -Acceptance: -1. D-56 gets an amendment recording that the unix socket server + C client + MCP server all landed (with the dated amendment line format the rest of the testing.md / architecture.md decisions use). -2. D-68 referenced as implemented in its own record. -3. CONTRIBUTING.md gets a ''Running clide from the shell'' section (`clide status`, `clide tail --events`, etc.). -4. T-119 (`clide panel resize` CLI verb) moved out of backlog into ready — its IPC dispatch path now exists. -5. T-120 (typed IPC schema framework) re-scoped if needed; the wire contract is now real. -6. T-99 itself transitions to done. - -Source: T-99 sketch. Depends on T-124 + T-125 + T-126 + T-127 + T-128 (+T-129, +T-130 if landed).', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:23', '2026-05-18 11:59:23', NULL, '4ea25c9c4d894b0fca02d1929fee7945', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'task', 'T-99', 'unix-domain IPC server, wired into Flutter app boot', 'First slice of T-99(a). Foundation for the rest. +**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.', 'done', 'medium', NULL, NULL, NULL, '2026-05-05 12:53:22', '2026-05-18 11:51:40', NULL, 'fea6f554862caab5d1f0dbb40a703bc8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AV8YXV4NYWS2ZQYW', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'unix-domain IPC server, wired into Flutter app boot', 'First slice of T-99(a). Foundation for the rest. Open a per-workspace unix-domain socket on Flutter app boot. Accept JSON-lines per the existing IpcRequest envelope. Route each request through the existing DaemonDispatcher (already wired in main.dart via daemonClientFactory). Tear down on app shutdown. @@ -2092,26 +597,8 @@ Open a per-workspace unix-domain socket on Flutter app boot. Accept JSON-lines p 5. Stale socket on boot (left over from a crashed clide) is detected and unlinked before binding — same `live-daemon probe` pattern already used elsewhere in the codebase. 6. No client yet — that lands in T-126. -Source: T-99 sketch. Coordinates with: T-127 (InProcessClient swap), T-130 (MCP).', 'backlog', 'high', NULL, NULL, NULL, '2026-05-18 11:58:47', '2026-05-18 12:42:56', NULL, 'e90d4b8786af0a9baa2b39c19a31540a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-124', 'task', 'T-99', 'unix-domain IPC server, wired into Flutter app boot', 'First slice of T-99(a). Foundation for the rest. - -Open a per-workspace unix-domain socket on Flutter app boot. Accept JSON-lines per the existing IpcRequest envelope. Route each request through the existing DaemonDispatcher (already wired in main.dart via daemonClientFactory). Tear down on app shutdown. - -**Architectural commitments (read these first):** -- D-70 — socket path is `$XDG_RUNTIME_DIR/clide/.sock` on Linux, `$HOME/Library/Caches/clide/.sock` on macOS. No env override. Workspace root = git toplevel. -- D-71 — socket file is `0600`, parent dir is `0700`. No token auth at this layer. -- D-72 — multi-connection accept loop, serial dispatch on the main isolate. Per-handler isolate offload as needed; not the IPC layer''s concern. - -**Acceptance:** -1. lib/src/ipc/server.dart exists; binds an AF_UNIX socket at the D-70 path; creates the parent dir with the D-71 perms. -2. App boot starts the server; app shutdown closes the socket file cleanly and removes it. -3. `socat - UNIX-CONNECT:$SOCK <<< ''{"command":"git.status"}''` returns a JSON-line response. Tests use a synthetic socket fixture (tempdir + XDG_RUNTIME_DIR override at the env level). -4. Multi-connection accept loop — concurrent socat invocations interleave at the I/O level but serialise through DaemonDispatcher (per D-72) without failing each other. -5. Stale socket on boot (left over from a crashed clide) is detected and unlinked before binding — same `live-daemon probe` pattern already used elsewhere in the codebase. -6. No client yet — that lands in T-126. - -Source: T-99 sketch. Coordinates with: T-127 (InProcessClient swap), T-130 (MCP).', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:58:47', '2026-05-18 12:51:08', NULL, 'eaa0d45789ecd577e81dc07ef476e31c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-125', 'task', 'T-99', 'argv→IpcRequest translator on the Dart side', 'Second slice of T-99(a). Owns the CLI grammar so the C client stays a dumb pipe. +Source: T-99 sketch. Coordinates with: T-127 (InProcessClient swap), T-130 (MCP).', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:58:47', '2026-05-18 12:51:08', NULL, 'c002da123b9382b8cfd7b4318ccf4a47', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KY9JVRKF22GGFMXR', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'argv→IpcRequest translator on the Dart side', 'Second slice of T-99(a). Owns the CLI grammar so the C client stays a dumb pipe. Add lib/src/cli/argv_to_request.dart: takes a List (the argv after `clide`) and returns an IpcRequest. Handles `clide [positionals...] [--flag value] [-- argv...]` per D-6''s shape. Errors (unknown subsystem/verb, malformed flag) surface as IpcResponse.err with the right exit code per pql''s contract. @@ -2121,8 +608,8 @@ Acceptance: 3. Round-trip tests against the existing IpcRequest envelope. 4. Sysexit-code parity with pql (0/1/2/3/4 + 64-78 reserved). -Source: T-99 sketch.', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:58:52', '2026-05-18 15:51:09', NULL, '99f60de735779ddc37e7701ef4dd3304', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-126', 'task', 'T-99', 'thin C `clide` client binary in native/clide-cli/', 'Third slice of T-99(a). The user-facing entry point. +Source: T-99 sketch.', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:58:52', '2026-05-18 15:51:09', NULL, 'fb49caf38db53b7e7bbedecfdbe3b52d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78E2NAM4YK3A2AV6R', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'thin C `clide` client binary in native/clide-cli/', 'Third slice of T-99(a). The user-facing entry point. ~150 LOC of C in native/clide-cli/. Resolves the socket path (env override + default), connects, sends a single JSON-line request shaped as {"argv": [...]} (parsing happens in Dart per T-125), reads the JSON-line response, writes stdout/stderr per the pql contract, exits with the response''s exit code. @@ -2133,8 +620,8 @@ Acceptance: 4. `clide nonsense` returns the right exit code (sysexit 64 = usage error per D-6). 5. Documented in assets/licenses.yaml + a one-paragraph note in CONTRIBUTING.md. -Source: T-99 sketch. Depends on T-124 (server) + T-125 (argv translator).', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:58:57', '2026-05-18 16:04:29', NULL, 'e4be31e3d80dbaadd64e3a4a2fa029e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'task', 'T-99', 'replace InProcessClient call sites with the socket loopback', 'Fourth slice of T-99(a). Convert the in-process callers — KernelServices.boot''s daemonClientFactory and friends — to talk to the local socket instead of the direct in-process dispatch path. Delete InProcessClient afterward. +Source: T-99 sketch. Depends on T-124 (server) + T-125 (argv translator).', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:58:57', '2026-05-18 16:04:29', NULL, '111faf9e03df8450abc4ef50bd38e80b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7J40S0A8Q8PC0PDY8', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'replace InProcessClient call sites with the socket loopback', 'Fourth slice of T-99(a). Convert the in-process callers — KernelServices.boot''s daemonClientFactory and friends — to talk to the local socket instead of the direct in-process dispatch path. Delete InProcessClient afterward. Acceptance: 1. lib/kernel/src/ipc/in_process.dart removed; nothing imports it. @@ -2143,8 +630,8 @@ Acceptance: 4. Wall-clock perf delta is within reason (no widget rebuild storm; round-trip ~ms) — measure before/after under a representative test. 5. Falls back to in-process direct dispatch if the perf delta is unacceptable; surface the finding in a Q-record before doing so. -Source: T-99 sketch. Depends on T-124 (server must exist). Blocks T-128 (legacy IPC cleanup).', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:59:02', '2026-05-19 10:03:26', NULL, 'af4a706880181411b2aecf51d90ea748', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-128', 'task', 'T-99', 'delete IsolateClient + Backend + backend_entry.dart; collapse service registration', 'Fifth slice of T-99(a). Pure cleanup once T-127 lands. +Source: T-99 sketch. Depends on T-124 (server must exist). Blocks T-128 (legacy IPC cleanup).', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:59:02', '2026-05-19 10:03:26', NULL, '4dbfa582f178cfb41925b65a24fdbb58', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KEY6593FMW8Y40VC', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'delete IsolateClient + Backend + backend_entry.dart; collapse service registration', 'Fifth slice of T-99(a). Pure cleanup once T-127 lands. Remove the third unused IPC path entirely. Today three implementations coexist: DaemonClient (socket — gets a real server in T-124), InProcessClient (deleted in T-127), and IsolateClient + Backend + backend_entry.dart (a backend isolate path that was never wired through). After T-127, none of those should still be referenced. @@ -2155,8 +642,8 @@ Acceptance: 2. Single service-registration site; no duplicate registerPaneCommands / registerFilesCommands / etc. across files. 3. flutter analyze + full test suite green. -Source: T-99 sketch. Depends on T-127.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:06', '2026-05-19 10:06:46', NULL, '481c51b17b027b5a04279a0ef6a3a029', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-129', 'task', 'T-99', 'event streaming over the socket — `clide tail --events`', 'Sixth slice of T-99(a). Long-lived subscription channel — the second half of D-6 (`clide tail --events [--filter [:]]`). +Source: T-99 sketch. Depends on T-127.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:06', '2026-05-19 10:06:46', NULL, 'd59b5ab3b77ef993dc3a9e776b851ff5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40XWGSN99DBDQDHJR', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'event streaming over the socket — `clide tail --events`', 'Sixth slice of T-99(a). Long-lived subscription channel — the second half of D-6 (`clide tail --events [--filter [:]]`). Client opens a connection, sends {"subscribe": "|*"}, server pushes JSON-line events until the client closes. Per D-6: replay buffer per subsystem (default depth 16) so a late subscriber still sees recent effects. @@ -2166,8 +653,8 @@ Acceptance: 3. Server doesn''t block writes on a slow client (back-pressure handling per Q-2 — drop with a warning or apply flow control; resolve in this ticket). 4. End-to-end smoke: launch app, run `clide tail --events --filter pane` in another shell, perform a pane action in the UI, observe the event. -Source: T-99 sketch. Depends on T-124 + T-126.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:11', '2026-05-19 12:19:51', NULL, '44c9141fea8635a9c03053afa92f3999', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-130', 'task', 'T-99', 'MCP server alongside the CLI, wrapping the same dispatcher (D-68)', 'Seventh slice of T-99(a). Adds an /ide-compatible MCP endpoint per D-68 so Claude Code instances outside clide can connect via /ide. +Source: T-99 sketch. Depends on T-124 + T-126.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:11', '2026-05-19 12:19:51', NULL, 'c93cdc6d0a99a0f89768b0c9b1fb83e4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5MW2V3QCMWKMY4VQM', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'MCP server alongside the CLI, wrapping the same dispatcher (D-68)', 'Seventh slice of T-99(a). Adds an /ide-compatible MCP endpoint per D-68 so Claude Code instances outside clide can connect via /ide. Minimum tools (per D-68): mcp__ide__getDiagnostics, mcp__ide__executeCode. Optional mcp__clide__* namespace deferred to Q-32. Transport choice (SSE vs WebSocket vs stdio) — resolve Q-33 at the start of this ticket. @@ -2179,22 +666,8 @@ Acceptance: 3. Both minimum tools work end-to-end against a real Claude Code session. 4. Q-32 + Q-33 closed (either as decisions or with resolutions written into the ticket). -Source: T-99 sketch. Depends on T-124 (server foundation must exist). Can land in parallel with T-127 / T-128 / T-129.', 'done', 'medium', NULL, NULL, 'D-68', '2026-05-18 11:59:17', '2026-05-19 12:33:52', NULL, '0d03fa2376bf179665ee817dc363e186', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'task', 'T-99', 'clide panel resize CLI verb (split from T-111)', 'Once T-99 lands an IPC dispatch path, register a ''panel.resize'' command that takes and args and calls LayoutArrangement.setSize / setEditorRatio. Keyboard parity already landed in T-111; this completes user/Claude parity per D-6.', 'ready', 'low', NULL, NULL, NULL, '2026-05-18 07:43:50', '2026-05-19 13:14:01', NULL, '08bd7d35f6ac787f1895d92a23a65cec', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'task', 'T-97', 'typed IPC command schema framework (split from T-104)', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. - -Re-scope (2026-05-19, post T-99): the wire contract is now real — IpcRequest envelopes flow over the unix socket (per D-70/71/72) and through the C clide client which serialises raw argv under the `_argv` sentinel (per D-72/D-6). The argv parser (lib/src/cli/argv_to_request.dart) is the de-facto schema today; T-120 should formalise it by: - -- Lifting the per-cmd grammar from argv_to_request.dart into a typed registry keyed by cmd, sharing it with both the argv parser and the dispatcher. -- Using the same registry to validate inbound IpcRequests at dispatch time (regex/charset constraints on branch/remote/path args). -- Exposing the registry to the MCP server (T-130) so the `tools/list` payload is generated rather than hand-rolled. - -Design still open: -- Where the schema lives (per-handler? central registry?). -- Whether it generates handler boilerplate or wraps existing handlers. - -Source: consultants.md "Security — Findings — [Major]" item 1.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-18 07:53:08', '2026-05-19 13:14:12', NULL, 'cac26b3ebcf14b056fdd178d4c60c721', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-131', 'task', 'T-99', 'T-99 wrap-up: governance + docs + unblock T-119 / T-120', 'Eighth and final slice of T-99(a). Bookkeeping once the substantive work lands. +Source: T-99 sketch. Depends on T-124 (server foundation must exist). Can land in parallel with T-127 / T-128 / T-129.', 'done', 'medium', NULL, NULL, 'D-68', '2026-05-18 11:59:17', '2026-05-19 12:33:52', NULL, '61783e1808aaf90f14b81186c104b236', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5ZSRZARG7SXCC71NM', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'T-99 wrap-up: governance + docs + unblock T-119 / T-120', 'Eighth and final slice of T-99(a). Bookkeeping once the substantive work lands. Acceptance: 1. D-56 gets an amendment recording that the unix socket server + C client + MCP server all landed (with the dated amendment line format the rest of the testing.md / architecture.md decisions use). @@ -2204,8 +677,8 @@ Acceptance: 5. T-120 (typed IPC schema framework) re-scoped if needed; the wire contract is now real. 6. T-99 itself transitions to done. -Source: T-99 sketch. Depends on T-124 + T-125 + T-126 + T-127 + T-128 (+T-129, +T-130 if landed).', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:23', '2026-05-19 13:14:41', NULL, '2acd8d20756c1615238472fedfe0d6df', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-99', 'task', 'T-97', 'implement (or amend) the IPC socket server per D-1/D-6/D-56/D-68', 'D-56 specifies the Flutter app hosts an in-process IPC server reachable by a thin C client over a Unix socket; D-1/D-6 commit to CLI-first. Today no `ServerSocket` for unix-domain exists anywhere in `lib/`. Three IPC clients coexist (`DaemonClient` socket, `InProcessClient`, `IsolateClient` + `Backend`/`backend_entry.dart`); two service-wiring sites duplicate registration. The load-bearing CLI-first guardrail has no runtime path. D-68 (filed in this session) commits to the dual-surface plan. +Source: T-99 sketch. Depends on T-124 + T-125 + T-126 + T-127 + T-128 (+T-129, +T-130 if landed).', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:23', '2026-05-19 13:14:41', NULL, '0a1e7189d21ee6542777379f23779dce', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM76Q97D8R01CRTTA9R', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'implement (or amend) the IPC socket server per D-1/D-6/D-56/D-68', 'D-56 specifies the Flutter app hosts an in-process IPC server reachable by a thin C client over a Unix socket; D-1/D-6 commit to CLI-first. Today no `ServerSocket` for unix-domain exists anywhere in `lib/`. Three IPC clients coexist (`DaemonClient` socket, `InProcessClient`, `IsolateClient` + `Backend`/`backend_entry.dart`); two service-wiring sites duplicate registration. The load-bearing CLI-first guardrail has no runtime path. D-68 (filed in this session) commits to the dual-surface plan. **Pick one path:** - **(a)** Implement the socket server per D-56 + the C `clide` client. Delete `InProcessClient` and `IsolateClient` + `backend_entry.dart`. Wire MCP server alongside per D-68. @@ -2217,10 +690,9 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, 3. Service registration happens in one place. 4. D-records updated to match reality. -Source: consultants.md "Architecture — Findings — [Critical] No IPC socket server exists".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:18', '2026-05-19 13:14:41', NULL, '2ad1fbf0b1b6f5b9e46d4a31d1ad830c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'task', 'T-99', 'clide panel resize CLI verb (split from T-111)', 'Once T-99 lands an IPC dispatch path, register a ''panel.resize'' command that takes and args and calls LayoutArrangement.setSize / setEditorRatio. Keyboard parity already landed in T-111; this completes user/Claude parity per D-6.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-18 07:43:50', '2026-05-19 14:44:43', NULL, 'ac4c7a1d76836e546dac66e77ed4cb67', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'task', 'T-99', 'clide panel resize CLI verb (split from T-111)', 'Once T-99 lands an IPC dispatch path, register a ''panel.resize'' command that takes and args and calls LayoutArrangement.setSize / setEditorRatio. Keyboard parity already landed in T-111; this completes user/Claude parity per D-6.', 'done', 'low', NULL, NULL, NULL, '2026-05-18 07:43:50', '2026-05-20 07:00:06', NULL, 'ba93872d695800697af1654a8bfd6e93', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'task', 'T-97', 'typed IPC command schema framework (split from T-104)', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. +Source: consultants.md "Architecture — Findings — [Critical] No IPC socket server exists".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:18', '2026-05-19 13:14:41', NULL, 'e4cc9c2bc07a53f7f49497cfed975ced', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54DENVAR46RXCCQQM', 'task', '06FB0TNQM76Q97D8R01CRTTA9R', 'clide panel resize CLI verb (split from T-111)', 'Once T-99 lands an IPC dispatch path, register a ''panel.resize'' command that takes and args and calls LayoutArrangement.setSize / setEditorRatio. Keyboard parity already landed in T-111; this completes user/Claude parity per D-6.', 'done', 'low', NULL, NULL, NULL, '2026-05-18 07:43:50', '2026-05-20 07:00:06', NULL, 'a2f9dbe79dc509701c70c8615b0ccbbb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47ARMYK2H24KJ0DAG', 'task', '06FB0TNQM56CF9W94FSFDSBKGW', 'typed IPC command schema framework (split from T-104)', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. --- Design resolved 2026-05-20 (see D-74). Co-registration model, NOT a central map: @@ -2239,92 +711,11 @@ Acceptance: 4. No third-party validation dep; no lint suppressions. 5. Tests cover: schema accept/reject per constraint kind, dispatcher-level rejection, and that an unschema-d command still dispatches. -Source: consultants.md "Security — Findings — [Major]" item 1. Decision: D-74.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-18 07:53:08', '2026-05-20 15:54:38', NULL, '3c33fccd4b7442444ca598d430fab7f8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'task', 'T-97', 'typed IPC command schema framework (split from T-104)', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. +Source: consultants.md "Security — Findings — [Major]" item 1. Decision: D-74.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 07:53:08', '2026-05-20 16:02:35', NULL, '7fc0d11030586b40bb0645cf46d8564e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM56CF9W94FSFDSBKGW', 'epic', NULL, 'Address 2026-05-14 consultant review', 'Six-reviewer external assessment found four critical gaps (workspace RCE, IPC server unimplemented, keyboard not operable, onboarding docs describe a dissolved architecture) plus ten major items and a handful of quick wins. Source: consultants.md (committed alongside this epic). Children are filed individually so they can land independently. ---- -Design resolved 2026-05-20 (see D-74). Co-registration model, NOT a central map: - -- The argv parser (lib/src/cli/argv_to_request.dart) only knows syntactic shape (identifier/flag charset, -- terminator); it has NO per-command argument knowledge. So this is build-fresh, not a lift — each handler currently hand-reads its own keys. -- Extend the DaemonDispatcher registration API to carry an optional typed schema per command. The dispatcher accumulates a cmd->schema registry as commands register (built-in register*Commands modules supply theirs; extension CommandContributions carry their own — central static map rejected because it cannot see extension-contributed commands, D-46). -- DaemonDispatcher.dispatch validates req.args against schema[cmd] BEFORE invoking the handler (hook at dispatcher.dart line 39, the `return h(req)` call). Violation -> userError (sysexit 64). Commands with no schema dispatch unvalidated (opt-in per command) until migrated. -- Constraint vocabulary is hand-rolled (prefer-zero-deps): per-arg required/optional, kind, charset/regex, numeric range. Collapses the _ResizeArgs-style hand-lifts (panel_commands.dart, T-119) into schema. -- KEEP existing T-104 checks as defense-in-depth: validateGitRef (lib/src/git/operations.dart:46) and the count/path caps (lib/src/daemon/git_commands.dart:18,23). The git client is callable directly from the Flutter UI, not only via the dispatcher — do not remove them. -- OUT OF SCOPE: MCP tools/list generation from the registry — deferred to the T-130 track. - -Acceptance: -1. Dispatcher validates req.args against a registered per-command schema before the handler runs; violations return userError. -2. At least the security-relevant commands (git ref/remote/path args, panel.resize) carry schemas; argv-injection + caps still enforced (no regression vs T-104). -3. validateGitRef + git caps retained as defense-in-depth. -4. No third-party validation dep; no lint suppressions. -5. Tests cover: schema accept/reject per constraint kind, dispatcher-level rejection, and that an unschema-d command still dispatches. - -Source: consultants.md "Security — Findings — [Major]" item 1. Decision: D-74.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 07:53:08', '2026-05-20 16:02:35', NULL, 'f52928536cfeb0f08091ec5d55bda3db', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-97', 'epic', NULL, 'Address 2026-05-14 consultant review', 'Six-reviewer external assessment found four critical gaps (workspace RCE, IPC server unimplemented, keyboard not operable, onboarding docs describe a dissolved architecture) plus ten major items and a handful of quick wins. Source: consultants.md (committed alongside this epic). Children are filed individually so they can land independently. - -Acceptance: all critical findings resolved or formally rejected with a D-record; all major findings either shipped or moved to a follow-up epic with rationale; quick-win batches closed.', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:08', '2026-05-20 16:02:46', NULL, 'bfc0abf31809a8e7afbb4bff62118d06', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'task', 'T-115', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle. - -Adding a tap-driven (or even render-only) test that pumps WelcomeView with a non-empty recents list hangs the Flutter test runner until timeout. - -Investigation 2026-05-22 (narrowed the cause): -- The hang is SYNCHRONOUS: `flutter test --timeout 45s` never fires; only an external `timeout` kills it. So the Dart isolate event loop is blocked, not awaiting. -- Reduced to a minimal repro: pumping two NESTED ClideTappable widgets under the shared test harness (test/helpers/widget_harness.dart) hangs. A SINGLE ClideTappable (as in _ActionRow) renders fine — that is why all existing WelcomeView tests pass (empty recents = no nested tappable). The recents path nests one: _RecentRow is a ClideTappable whose subtree contains _StickyToggle (a Semantics-wrapped ClideTappable). -- The shared harness uses `Overlay(canSizeOverlay: true)` over a zero-size `MediaQuery`, which triggers an intrinsic-sizing pass. Strongly suspect the nested ClideTappable (Focus + MouseRegion + GestureDetector + DecoratedBox stack) diverges under intrinsic dimension computation. -- Workarounds that did NOT help: setting tester.view.physicalSize; wrapping in SizedBox; wrapping in Center+SizedBox; a custom bounded MediaQuery harness (that custom harness hung even on a single tappable — likely its own confound, do not reuse it). -- The prior ClideTooltip/MouseRegion-timer hypothesis is WRONG: the recents widgets pass no tooltip to ClideTappable, so no ClideTooltip is built, and _StickyToggle uses Semantics(tooltip:) (metadata only, no widget/timer). - -Next steps: -- Decide whether this is a real ClideTappable bug (nested tappables would also hang the live app welcome screen with recents) or strictly a canSizeOverlay-intrinsic-sizing test artifact. Check the running app: open welcome with >=1 recent project and confirm it does NOT hang. If the app is fine, the fix is harness-side (give WelcomeView tight constraints so no intrinsic pass), and the shared harness or a welcome-specific harness needs adjusting. If the app DOES hang, ClideTappable has a real nested-layout bug — fix it (likely in lib/widgets/src/clide_tappable.dart intrinsic/layout handling) and this becomes higher priority. -- Once unblocked, this also unblocks lib/builtin/welcome/src/welcome_view.dart coverage (74 uncovered lines, the dominant lib/builtin/ gap toward the T-89 95% target). - -ProjectManager sticky-startup logic is already covered by test/kernel/src/project_test.dart (14 cases).', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-18 09:06:59', '2026-05-22 06:55:02', NULL, 'bcb79420d9e359813ad5f75dfb89cc05', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-91', 'task', 'T-89', 'test sweep: drive lib/src/terminal/ to 95%', 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. - -**Today''s distribution within `lib/src/terminal/src/`:** - -| sub-area | hit / total | % | shape | -|---|---|---|---| -| `core/buffer/` | 0 / 581 | 0.0% | pure data structures | -| `core/escape/` | 1 / 473 | 0.2% | pure parser logic | -| `core/input/` | 4 / 292 | 1.4% | key + keytab tables | -| `core/(root)` | 0 / 188 | 0.0% | callback wiring | -| `core/mouse/` | 3 / 51 | 5.9% | encoder logic | -| `ui/` | 3 / 823 | 0.4% | painters + render objects | -| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` | -| `utils/` | 1 / 251 | 0.4% | circular buffer etc. | - -**Strategy — pure logic first, then UI:** - -1. **`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. -2. **`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. -3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode. -4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast. -5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests. -6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests. -7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive. - -**Acceptance:** -- `lib/src/terminal/` line coverage ≥ 95%. -- All tests fast (<5 s for the area''s test files combined; no real PTY shell-out). -- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files. -- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage. -- `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). - -**Strategy on commit cadence:** -Land tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch: -- Adds tests under `test/terminal//`. -- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows). -- Documents in CHANGELOG under "Changed" (test-only changes can also fold into one CHANGELOG entry per area). - -**Out of scope:** -- 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. -- Branch coverage (D-66 explicitly excludes). -- Mouse / keyboard *protocol* round-trips against real TUIs (that''s T-74 and integration work). - -**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing). -', 'done', 'high', NULL, NULL, 'D-66', '2026-05-06 20:49:47', '2026-05-22 13:37:46', NULL, '076081a76e2301e97262f230c5cd5516', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-89', 'epic', NULL, 'drive line coverage from 35% to 95%', '## OUTCOME (closed 2026-05-18) +Acceptance: all critical findings resolved or formally rejected with a D-record; all major findings either shipped or moved to a follow-up epic with rationale; quick-win batches closed.', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:08', '2026-05-20 16:02:46', NULL, '9a79f6058c693d3ee4aa33901a0274a6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6TNXTJCQBH9VADC4M', 'epic', NULL, 'drive line coverage from 35% to 95%', '## OUTCOME (closed 2026-05-18) Epic met. Final measured line coverage **95.27%** (9551 / 10025), floor `coverage_floor: 95` in `pubspec.yaml`, gate wired into `make push-check`. @@ -2395,27 +786,54 @@ The gate plumbing lands first so future test additions are visible as floor bump - `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. **Cross-references:** D-66 (this epic''s decision record), D-29 (pre-push gate scope), "Own the rendering stack" guardrail (CLAUDE.md). -', 'done', 'high', NULL, NULL, 'D-66', '2026-05-06 20:36:42', '2026-05-22 13:37:46', NULL, '294d10b6817279f2c76e147f3066db50', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'epic', NULL, 'Native Claude integration: transcript-rendered conversation + tmux agent-team panels', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. +', 'done', 'high', NULL, NULL, 'D-66', '2026-05-06 20:36:42', '2026-05-22 13:37:46', NULL, '788cbb0e54e483350dfc40399ed383bf', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4HGK6KR0314P2M32M', 'task', '06FB0TNQM6TNXTJCQBH9VADC4M', 'test sweep: drive lib/src/terminal/ to 95%', 'Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap. -Why: -1. No cross-widget text selection/copy in clide''s native widgets today (regression vs a terminal). -2. PTY/TUI rendering is the fragile, OS-variant part of the Claude pane. -3. Claude Code''s experimental tmux team mode (lead + N teammates, each a tmux pane) is the flagship capability to surface as real GUI panels, not scraped TUI. +**Today''s distribution within `lib/src/terminal/src/`:** -Direction: run Claude headless in tmux; render its conversation natively from the transcript JSONL; lead panel (left, with composer) + teammate tiles (right) in a responsive auto-wrap grid (1->2->3 cols); team-meta sidebar with roster + token budget. +| sub-area | hit / total | % | shape | +|---|---|---|---| +| `core/buffer/` | 0 / 581 | 0.0% | pure data structures | +| `core/escape/` | 1 / 473 | 0.2% | pure parser logic | +| `core/input/` | 4 / 292 | 1.4% | key + keytab tables | +| `core/(root)` | 0 / 188 | 0.0% | callback wiring | +| `core/mouse/` | 3 / 51 | 5.9% | encoder logic | +| `ui/` | 3 / 823 | 0.4% | painters + render objects | +| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` | +| `utils/` | 1 / 251 | 0.4% | circular buffer etc. | -Principle: Claude-centric first, CLI-first (D-6) a strong second. Accept isolated, version-pinned coupling to Claude Code internal contracts where it serves the Claude integration; preserve CLI/event surfaces where sensible. +**Strategy — pure logic first, then UI:** -Acceptance: all child tickets done; D-record landed; Claude pane renders natively with working select+copy and no terminal; a tmux team surfaces as lead + auto-wrapping teammate tiles + meta sidebar; all fragile CC-internals parsing isolated and version-pinned. +1. **`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. +2. **`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. +3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode. +4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast. +5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests. +6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests. +7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive. -Source: plan iridescent-tinkering-umbrella (2026-05-22), grounded in an-idea.md + read-only validation of the transcript schema this session.', 'backlog', 'high', NULL, NULL, NULL, '2026-05-22 15:45:15', '2026-05-22 15:45:15', NULL, '0812d909e01decc3a5e8ce2b1b4f00d6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-140', 'story', 'T-132', 'Teammate tile grid (faithful tiling, responsive auto-wrap)', 'Tiling host: lead ConversationView (left, with composer) + teammate ConversationViews (right) in a responsive auto-wrap grid (1->2->3 columns by count); resizable lead/teammate split. Workspace currently only vertical-splits, so extend _WorkspaceSlot (lib/app.dart) or add team slots in lib/kernel/src/panels/layout_preset.dart; reuse DragResizeHandle (lib/kernel/src/panels/drag_resize.dart). Driven by TeamMemberBorn/Died from the observer. Acceptance: tiles appear/disappear with lifecycle; grid wraps 1->2->3 by count; split resizes; select-across-tiles works. Blocked by T5, T7.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 15:59:34', NULL, '0896a53c07b866a7deab8bbcfc2f95aa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-141', 'task', 'T-132', 'Team-meta sidebar (roster + token budget)', 'New sidebar panel contributed to Slots.sidebar: roster from ~/.claude/teams config; live status verb / elapsed / down+up tokens / edit-mode from transcript tailing; team token budget pulled from the Anthropic usage API on a timer. Acceptance: roster matches list-panes/config; budget refreshes on the timer; appears in team mode. Blocked by T7.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 15:59:34', NULL, '47e4d36a5ae60ba83a2f566f01e30dda', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-138', 'task', 'T-132', 'Native composer + file/image paste', 'New lib/builtin/claude/src/claude_composer.dart: native text field -> tmux -L clide send-keys -t (Enter submit, Shift+Enter newline). Paste text inline; file/image per the T2 spike finding (likely temp-file + path/@-reference, since send-keys carries text only). Acceptance: typing in the composer reaches Claude; pasting a text file and an image both work via the validated mechanism. Blocked by T2, T5.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 15:59:34', NULL, '7c2c380a808455658a034d6f84721ec8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-139', 'task', 'T-132', 'tmux team observer (lifecycle + identity + per-teammate transcripts)', 'New lib/builtin/claude/src/team_observer.dart: detect teammate spawn/exit (control-mode or list-panes -a poll on -L clide), map pane->teammate via ~/.claude/teams config + agentId, spin a TranscriptReader per teammate, emit new TeamMemberBorn/TeamMemberDied ClideEvents (lib/kernel/src/events/types.dart). ALL fragile CC-internals parsing isolated here (single drift-containment point; key off the version field). Cite D-75. Acceptance: a real team run emits born/died with correct identity; per-teammate content streams. Blocked by T2, T4.', 'backlog', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 15:59:34', NULL, '811c3f22582742212e7fe7d7b417e80d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-137', 'story', 'T-132', 'ConversationView + replace the terminal in the Claude pane', 'New lib/builtin/claude/src/conversation_view.dart rendering user / assistant(markdown via ClideMarkdown) / tool-call card / tool-result+diff card; whole tree under one SelectionArea. Swap ClidePtyView -> ConversationView in lib/builtin/claude/src/claude_pane.dart (~line 287). Claude still runs in tmux (detached / output ignored) but content comes from the transcript (TranscriptReader). Keep the terminal builtin + ClidePtyView for general use. Surface conversation as events too (D-6). Cite D-75. Acceptance: make run shows the native Claude conversation from the live transcript; select+copy across cards works; no terminal in the Claude pane; terminal builtin still works; widget tests. Blocked by T3, T4.', 'backlog', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 15:59:34', NULL, 'ac8ce4f7f76378f6cdec1544aa5390c9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-135', 'task', 'T-132', 'Native text-selection foundation (SelectionArea-compatible widgets)', 'Make clide''s native text widgets selectable so a SelectionArea can select across them. Change lib/widgets/src/clide_markdown.dart RichText (line ~68) -> Text.rich; same for lib/widgets/src/clide_code_block.dart; add a selectionBackground token to SurfaceTokens (lib/kernel/src/theme/tokens.dart + resolver + bundled theme YAMLs); verify copy via existing ClideClipboard (lib/kernel/src/clipboard.dart). Acceptance: a SelectionArea-wrapped column of markdown + code blocks supports select-across-blocks + Ctrl+C copy; widget tests; a11y unaffected; coverage >= floor. Reusable foundation. Blocks T5. +**Acceptance:** +- `lib/src/terminal/` line coverage ≥ 95%. +- All tests fast (<5 s for the area''s test files combined; no real PTY shell-out). +- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files. +- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage. +- `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). + +**Strategy on commit cadence:** +Land tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch: +- Adds tests under `test/terminal//`. +- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows). +- Documents in CHANGELOG under "Changed" (test-only changes can also fold into one CHANGELOG entry per area). + +**Out of scope:** +- 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. +- Branch coverage (D-66 explicitly excludes). +- Mouse / keyboard *protocol* round-trips against real TUIs (that''s T-74 and integration work). + +**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing). +', 'done', 'high', NULL, NULL, 'D-66', '2026-05-06 20:49:47', '2026-05-22 13:37:46', NULL, 'f107220fb3f72e619a9a0f6bf8843168', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54G017Y29APWJTCN0', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Author D-75: native transcript rendering + accepted CC-internals coupling', 'Author the D-75 architecture record (id already claimed via pql decisions claim): Claude is rendered natively from transcripts, not the terminal; accept isolated, version-pinned coupling to Claude Code internal contracts (transcript JSONL schema, ~/.claude/teams config, tmux control mode); terminal retained as a general IDE tool only. Record Claude-centric > CLI-first ordering with D-6 surfaces preserved. Acceptance: D-75 written in governance/decisions/architecture.md with Decision/Rationale/Cost/Cross-reference/Raised-by; governance README synced (pql decisions sync) + pql decisions validate ok; cited by T5/T7. Do early — frames the epic.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:50:06', '2026-05-22 17:37:23', NULL, '0f4ad39a71e65ed066d5de55261e8323', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SRMD12DMXCX6N7DM', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Native text-selection foundation (SelectionArea-compatible widgets)', 'Make clide''s native text widgets selectable so a SelectionArea can select across them. Change lib/widgets/src/clide_markdown.dart RichText (line ~68) -> Text.rich; same for lib/widgets/src/clide_code_block.dart; add a selectionBackground token to SurfaceTokens (lib/kernel/src/theme/tokens.dart + resolver + bundled theme YAMLs); verify copy via existing ClideClipboard (lib/kernel/src/clipboard.dart). Acceptance: a SelectionArea-wrapped column of markdown + code blocks supports select-across-blocks + Ctrl+C copy; widget tests; a11y unaffected; coverage >= floor. Reusable foundation. Blocks T5. --- Refinement (2026-05-22, refinement-agent review): @@ -2424,20 +842,8 @@ Refinement (2026-05-22, refinement-agent review): - selectionBackground token: add to SurfaceTokens (field + TokenKeys + TokenKeys.all) + resolver.dart (_defaultSurfaceMap default + constructor). Bundled theme YAMLs do NOT need it (default map covers it); per-theme override only if the default reads wrong. Do NOT retune existing palettes (published themes are user contracts). - Add a selection contrast pair (globalForeground on selectionBackground) to lib/kernel/src/theme/contrast.dart canonicalPairs; re-run test/a11y. (Note: summer-night.yaml is missing from contrast_test bundledPaths -- pre-existing inconsistency, out of scope.) - COPY PATH CORRECTION: SelectionArea Ctrl+C goes through Flutter services.Clipboard directly, NOT ClideClipboard. Test verifies Clipboard.getData returns the selection after copy. -- Coverage floor is 95%.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 16:46:34', NULL, '0bea86c9ee213c0c7ef3c47f10232b77', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-134', 'task', 'T-132', 'Spike: validate Claude Code team/transcript internals (pin a CC version)', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7. - ---- -Refinement (2026-05-22, refinement-agent review): -- Environment confirmed: tmux 3.6a; claude 2.1.148 (the version an-idea.md verified for tmux teammate spawning) -- PIN findings to 2.1.148. teammateMode absent from ~/.claude/settings.json (default auto) -> set teammateMode:tmux explicitly OR launch the lead inside tmux and rely on auto. -- Check 3 ALREADY answered from a real ~/.claude/teams/*/config.json: {name,description,createdAt,leadAgentId,leadSessionId,members[]}; member = {agentId,name,agentType,model,joinedAt,tmuxPaneId (empty for lead, e.g. %154 for teammates),cwd,subscriptions,backendType:''tmux'',isActive,mode}. tmuxPaneId DOES populate -- spike just re-verifies on the pinned version. -- Subagent path: //subagents/agent-.jsonl (session UUID is a directory). -- Check 5 (session-id) resolved by T-136: newest *.jsonl by mtime. Spike confirms only. -- tmux -L clide socket may not exist unless the app is running; spike creates its own: tmux -L clide new-session -d -s teamhost ... claude. -- Check 4 (paste): scope to @path file references (verifiable via send-keys headlessly). Clipboard-image needs an interactive display ($DISPLAY) -- note as interactive-only. -- ACP directionality (an-idea.md s5) is OUT OF SCOPE: Zed-client concern; clide owns its GUI and does not use ACP. -- Deliverable: findings note at docs/spikes/cc-team-internals-2.1.148.md (in-repo), feeding D-75 when authored (T-133). D-75 is claimed-but-unauthored (T-133''s job) -- the reference is forward-looking, not dangling.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 16:46:34', NULL, '1ca53c263efc8f5d5d49af23569b51b0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-136', 'task', 'T-132', 'TranscriptReader: tail + parse Claude transcript JSONL', 'New lib/builtin/claude/src/transcript_reader.dart. Resolve munged project path (/ -> -) + session-id; tail the append-only JSONL; parse records (type: user/assistant/attachment/tool; message; agentId; isSidechain) into ConversationItem models; drift-guard on the version field (log + degrade on unknown schema). Paths validated this session: ~/.claude/projects//.jsonl (lead) and /subagents/agent-.jsonl (teammates). Acceptance: unit tests against a snapshotted JSONL fixture (assistant text, tool_use, tool_result); appended lines stream in; unknown version degrades gracefully + logs. Blocks T5. +- Coverage floor is 95%.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 17:37:23', NULL, '5bc2b7bf7f348e6ed8da2f928dd47b79', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54N0ZDJRS7G6MP1XC', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'TranscriptReader: tail + parse Claude transcript JSONL', 'New lib/builtin/claude/src/transcript_reader.dart. Resolve munged project path (/ -> -) + session-id; tail the append-only JSONL; parse records (type: user/assistant/attachment/tool; message; agentId; isSidechain) into ConversationItem models; drift-guard on the version field (log + degrade on unknown schema). Paths validated this session: ~/.claude/projects//.jsonl (lead) and /subagents/agent-.jsonl (teammates). Acceptance: unit tests against a snapshotted JSONL fixture (assistant text, tool_use, tool_result); appended lines stream in; unknown version degrades gracefully + logs. Blocks T5. --- Refinement (2026-05-22, refinement-agent review): READY. Verified against real transcripts. @@ -2446,30 +852,8 @@ Refinement (2026-05-22, refinement-agent review): READY. Verified against real t - SESSION-ID: claude does not expose its session UUID via argv/env (verified in claude_pane spawn). Approach: newest *.jsonl by mtime in ~/.claude/projects//; poll mtime and switch the tail when a newer session appears. - Subagent transcripts: //subagents/agent-.jsonl + sibling .meta.json ({agentType,description}); each record carries agentId. - Tail: dart:io File.openRead with a byte-position cursor (File.length + RandomAccessFile.setPosition on poll). Do NOT use lib/src/files/watcher.dart. Zero new deps. -- Logger: lib/kernel/src/log.dart for the version drift-guard. Coverage floor 95%.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 16:46:34', NULL, '54d91e3d25e042cdf931610197a12f0e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-133', 'task', 'T-132', 'Author D-75: native transcript rendering + accepted CC-internals coupling', 'Author the D-75 architecture record (id already claimed via pql decisions claim): Claude is rendered natively from transcripts, not the terminal; accept isolated, version-pinned coupling to Claude Code internal contracts (transcript JSONL schema, ~/.claude/teams config, tmux control mode); terminal retained as a general IDE tool only. Record Claude-centric > CLI-first ordering with D-6 surfaces preserved. Acceptance: D-75 written in governance/decisions/architecture.md with Decision/Rationale/Cost/Cross-reference/Raised-by; governance README synced (pql decisions sync) + pql decisions validate ok; cited by T5/T7. Do early — frames the epic.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-22 15:50:06', '2026-05-22 16:46:34', NULL, '7ebcaf6b78bf011e1b52edcd91a568a4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-133', 'task', 'T-132', 'Author D-75: native transcript rendering + accepted CC-internals coupling', 'Author the D-75 architecture record (id already claimed via pql decisions claim): Claude is rendered natively from transcripts, not the terminal; accept isolated, version-pinned coupling to Claude Code internal contracts (transcript JSONL schema, ~/.claude/teams config, tmux control mode); terminal retained as a general IDE tool only. Record Claude-centric > CLI-first ordering with D-6 surfaces preserved. Acceptance: D-75 written in governance/decisions/architecture.md with Decision/Rationale/Cost/Cross-reference/Raised-by; governance README synced (pql decisions sync) + pql decisions validate ok; cited by T5/T7. Do early — frames the epic.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:50:06', '2026-05-22 17:37:23', NULL, '4b4b15c450f6791a4b41b85ad8817980', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-135', 'task', 'T-132', 'Native text-selection foundation (SelectionArea-compatible widgets)', 'Make clide''s native text widgets selectable so a SelectionArea can select across them. Change lib/widgets/src/clide_markdown.dart RichText (line ~68) -> Text.rich; same for lib/widgets/src/clide_code_block.dart; add a selectionBackground token to SurfaceTokens (lib/kernel/src/theme/tokens.dart + resolver + bundled theme YAMLs); verify copy via existing ClideClipboard (lib/kernel/src/clipboard.dart). Acceptance: a SelectionArea-wrapped column of markdown + code blocks supports select-across-blocks + Ctrl+C copy; widget tests; a11y unaffected; coverage >= floor. Reusable foundation. Blocks T5. - ---- -Refinement (2026-05-22, refinement-agent review): -- Convert ALL RichText sites, not one: clide_markdown.dart lines 68, 238, 242 + clide_code_block.dart:79 -> Text.rich (Text.rich wires the SelectionArea selectionRegistrar; same underlying render object). -- Tables (clide_markdown.dart:230) and tappable WidgetSpan links (ClideTappable ~293-310) do NOT participate in SelectionArea. v1 scope: text + code blocks are the selectable surface; tables/link-spans are known non-selectable islands (revisit later). Acceptance = select+copy across text/code blocks. -- selectionBackground token: add to SurfaceTokens (field + TokenKeys + TokenKeys.all) + resolver.dart (_defaultSurfaceMap default + constructor). Bundled theme YAMLs do NOT need it (default map covers it); per-theme override only if the default reads wrong. Do NOT retune existing palettes (published themes are user contracts). -- Add a selection contrast pair (globalForeground on selectionBackground) to lib/kernel/src/theme/contrast.dart canonicalPairs; re-run test/a11y. (Note: summer-night.yaml is missing from contrast_test bundledPaths -- pre-existing inconsistency, out of scope.) -- COPY PATH CORRECTION: SelectionArea Ctrl+C goes through Flutter services.Clipboard directly, NOT ClideClipboard. Test verifies Clipboard.getData returns the selection after copy. -- Coverage floor is 95%.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 17:37:23', NULL, '8f5c2e3763cd186de3d0744f800a7980', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-136', 'task', 'T-132', 'TranscriptReader: tail + parse Claude transcript JSONL', 'New lib/builtin/claude/src/transcript_reader.dart. Resolve munged project path (/ -> -) + session-id; tail the append-only JSONL; parse records (type: user/assistant/attachment/tool; message; agentId; isSidechain) into ConversationItem models; drift-guard on the version field (log + degrade on unknown schema). Paths validated this session: ~/.claude/projects//.jsonl (lead) and /subagents/agent-.jsonl (teammates). Acceptance: unit tests against a snapshotted JSONL fixture (assistant text, tool_use, tool_result); appended lines stream in; unknown version degrades gracefully + logs. Blocks T5. - ---- -Refinement (2026-05-22, refinement-agent review): READY. Verified against real transcripts. -- Munge = absolutePath.replaceAll(''/'',''-'') (KEEP the leading ''-''; dirs start with -var-). -- Envelope fields: type, uuid, parentUuid, isSidechain, sessionId, version, timestamp. Render: user (message.content = String OR array of {type:text|tool_result}); assistant (message.content = array of {type:thinking|text|tool_use}). Skip attachment/system/last-prompt/permission-mode/file-history-snapshot/queue-operation (bookkeeping). -- SESSION-ID: claude does not expose its session UUID via argv/env (verified in claude_pane spawn). Approach: newest *.jsonl by mtime in ~/.claude/projects//; poll mtime and switch the tail when a newer session appears. -- Subagent transcripts: //subagents/agent-.jsonl + sibling .meta.json ({agentType,description}); each record carries agentId. -- Tail: dart:io File.openRead with a byte-position cursor (File.length + RandomAccessFile.setPosition on poll). Do NOT use lib/src/files/watcher.dart. Zero new deps. -- Logger: lib/kernel/src/log.dart for the version drift-guard. Coverage floor 95%.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 17:37:23', NULL, 'f68fd700a3487296ac6a00632b050543', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-134', 'task', 'T-132', 'Spike: validate Claude Code team/transcript internals (pin a CC version)', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7. +- Logger: lib/kernel/src/log.dart for the version drift-guard. Coverage floor 95%.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 17:37:23', NULL, 'f78456fd152da1cd9eac8f7554313119', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4EQAERS4J9X9RAETR', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Spike: validate Claude Code team/transcript internals (pin a CC version)', 'Investigate empirically and write a findings note pinned to the current Claude Code version. 1) Team spawn inside tmux -L clide with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 + teammateMode: tmux -> teammate panes (list-panes -a) + each teammate''s /subagents/agent-*.jsonl grows. 2) Lifecycle: tmux -L clide -C control-mode pane-add/pane-died events vs polling list-panes. 3) ~/.claude/teams//config.json layout: tmuxPaneId, agentId, agentType populate at spawn. 4) Image/file paste intake when driven via send-keys (path/@-ref vs clipboard-image vs unsupported). 5) Active session-id discovery (capture at spawn vs newest JSONL in munged dir). Already validated this session: transcript paths + schema (type/message/agentId/version) + subagent transcript location. Acceptance: findings note answering 1-5, CC version pinned. Blocks T6, T7. --- Refinement (2026-05-22, refinement-agent review): @@ -2483,46 +867,15 @@ Refinement (2026-05-22, refinement-agent review): - Deliverable: findings note at docs/spikes/cc-team-internals-2.1.148.md (in-repo), feeding D-75 when authored (T-133). D-75 is claimed-but-unauthored (T-133''s job) -- the reference is forward-looking, not dangling. --- -Findings (2026-05-22): docs/spikes/cc-team-internals-2.1.148.md. All 5 checks answered from real artifacts + tmux manual + synthetic control-mode test (no live run/quota). Key risk for T-139: pane->teammate->transcript identity joins only on agentType (config <-> .meta.json), ambiguous for same-type teammates -> needs joinedAt/mtime or lead-transcript tiebreaker. tmux 3.6a has no %pane-died; poll list-panes -a. Teammate transcripts at //subagents/agent-.jsonl + .meta.json{agentType,description}.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 17:55:11', NULL, '2f9a445df8c60beafe126aff339410b8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-137', 'story', 'T-132', 'ConversationView + replace the terminal in the Claude pane', 'New lib/builtin/claude/src/conversation_view.dart rendering user / assistant(markdown via ClideMarkdown) / tool-call card / tool-result+diff card; whole tree under one SelectionArea. Swap ClidePtyView -> ConversationView in lib/builtin/claude/src/claude_pane.dart (~line 287). Claude still runs in tmux (detached / output ignored) but content comes from the transcript (TranscriptReader). Keep the terminal builtin + ClidePtyView for general use. Surface conversation as events too (D-6). Cite D-75. Acceptance: make run shows the native Claude conversation from the live transcript; select+copy across cards works; no terminal in the Claude pane; terminal builtin still works; widget tests. Blocked by T3, T4.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 17:58:07', NULL, 'e8244b90386314574545f0c1a7bd012c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-137', 'story', 'T-132', 'ConversationView + replace the terminal in the Claude pane', 'New lib/builtin/claude/src/conversation_view.dart rendering user / assistant(markdown via ClideMarkdown) / tool-call card / tool-result+diff card; whole tree under one SelectionArea. Swap ClidePtyView -> ConversationView in lib/builtin/claude/src/claude_pane.dart (~line 287). Claude still runs in tmux (detached / output ignored) but content comes from the transcript (TranscriptReader). Keep the terminal builtin + ClidePtyView for general use. Surface conversation as events too (D-6). Cite D-75. Acceptance: make run shows the native Claude conversation from the live transcript; select+copy across cards works; no terminal in the Claude pane; terminal builtin still works; widget tests. Blocked by T3, T4.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 18:14:32', NULL, '6651a2c3c9099f7d38553f6cf9e41e98', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-138', 'task', 'T-132', 'Native composer + file/image paste', 'New lib/builtin/claude/src/claude_composer.dart: native text field -> tmux -L clide send-keys -t (Enter submit, Shift+Enter newline). Paste text inline; file/image per the T2 spike finding (likely temp-file + path/@-reference, since send-keys carries text only). Acceptance: typing in the composer reaches Claude; pasting a text file and an image both work via the validated mechanism. Blocked by T2, T5.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 21:08:05', NULL, '2e6b932ad78ac78d83c86c5e08290ed3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-138', 'task', 'T-132', 'Native composer + file/image paste', 'New lib/builtin/claude/src/claude_composer.dart: native text field -> tmux -L clide send-keys -t (Enter submit, Shift+Enter newline). Paste text inline; file/image per the T2 spike finding (likely temp-file + path/@-reference, since send-keys carries text only). Acceptance: typing in the composer reaches Claude; pasting a text file and an image both work via the validated mechanism. Blocked by T2, T5.', 'done', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 21:49:09', NULL, '8b247ce6f9fc63c6f56030303aece2e8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-142', 'task', 'T-132', 'Composer attachment chips (thumbnail/icon on paste)', 'Show pasted files/images as chips above the composer input instead of inserting the @path as text. Image chips render a thumbnail (Image.file of the cache/temp file); other types show a Phosphor file icon + basename; each chip has a remove ×. On submit, the typed text plus the chips'' @path tokens are assembled into the message sent via tmux.sendMessage, then text + chips clear. Typed @path text still works. resolveClipboardAttachment changes to return a list of attachment descriptors (path + isImage). No new package deps (Image.file + existing Phosphor icons). Builds on T-138.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-22 22:06:59', '2026-05-22 22:09:09', NULL, 'da746aa3a523e3b351eaa13f19fa51e1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-142', 'task', 'T-132', 'Composer attachment chips (thumbnail/icon on paste)', 'Show pasted files/images as chips above the composer input instead of inserting the @path as text. Image chips render a thumbnail (Image.file of the cache/temp file); other types show a Phosphor file icon + basename; each chip has a remove ×. On submit, the typed text plus the chips'' @path tokens are assembled into the message sent via tmux.sendMessage, then text + chips clear. Typed @path text still works. resolveClipboardAttachment changes to return a list of attachment descriptors (path + isImage). No new package deps (Image.file + existing Phosphor icons). Builds on T-138.', 'done', 'medium', NULL, NULL, NULL, '2026-05-22 22:06:59', '2026-05-22 22:18:02', NULL, '384c57378557c6f32f2cf93af99873c4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-143', 'task', 'T-132', 'Distinguish user message cards in the conversation', 'User messages render flat (label + text on the canvas) like Claude''s responses, so it''s hard to scan what you said vs what Claude said. Give UserMessage a distinct card: a filled background (globalBackground vs the panelBackground canvas), a left accent stripe (globalFocus), padding and rounded corners. Claude''s text responses stay flat markdown (better for long answers). conversation_view.dart _ConversationTurn.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-22 22:31:22', '2026-05-22 22:31:25', NULL, 'efa90d878f79235f81d54d00c04014c8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-143', 'task', 'T-132', 'Distinguish user message cards in the conversation', 'User messages render flat (label + text on the canvas) like Claude''s responses, so it''s hard to scan what you said vs what Claude said. Give UserMessage a distinct card: a filled background (globalBackground vs the panelBackground canvas), a left accent stripe (globalFocus), padding and rounded corners. Claude''s text responses stay flat markdown (better for long answers). conversation_view.dart _ConversationTurn.', 'done', 'low', NULL, NULL, NULL, '2026-05-22 22:31:22', '2026-05-22 22:36:09', NULL, '11ebd7f43b4371b15acb0729010cdec9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-144', 'task', 'T-132', 'Claude message card with brand-orange accent', 'Extend the distinct-card treatment (T-143) to Claude''s text responses with a different accent: Claude''s brand coral-orange (#D97757), vs the user card''s focus-colour stripe. Refactor _userCard into a shared _messageCard(label, accent, body). conversation_view.dart.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-22 22:40:42', '2026-05-22 22:41:28', NULL, '4c0eebba4edb4394fb47ba2d81ef42ad', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-144', 'task', 'T-132', 'Claude message card with brand-orange accent', 'Extend the distinct-card treatment (T-143) to Claude''s text responses with a different accent: Claude''s brand coral-orange (#D97757), vs the user card''s focus-colour stripe. Refactor _userCard into a shared _messageCard(label, accent, body). conversation_view.dart.', 'done', 'low', NULL, NULL, NULL, '2026-05-22 22:40:42', '2026-05-22 22:43:45', NULL, '1ce891624631502279b0356c5cdffae2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-139', 'task', 'T-132', 'tmux team observer (lifecycle + identity + per-teammate transcripts)', 'New lib/builtin/claude/src/team_observer.dart: detect teammate spawn/exit (control-mode or list-panes -a poll on -L clide), map pane->teammate via ~/.claude/teams config + agentId, spin a TranscriptReader per teammate, emit new TeamMemberBorn/TeamMemberDied ClideEvents (lib/kernel/src/events/types.dart). ALL fragile CC-internals parsing isolated here (single drift-containment point; key off the version field). Cite D-75. Acceptance: a real team run emits born/died with correct identity; per-teammate content streams. Blocked by T2, T4.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 22:50:41', NULL, 'a5f4b20a0a37332ab35de8fa07e22298', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-139', 'task', 'T-132', 'tmux team observer (lifecycle + identity + per-teammate transcripts)', 'New lib/builtin/claude/src/team_observer.dart: detect teammate spawn/exit (control-mode or list-panes -a poll on -L clide), map pane->teammate via ~/.claude/teams config + agentId, spin a TranscriptReader per teammate, emit new TeamMemberBorn/TeamMemberDied ClideEvents (lib/kernel/src/events/types.dart). ALL fragile CC-internals parsing isolated here (single drift-containment point; key off the version field). Cite D-75. Acceptance: a real team run emits born/died with correct identity; per-teammate content streams. Blocked by T2, T4.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 23:16:28', NULL, 'bd5e9cc5ae0e8bdbac4ad8cc57b33b58', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-140', 'story', 'T-132', 'Teammate tile grid (faithful tiling, responsive auto-wrap)', 'Tiling host: lead ConversationView (left, with composer) + teammate ConversationViews (right) in a responsive auto-wrap grid (1->2->3 columns by count); resizable lead/teammate split. Workspace currently only vertical-splits, so extend _WorkspaceSlot (lib/app.dart) or add team slots in lib/kernel/src/panels/layout_preset.dart; reuse DragResizeHandle (lib/kernel/src/panels/drag_resize.dart). Driven by TeamMemberBorn/Died from the observer. Acceptance: tiles appear/disappear with lifecycle; grid wraps 1->2->3 by count; split resizes; select-across-tiles works. Blocked by T5, T7.', 'done', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 23:31:46', NULL, '9c4254dcfca0f81367a0d909c72e9dd4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-145', 'task', 'T-132', 'Per-session status strip: model / permission-mode / context%', 'A compact per-session status strip in the lead Claude pane header AND each teammate tile header (T-140), showing: current model, permission mode (accept-edits / plan / default / bypass), and context-window usage %. - -Shared status source (this ticket owns it): extract per-session status in the drift-contained reader/observer (D-75) — do NOT parse CC internals in the widgets. -- Model: team config member.model, overridden by the latest assistant message.model in the transcript when present. -- Permission mode: the transcript''s `permission-mode` records (TranscriptReader currently SKIPS these — surface the latest instead). Also honor config member.planModeRequired for teammates. -- Context %: latest assistant message.usage (input_tokens + cache_read_input_tokens + cache_creation_input_tokens + output_tokens) over the model''s context limit. - -Delivery: emit a per-session status to the UI via the bus/events (e.g. a TeamMemberStatus event or an extra MessageBus channel) so BOTH the header strip and the T-141 sidebar render from one source without re-tailing. - -Applies to the lead session and every teammate. Re-validate field names against a live team run / CC version bump (undocumented, per T-134). - -Acceptance: lead pane header and each teammate tile header show current model + an accept-edits/plan-mode badge + a context-usage indicator, updating live from the transcript. Blocked by T-137 (done), T-140 (done). -', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-23 06:07:59', '2026-05-23 06:07:59', NULL, '5047ae730e9065c684fcecf557353de7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-141', 'task', 'T-132', 'Team-meta sidebar (roster + token budget)', 'New sidebar panel (contributed to Slots.sidebar), visible only in team mode: a roster row per team member that MIRRORS the per-session status surfaced by T-145, plus team-wide meta. - -Per-member row: -- Identity from the team config + observer: name, agentType, color; membership/lifecycle from TeamMemberJoined/Left. -- Mirror of T-145''s shared status: current model, permission-mode badge (accept-edits / plan / default / bypass), context-window usage %. Consume the SAME status source/event T-145 emits — do NOT re-tail transcripts or re-parse CC internals here (D-75). -- Live status verb + elapsed-since-last-activity + down/up token deltas from that same status feed. - -Team-wide: -- Team/account token budget pulled from the Anthropic usage API on a timer (distinct from per-conversation context above). - -Acceptance: roster matches list-panes/config; each row mirrors the member''s model + accept-edits/plan badge + context-usage; status verb and token deltas update live; account budget refreshes on its timer; panel appears only when a team is active. Depends on T-145 (status source) + T-139 (done). -', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-23 06:08:24', NULL, '54edba4b89b8926c3527b4f1174c2a4c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-146', 'bug', 'T-132', 'Secondary Claude panes render the primary''s transcript', 'Regression from T-137: every Claude pane renders the newest .jsonl in the workspace''s munged dir, so concurrent sessions in one workspace (primary + secondaries) all converge on the same transcript — a secondary tab shows the primary''s conversation (and the primary follows whichever session is newest). The pre-T-137 terminal pane rendered each tab''s own PTY, so tabs were independent. +Findings (2026-05-22): docs/spikes/cc-team-internals-2.1.148.md. All 5 checks answered from real artifacts + tmux manual + synthetic control-mode test (no live run/quota). Key risk for T-139: pane->teammate->transcript identity joins only on agentType (config <-> .meta.json), ambiguous for same-type teammates -> needs joinedAt/mtime or lead-transcript tiebreaker. tmux 3.6a has no %pane-died; poll list-panes -a. Teammate transcripts at //subagents/agent-.jsonl + .meta.json{agentType,description}.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 17:55:11', NULL, 'fd761165a2bd6d554de00f51b4b776be', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6E3X5WWRS09GCDGK8', 'story', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'ConversationView + replace the terminal in the Claude pane', 'New lib/builtin/claude/src/conversation_view.dart rendering user / assistant(markdown via ClideMarkdown) / tool-call card / tool-result+diff card; whole tree under one SelectionArea. Swap ClidePtyView -> ConversationView in lib/builtin/claude/src/claude_pane.dart (~line 287). Claude still runs in tmux (detached / output ignored) but content comes from the transcript (TranscriptReader). Keep the terminal builtin + ClidePtyView for general use. Surface conversation as events too (D-6). Cite D-75. Acceptance: make run shows the native Claude conversation from the live transcript; select+copy across cards works; no terminal in the Claude pane; terminal builtin still works; widget tests. Blocked by T3, T4.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 18:14:32', NULL, 'c7907de905ec5dfff0c89ca2fad57b51', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4V1PZ0EK6G60699EM', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Native composer + file/image paste', 'New lib/builtin/claude/src/claude_composer.dart: native text field -> tmux -L clide send-keys -t (Enter submit, Shift+Enter newline). Paste text inline; file/image per the T2 spike finding (likely temp-file + path/@-reference, since send-keys carries text only). Acceptance: typing in the composer reaches Claude; pasting a text file and an image both work via the validated mechanism. Blocked by T2, T5.', 'done', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 21:49:09', NULL, '893e645c6a0d43d5ee47ec04fb1df52d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427ZFSTWJ21EBK5FG', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Composer attachment chips (thumbnail/icon on paste)', 'Show pasted files/images as chips above the composer input instead of inserting the @path as text. Image chips render a thumbnail (Image.file of the cache/temp file); other types show a Phosphor file icon + basename; each chip has a remove ×. On submit, the typed text plus the chips'' @path tokens are assembled into the message sent via tmux.sendMessage, then text + chips clear. Typed @path text still works. resolveClipboardAttachment changes to return a list of attachment descriptors (path + isImage). No new package deps (Image.file + existing Phosphor icons). Builds on T-138.', 'done', 'medium', NULL, NULL, NULL, '2026-05-22 22:06:59', '2026-05-22 22:18:02', NULL, '1f97c9b10d740bbc4c27d87a1cc17eab', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YWAC8SSGQBK31T98', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Distinguish user message cards in the conversation', 'User messages render flat (label + text on the canvas) like Claude''s responses, so it''s hard to scan what you said vs what Claude said. Give UserMessage a distinct card: a filled background (globalBackground vs the panelBackground canvas), a left accent stripe (globalFocus), padding and rounded corners. Claude''s text responses stay flat markdown (better for long answers). conversation_view.dart _ConversationTurn.', 'done', 'low', NULL, NULL, NULL, '2026-05-22 22:31:22', '2026-05-22 22:36:09', NULL, '29cc73005b5c1574032b33a2da19469d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BV0CXWCT04Z81EG0', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Claude message card with brand-orange accent', 'Extend the distinct-card treatment (T-143) to Claude''s text responses with a different accent: Claude''s brand coral-orange (#D97757), vs the user card''s focus-colour stripe. Refactor _userCard into a shared _messageCard(label, accent, body). conversation_view.dart.', 'done', 'low', NULL, NULL, NULL, '2026-05-22 22:40:42', '2026-05-22 22:43:45', NULL, '0f5df8bc9b99556647f168d9d60b20e6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM653J6604KYDK6RT8R', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'tmux team observer (lifecycle + identity + per-teammate transcripts)', 'New lib/builtin/claude/src/team_observer.dart: detect teammate spawn/exit (control-mode or list-panes -a poll on -L clide), map pane->teammate via ~/.claude/teams config + agentId, spin a TranscriptReader per teammate, emit new TeamMemberBorn/TeamMemberDied ClideEvents (lib/kernel/src/events/types.dart). ALL fragile CC-internals parsing isolated here (single drift-containment point; key off the version field). Cite D-75. Acceptance: a real team run emits born/died with correct identity; per-teammate content streams. Blocked by T2, T4.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 23:16:28', NULL, 'bdae2494e404fab83365166647dcba42', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AVSMKD15KBJYX48W', 'story', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Teammate tile grid (faithful tiling, responsive auto-wrap)', 'Tiling host: lead ConversationView (left, with composer) + teammate ConversationViews (right) in a responsive auto-wrap grid (1->2->3 columns by count); resizable lead/teammate split. Workspace currently only vertical-splits, so extend _WorkspaceSlot (lib/app.dart) or add team slots in lib/kernel/src/panels/layout_preset.dart; reuse DragResizeHandle (lib/kernel/src/panels/drag_resize.dart). Driven by TeamMemberBorn/Died from the observer. Acceptance: tiles appear/disappear with lifecycle; grid wraps 1->2->3 by count; split resizes; select-across-tiles works. Blocked by T5, T7.', 'done', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-22 23:31:46', NULL, 'a862d2c4981582f6baa639f0c840ef40', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM558PCHET90BBSHGTC', 'bug', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Secondary Claude panes render the primary''s transcript', 'Regression from T-137: every Claude pane renders the newest .jsonl in the workspace''s munged dir, so concurrent sessions in one workspace (primary + secondaries) all converge on the same transcript — a secondary tab shows the primary''s conversation (and the primary follows whichever session is newest). The pre-T-137 terminal pane rendered each tab''s own PTY, so tabs were independent. Fix: bind each pane to a specific Claude session id. - claude CLI supports `--session-id ` (verified via --help). Spawn each pane''s claude with its own UUID; a transcript is named .jsonl, so the pane''s transcript is /.jsonl. @@ -2535,27 +888,10 @@ Migration caveat: an existing tmux session created by the OLD code (no --session Open question to validate live: whether `claude --session-id ` resumes vs. errors when re-spawned after the tmux session was killed (display works either way since the file is .jsonl, but worth confirming claude starts). Acceptance: a secondary tab shows its OWN fresh conversation, independent of the primary; the primary keeps its conversation; both update live. Regression of T-137; relates to D-41. -', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-23 06:19:51', '2026-05-23 06:19:51', NULL, 'c47ada73a71c18d1998cc6fdc36d1b22', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-146', 'bug', 'T-132', 'Secondary Claude panes render the primary''s transcript', 'Regression from T-137: every Claude pane renders the newest .jsonl in the workspace''s munged dir, so concurrent sessions in one workspace (primary + secondaries) all converge on the same transcript — a secondary tab shows the primary''s conversation (and the primary follows whichever session is newest). The pre-T-137 terminal pane rendered each tab''s own PTY, so tabs were independent. - -Fix: bind each pane to a specific Claude session id. -- claude CLI supports `--session-id ` (verified via --help). Spawn each pane''s claude with its own UUID; a transcript is named .jsonl, so the pane''s transcript is /.jsonl. -- Reader targets that exact file via TranscriptReader''s `file:` param (added in T-139). Guard _tail against the file not existing yet (claude writes it shortly after spawn). -- Each pane publishes/subscribes on a per-session MessageBus channel (conversation/) so primary and secondary controllers don''t cross-talk. -- UUID per pane, seeded by the tmux session name (the identity we already have): primary = deterministic from its stable session name (clide-claude-) so it RESUMES across restarts (D-41 continuity); secondary = random per spawn so each is a clean/fresh session (a deterministic secondary id would make starting a clean session impossible). - -Migration caveat: an existing tmux session created by the OLD code (no --session-id, claude picked its own id) won''t match the new deterministic id — it must be killed (claude.kill-all-sessions) once so the next spawn binds the controlled id. - -Open question to validate live: whether `claude --session-id ` resumes vs. errors when re-spawned after the tmux session was killed (display works either way since the file is .jsonl, but worth confirming claude starts). - -Acceptance: a secondary tab shows its OWN fresh conversation, independent of the primary; the primary keeps its conversation; both update live. Regression of T-137; relates to D-41. -', 'done', 'high', NULL, NULL, NULL, '2026-05-23 06:19:51', '2026-05-23 06:30:47', NULL, '2e1843c9b540a3d7ced03052d9efcbc4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-147', 'bug', 'T-132', 'Claude pane gets stuck waiting on a stale/legacy session', 'When the bound transcript (/.jsonl) never appears — e.g. attaching via new-session -A to a session created before --session-id binding (T-146), or any unconnectable session — the pane shows ''Waiting for Claude…'' forever. Fix: before spawning, if the bound transcript for our deterministic session id does not exist, kill the clide tmux session of that exact name so new-session creates a clean session with our --session-id. SAFETY (hard requirement): only ever kill clide-initiated sessions — by exact clide-claude- name, on the private -L clide socket (a user''s terminal claude is never on that socket); never delete any transcript file. Also makes clean-install/first-run robust. Live-verify the stuck primary self-heals on relaunch and a secondary still gets a fresh independent session.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 07:03:18', NULL, '21f7ebec0ddcab48a3ea7e8be0c0e979', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-148', 'task', 'T-132', 'Claude transcript disk-usage indicator + cleanup button', 'Surface transcript/session disk usage in the Claude panel with a cleanup affordance so the user can pick and choose what to remove. Per-session sizes (the /*.jsonl files + subagents/), a total, and a button to delete selected sessions'' transcripts. User-driven only — clide never auto-deletes transcript content. Future/''at some point'' per user. Relates to T-146 (session ids) and D-75.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 07:03:18', NULL, '7bbe7f4baf80027516cc46cff93a8dfe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-147', 'bug', 'T-132', 'Claude pane gets stuck waiting on a stale/legacy session', 'When the bound transcript (/.jsonl) never appears — e.g. attaching via new-session -A to a session created before --session-id binding (T-146), or any unconnectable session — the pane shows ''Waiting for Claude…'' forever. Fix: before spawning, if the bound transcript for our deterministic session id does not exist, kill the clide tmux session of that exact name so new-session creates a clean session with our --session-id. SAFETY (hard requirement): only ever kill clide-initiated sessions — by exact clide-claude- name, on the private -L clide socket (a user''s terminal claude is never on that socket); never delete any transcript file. Also makes clean-install/first-run robust. Live-verify the stuck primary self-heals on relaunch and a secondary still gets a fresh independent session.', 'done', 'high', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 07:06:54', NULL, '4c08227959a8fe6905521ebdc42487c1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-149', 'task', 'T-132', 'Native Claude pane startup banner (empty state)', 'Replace the bland ''Waiting for Claude…'' empty state with a native clide banner: clide logo, a Claude label (brand orange), the session role (primary/secondary N), the workspace path (home-collapsed), and the tmux session/status line, plus a ''warming up'' hint. Owned/native — no tmux capture-pane, no CC-internals coupling. ConversationView gains an optional emptyState widget; ClaudePane supplies a ClaudeBanner built from data it already has (repoRoot, statusLine, isPrimary).', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-23 07:14:36', '2026-05-23 07:14:36', NULL, '741b021184df1596b364aa2d09167e85', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-149', 'task', 'T-132', 'Native Claude pane startup banner (empty state)', 'Replace the bland ''Waiting for Claude…'' empty state with a native clide banner: clide logo, a Claude label (brand orange), the session role (primary/secondary N), the workspace path (home-collapsed), and the tmux session/status line, plus a ''warming up'' hint. Owned/native — no tmux capture-pane, no CC-internals coupling. ConversationView gains an optional emptyState widget; ClaudePane supplies a ClaudeBanner built from data it already has (repoRoot, statusLine, isPrimary).', 'done', 'low', NULL, NULL, NULL, '2026-05-23 07:14:36', '2026-05-23 07:27:32', NULL, '1d922f79ed8f98bea75042af8662dd74', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-145', 'task', 'T-132', 'Per-session status strip: model / permission-mode / context%', 'A compact per-session status strip in the lead Claude pane header AND each teammate tile header (T-140), showing: current model, permission mode (accept-edits / plan / default / bypass), and context-window usage %. +', 'done', 'high', NULL, NULL, NULL, '2026-05-23 06:19:51', '2026-05-23 06:30:47', NULL, '146ac53bba08ab11c1e6d822e16e4699', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM47FEEMHPE8J5VVBKM', 'bug', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Claude pane gets stuck waiting on a stale/legacy session', 'When the bound transcript (/.jsonl) never appears — e.g. attaching via new-session -A to a session created before --session-id binding (T-146), or any unconnectable session — the pane shows ''Waiting for Claude…'' forever. Fix: before spawning, if the bound transcript for our deterministic session id does not exist, kill the clide tmux session of that exact name so new-session creates a clean session with our --session-id. SAFETY (hard requirement): only ever kill clide-initiated sessions — by exact clide-claude- name, on the private -L clide socket (a user''s terminal claude is never on that socket); never delete any transcript file. Also makes clean-install/first-run robust. Live-verify the stuck primary self-heals on relaunch and a secondary still gets a fresh independent session.', 'done', 'high', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 07:06:54', NULL, '0e52f0983f071764a1cd2282c2b9bf9f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GDS1R8H2TW12WX7G', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Native Claude pane startup banner (empty state)', 'Replace the bland ''Waiting for Claude…'' empty state with a native clide banner: clide logo, a Claude label (brand orange), the session role (primary/secondary N), the workspace path (home-collapsed), and the tmux session/status line, plus a ''warming up'' hint. Owned/native — no tmux capture-pane, no CC-internals coupling. ConversationView gains an optional emptyState widget; ClaudePane supplies a ClaudeBanner built from data it already has (repoRoot, statusLine, isPrimary).', 'done', 'low', NULL, NULL, NULL, '2026-05-23 07:14:36', '2026-05-23 07:27:32', NULL, 'b7112988f9ecc4dfab344d552f492287', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KN50C7A723SEKZXG', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Per-session status strip: model / permission-mode / context%', 'A compact per-session status strip in the lead Claude pane header AND each teammate tile header (T-140), showing: current model, permission mode (accept-edits / plan / default / bypass), and context-window usage %. Shared status source (this ticket owns it): extract per-session status in the drift-contained reader/observer (D-75) — do NOT parse CC internals in the widgets. - Model: team config member.model, overridden by the latest assistant message.model in the transcript when present. @@ -2567,38 +903,12 @@ Delivery: emit a per-session status to the UI via the bus/events (e.g. a TeamMem Applies to the lead session and every teammate. Re-validate field names against a live team run / CC version bump (undocumented, per T-134). Acceptance: lead pane header and each teammate tile header show current model + an accept-edits/plan-mode badge + a context-usage indicator, updating live from the transcript. Blocked by T-137 (done), T-140 (done). -', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-23 06:07:59', '2026-05-23 08:03:56', NULL, '917515bd4833dddf3299994c1d6739d2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-145', 'task', 'T-132', 'Per-session status strip: model / permission-mode / context%', 'A compact per-session status strip in the lead Claude pane header AND each teammate tile header (T-140), showing: current model, permission mode (accept-edits / plan / default / bypass), and context-window usage %. - -Shared status source (this ticket owns it): extract per-session status in the drift-contained reader/observer (D-75) — do NOT parse CC internals in the widgets. -- Model: team config member.model, overridden by the latest assistant message.model in the transcript when present. -- Permission mode: the transcript''s `permission-mode` records (TranscriptReader currently SKIPS these — surface the latest instead). Also honor config member.planModeRequired for teammates. -- Context %: latest assistant message.usage (input_tokens + cache_read_input_tokens + cache_creation_input_tokens + output_tokens) over the model''s context limit. - -Delivery: emit a per-session status to the UI via the bus/events (e.g. a TeamMemberStatus event or an extra MessageBus channel) so BOTH the header strip and the T-141 sidebar render from one source without re-tailing. - -Applies to the lead session and every teammate. Re-validate field names against a live team run / CC version bump (undocumented, per T-134). - -Acceptance: lead pane header and each teammate tile header show current model + an accept-edits/plan-mode badge + a context-usage indicator, updating live from the transcript. Blocked by T-137 (done), T-140 (done). -', 'done', 'medium', NULL, NULL, NULL, '2026-05-23 06:07:59', '2026-05-23 08:43:19', NULL, 'e7823e9d4aab062c19296bcbe5f5fc59', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-150', 'task', 'T-132', 'ClidePane primitive + focus-driven status-bar slot', 'A behavior-only pane wrapper (no chrome) every pane uses, plus the plumbing to surface a pane status widget to the bottom status bar. FocusTracker gains: activeStatusWidget (the focused pane status widget), setStatusWidget(contributionId, widget) applied ONLY when that contribution is focused (background panes ignored; they keep content locally and re-convey on focus), and clears activeStatusWidget on focus change. ClidePane(contributionId, statusWidget, active, child): when it is the shown pane (active sub-tab AND its contribution focused) it conveys statusWidget on focus-gain and on content change. New status-bar item renders focus.activeStatusWidget in a fixed-height Container that marquee-scrolls on overflow (new ClideMarquee primitive, own-the-stack, no package). Claude adopts ClidePane (statusWidget = the model/mode/context line), replacing the bus-text slot from 06cf9f8. Disconnects slot plumbing from what a pane renders: text, icon, dropdown, animation, anything.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-23 09:04:40', '2026-05-23 09:04:54', NULL, '14e4ec869de090b66ff3003f39924361', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-150', 'task', 'T-132', 'ClidePane primitive + focus-driven status-bar slot', 'A behavior-only pane wrapper (no chrome) every pane uses, plus the plumbing to surface a pane status widget to the bottom status bar. FocusTracker gains: activeStatusWidget (the focused pane status widget), setStatusWidget(contributionId, widget) applied ONLY when that contribution is focused (background panes ignored; they keep content locally and re-convey on focus), and clears activeStatusWidget on focus change. ClidePane(contributionId, statusWidget, active, child): when it is the shown pane (active sub-tab AND its contribution focused) it conveys statusWidget on focus-gain and on content change. New status-bar item renders focus.activeStatusWidget in a fixed-height Container that marquee-scrolls on overflow (new ClideMarquee primitive, own-the-stack, no package). Claude adopts ClidePane (statusWidget = the model/mode/context line), replacing the bus-text slot from 06cf9f8. Disconnects slot plumbing from what a pane renders: text, icon, dropdown, animation, anything.', 'done', 'medium', NULL, NULL, NULL, '2026-05-23 09:04:40', '2026-05-23 09:33:03', NULL, 'dd56efcec1ac1988c5e4cf4c4e693bcb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-151', 'task', 'T-132', 'ClaudeConfig service: layered config + skills/commands + version-keyed slash probe', 'Builtin-owned (lib/builtin/claude/) app-wide source of truth for Claude Code''s environment per D-76. Loads and layers GLOBAL (~/.claude) as base with LOCAL (.claude) overriding: skills (skills/*/SKILL.md frontmatter name+description), custom slash commands (commands/*.md), settings.json, and permission rules (allow/deny/ask). Built-in slash commands (not on disk) come from a one-shot ''claude --output-format stream-json'' probe, cached keyed on the resolved claude version id so additions/deprecations re-capture on upgrade; a small static list is the fallback. FileWatcher (lib/src/files/watcher.dart) on both .claude dirs plus an explicit refresh; expose typed, listenable views. No kernel changes — builtin-owned (Claude is a non-disableable extension, but still an extension). Acceptance: empirically confirm the stream-json init message carries slash_commands for the pinned CC version (spike folded in here) before consumers rely on it; unit tests with fixture global+local .claude dirs covering layering, watcher-driven refresh, probe cache keyed on version, and static fallback; degrades gracefully on parse miss; coverage >= floor. Blocks the typeahead, command-aware send, and status-pane wiring.', 'backlog', 'high', NULL, NULL, 'D-76', '2026-05-23 09:52:14', '2026-05-23 09:52:14', NULL, '8e0e460b9281e9e8bb35e4c951c11bf6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-152', 'task', 'T-132', 'Composer slash-command typeahead (inline detection, CLI-style)', 'Make ClaudeComposer (lib/builtin/claude/src/claude_composer.dart) slash-aware. Detect a /token at the cursor ANYWHERE in the text — inline mid-sentence, not only at position 0 (user requirement) — and show a typeahead popup listing matching slash commands + skills sourced from ClaudeConfig (T-151), filtering as the token grows. Keyboard nav: Up/Down to move, Tab/Enter to complete, Esc to dismiss; reuse ClidePalette interaction patterns. Completing inserts the command token at the cursor. Never re-scans the filesystem — reads from ClaudeConfig and refreshes when it changes. Acceptance: typing / opens the list; an inline / after existing text also opens it; arrow/Tab/Enter/Esc behave; the list reflects ClaudeConfig (skills + custom commands + built-ins); widget tests; a11y (focusable, labelled, contrast). Blocked by T-151.', 'backlog', 'medium', NULL, NULL, 'D-76', '2026-05-23 09:52:20', '2026-05-23 09:52:20', NULL, 'b83a83ba0e72ba1855afca1bc5f9f8cd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-153', 'task', 'T-132', 'Command-aware send: bypass bracketed-paste for valid slash commands', 'Fix the delivery mismatch where slash commands behave differently in clide than the CLI. tmux.sendMessage (lib/builtin/claude/src/tmux_session.dart) always uses paste-buffer -p (bracketed paste), and Claude''s TUI deliberately does not run slash-command parsing on bracketed-pasted content — so /cmd and /skill arrive as literal prompt text instead of invoking. Fix: when the submitted input is a valid slash command (per ClaudeConfig, T-151) — or single-line input generally — deliver it typed via tmux send-keys -l -- then Enter, so the TUI parses it like the CLI; keep paste-buffer -p only for multi-line content (mirrors encodeClaudeInput''s existing single-vs-multiline logic on the PTY fallback path). Acceptance: a typed or typeahead-selected slash command actually invokes the skill/command in a real make-run session (parity with the CLI); multi-line messages still arrive as one block, not a stream of submits; unit tests on the send-encoding branches (single-line vs multi-line vs recognized-command); coverage >= floor. Blocked by T-151.', 'backlog', 'medium', NULL, NULL, 'D-76', '2026-05-23 09:52:27', '2026-05-23 09:52:27', NULL, '61392ef8e182bc4be406639b9898bcac', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-154', 'task', 'T-132', 'Surface ClaudeConfig in the Claude status pane', 'The status surface shows LIVE model / permission-mode / context-tokens from the active transcript (T-145, T-150). Complement it with the CONFIGURED side from ClaudeConfig (T-151): available-skills count and/or configured permission/model defaults — static environment state alongside live session state. Reads from ClaudeConfig, not the filesystem; updates when config changes. Acceptance: the status surface reflects ClaudeConfig values, refreshes on config change, and keeps the live transcript-driven fields working; widget test; coverage >= floor. Blocked by T-151.', 'backlog', 'low', NULL, NULL, 'D-76', '2026-05-23 09:52:32', '2026-05-23 09:52:32', NULL, '407255f39712397b5e6fdde42a0cce0c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-151', 'task', 'T-132', 'ClaudeConfig service: layered config + skills/commands + version-keyed slash probe', 'Builtin-owned (lib/builtin/claude/) app-wide source of truth for Claude Code''s environment per D-76. Loads and layers GLOBAL (~/.claude) as base with LOCAL (.claude) overriding: skills (skills/*/SKILL.md frontmatter name+description), custom slash commands (commands/*.md), settings.json, and permission rules (allow/deny/ask). Built-in slash commands (not on disk) come from a one-shot ''claude --output-format stream-json'' probe, cached keyed on the resolved claude version id so additions/deprecations re-capture on upgrade; a small static list is the fallback. FileWatcher (lib/src/files/watcher.dart) on both .claude dirs plus an explicit refresh; expose typed, listenable views. No kernel changes — builtin-owned (Claude is a non-disableable extension, but still an extension). Acceptance: empirically confirm the stream-json init message carries slash_commands for the pinned CC version (spike folded in here) before consumers rely on it; unit tests with fixture global+local .claude dirs covering layering, watcher-driven refresh, probe cache keyed on version, and static fallback; degrades gracefully on parse miss; coverage >= floor. Blocks the typeahead, command-aware send, and status-pane wiring.', 'in_progress', 'high', NULL, NULL, 'D-76', '2026-05-23 09:52:14', '2026-05-23 09:54:31', NULL, '65cd97999dfa1b15ffbf59e88478d5e2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-151', 'task', 'T-132', 'ClaudeConfig service: layered config + skills/commands + version-keyed slash probe', 'Builtin-owned (lib/builtin/claude/) app-wide source of truth for Claude Code''s environment per D-76. Loads and layers GLOBAL (~/.claude) as base with LOCAL (.claude) overriding: skills (skills/*/SKILL.md frontmatter name+description), custom slash commands (commands/*.md), settings.json, and permission rules (allow/deny/ask). Built-in slash commands (not on disk) come from a one-shot ''claude --output-format stream-json'' probe, cached keyed on the resolved claude version id so additions/deprecations re-capture on upgrade; a small static list is the fallback. FileWatcher (lib/src/files/watcher.dart) on both .claude dirs plus an explicit refresh; expose typed, listenable views. No kernel changes — builtin-owned (Claude is a non-disableable extension, but still an extension). Acceptance: empirically confirm the stream-json init message carries slash_commands for the pinned CC version (spike folded in here) before consumers rely on it; unit tests with fixture global+local .claude dirs covering layering, watcher-driven refresh, probe cache keyed on version, and static fallback; degrades gracefully on parse miss; coverage >= floor. Blocks the typeahead, command-aware send, and status-pane wiring.', 'done', 'high', NULL, NULL, 'D-76', '2026-05-23 09:52:14', '2026-05-23 10:20:12', NULL, '3bd3ace6e31bca9f7458145fb2171425', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-153', 'task', 'T-132', 'Command-aware send: bypass bracketed-paste for valid slash commands', 'Fix the delivery mismatch where slash commands behave differently in clide than the CLI. tmux.sendMessage (lib/builtin/claude/src/tmux_session.dart) always uses paste-buffer -p (bracketed paste), and Claude''s TUI deliberately does not run slash-command parsing on bracketed-pasted content — so /cmd and /skill arrive as literal prompt text instead of invoking. Fix: when the submitted input is a valid slash command (per ClaudeConfig, T-151) — or single-line input generally — deliver it typed via tmux send-keys -l -- then Enter, so the TUI parses it like the CLI; keep paste-buffer -p only for multi-line content (mirrors encodeClaudeInput''s existing single-vs-multiline logic on the PTY fallback path). Acceptance: a typed or typeahead-selected slash command actually invokes the skill/command in a real make-run session (parity with the CLI); multi-line messages still arrive as one block, not a stream of submits; unit tests on the send-encoding branches (single-line vs multi-line vs recognized-command); coverage >= floor. Blocked by T-151.', 'in_progress', 'medium', NULL, NULL, 'D-76', '2026-05-23 09:52:27', '2026-05-23 10:31:31', NULL, 'fc5104b0410e9db5da9f85962f7f3ac9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-153', 'task', 'T-132', 'Command-aware send: bypass bracketed-paste for valid slash commands', 'Fix the delivery mismatch where slash commands behave differently in clide than the CLI. tmux.sendMessage (lib/builtin/claude/src/tmux_session.dart) always uses paste-buffer -p (bracketed paste), and Claude''s TUI deliberately does not run slash-command parsing on bracketed-pasted content — so /cmd and /skill arrive as literal prompt text instead of invoking. Fix: when the submitted input is a valid slash command (per ClaudeConfig, T-151) — or single-line input generally — deliver it typed via tmux send-keys -l -- then Enter, so the TUI parses it like the CLI; keep paste-buffer -p only for multi-line content (mirrors encodeClaudeInput''s existing single-vs-multiline logic on the PTY fallback path). Acceptance: a typed or typeahead-selected slash command actually invokes the skill/command in a real make-run session (parity with the CLI); multi-line messages still arrive as one block, not a stream of submits; unit tests on the send-encoding branches (single-line vs multi-line vs recognized-command); coverage >= floor. Blocked by T-151.', 'done', 'medium', NULL, NULL, 'D-76', '2026-05-23 09:52:27', '2026-05-23 10:35:56', NULL, '05aa3d095767c48b831ece6289897483', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-152', 'task', 'T-132', 'Composer slash-command typeahead (inline detection, CLI-style)', 'Make ClaudeComposer (lib/builtin/claude/src/claude_composer.dart) slash-aware. Detect a /token at the cursor ANYWHERE in the text — inline mid-sentence, not only at position 0 (user requirement) — and show a typeahead popup listing matching slash commands + skills sourced from ClaudeConfig (T-151), filtering as the token grows. Keyboard nav: Up/Down to move, Tab/Enter to complete, Esc to dismiss; reuse ClidePalette interaction patterns. Completing inserts the command token at the cursor. Never re-scans the filesystem — reads from ClaudeConfig and refreshes when it changes. Acceptance: typing / opens the list; an inline / after existing text also opens it; arrow/Tab/Enter/Esc behave; the list reflects ClaudeConfig (skills + custom commands + built-ins); widget tests; a11y (focusable, labelled, contrast). Blocked by T-151.', 'in_progress', 'medium', NULL, NULL, 'D-76', '2026-05-23 09:52:20', '2026-05-23 10:37:57', NULL, '470583866efdc3cb6aec47e013519348', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-152', 'task', 'T-132', 'Composer slash-command typeahead (inline detection, CLI-style)', 'Make ClaudeComposer (lib/builtin/claude/src/claude_composer.dart) slash-aware. Detect a /token at the cursor ANYWHERE in the text — inline mid-sentence, not only at position 0 (user requirement) — and show a typeahead popup listing matching slash commands + skills sourced from ClaudeConfig (T-151), filtering as the token grows. Keyboard nav: Up/Down to move, Tab/Enter to complete, Esc to dismiss; reuse ClidePalette interaction patterns. Completing inserts the command token at the cursor. Never re-scans the filesystem — reads from ClaudeConfig and refreshes when it changes. Acceptance: typing / opens the list; an inline / after existing text also opens it; arrow/Tab/Enter/Esc behave; the list reflects ClaudeConfig (skills + custom commands + built-ins); widget tests; a11y (focusable, labelled, contrast). Blocked by T-151.', 'done', 'medium', NULL, NULL, 'D-76', '2026-05-23 09:52:20', '2026-05-23 10:43:52', NULL, 'baa4e56a765b8078c23babcbc739e26e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-155', 'task', NULL, 'Finish i18n catalog plumbing: auto-load extension namespaces, kill ''namespace not registered'' warnings', 'Five builtins (tickets, decisions, git, pql, problems) declare titleKey: ''tab.title'' + i18nNamespace on their TabContribution but ship NO catalog file (lib/kernel/src/i18n/catalog/ has only the 8 tier-0 namespaces) and aren''t in the hand-maintained _tier0Namespaces preload list (lib/main.dart). Result: every boot logs ''i18n: namespace not registered: builtin.X (key: tab.title)'' for each. Tabs still render correctly via the placeholder fallback, so it''s cosmetic console noise — but it recurs for every new builtin because the preload list is maintained by hand. - -Proper fix: when an extension activates, auto-load the i18nNamespace(s) declared by its contributions via i18n.ensureNamespaceLoaded — removing the manual _tier0Namespaces list and the drift it causes. AssetCatalogLoader.load returns empty for a missing file, so auto-preloading alone would just shift the warning to ''missing key tab.title'' — so the missing catalogs must also be created: builtin.{tickets,decisions,git,pql,problems}_en_us.json with at least tab.title plus any other keys those extensions look up. - -Acceptance: clean boot with all builtins active logs zero i18n warnings; a new builtin that declares a titleKey gets its namespace loaded automatically with no manual list edit; the five catalog files exist; tests cover the auto-load-on-activate path and the missing-catalog case; coverage >= floor.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-23 11:06:50', '2026-05-23 11:06:50', NULL, 'c41b10fdead4afe4aac9a01af31d0fdd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-156', 'bug', 'T-132', 'Claude pane doesn''t follow session switches (/clear, /compact) — interaction wedges', 'Now that T-153 makes slash commands actually fire, session-mutating commands wedge the Claude pane. /clear makes Claude Code start a NEW session-id (a fresh .jsonl). The composer''s keystrokes still reach the same tmux/claude process, but clide''s TranscriptReader is pinned to the spawn --session-id file (claude_pane binds the reader to a fixed transcriptFile; transcript_reader.dart has zero session-change handling). After /clear: the old transcript freezes, Claude writes to the new session file, and clide keeps tailing the dead file — no responses appear and the pane looks completely broken. Affects /clear and likely /compact, /resume, --fork-session. +', 'done', 'medium', NULL, NULL, NULL, '2026-05-23 06:07:59', '2026-05-23 08:43:19', NULL, 'dd8a218adfef6ab3bfeaa6897caf242a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6F9XA7F2BX9P18BR4', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'ClidePane primitive + focus-driven status-bar slot', 'A behavior-only pane wrapper (no chrome) every pane uses, plus the plumbing to surface a pane status widget to the bottom status bar. FocusTracker gains: activeStatusWidget (the focused pane status widget), setStatusWidget(contributionId, widget) applied ONLY when that contribution is focused (background panes ignored; they keep content locally and re-convey on focus), and clears activeStatusWidget on focus change. ClidePane(contributionId, statusWidget, active, child): when it is the shown pane (active sub-tab AND its contribution focused) it conveys statusWidget on focus-gain and on content change. New status-bar item renders focus.activeStatusWidget in a fixed-height Container that marquee-scrolls on overflow (new ClideMarquee primitive, own-the-stack, no package). Claude adopts ClidePane (statusWidget = the model/mode/context line), replacing the bus-text slot from 06cf9f8. Disconnects slot plumbing from what a pane renders: text, icon, dropdown, animation, anything.', 'done', 'medium', NULL, NULL, NULL, '2026-05-23 09:04:40', '2026-05-23 09:33:03', NULL, 'fbf0c7bfe20c3fab8b8bea0ed2db8c69', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM77GWREHKA9K45P020', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'ClaudeConfig service: layered config + skills/commands + version-keyed slash probe', 'Builtin-owned (lib/builtin/claude/) app-wide source of truth for Claude Code''s environment per D-76. Loads and layers GLOBAL (~/.claude) as base with LOCAL (.claude) overriding: skills (skills/*/SKILL.md frontmatter name+description), custom slash commands (commands/*.md), settings.json, and permission rules (allow/deny/ask). Built-in slash commands (not on disk) come from a one-shot ''claude --output-format stream-json'' probe, cached keyed on the resolved claude version id so additions/deprecations re-capture on upgrade; a small static list is the fallback. FileWatcher (lib/src/files/watcher.dart) on both .claude dirs plus an explicit refresh; expose typed, listenable views. No kernel changes — builtin-owned (Claude is a non-disableable extension, but still an extension). Acceptance: empirically confirm the stream-json init message carries slash_commands for the pinned CC version (spike folded in here) before consumers rely on it; unit tests with fixture global+local .claude dirs covering layering, watcher-driven refresh, probe cache keyed on version, and static fallback; degrades gracefully on parse miss; coverage >= floor. Blocks the typeahead, command-aware send, and status-pane wiring.', 'done', 'high', NULL, NULL, 'D-76', '2026-05-23 09:52:14', '2026-05-23 10:20:12', NULL, 'a76dde29c433ca59418be60bbd454a69', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7BJS5E1JJGSJ9XHG0', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Command-aware send: bypass bracketed-paste for valid slash commands', 'Fix the delivery mismatch where slash commands behave differently in clide than the CLI. tmux.sendMessage (lib/builtin/claude/src/tmux_session.dart) always uses paste-buffer -p (bracketed paste), and Claude''s TUI deliberately does not run slash-command parsing on bracketed-pasted content — so /cmd and /skill arrive as literal prompt text instead of invoking. Fix: when the submitted input is a valid slash command (per ClaudeConfig, T-151) — or single-line input generally — deliver it typed via tmux send-keys -l -- then Enter, so the TUI parses it like the CLI; keep paste-buffer -p only for multi-line content (mirrors encodeClaudeInput''s existing single-vs-multiline logic on the PTY fallback path). Acceptance: a typed or typeahead-selected slash command actually invokes the skill/command in a real make-run session (parity with the CLI); multi-line messages still arrive as one block, not a stream of submits; unit tests on the send-encoding branches (single-line vs multi-line vs recognized-command); coverage >= floor. Blocked by T-151.', 'done', 'medium', NULL, NULL, 'D-76', '2026-05-23 09:52:27', '2026-05-23 10:35:56', NULL, 'aa6834831d4423fd3c0718d6b167da38', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YSQYCE096R2SFRXG', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Composer slash-command typeahead (inline detection, CLI-style)', 'Make ClaudeComposer (lib/builtin/claude/src/claude_composer.dart) slash-aware. Detect a /token at the cursor ANYWHERE in the text — inline mid-sentence, not only at position 0 (user requirement) — and show a typeahead popup listing matching slash commands + skills sourced from ClaudeConfig (T-151), filtering as the token grows. Keyboard nav: Up/Down to move, Tab/Enter to complete, Esc to dismiss; reuse ClidePalette interaction patterns. Completing inserts the command token at the cursor. Never re-scans the filesystem — reads from ClaudeConfig and refreshes when it changes. Acceptance: typing / opens the list; an inline / after existing text also opens it; arrow/Tab/Enter/Esc behave; the list reflects ClaudeConfig (skills + custom commands + built-ins); widget tests; a11y (focusable, labelled, contrast). Blocked by T-151.', 'done', 'medium', NULL, NULL, 'D-76', '2026-05-23 09:52:20', '2026-05-23 10:43:52', NULL, '266c657f7d1be0299a20414bfad4ddab', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5B47G9GG22N9KBNEG', 'bug', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Claude pane doesn''t follow session switches (/clear, /compact) — interaction wedges', 'Now that T-153 makes slash commands actually fire, session-mutating commands wedge the Claude pane. /clear makes Claude Code start a NEW session-id (a fresh .jsonl). The composer''s keystrokes still reach the same tmux/claude process, but clide''s TranscriptReader is pinned to the spawn --session-id file (claude_pane binds the reader to a fixed transcriptFile; transcript_reader.dart has zero session-change handling). After /clear: the old transcript freezes, Claude writes to the new session file, and clide keeps tailing the dead file — no responses appear and the pane looks completely broken. Affects /clear and likely /compact, /resume, --fork-session. Observed (2026-05-23): a new .jsonl (8b21f12b…) appeared at 13:11 while the prior froze at 13:07, matching a /clear at that time. Pre-T-153 this was hidden because slash commands were pasted as literal text (no-op). @@ -2606,30 +916,15 @@ Tension to respect: T-146 deliberately pinned each pane to its own --session-id Direction (needs a small decision/spike): detect when Claude switches the active session for a pane and re-bind the TranscriptReader to the new session-id without re-introducing the T-146 cross-pane bug — e.g. via the transcript''s session lineage (parentUuid/leaf, or a session-change marker), or by querying the active session for the pane''s tmux session. Near-term safety to weigh: detect session-mutating commands (/clear, /compact, /resume) and handle/warn rather than silently wedging. Immediate user workaround: restart clide (primary re-binds + --resume to the pre-clear session). -Acceptance: after a /clear in a Claude pane, clide follows to the new session and continues to show prompts + responses; secondaries still bind only to their own session (T-146 not regressed); tests cover a simulated session switch re-binding the reader; coverage >= floor.', 'in_progress', 'high', NULL, NULL, 'D-75', '2026-05-23 11:15:08', '2026-05-23 11:30:35', NULL, '8a1383c7726d30afc1336d47b6a4984c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-156', 'bug', 'T-132', 'Claude pane doesn''t follow session switches (/clear, /compact) — interaction wedges', 'Now that T-153 makes slash commands actually fire, session-mutating commands wedge the Claude pane. /clear makes Claude Code start a NEW session-id (a fresh .jsonl). The composer''s keystrokes still reach the same tmux/claude process, but clide''s TranscriptReader is pinned to the spawn --session-id file (claude_pane binds the reader to a fixed transcriptFile; transcript_reader.dart has zero session-change handling). After /clear: the old transcript freezes, Claude writes to the new session file, and clide keeps tailing the dead file — no responses appear and the pane looks completely broken. Affects /clear and likely /compact, /resume, --fork-session. - -Observed (2026-05-23): a new .jsonl (8b21f12b…) appeared at 13:11 while the prior froze at 13:07, matching a /clear at that time. Pre-T-153 this was hidden because slash commands were pasted as literal text (no-op). - -Tension to respect: T-146 deliberately pinned each pane to its own --session-id to stop secondaries rendering the primary''s transcript (the older newest-jsonl approach crossed panes). So the fix must NOT just follow the newest jsonl globally — it must follow THIS pane''s session lineage when Claude forks it. - -Direction (needs a small decision/spike): detect when Claude switches the active session for a pane and re-bind the TranscriptReader to the new session-id without re-introducing the T-146 cross-pane bug — e.g. via the transcript''s session lineage (parentUuid/leaf, or a session-change marker), or by querying the active session for the pane''s tmux session. Near-term safety to weigh: detect session-mutating commands (/clear, /compact, /resume) and handle/warn rather than silently wedging. Immediate user workaround: restart clide (primary re-binds + --resume to the pre-clear session). - -Acceptance: after a /clear in a Claude pane, clide follows to the new session and continues to show prompts + responses; secondaries still bind only to their own session (T-146 not regressed); tests cover a simulated session switch re-binding the reader; coverage >= floor.', 'done', 'high', NULL, NULL, 'D-75', '2026-05-23 11:15:08', '2026-05-23 12:05:19', NULL, '35311a7e0ad2473412db12bb0c82a4e7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-154', 'task', 'T-132', 'Surface ClaudeConfig in the Claude status pane', 'The status surface shows LIVE model / permission-mode / context-tokens from the active transcript (T-145, T-150). Complement it with the CONFIGURED side from ClaudeConfig (T-151): available-skills count and/or configured permission/model defaults — static environment state alongside live session state. Reads from ClaudeConfig, not the filesystem; updates when config changes. Acceptance: the status surface reflects ClaudeConfig values, refreshes on config change, and keeps the live transcript-driven fields working; widget test; coverage >= floor. Blocked by T-151.', 'in_progress', 'low', NULL, NULL, 'D-76', '2026-05-23 09:52:32', '2026-05-23 14:40:01', NULL, 'e375208758cf11ba342c0c3cb88f1767', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-155', 'task', NULL, 'Finish i18n catalog plumbing: auto-load extension namespaces, kill ''namespace not registered'' warnings', 'Five builtins (tickets, decisions, git, pql, problems) declare titleKey: ''tab.title'' + i18nNamespace on their TabContribution but ship NO catalog file (lib/kernel/src/i18n/catalog/ has only the 8 tier-0 namespaces) and aren''t in the hand-maintained _tier0Namespaces preload list (lib/main.dart). Result: every boot logs ''i18n: namespace not registered: builtin.X (key: tab.title)'' for each. Tabs still render correctly via the placeholder fallback, so it''s cosmetic console noise — but it recurs for every new builtin because the preload list is maintained by hand. +Acceptance: after a /clear in a Claude pane, clide follows to the new session and continues to show prompts + responses; secondaries still bind only to their own session (T-146 not regressed); tests cover a simulated session switch re-binding the reader; coverage >= floor.', 'done', 'high', NULL, NULL, 'D-75', '2026-05-23 11:15:08', '2026-05-23 12:05:19', NULL, '75443abc3658418fb215098d1e12a2d3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R9Q3T7CBMTHM2GFR', 'task', NULL, 'Finish i18n catalog plumbing: auto-load extension namespaces, kill ''namespace not registered'' warnings', 'Five builtins (tickets, decisions, git, pql, problems) declare titleKey: ''tab.title'' + i18nNamespace on their TabContribution but ship NO catalog file (lib/kernel/src/i18n/catalog/ has only the 8 tier-0 namespaces) and aren''t in the hand-maintained _tier0Namespaces preload list (lib/main.dart). Result: every boot logs ''i18n: namespace not registered: builtin.X (key: tab.title)'' for each. Tabs still render correctly via the placeholder fallback, so it''s cosmetic console noise — but it recurs for every new builtin because the preload list is maintained by hand. Proper fix: when an extension activates, auto-load the i18nNamespace(s) declared by its contributions via i18n.ensureNamespaceLoaded — removing the manual _tier0Namespaces list and the drift it causes. AssetCatalogLoader.load returns empty for a missing file, so auto-preloading alone would just shift the warning to ''missing key tab.title'' — so the missing catalogs must also be created: builtin.{tickets,decisions,git,pql,problems}_en_us.json with at least tab.title plus any other keys those extensions look up. -Acceptance: clean boot with all builtins active logs zero i18n warnings; a new builtin that declares a titleKey gets its namespace loaded automatically with no manual list edit; the five catalog files exist; tests cover the auto-load-on-activate path and the missing-catalog case; coverage >= floor.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-23 11:06:50', '2026-05-23 14:40:05', NULL, 'a26532f49dab8052e03654393747ce63', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-154', 'task', 'T-132', 'Surface ClaudeConfig in the Claude status pane', 'The status surface shows LIVE model / permission-mode / context-tokens from the active transcript (T-145, T-150). Complement it with the CONFIGURED side from ClaudeConfig (T-151): available-skills count and/or configured permission/model defaults — static environment state alongside live session state. Reads from ClaudeConfig, not the filesystem; updates when config changes. Acceptance: the status surface reflects ClaudeConfig values, refreshes on config change, and keeps the live transcript-driven fields working; widget test; coverage >= floor. Blocked by T-151.', 'done', 'low', NULL, NULL, 'D-76', '2026-05-23 09:52:32', '2026-05-23 14:50:17', NULL, '0908cd711d4c826389e21c08cffe0bf6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-155', 'task', NULL, 'Finish i18n catalog plumbing: auto-load extension namespaces, kill ''namespace not registered'' warnings', 'Five builtins (tickets, decisions, git, pql, problems) declare titleKey: ''tab.title'' + i18nNamespace on their TabContribution but ship NO catalog file (lib/kernel/src/i18n/catalog/ has only the 8 tier-0 namespaces) and aren''t in the hand-maintained _tier0Namespaces preload list (lib/main.dart). Result: every boot logs ''i18n: namespace not registered: builtin.X (key: tab.title)'' for each. Tabs still render correctly via the placeholder fallback, so it''s cosmetic console noise — but it recurs for every new builtin because the preload list is maintained by hand. - -Proper fix: when an extension activates, auto-load the i18nNamespace(s) declared by its contributions via i18n.ensureNamespaceLoaded — removing the manual _tier0Namespaces list and the drift it causes. AssetCatalogLoader.load returns empty for a missing file, so auto-preloading alone would just shift the warning to ''missing key tab.title'' — so the missing catalogs must also be created: builtin.{tickets,decisions,git,pql,problems}_en_us.json with at least tab.title plus any other keys those extensions look up. - -Acceptance: clean boot with all builtins active logs zero i18n warnings; a new builtin that declares a titleKey gets its namespace loaded automatically with no manual list edit; the five catalog files exist; tests cover the auto-load-on-activate path and the missing-catalog case; coverage >= floor.', 'done', 'low', NULL, NULL, NULL, '2026-05-23 11:06:50', '2026-05-23 14:50:17', NULL, '5ed6f316a13c8d8bc09139b545b3fca7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-148', 'task', 'T-132', 'Claude transcript disk-usage indicator + cleanup button', 'Surface transcript/session disk usage in the Claude panel with a cleanup affordance so the user can pick and choose what to remove. Per-session sizes (the /*.jsonl files + subagents/), a total, and a button to delete selected sessions'' transcripts. User-driven only — clide never auto-deletes transcript content. Future/''at some point'' per user. Relates to T-146 (session ids) and D-75.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 14:51:55', NULL, '9f558f4aedccea6d0294faff738a38c3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-141', 'task', 'T-132', 'Team-meta sidebar (roster + token budget)', 'New sidebar panel (contributed to Slots.sidebar), visible only in team mode: a roster row per team member that MIRRORS the per-session status surfaced by T-145, plus team-wide meta. +Acceptance: clean boot with all builtins active logs zero i18n warnings; a new builtin that declares a titleKey gets its namespace loaded automatically with no manual list edit; the five catalog files exist; tests cover the auto-load-on-activate path and the missing-catalog case; coverage >= floor.', 'done', 'low', NULL, NULL, NULL, '2026-05-23 11:06:50', '2026-05-23 14:50:17', NULL, '0d925bbfd51d32f40da97318d3125428', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XM8EY708NAM1JK00', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Surface ClaudeConfig in the Claude status pane', 'The status surface shows LIVE model / permission-mode / context-tokens from the active transcript (T-145, T-150). Complement it with the CONFIGURED side from ClaudeConfig (T-151): available-skills count and/or configured permission/model defaults — static environment state alongside live session state. Reads from ClaudeConfig, not the filesystem; updates when config changes. Acceptance: the status surface reflects ClaudeConfig values, refreshes on config change, and keeps the live transcript-driven fields working; widget test; coverage >= floor. Blocked by T-151.', 'done', 'low', NULL, NULL, 'D-76', '2026-05-23 09:52:32', '2026-05-23 14:50:17', NULL, '57770b6e8e5b27a8f99c501892f8518a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6CYT7NW4WE8ZP1TXR', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Claude transcript disk-usage indicator + cleanup button', 'Surface transcript/session disk usage in the Claude panel with a cleanup affordance so the user can pick and choose what to remove. Per-session sizes (the /*.jsonl files + subagents/), a total, and a button to delete selected sessions'' transcripts. User-driven only — clide never auto-deletes transcript content. Future/''at some point'' per user. Relates to T-146 (session ids) and D-75.', 'done', 'low', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 15:05:29', NULL, '705f6fd98660484bc44f941fe453fc6a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V9RNW2BGT5GHRTRG', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Team-meta sidebar (roster + token budget)', 'New sidebar panel (contributed to Slots.sidebar), visible only in team mode: a roster row per team member that MIRRORS the per-session status surfaced by T-145, plus team-wide meta. Per-member row: - Identity from the team config + observer: name, agentType, color; membership/lifecycle from TeamMemberJoined/Left. @@ -2640,32 +935,10 @@ Team-wide: - Team/account token budget pulled from the Anthropic usage API on a timer (distinct from per-conversation context above). Acceptance: roster matches list-panes/config; each row mirrors the member''s model + accept-edits/plan badge + context-usage; status verb and token deltas update live; account budget refreshes on its timer; panel appears only when a team is active. Depends on T-145 (status source) + T-139 (done). -', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-23 14:51:55', NULL, 'e674a22d7a2c67d4fcefc12968185118', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-148', 'task', 'T-132', 'Claude transcript disk-usage indicator + cleanup button', 'Surface transcript/session disk usage in the Claude panel with a cleanup affordance so the user can pick and choose what to remove. Per-session sizes (the /*.jsonl files + subagents/), a total, and a button to delete selected sessions'' transcripts. User-driven only — clide never auto-deletes transcript content. Future/''at some point'' per user. Relates to T-146 (session ids) and D-75.', 'done', 'low', NULL, NULL, NULL, '2026-05-23 07:03:18', '2026-05-23 15:05:29', NULL, '90efd726ef844874609653bf21fcfa5c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'task', 'T-132', 'Team sidebar: live per-member status (mode + context)', 'Extend the Claude meta sidebar (T-141) roster rows with each teammate''s LIVE status — permission-mode badge (default/accept-edits/plan/bypass) and context-token usage — mirroring the per-session strip (T-145/T-150). The data exists: TeamObserver creates a TranscriptPublisher per teammate whose statusStream emits SessionStatus, but it is NEVER published to the MessageBus. Wire it: in team_observer _joined, subscribe to the publisher''s statusStream and publish {agentId, model, permissionMode, contextTokens} to a new ClaudeConversation.memberStatusChannel; cancel on _left. The sidebar subscribes to that channel and updates the matching roster row. Do NOT re-tail transcripts (D-75) — reuse the existing stream. Acceptance: a running team shows each member''s mode badge + context tokens, updating live; module is the single status-forwarding point; tests; coverage >= floor.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-23 20:48:31', '2026-05-23 20:48:31', NULL, 'e7b13a20df653bee06b7b7ac031d29db', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-158', 'task', 'T-132', 'Team sidebar: account/team token budget (blocked on upstream)', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-23 20:48:38', '2026-05-23 20:48:38', NULL, '4e239019c4cf4982bd653e5dd2c96990', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-141', 'task', 'T-132', 'Team-meta sidebar (roster + token budget)', 'New sidebar panel (contributed to Slots.sidebar), visible only in team mode: a roster row per team member that MIRRORS the per-session status surfaced by T-145, plus team-wide meta. - -Per-member row: -- Identity from the team config + observer: name, agentType, color; membership/lifecycle from TeamMemberJoined/Left. -- Mirror of T-145''s shared status: current model, permission-mode badge (accept-edits / plan / default / bypass), context-window usage %. Consume the SAME status source/event T-145 emits — do NOT re-tail transcripts or re-parse CC internals here (D-75). -- Live status verb + elapsed-since-last-activity + down/up token deltas from that same status feed. - -Team-wide: -- Team/account token budget pulled from the Anthropic usage API on a timer (distinct from per-conversation context above). - -Acceptance: roster matches list-panes/config; each row mirrors the member''s model + accept-edits/plan badge + context-usage; status verb and token deltas update live; account budget refreshes on its timer; panel appears only when a team is active. Depends on T-145 (status source) + T-139 (done). -', 'done', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-23 20:49:44', NULL, 'c9e79b9986f436abadba095d77d540f7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'task', 'T-132', 'Team sidebar: live per-member status (mode + context)', 'Extend the Claude meta sidebar (T-141) roster rows with each teammate''s LIVE status — permission-mode badge (default/accept-edits/plan/bypass) and context-token usage — mirroring the per-session strip (T-145/T-150). The data exists: TeamObserver creates a TranscriptPublisher per teammate whose statusStream emits SessionStatus, but it is NEVER published to the MessageBus. Wire it: in team_observer _joined, subscribe to the publisher''s statusStream and publish {agentId, model, permissionMode, contextTokens} to a new ClaudeConversation.memberStatusChannel; cancel on _left. The sidebar subscribes to that channel and updates the matching roster row. Do NOT re-tail transcripts (D-75) — reuse the existing stream. Acceptance: a running team shows each member''s mode badge + context tokens, updating live; module is the single status-forwarding point; tests; coverage >= floor.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-23 20:48:31', '2026-05-23 20:52:47', NULL, '386818f67d3cbcc1a1b7a528c2baaca1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'task', 'T-132', 'Team sidebar: live per-member status (mode + context)', 'Extend the Claude meta sidebar (T-141) roster rows with each teammate''s LIVE status — permission-mode badge (default/accept-edits/plan/bypass) and context-token usage — mirroring the per-session strip (T-145/T-150). The data exists: TeamObserver creates a TranscriptPublisher per teammate whose statusStream emits SessionStatus, but it is NEVER published to the MessageBus. Wire it: in team_observer _joined, subscribe to the publisher''s statusStream and publish {agentId, model, permissionMode, contextTokens} to a new ClaudeConversation.memberStatusChannel; cancel on _left. The sidebar subscribes to that channel and updates the matching roster row. Do NOT re-tail transcripts (D-75) — reuse the existing stream. Acceptance: a running team shows each member''s mode badge + context tokens, updating live; module is the single status-forwarding point; tests; coverage >= floor.', 'done', 'low', NULL, NULL, NULL, '2026-05-23 20:48:31', '2026-05-23 20:57:07', NULL, '6fd17798d24f81a3f81f284c7bd595e2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-159', 'bug', 'T-132', 'ClidePane convey notifies focus listeners during build (markNeedsBuild crash)', 'Found via make run (2026-05-24). ClidePane.didChangeDependencies/didUpdateWidget run during the build phase and call FocusTracker.setStatusWidget -> notifyListeners() synchronously, which rebuilds the focus-listening status-bar item (PaneContextStatusItem''s ListenableBuilder) mid-build -> ''setState()/markNeedsBuild() called during build'' (thrown repeatedly each frame). Introduced by T-150; the T-150 widget tests missed it because no focus listener was in their tree. Fix: ClidePane defers the convey to a post-frame callback when schedulerPhase == persistentCallbacks (mid-build), re-checking focus then; immediate otherwise. Regression test added with a PaneContextStatusItem in the tree while a ClidePane mounts focused.', 'backlog', 'high', NULL, NULL, NULL, '2026-05-24 08:53:13', '2026-05-24 08:53:13', NULL, '52caaaca78b7c2bf6cae907fcf0fcdae', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-159', 'bug', 'T-132', 'ClidePane convey notifies focus listeners during build (markNeedsBuild crash)', 'Found via make run (2026-05-24). ClidePane.didChangeDependencies/didUpdateWidget run during the build phase and call FocusTracker.setStatusWidget -> notifyListeners() synchronously, which rebuilds the focus-listening status-bar item (PaneContextStatusItem''s ListenableBuilder) mid-build -> ''setState()/markNeedsBuild() called during build'' (thrown repeatedly each frame). Introduced by T-150; the T-150 widget tests missed it because no focus listener was in their tree. Fix: ClidePane defers the convey to a post-frame callback when schedulerPhase == persistentCallbacks (mid-build), re-checking focus then; immediate otherwise. Regression test added with a PaneContextStatusItem in the tree while a ClidePane mounts focused.', 'done', 'high', NULL, NULL, NULL, '2026-05-24 08:53:13', '2026-05-24 08:54:30', NULL, 'a2200e366f389c81f2e52e986715f9d1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-160', 'bug', 'T-132', 'Status bar overflows instead of marquee-scrolling the in-pane slot', 'Observed via make run (2026-05-24): the bottom status bar shows a RenderFlex ''overflowed by 77 PIXELS'' stripe after the in-pane context line (opus 4.7 · default · 21k ctx · 10 skills); the ClideMarquee never engages. - -Cause: StatusbarHost (lib/app.dart ~1143) lays items in a Row [left items..., Spacer(), right items...], each at INTRINSIC width with no Flexible. PaneContextStatusItem wraps its text in ConstrainedBox(maxWidth:360) + ClideMarquee, but because the text (~280px) is under 360 the slot takes content width, so the marquee''s viewport == its content and it never scrolls. T-154 appended ''· N skills'', lengthening the line enough that git-branch + context slot + right items exceed the bar width -> the Row overflows (~77px) instead of the slot shrinking + marquee scrolling. - -Fix direction: give the in-pane context slot a flexible/bounded width in the status bar (e.g. wrap it in Flexible(fit: loose), or have the bar allot it a shrinkable share) so it yields width when the bar is tight and ClideMarquee scrolls within the allotted width. Verify the interaction with the Spacer and the right-aligned items (priority>=100), and reconsider the fixed maxWidth:360. Acceptance: at narrow widths the in-pane slot marquee-scrolls and the status bar never shows an overflow stripe; widget test at a constrained width. Introduced by T-150 (marquee) + T-154 (longer line).', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-24 09:13:42', '2026-05-24 09:13:42', NULL, 'd7c01b7742f730c2c7e2fb5473cdbefd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-161', 'bug', 'T-132', 'Typed input reaches no live Claude after respawn — pane renders transcript only', 'Observed via make run (2026-05-24). After using /clear and /resume (which appear to work), regular typed messages produce no response and never appear in the conversation stream. +', 'done', 'medium', NULL, NULL, NULL, '2026-05-22 15:59:34', '2026-05-23 20:49:44', NULL, '80f1fda6faec5f6aa86b6d212d2b5a8f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QZ3JJF1704AV5JCW', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Team sidebar: live per-member status (mode + context)', 'Extend the Claude meta sidebar (T-141) roster rows with each teammate''s LIVE status — permission-mode badge (default/accept-edits/plan/bypass) and context-token usage — mirroring the per-session strip (T-145/T-150). The data exists: TeamObserver creates a TranscriptPublisher per teammate whose statusStream emits SessionStatus, but it is NEVER published to the MessageBus. Wire it: in team_observer _joined, subscribe to the publisher''s statusStream and publish {agentId, model, permissionMode, contextTokens} to a new ClaudeConversation.memberStatusChannel; cancel on _left. The sidebar subscribes to that channel and updates the matching roster row. Do NOT re-tail transcripts (D-75) — reuse the existing stream. Acceptance: a running team shows each member''s mode badge + context tokens, updating live; module is the single status-forwarding point; tests; coverage >= floor.', 'done', 'low', NULL, NULL, NULL, '2026-05-23 20:48:31', '2026-05-23 20:57:07', NULL, '7fb8f4a122c58cc57287ebf8fa36b141', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6KD2DKTGHC6CF1MJ4', 'bug', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'ClidePane convey notifies focus listeners during build (markNeedsBuild crash)', 'Found via make run (2026-05-24). ClidePane.didChangeDependencies/didUpdateWidget run during the build phase and call FocusTracker.setStatusWidget -> notifyListeners() synchronously, which rebuilds the focus-listening status-bar item (PaneContextStatusItem''s ListenableBuilder) mid-build -> ''setState()/markNeedsBuild() called during build'' (thrown repeatedly each frame). Introduced by T-150; the T-150 widget tests missed it because no focus listener was in their tree. Fix: ClidePane defers the convey to a post-frame callback when schedulerPhase == persistentCallbacks (mid-build), re-checking focus then; immediate otherwise. Regression test added with a PaneContextStatusItem in the tree while a ClidePane mounts focused.', 'done', 'high', NULL, NULL, NULL, '2026-05-24 08:53:13', '2026-05-24 08:54:30', NULL, '8e4e5178c3f40d111ee8e038cbc9e9f3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SPRJZ519RZK1ZPAW', 'bug', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Typed input reaches no live Claude after respawn — pane renders transcript only', 'Observed via make run (2026-05-24). After using /clear and /resume (which appear to work), regular typed messages produce no response and never appear in the conversation stream. Diagnosis (live process inspection): the clide app (PID 77023) had ZERO child processes; ''tmux -L clide list-sessions'' reported ''no server running''. clide spawns pane processes (tmux client / claude) via posix_spawn as its own children, so no children + no -L clide server = NO live Claude backing the pane. The two stray ''claude'' procs were the user''s other sessions (an agent + a game-coding session), not clide''s. The conversation still renders because TranscriptReader tails the transcript FILE — so /clear and /resume only re-point the reader at a different file; there is no live session behind them. @@ -2673,41 +946,8 @@ Leading hypothesis: T-156''s _respawnWithSession (/clear, /resume) kills the tmu To localise: does a BRAND-NEW Claude tab (fresh spawn, no respawn) accept input and respond? If yes, the respawn path is the culprit; if no, the spawn path itself regressed. Also check the make-run console for pane.spawn errors / ''session exited''. -Acceptance: after /clear and /resume, a live tmux session (or no-tmux PTY) exists and typed input reaches Claude and streams a response; clide''s _usingTmux/_paneId/_sessionName stay consistent with the actual spawned process; regression test or a documented manual verification. Relates to T-156, T-147 (self-heal), D-41.', 'backlog', 'high', NULL, NULL, NULL, '2026-05-24 09:24:02', '2026-05-24 09:24:02', NULL, '7ef055c3f51dffb9efe3e03c785a10be', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-162', 'bug', 'T-132', 'Slash typeahead omits clide-owned commands (/resume)', '/resume works when typed but doesn''t appear in the composer slash typeahead. Cause: the typeahead sources its command list from activeClaudeConfig.slashCommands (the CLI stream-json probe), which does not include ''resume'' (it''s not an in-session slash command the CLI advertises; clide owns/intercepts it). /clear appears because ''clear'' IS in the probe list. Fix: union kClideOwnedCommands (slash_commands.dart) into the typeahead''s command source — e.g. in ClaudeComposer''s default slashCommandsResolver return {...?activeClaudeConfig?.slashCommands, ...kClideOwnedCommands}. Acceptance: typing /res suggests /resume; /clear still suggested; widget test asserting clide-owned commands appear even when absent from the probe list. Follow-up to T-152/T-156.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-24 09:29:07', '2026-05-24 09:29:07', NULL, '403801e2c738160eac2e49e474eb30d0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-163', 'task', 'T-132', 'Composer prompt history via Up/Down (Claude-CLI-style)', 'Arrow Up/Down in the Claude composer should scroll through previously-sent prompts, matching the Claude Code CLI UX: -- Up: if the caret isn''t on the first visual line, move up one line (normal editing); once on the first line, Up steps to the previous prompt in history. Down mirrors it (move down within multiline; at the last line, step to the next/newer entry). -- When entering history, stash the current draft as the newest entry so Down past the most recent restores exactly what the user was typing. -- History is the sequence of prompts submitted from this composer (this session at least; persisting across restarts is a nice-to-have). -- Plays well with the existing Enter-submit / Shift+Enter-newline and the slash typeahead (Up/Down already drive the typeahead when it''s open — history nav only applies when the typeahead is closed). - -Implementation: a small history controller + caret-line detection in ClaudeComposer (EditableText); the Up/Down handling lives alongside the existing _onKey/Shortcuts. Acceptance: send a few prompts, Up walks back through them, Down returns through them to the restored draft; multiline prompts navigate line-by-line before crossing into history; widget tests for the bound conditions (first/last line) and draft stash/restore.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-24 09:42:44', '2026-05-24 09:42:44', NULL, 'c2d0a0a7c96dba20b920a2cc9d20d2e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-161', 'bug', 'T-132', 'Typed input reaches no live Claude after respawn — pane renders transcript only', 'Observed via make run (2026-05-24). After using /clear and /resume (which appear to work), regular typed messages produce no response and never appear in the conversation stream. - -Diagnosis (live process inspection): the clide app (PID 77023) had ZERO child processes; ''tmux -L clide list-sessions'' reported ''no server running''. clide spawns pane processes (tmux client / claude) via posix_spawn as its own children, so no children + no -L clide server = NO live Claude backing the pane. The two stray ''claude'' procs were the user''s other sessions (an agent + a game-coding session), not clide''s. The conversation still renders because TranscriptReader tails the transcript FILE — so /clear and /resume only re-point the reader at a different file; there is no live session behind them. - -Leading hypothesis: T-156''s _respawnWithSession (/clear, /resume) kills the tmux session (killing the last session also kills the -L clide server) and the subsequent _spawn does not leave a durable session — but clide may still have _usingTmux=true and routes input via tmux.sendMessage to a now-dead session, where set-buffer/paste-buffer/send-keys silently no-op (tmuxRunner ignores non-zero exit). Net: keystrokes vanish. - -To localise: does a BRAND-NEW Claude tab (fresh spawn, no respawn) accept input and respond? If yes, the respawn path is the culprit; if no, the spawn path itself regressed. Also check the make-run console for pane.spawn errors / ''session exited''. - -Acceptance: after /clear and /resume, a live tmux session (or no-tmux PTY) exists and typed input reaches Claude and streams a response; clide''s _usingTmux/_paneId/_sessionName stay consistent with the actual spawned process; regression test or a documented manual verification. Relates to T-156, T-147 (self-heal), D-41.', 'done', 'high', NULL, NULL, NULL, '2026-05-24 09:24:02', '2026-05-24 09:45:07', NULL, 'e7e053905caff3775e6aeb5eab4c0039', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-164', 'epic', NULL, 'Claude via stream-json + unified clide-managed agent/session model', 'Per D-77: pivot the Claude pane from the interactive tmux TUI to Claude Code''s stream-json control protocol, and build a unified clide-managed agent/session model on top. - -Why: the interactive TUI can''t surface AskUserQuestion / permission prompts (they never reach the transcript), transcript-file tailing is lossy + caused the session-fork/--session-id bug class, and Claude''s agent-team mode is tmux/interactive-only. stream-json fixes all three: structured events, native canUseTool prompt handling, --resume persistence — and lets clide OWN orchestration (teammate / secondary tab / forked branch / inline subagent all collapse into one primitive: a clide-managed session rendered as a pane; the sidebar becomes its cockpit). - -Phase 1 (single-agent, ships the prompt-gap + lifecycle fix): stream-json transport, native permission/AskUserQuestion UI, --resume persistence, leveled-up native rendering. -Phase 2 (unified model): session orchestrator (spawn/show/hide), clide-hosted MCP coordination broker + injected team-awareness, sidebar cockpit (tasks + messages + roster controls), fork-into-a-pane. - -Supersedes the transcript-tail + observe-tmux-team approach of epic T-132. Acceptance: Claude runs over stream-json with native prompts + --resume; teams are clide-orchestrated managed sessions controlled from the sidebar; the tmux dependency for Claude is gone (terminal builtin retained for general shell).', 'backlog', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:05', '2026-05-24 16:26:05', NULL, '024365f2199feee1ce06cd6d62ed2013', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-166', 'task', 'T-164', 'P1: native permission + AskUserQuestion prompts via canUseTool', 'Handle interactive prompts that the TUI model couldn''t surface. Wire Claude''s permission requests and the AskUserQuestion tool through the control mechanism (--permission-prompt-tool / SDK-style canUseTool: toolName + input, return allow/deny or the chosen answers). Render NATIVE prompt cards in the Claude pane — allow/deny for permissions, an option picker (matching AskUserQuestion''s questions/options) for AskUserQuestion — and return the user''s choice over the protocol. Acceptance: asking Claude for an AskUserQuestion shows selectable options inline and the selection is delivered; a tool needing permission shows allow/deny and the decision flows back; widget tests for the prompt cards + a fake control channel. Closes the prompt gap that motivated D-77. Blocked by T-165.', 'backlog', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:23', '2026-05-24 16:26:23', NULL, '7815b0a0af62b3aff885a94a96bbc47f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-167', 'task', 'T-164', 'P1: session persistence + /clear + /resume on stream-json (--resume)', 'Move session lifecycle onto the stream-json model. New session: --session-id ; resume an existing one: --resume (T-161 confirmed --session-id refuses an existing id). Re-implement the clide-owned commands on this model: /clear = new fresh session, /resume = picker then resume the picked id. Retire the tmux session lifecycle for Claude (kill/hasSession/reap, new-session -A) and the --session-id-in-use handling once the transport no longer uses tmux. Acceptance: restart resumes the primary''s session; /clear starts empty; /resume switches to a picked session; all without tmux; tests for the new/resume argv selection and the command flows. Supersedes the mechanics of T-156/T-161. Blocked by T-165. Amends D-41 (tmux persistence → --resume).', 'backlog', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:30', '2026-05-24 16:26:30', NULL, '076b301a0ab50ad9c9f845dc43ba174e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-168', 'task', 'T-164', 'P1: native rendering leveled up — typed event cards + streaming + live status', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:39', '2026-05-24 16:26:39', NULL, '07595343a6ca4aab533282ae045e30d1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'task', 'T-164', 'P2: session orchestrator — one managed-session primitive (spawn/show/hide)', 'Generalize the Claude pane into a clide-managed SESSION primitive: clide spawns/owns each stream-json Claude process; a pane is just a view on a session; teammate / secondary tab / forked branch / inline subagent are all the same thing. Provide spawn / show / hide / close over the set of live sessions; visibility is a UI toggle, not a lifecycle change (sessions stay live when hidden). Re-point the T-139 TeamObserver from observing a Claude tmux team to ORCHESTRATING clide-managed sessions; the T-140 tiles render whatever sessions are shown. Acceptance: multiple managed sessions run concurrently; each can be shown/hidden as a pane from a roster without tearing down its process; tests for the orchestrator lifecycle. Blocked by T-165 (phase 1 transport).', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:49', '2026-05-24 16:26:49', NULL, '02a0991d91d79696af98db0673c5b7df', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-170', 'task', 'T-164', 'P2: clide-hosted MCP coordination broker + team-awareness injection', 'Give clide-managed sessions team behavior that Claude''s tmux mode provided (and that is undocumented + headless-unavailable). clide hosts a small MCP server (attached to each agent via --mcp-config) exposing team tools: send_message(to,text), broadcast(text), list_teammates(), inbox(), and a shared claim_task/task_status. clide is the BROKER — a tool call from agent A is delivered into agent B''s next turn on B''s stream-json stdin; clide owns routing/ordering. Inject team-awareness into each agent via --append-system-prompt (roster + role) and/or --agents. Acceptance: two managed agents exchange messages through the broker and see a shared task list; a lead can address one teammate by name; tests for the broker routing + the MCP tool surface. Blocked by T-169.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:27:01', '2026-05-24 16:27:01', NULL, '11457574f4c3a752ef4b60edb639848f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-171', 'task', 'T-164', 'P2: sidebar cockpit — task list + messages + roster controls', 'Evolve the Claude meta sidebar (T-141/T-157) from a read-only roster into the CONTROL surface for clide-managed agents. Because clide owns the broker, the task list and inter-agent messages are local data — so the sidebar both shows them live and lets the user ACT: spawn / show / hide / mute an agent, reassign a task, inject or redirect a message. Roster rows keep the live per-member status (T-157) but gain controls. Acceptance: from the sidebar the user can spawn/show/hide an agent, watch the shared task list update, see the message flow, and intervene (reassign/inject); widget tests for the controls wired to the orchestrator + broker. Blocked by T-169 (orchestrator) and T-170 (broker).', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:27:09', '2026-05-24 16:27:09', NULL, '1134d3b5bbef2b2017d43a56b9fdac39', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-172', 'task', 'T-164', 'P2: fork-into-a-pane (--fork-session)', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator).', 'backlog', 'low', NULL, NULL, 'D-77', '2026-05-24 16:27:16', '2026-05-24 16:27:16', NULL, 'e27d5e85bb9a9ce26ae8756b36448d44', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-165', 'task', 'T-164', 'P1: stream-json transport — drive the Claude pane via the control protocol', 'Spawn the Claude pane as ''claude --input-format stream-json --output-format stream-json --verbose'' (+ --include-partial-messages later) instead of an interactive TUI in tmux. Multiplex its stdin/stdout; parse the event stream (system/init, assistant, tool_use, tool_result, result, rate_limit_event); feed the existing ConversationController/MessageBus from events rather than tailing the transcript JSONL. Isolate the protocol behind one module (the drift-containment point, like the transcript reader). Acceptance: a Claude pane converses end-to-end over stream-json (send a prompt, stream the reply) with no tmux session; events drive the native conversation view; unit tests against canned event fixtures. Blocks the rest of phase 1. Evolves D-75 (rendering now sourced from events).', 'in_progress', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:15', '2026-05-24 16:32:59', NULL, '60aad91b8c9af23881c71ffc6c3082a8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-173', 'task', 'T-164', 'Conversation message-card primitive: hover copy + collapse/expand, extensible base', 'Replace the three ad-hoc card shapes in conversation_view.dart (_messageCard stripe / _labelled / _card bordered) with ONE base primitive every turn renders through, with hover affordances wired into the base so all message types (now + future) get them consistently. +Acceptance: after /clear and /resume, a live tmux session (or no-tmux PTY) exists and typed input reaches Claude and streams a response; clide''s _usingTmux/_paneId/_sessionName stay consistent with the actual spawned process; regression test or a documented manual verification. Relates to T-156, T-147 (self-heal), D-41.', 'done', 'high', NULL, NULL, NULL, '2026-05-24 09:24:02', '2026-05-24 09:45:07', NULL, '5955ed0881de7620205a7b8a2a5721eb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5932T486Z1F1M6X6W', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Conversation message-card primitive: hover copy + collapse/expand, extensible base', 'Replace the three ad-hoc card shapes in conversation_view.dart (_messageCard stripe / _labelled / _card bordered) with ONE base primitive every turn renders through, with hover affordances wired into the base so all message types (now + future) get them consistently. PRIMITIVE — ConversationCard (lib/builtin/claude/src/conversation_card.dart): - Props: variant (stripe | bordered | bare — preserves the user/claude vs tool/thinking visual distinction), accent (Color), label (String), body (Widget content slot), copyText (raw text the copy action yields), actions (List — extensible), collapsible (bool) + collapsedByDefault (bool). @@ -2722,48 +962,26 @@ FUTURE-PROOFING (the point): MIGRATION: route the existing switch in _ConversationTurn through the primitive; the three shapes become variants. Lands independently of the stream-json transport (works on the current conversation_view) but is the base T-168 builds on. -Acceptance: every turn type renders through the one primitive; hover-copy yields the right raw text per type; collapsible types expand/collapse (tool-result/thinking default-collapsed); actions are keyboard-focusable + Semantics-labelled (a11y); widget tests for copy, collapse, each variant, and an injected custom action; coverage >= floor. Feeds T-168.', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:45:40', '2026-05-24 17:00:14', NULL, '315d3cc6c134fc5a48a8527e62290554', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-173', 'task', 'T-164', 'Conversation message-card primitive: hover copy + collapse/expand, extensible base', 'Replace the three ad-hoc card shapes in conversation_view.dart (_messageCard stripe / _labelled / _card bordered) with ONE base primitive every turn renders through, with hover affordances wired into the base so all message types (now + future) get them consistently. +Acceptance: every turn type renders through the one primitive; hover-copy yields the right raw text per type; collapsible types expand/collapse (tool-result/thinking default-collapsed); actions are keyboard-focusable + Semantics-labelled (a11y); widget tests for copy, collapse, each variant, and an injected custom action; coverage >= floor. Feeds T-168.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:45:40', '2026-05-24 17:10:07', NULL, '551b0d25926bf71997a92a70ab3516ef', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AHSN3BXZFFWS0EPM', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'P1: stream-json transport — drive the Claude pane via the control protocol', 'Spawn the Claude pane as ''claude --input-format stream-json --output-format stream-json --verbose'' (+ --include-partial-messages later) instead of an interactive TUI in tmux. Multiplex its stdin/stdout; parse the event stream (system/init, assistant, tool_use, tool_result, result, rate_limit_event); feed the existing ConversationController/MessageBus from events rather than tailing the transcript JSONL. Isolate the protocol behind one module (the drift-containment point, like the transcript reader). Acceptance: a Claude pane converses end-to-end over stream-json (send a prompt, stream the reply) with no tmux session; events drive the native conversation view; unit tests against canned event fixtures. Blocks the rest of phase 1. Evolves D-75 (rendering now sourced from events).', 'done', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:15', '2026-05-25 06:29:25', NULL, '34c4aab735c6b8d761cf79683110fd37', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7KA37YDBJ89GJ27H4', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'P1: native permission + AskUserQuestion prompts via canUseTool', 'Handle interactive prompts that the TUI model couldn''t surface. Wire Claude''s permission requests and the AskUserQuestion tool through the control mechanism (--permission-prompt-tool / SDK-style canUseTool: toolName + input, return allow/deny or the chosen answers). Render NATIVE prompt cards in the Claude pane — allow/deny for permissions, an option picker (matching AskUserQuestion''s questions/options) for AskUserQuestion — and return the user''s choice over the protocol. Acceptance: asking Claude for an AskUserQuestion shows selectable options inline and the selection is delivered; a tool needing permission shows allow/deny and the decision flows back; widget tests for the prompt cards + a fake control channel. Closes the prompt gap that motivated D-77. Blocked by T-165.', 'done', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:23', '2026-05-25 06:31:08', NULL, 'a85bf25a70c97c958f65b1fba8c258c8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RP0Q312AZJ8S1Y6R', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Conversation cards: collapse tool_use/tool_result only when multi-line', 'tool_use and tool_result conversation cards start collapsed ONLY when their body is multi-line; single-line bodies render inline (collapse bulk, not chrome - if it fits on one line, just show it). Collapsed header shows a one-line summary (tool_use: tool + key arg, e.g. Write /tmp/x; tool_result: first line or byte size). thinking already collapses; user/assistant text stay expanded. Acceptance: a one-line result renders inline with no caret; a multi-line result starts collapsed with a summary header; widget tests. Relates to T-173 (ConversationCard primitive).', 'done', 'medium', NULL, NULL, 'D-78', '2026-05-25 07:11:22', '2026-05-25 07:25:02', NULL, '7f1cab9ef21c3686c0de7ada8383c104', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7K30Y9WAYZ3D330YR', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Permission prompt: Allow / Allow-and-remember / Deny+note', 'Bottom-zone permission card (prompt_card.dart): replace binary Allow/Deny with three options. (1) Allow -> allow{updatedInput}. (2) Allow and do not ask again -> shown only when the can_use_tool request carries a permission_suggestions entry (session/localSettings destination); returns allow{updatedInput, updatedPermissions:[suggestion]}. AllowTool gains an optional updatedPermissions field; ToolPrompt + StreamJsonSession carry the request permission_suggestions through. (3) Deny -> reveals an optional note field -> deny{message}. Plus allow-with-note: clide sends the typed note as a follow-up user message right after allowing (protocol has no allow-with-message per D-78; closest faithful approximation). Acceptance: three options render (option 2 only when a suggestion exists); allow / deny / updatedPermissions / deny-note all flow over the control channel; allow+note sends a follow-up; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/04-permission.png', 'done', 'high', NULL, NULL, 'D-78', '2026-05-25 07:11:09', '2026-05-25 07:25:02', NULL, '8815658122b516758d178eb83384a179', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AXEWDKD4VQNQCACM', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'AskUserQuestion native picker: stepper + review, Other, per-choice note', 'Overhaul the AskUserQuestion bottom-zone card (prompt_card.dart). Single question -> no stepper, bare question + options. Multiple (2-4) -> stepper: top nav showing "N - Header" per step (check on answered, box on current) + prev/next arrows, one question at a time with Back/Next, then a Review screen (question -> answer, Back / Submit answers). Each question: numbered option buttons (1-4) + an Other choice that reveals a free-text field (typed text becomes the answer value, not the word Other) + a +note affordance on a picked option (answer becomes Label - note). multiSelect toggles and comma-joins labels. A chat-instead escape denies the prompt (message: user chose to chat instead) and refocuses the composer. On submit -> allow{updatedInput:{questions, answers}} -> card closes -> clide appends a compact answered echo item to the conversation log. Acceptance: single vs multi layouts; Other + note produce correct answer strings; review step; chat-instead denies + refocuses; widget tests. Wireframes: docs/design/wireframes/claude-prompts/ (01 single, 02 stepper, 03 review).', 'done', 'high', NULL, NULL, 'D-78', '2026-05-25 07:11:16', '2026-05-25 07:27:57', NULL, '63d4bf37ad7c9d165089b11fbdc210e8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4VNVEAEYBX3S9Z8Q8', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'De-emphasize harness-injected user messages (skill/command injects)', 'When a skill or slash-command is invoked, Claude injects its content as a user-role message (e.g. skill text begins "Base directory for this skill:", commands wrap content in //, system reminders in ). These are NOT typed by the user but render as a normal blue "you" card. Detect injected user messages (content markers; also isMeta when present) and render them de-emphasized: a collapsed, muted "context" card (bare variant), NOT the blue "you" accent. UserMessage gains an injected flag set in parseTranscriptChunk; conversation_view renders injected user messages collapsed/muted. Acceptance: a skill-inject user message renders collapsed + muted + not labelled "you"; a normal typed message still renders as "you"; widget + parser tests.', 'done', 'medium', NULL, NULL, 'D-78', '2026-05-25 08:10:10', '2026-05-25 08:21:46', NULL, 'bbc992a36a3daa1109c920a15f3e2c67', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4DTGYPAB29MRDN000', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Permission prompt shows the command; resolved tool-use collapses with green/red border', 'Two parts. (1) The permission prompt UI must show what is being permitted: render the tool input (e.g. the Bash command / Write content) in the prompt card as a collapsible, trimmed code block, so the user can see what they approve. (2) Replace the current full-hide of a permission-prompted tool-use card: while the prompt is open, keep the payload in the prompt (hidden from the log); after resolution, show a COLLAPSED one-line summary in the log with a green border when approved and a red border when denied. Requires the session to track per-tool_use_id outcome (pending/allow/deny) and expose it; the conversation view renders prompted tool-use cards collapsed + colour-bordered by outcome (and keeps the result). Acceptance: prompt shows the command (collapsible); after allow the tool-use shows collapsed with a green border, after deny a red border; widget tests.', 'done', 'medium', NULL, NULL, 'D-78', '2026-05-25 08:10:18', '2026-05-25 08:26:21', NULL, '6f23be887ee48cc2272301e79c386c72', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48P8T23ZPR0BRK6PR', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'P2: session orchestrator — one managed-session primitive (spawn/show/hide)', 'Generalize the Claude pane into a clide-managed SESSION primitive: clide spawns/owns each stream-json Claude process; a pane is just a view on a session; teammate / secondary tab / forked branch / inline subagent are all the same thing. Provide spawn / show / hide / close over the set of live sessions; visibility is a UI toggle, not a lifecycle change (sessions stay live when hidden). Re-point the T-139 TeamObserver from observing a Claude tmux team to ORCHESTRATING clide-managed sessions; the T-140 tiles render whatever sessions are shown. Acceptance: multiple managed sessions run concurrently; each can be shown/hidden as a pane from a roster without tearing down its process; tests for the orchestrator lifecycle. Blocked by T-165 (phase 1 transport).', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:49', '2026-05-25 12:12:29', NULL, '4be6357fef2273610e8ca601b4f4116e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM654XV2TG4K0RVV1CW', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'P2: clide-hosted MCP coordination broker + team-awareness injection', 'Give clide-managed sessions team behavior that Claude''s tmux mode provided (and that is undocumented + headless-unavailable). clide hosts a small MCP server (attached to each agent via --mcp-config) exposing team tools: send_message(to,text), broadcast(text), list_teammates(), inbox(), and a shared claim_task/task_status. clide is the BROKER — a tool call from agent A is delivered into agent B''s next turn on B''s stream-json stdin; clide owns routing/ordering. Inject team-awareness into each agent via --append-system-prompt (roster + role) and/or --agents. Acceptance: two managed agents exchange messages through the broker and see a shared task list; a lead can address one teammate by name; tests for the broker routing + the MCP tool surface. Blocked by T-169.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:27:01', '2026-05-25 19:38:16', NULL, '2c6b2f64ed0a270fc421c85bc2be8a1d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4R8YY69YAARCVE4AC', 'bug', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Status bar overflows instead of marquee-scrolling the in-pane slot', 'Observed via make run (2026-05-24): the bottom status bar shows a RenderFlex ''overflowed by 77 PIXELS'' stripe after the in-pane context line (opus 4.7 · default · 21k ctx · 10 skills); the ClideMarquee never engages. -PRIMITIVE — ConversationCard (lib/builtin/claude/src/conversation_card.dart): -- Props: variant (stripe | bordered | bare — preserves the user/claude vs tool/thinking visual distinction), accent (Color), label (String), body (Widget content slot), copyText (raw text the copy action yields), actions (List — extensible), collapsible (bool) + collapsedByDefault (bool). -- Chrome: the label row + a hover-revealed action bar (top-right): a copy button (ClideClipboard, copies copyText), a collapse/expand toggle when collapsible, and an extensible slot for future per-message actions. Body sits below. -- Behaviors: actions reveal on hover (MouseRegion) but are ALWAYS reachable for keyboard/a11y (Semantics button + focusable, per the keyboard-operability work — never hover-only); collapse renders a short preview + an expand affordance; default-collapsed for noisy types (tool results, thinking, very long bodies); coexists with the surrounding SelectionArea (button-copy AND drag-select both work — no nested SelectionArea). +Cause: StatusbarHost (lib/app.dart ~1143) lays items in a Row [left items..., Spacer(), right items...], each at INTRINSIC width with no Flexible. PaneContextStatusItem wraps its text in ConstrainedBox(maxWidth:360) + ClideMarquee, but because the text (~280px) is under 360 the slot takes content width, so the marquee''s viewport == its content and it never scrolls. T-154 appended ''· N skills'', lengthening the line enough that git-branch + context slot + right items exceed the bar width -> the Row overflows (~77px) instead of the slot shrinking + marquee scrolling. -FUTURE-PROOFING (the point): -- MessageAction { icon, label, onInvoke } is an open list — copy is just the first. Future actions (fork-from-here, retry, edit, jump-to, save) drop in without touching the template; ties directly into D-77''s unified-session model (e.g. fork-from-here = spawn a managed session seeded from this turn). -- The card is DECOUPLED from ConversationItem — _ConversationTurn maps each item to (variant, accent, label, body, copyText, actions). So T-168''s richer typed cards (Edit/Write diff, Bash command+output, Read/Grep) reuse the SAME chrome with a different body + copyText + maybe extra actions; nothing in the template hardcodes a message kind. -- Per-card collapse policy (not global) so prose stays open while tool noise collapses. -- Variant enum keeps today''s look while unifying the implementation. +Fix direction: give the in-pane context slot a flexible/bounded width in the status bar (e.g. wrap it in Flexible(fit: loose), or have the bar allot it a shrinkable share) so it yields width when the bar is tight and ClideMarquee scrolls within the allotted width. Verify the interaction with the Spacer and the right-aligned items (priority>=100), and reconsider the fixed maxWidth:360. Acceptance: at narrow widths the in-pane slot marquee-scrolls and the status bar never shows an overflow stripe; widget test at a constrained width. Introduced by T-150 (marquee) + T-154 (longer line). -MIGRATION: route the existing switch in _ConversationTurn through the primitive; the three shapes become variants. Lands independently of the stream-json transport (works on the current conversation_view) but is the base T-168 builds on. - -Acceptance: every turn type renders through the one primitive; hover-copy yields the right raw text per type; collapsible types expand/collapse (tool-result/thinking default-collapsed); actions are keyboard-focusable + Semantics-labelled (a11y); widget tests for copy, collapse, each variant, and an injected custom action; coverage >= floor. Feeds T-168.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:45:40', '2026-05-24 17:10:07', NULL, '40e4f102209bfacd4b437a5ee268a9d7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-174', 'task', 'T-164', 'ConversationCard actions: keyboard-focusable + always-reachable (not hover-only)', 'Follow-up split out of T-173. The ConversationCard action bar (copy button + custom MessageActions) currently renders only while the card is hovered (lib/builtin/claude/src/conversation_card.dart, _header: ''if (_hover) ..._actions''). The collapse/expand caret is always visible, but the action buttons are not keyboard/AT reachable when not hovering. - -Make the actions always reachable per T-173''s acceptance: keep them in the tree always (reveal visually on hover OR focus via opacity), add Semantics onTap so assistive tech can activate them, and wire keyboard focus traversal + Enter/Space activation through clide''s keyboard-operability framework (the _tap helper uses a bare GestureDetector today, which is mouse/AT-tap only, not focus-traversable). Applies to both the action buttons and the caret. - -Acceptance: copy + custom actions are reachable and activatable by keyboard alone (Tab to focus, Enter/Space to invoke) with no mouse; Semantics button labels present; widget test drives a copy via focus+activation (no hover); a11y gate green.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-24 17:11:17', '2026-05-24 17:11:17', NULL, '314da812ab94cd8467d14310d8ceaedf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-165', 'task', 'T-164', 'P1: stream-json transport — drive the Claude pane via the control protocol', 'Spawn the Claude pane as ''claude --input-format stream-json --output-format stream-json --verbose'' (+ --include-partial-messages later) instead of an interactive TUI in tmux. Multiplex its stdin/stdout; parse the event stream (system/init, assistant, tool_use, tool_result, result, rate_limit_event); feed the existing ConversationController/MessageBus from events rather than tailing the transcript JSONL. Isolate the protocol behind one module (the drift-containment point, like the transcript reader). Acceptance: a Claude pane converses end-to-end over stream-json (send a prompt, stream the reply) with no tmux session; events drive the native conversation view; unit tests against canned event fixtures. Blocks the rest of phase 1. Evolves D-75 (rendering now sourced from events).', 'done', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:15', '2026-05-25 06:29:25', NULL, '7412d6cc50f12cf6744b233a693fe0d7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-166', 'task', 'T-164', 'P1: native permission + AskUserQuestion prompts via canUseTool', 'Handle interactive prompts that the TUI model couldn''t surface. Wire Claude''s permission requests and the AskUserQuestion tool through the control mechanism (--permission-prompt-tool / SDK-style canUseTool: toolName + input, return allow/deny or the chosen answers). Render NATIVE prompt cards in the Claude pane — allow/deny for permissions, an option picker (matching AskUserQuestion''s questions/options) for AskUserQuestion — and return the user''s choice over the protocol. Acceptance: asking Claude for an AskUserQuestion shows selectable options inline and the selection is delivered; a tool needing permission shows allow/deny and the decision flows back; widget tests for the prompt cards + a fake control channel. Closes the prompt gap that motivated D-77. Blocked by T-165.', 'done', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:23', '2026-05-25 06:31:08', NULL, 'f977efccacc49cfbf0c83a465c137479', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-175', 'task', 'T-164', 'Permission prompt: Allow / Allow-and-remember / Deny+note', 'Bottom-zone permission card (prompt_card.dart): replace binary Allow/Deny with three options. (1) Allow -> allow{updatedInput}. (2) Allow and do not ask again -> shown only when the can_use_tool request carries a permission_suggestions entry (session/localSettings destination); returns allow{updatedInput, updatedPermissions:[suggestion]}. AllowTool gains an optional updatedPermissions field; ToolPrompt + StreamJsonSession carry the request permission_suggestions through. (3) Deny -> reveals an optional note field -> deny{message}. Plus allow-with-note: clide sends the typed note as a follow-up user message right after allowing (protocol has no allow-with-message per D-78; closest faithful approximation). Acceptance: three options render (option 2 only when a suggestion exists); allow / deny / updatedPermissions / deny-note all flow over the control channel; allow+note sends a follow-up; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/04-permission.png', 'done', 'high', NULL, NULL, 'D-78', '2026-05-25 07:11:09', '2026-05-25 07:25:02', NULL, 'ae547748304f0b6b388338ed501dfb60', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-177', 'task', 'T-164', 'Conversation cards: collapse tool_use/tool_result only when multi-line', 'tool_use and tool_result conversation cards start collapsed ONLY when their body is multi-line; single-line bodies render inline (collapse bulk, not chrome - if it fits on one line, just show it). Collapsed header shows a one-line summary (tool_use: tool + key arg, e.g. Write /tmp/x; tool_result: first line or byte size). thinking already collapses; user/assistant text stay expanded. Acceptance: a one-line result renders inline with no caret; a multi-line result starts collapsed with a summary header; widget tests. Relates to T-173 (ConversationCard primitive).', 'done', 'medium', NULL, NULL, 'D-78', '2026-05-25 07:11:22', '2026-05-25 07:25:02', NULL, 'f4a0e3207adf3134901fbee447e2824b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-176', 'task', 'T-164', 'AskUserQuestion native picker: stepper + review, Other, per-choice note', 'Overhaul the AskUserQuestion bottom-zone card (prompt_card.dart). Single question -> no stepper, bare question + options. Multiple (2-4) -> stepper: top nav showing "N - Header" per step (check on answered, box on current) + prev/next arrows, one question at a time with Back/Next, then a Review screen (question -> answer, Back / Submit answers). Each question: numbered option buttons (1-4) + an Other choice that reveals a free-text field (typed text becomes the answer value, not the word Other) + a +note affordance on a picked option (answer becomes Label - note). multiSelect toggles and comma-joins labels. A chat-instead escape denies the prompt (message: user chose to chat instead) and refocuses the composer. On submit -> allow{updatedInput:{questions, answers}} -> card closes -> clide appends a compact answered echo item to the conversation log. Acceptance: single vs multi layouts; Other + note produce correct answer strings; review step; chat-instead denies + refocuses; widget tests. Wireframes: docs/design/wireframes/claude-prompts/ (01 single, 02 stepper, 03 review).', 'done', 'high', NULL, NULL, 'D-78', '2026-05-25 07:11:16', '2026-05-25 07:27:57', NULL, 'c63148c0b3d03a97b5ccc7e75133916d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-178', 'task', 'T-164', 'De-emphasize harness-injected user messages (skill/command injects)', 'When a skill or slash-command is invoked, Claude injects its content as a user-role message (e.g. skill text begins "Base directory for this skill:", commands wrap content in //, system reminders in ). These are NOT typed by the user but render as a normal blue "you" card. Detect injected user messages (content markers; also isMeta when present) and render them de-emphasized: a collapsed, muted "context" card (bare variant), NOT the blue "you" accent. UserMessage gains an injected flag set in parseTranscriptChunk; conversation_view renders injected user messages collapsed/muted. Acceptance: a skill-inject user message renders collapsed + muted + not labelled "you"; a normal typed message still renders as "you"; widget + parser tests.', 'done', 'medium', NULL, NULL, 'D-78', '2026-05-25 08:10:10', '2026-05-25 08:21:46', NULL, '0fdbcde0168c2563152bbd862fc69d50', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-179', 'task', 'T-164', 'Permission prompt shows the command; resolved tool-use collapses with green/red border', 'Two parts. (1) The permission prompt UI must show what is being permitted: render the tool input (e.g. the Bash command / Write content) in the prompt card as a collapsible, trimmed code block, so the user can see what they approve. (2) Replace the current full-hide of a permission-prompted tool-use card: while the prompt is open, keep the payload in the prompt (hidden from the log); after resolution, show a COLLAPSED one-line summary in the log with a green border when approved and a red border when denied. Requires the session to track per-tool_use_id outcome (pending/allow/deny) and expose it; the conversation view renders prompted tool-use cards collapsed + colour-bordered by outcome (and keeps the result). Acceptance: prompt shows the command (collapsible); after allow the tool-use shows collapsed with a green border, after deny a red border; widget tests.', 'done', 'medium', NULL, NULL, 'D-78', '2026-05-25 08:10:18', '2026-05-25 08:26:21', NULL, 'b0679e5eaf9cc6cff3ccecde0ed31909', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-180', 'task', 'T-164', 'Team message inbox — broker chat (sidebar widget + poppable pane)', 'A team chat / message inbox: render the clide broker traffic as a chat-client timeline and make the user a first-class participant. (1) Timeline: every inter-agent message (send_message/broadcast) shown as chat rows with a colour-coded sender chip (tyre/gestalt/lead/you/...). (2) User as a broker participant: extend the T-170 broker so send_message(to: user) delivers into the inbox, and the team-awareness roster injected into each agent lists "you" as addressable; the user can post into the channel. (3) @-routing from the user: a recipient picker / @name parses to a broker route, @team (or no tag) broadcasts; reuse the slash-typeahead machinery for @-completion. (4) Interrupt tickbox on the composer: unchecked queues the message into the target next turn (normal broker delivery); checked sends an interrupt control_request to the target session (cancels its current turn) then delivers; default OFF. (5) One chat model, two surfaces: a compact sidebar widget in the cockpit (live feed + quick @-post) that pops out into a full chat pane. Wireframes: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png (widget) + 06-team-chat-pane.png (pane). Acceptance: broker messages render as chat; user posts route by @tag (one agent) or broadcast; interrupt tickbox cancels+delivers vs queues; widget and pane share one model; tests for routing + interrupt + render. Resolves the user-participation gaps from the team-mode screen analysis. Blocked by T-170 (broker).', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:15:58', '2026-05-25 09:15:58', NULL, '7ab0fe757562e3b3b89398697e15e0a1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'task', 'T-164', 'Per-agent permission-mode cycle badge in the cockpit roster', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator).', 'backlog', 'low', NULL, NULL, 'D-77', '2026-05-25 09:16:05', '2026-05-25 09:16:05', NULL, '1223cc1467844aea060d17e4724d28bc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'task', 'T-164', 'Claude sidebar sub-tabs: Activity / Team / Config', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view. Default to Activity. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; existing stats/team content slots into Activity/Team unchanged; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:26:31', '2026-05-25 09:26:31', NULL, 'bd3215543c20c774969362dc59dad249', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'task', 'T-164', 'Claude Config sidebar tab — skills/agents/commands/settings/permissions/hooks/MCP', 'A comprehensive read view (with edit affordances where sensible) of the Claude environment, as the Config sub-tab of the sidebar (T-182). Sources: ClaudeConfig (D-76 / T-151, already reads skills, commands, settings, permissions from ~/.claude + .claude layered) plus the stream-json init/initialize payloads (agents, output styles + available styles, MCP servers, models, plugins). Sections: SKILLS, AGENTS, COMMANDS, PERMISSIONS (allow/ask/deny, colour-coded), HOOKS, MCP SERVERS, SETTINGS (model, output style). Show local+global layering (local wins) and that it is watched/live. Covers the interaction-type surfaces documented on anthropic.com so the user can see everything configured for Claude in one place. Acceptance: each section lists its entries from ClaudeConfig + init data; permissions colour-coded by rule kind; layering + source shown; updates live on a watched .claude change; widget tests. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182 (sub-tab shell).', 'backlog', 'medium', NULL, NULL, 'D-76', '2026-05-25 09:26:40', '2026-05-25 09:26:40', NULL, '9cbe6736b38163e60c448b0e0af3153d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'task', 'T-164', 'Claude Config sidebar tab — skills/agents/commands/settings/permissions/hooks/MCP', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Each category is an EXPANDABLE accordion section (no ellipsis truncation): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS, MCP SERVERS, SETTINGS, each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what that skill/agent/command says. Non-file entries render inline: permissions (allow/ask/deny, colour-coded), MCP servers, model, output style; SETTINGS can open settings.json. Show local+global layering (local wins) and that it is watched/live. Acceptance: sections expand/collapse to the full list; clicking a file-backed item opens its .md in the reader; permissions colour-coded by rule kind; updates live on a watched .claude change; widget tests for expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', 'backlog', 'medium', NULL, NULL, 'D-76', '2026-05-25 09:26:40', '2026-05-25 09:31:37', NULL, '2011edc54063ca86e8418fdb0d4a81ac', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'task', 'T-164', 'Claude Config sidebar tab — skills/agents/commands/settings/permissions/hooks/MCP', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered, local wins) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Layout: SETTINGS is a FIXED key->value table pinned at the top (not collapsible) — model, output style, permission mode, source. Everything variable-length is an EXPANDABLE accordion section showing the FULL list when expanded, never truncated (truncating to first-N + ellipsis falsely prioritises the first entries and makes the list pointless): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS (grouped allow/ask/deny, colour-coded, each rule on its own line), MCP SERVERS — each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what it says. Watched/live on .claude changes. Acceptance: settings render as a top table; every variable section expands to its complete list (no ellipsis); clicking a file-backed item opens its .md in the reader; permissions colour-coded by kind; widget tests for table + expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', 'backlog', 'medium', NULL, NULL, 'D-76', '2026-05-25 09:26:40', '2026-05-25 09:35:47', NULL, 'a4962b8a69cffe93b72275d098251e1c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'task', 'T-164', 'Claude sidebar sub-tabs: Activity / Team / Config', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view (T-183). Default to Activity. SHARED GEOMETRY: Activity values and the Config settings table use the SAME two-column label->value table (same column x + row pitch + section-header style) so toggling between tabs does not visually jump. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; Activity + Config settings render on the shared table so toggling is seamless; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:26:31', '2026-05-25 09:38:15', NULL, 'de0ab01119b20a1ef66ecba1821cdcd4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'task', 'T-164', 'P2: session orchestrator — one managed-session primitive (spawn/show/hide)', 'Generalize the Claude pane into a clide-managed SESSION primitive: clide spawns/owns each stream-json Claude process; a pane is just a view on a session; teammate / secondary tab / forked branch / inline subagent are all the same thing. Provide spawn / show / hide / close over the set of live sessions; visibility is a UI toggle, not a lifecycle change (sessions stay live when hidden). Re-point the T-139 TeamObserver from observing a Claude tmux team to ORCHESTRATING clide-managed sessions; the T-140 tiles render whatever sessions are shown. Acceptance: multiple managed sessions run concurrently; each can be shown/hidden as a pane from a roster without tearing down its process; tests for the orchestrator lifecycle. Blocked by T-165 (phase 1 transport).', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:49', '2026-05-25 10:49:30', NULL, '698c3527c5cdfba6a19b51fee6cc3cbc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'task', 'T-164', 'P2: session orchestrator — one managed-session primitive (spawn/show/hide)', 'Generalize the Claude pane into a clide-managed SESSION primitive: clide spawns/owns each stream-json Claude process; a pane is just a view on a session; teammate / secondary tab / forked branch / inline subagent are all the same thing. Provide spawn / show / hide / close over the set of live sessions; visibility is a UI toggle, not a lifecycle change (sessions stay live when hidden). Re-point the T-139 TeamObserver from observing a Claude tmux team to ORCHESTRATING clide-managed sessions; the T-140 tiles render whatever sessions are shown. Acceptance: multiple managed sessions run concurrently; each can be shown/hidden as a pane from a roster without tearing down its process; tests for the orchestrator lifecycle. Blocked by T-165 (phase 1 transport).', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:49', '2026-05-25 12:12:29', NULL, '187a203302a7f0eb983acd61122a050a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-170', 'task', 'T-164', 'P2: clide-hosted MCP coordination broker + team-awareness injection', 'Give clide-managed sessions team behavior that Claude''s tmux mode provided (and that is undocumented + headless-unavailable). clide hosts a small MCP server (attached to each agent via --mcp-config) exposing team tools: send_message(to,text), broadcast(text), list_teammates(), inbox(), and a shared claim_task/task_status. clide is the BROKER — a tool call from agent A is delivered into agent B''s next turn on B''s stream-json stdin; clide owns routing/ordering. Inject team-awareness into each agent via --append-system-prompt (roster + role) and/or --agents. Acceptance: two managed agents exchange messages through the broker and see a shared task list; a lead can address one teammate by name; tests for the broker routing + the MCP tool surface. Blocked by T-169.', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:27:01', '2026-05-25 12:12:33', NULL, '8238e6266f9adc7e7e5e4e5d08813de1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-170', 'task', 'T-164', 'P2: clide-hosted MCP coordination broker + team-awareness injection', 'Give clide-managed sessions team behavior that Claude''s tmux mode provided (and that is undocumented + headless-unavailable). clide hosts a small MCP server (attached to each agent via --mcp-config) exposing team tools: send_message(to,text), broadcast(text), list_teammates(), inbox(), and a shared claim_task/task_status. clide is the BROKER — a tool call from agent A is delivered into agent B''s next turn on B''s stream-json stdin; clide owns routing/ordering. Inject team-awareness into each agent via --append-system-prompt (roster + role) and/or --agents. Acceptance: two managed agents exchange messages through the broker and see a shared task list; a lead can address one teammate by name; tests for the broker routing + the MCP tool surface. Blocked by T-169.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:27:01', '2026-05-25 19:38:16', NULL, '67f2ccb114bf9cf4f3fec9c28827a80d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'task', 'T-164', 'Claude sidebar sub-tabs: Activity / Team / Config', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view (T-183). Default to Activity. SHARED GEOMETRY: Activity values and the Config settings table use the SAME two-column label->value table (same column x + row pitch + section-header style) so toggling between tabs does not visually jump. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; Activity + Config settings render on the shared table so toggling is seamless; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:26:31', '2026-05-25 20:01:12', NULL, '0fa8449309d4b717d3b48298fbe3df05', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-168', 'task', 'T-164', 'P1: native rendering leveled up — typed event cards + streaming + live status', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165. +--- +Refinement (2026-05-30, /whats-next): pulled into the T-167/T-168 batch and sequenced FIRST. T-168 adds live cost / permission-mode / context / rate-limit to this same in-pane status slot; the Flexible/marquee fix here must land before T-168 lengthens the line. After this fix, T-168 can safely add fields to the slot.', 'done', 'medium', NULL, NULL, NULL, '2026-05-24 09:13:42', '2026-05-30 11:05:56', NULL, '5a35baef15f92d9e04731cd307b5365e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7G7MK8MP3BGQC36HG', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'P1: session persistence + /clear + /resume on stream-json (--resume)', 'Move session lifecycle onto the stream-json model. New session: --session-id ; resume an existing one: --resume (T-161 confirmed --session-id refuses an existing id). Re-implement the clide-owned commands on this model: /clear = new fresh session, /resume = picker then resume the picked id. Retire the tmux session lifecycle for Claude (kill/hasSession/reap, new-session -A) and the --session-id-in-use handling once the transport no longer uses tmux. Acceptance: restart resumes the primary''s session; /clear starts empty; /resume switches to a picked session; all without tmux; tests for the new/resume argv selection and the command flows. Supersedes the mechanics of T-156/T-161. Blocked by T-165. Amends D-41 (tmux persistence → --resume).', 'done', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:30', '2026-05-30 11:05:56', NULL, 'af6188c78b5ff7df9e797dd4d7af0f7c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78W48QX3JZP0QYAVW', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'P1: native rendering leveled up — typed event cards + streaming + live status', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165. --- Refinement (2026-05-30, /whats-next) — five seam decisions resolved: @@ -2780,113 +998,30 @@ Refinement (2026-05-30, /whats-next) — five seam decisions resolved: Files an implementer touches: conversation_view.dart, conversation_controller.dart, transcript_reader.dart, stream_json_session.dart, claude_pane.dart, plus test/builtin/claude/*. Commit the in-flight prompt_card.dart change first (disjoint surface — permission-card Write-description dedup). -Sequencing within the batch: T-160 → T-168.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:39', '2026-05-30 10:44:06', NULL, '340abc28714ab23a36c98d38127d9a87', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-160', 'bug', 'T-132', 'Status bar overflows instead of marquee-scrolling the in-pane slot', 'Observed via make run (2026-05-24): the bottom status bar shows a RenderFlex ''overflowed by 77 PIXELS'' stripe after the in-pane context line (opus 4.7 · default · 21k ctx · 10 skills); the ClideMarquee never engages. - -Cause: StatusbarHost (lib/app.dart ~1143) lays items in a Row [left items..., Spacer(), right items...], each at INTRINSIC width with no Flexible. PaneContextStatusItem wraps its text in ConstrainedBox(maxWidth:360) + ClideMarquee, but because the text (~280px) is under 360 the slot takes content width, so the marquee''s viewport == its content and it never scrolls. T-154 appended ''· N skills'', lengthening the line enough that git-branch + context slot + right items exceed the bar width -> the Row overflows (~77px) instead of the slot shrinking + marquee scrolling. - -Fix direction: give the in-pane context slot a flexible/bounded width in the status bar (e.g. wrap it in Flexible(fit: loose), or have the bar allot it a shrinkable share) so it yields width when the bar is tight and ClideMarquee scrolls within the allotted width. Verify the interaction with the Spacer and the right-aligned items (priority>=100), and reconsider the fixed maxWidth:360. Acceptance: at narrow widths the in-pane slot marquee-scrolls and the status bar never shows an overflow stripe; widget test at a constrained width. Introduced by T-150 (marquee) + T-154 (longer line). - ---- -Refinement (2026-05-30, /whats-next): pulled into the T-167/T-168 batch and sequenced FIRST. T-168 adds live cost / permission-mode / context / rate-limit to this same in-pane status slot; the Flexible/marquee fix here must land before T-168 lengthens the line. After this fix, T-168 can safely add fields to the slot.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-24 09:13:42', '2026-05-30 10:44:10', NULL, '5ed66d193e73c29141a8525a44a242f7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-168', 'task', 'T-164', 'P1: native rendering leveled up — typed event cards + streaming + live status', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165. - ---- -Refinement (2026-05-30, /whats-next) — five seam decisions resolved: - -1. tool_use/result pairing: add a Map toolUseById index to ConversationController. The result card looks up its tool_use and renders the Edit/Write diff (reuse prompt_card''s _editBody style) or the is_error failure IN PLACE in the result card. Files: conversation_controller.dart (index), conversation_view.dart (_toolUse / _toolResult bodies — they currently render indented JSON only and can''t see each other). - -2. Per-tool card bodies: Edit/Write → diff; Bash → command+output; Read/Grep → file/query. Selected by tool name off the paired tool_use input. - -3. Partial-message streaming: pass --include-partial-messages in ClaudeStreamJsonProcess.start(); accumulate partials keyed by message.id in StreamJsonSession._onLine BEFORE emitting to the controller, so one item updates in place (no duplicate AssistantTextMessage items). Thinking blocks stream the same way. Do NOT add a new streaming item type in the controller. - -4. Live cost/context (was an open gap, defaulted): extend SessionStatus (transcript_reader.dart) with cost + contextWindow fields; extract them from result events in stream_json_session.dart::_statusFromEvent (currently only handles system/init) — total_cost_usd + modelUsage..contextWindow per the spike doc docs/spikes/cc-stream-json-control-protocol-2.1.150.md. - -5. rate_limit_event + the focus status line: surface live model/mode/context/cost/rate-limit in the in-pane status slot. This DEPENDS ON T-160 (status-bar overflow / marquee) which is pulled into this batch and sequenced FIRST — do not lengthen the status line until T-160''s Flexible/marquee fix lands. ClaudeConfig.ensureProbe() is retired only for the ACTIVE-pane status; the probe survives for slash-command typeahead. - -Files an implementer touches: conversation_view.dart, conversation_controller.dart, transcript_reader.dart, stream_json_session.dart, claude_pane.dart, plus test/builtin/claude/*. Commit the in-flight prompt_card.dart change first (disjoint surface — permission-card Write-description dedup). - -Sequencing within the batch: T-160 → T-168.', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:39', '2026-05-30 10:48:32', NULL, '24a7adcd7c1e8cb3f9c4e042c43aa9b2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-167', 'task', 'T-164', 'P1: session persistence + /clear + /resume on stream-json (--resume)', 'Move session lifecycle onto the stream-json model. New session: --session-id ; resume an existing one: --resume (T-161 confirmed --session-id refuses an existing id). Re-implement the clide-owned commands on this model: /clear = new fresh session, /resume = picker then resume the picked id. Retire the tmux session lifecycle for Claude (kill/hasSession/reap, new-session -A) and the --session-id-in-use handling once the transport no longer uses tmux. Acceptance: restart resumes the primary''s session; /clear starts empty; /resume switches to a picked session; all without tmux; tests for the new/resume argv selection and the command flows. Supersedes the mechanics of T-156/T-161. Blocked by T-165. Amends D-41 (tmux persistence → --resume).', 'in_progress', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:30', '2026-05-30 10:48:32', NULL, '4c9095cfb7c8caa57c4b720582a73ef7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-160', 'bug', 'T-132', 'Status bar overflows instead of marquee-scrolling the in-pane slot', 'Observed via make run (2026-05-24): the bottom status bar shows a RenderFlex ''overflowed by 77 PIXELS'' stripe after the in-pane context line (opus 4.7 · default · 21k ctx · 10 skills); the ClideMarquee never engages. - -Cause: StatusbarHost (lib/app.dart ~1143) lays items in a Row [left items..., Spacer(), right items...], each at INTRINSIC width with no Flexible. PaneContextStatusItem wraps its text in ConstrainedBox(maxWidth:360) + ClideMarquee, but because the text (~280px) is under 360 the slot takes content width, so the marquee''s viewport == its content and it never scrolls. T-154 appended ''· N skills'', lengthening the line enough that git-branch + context slot + right items exceed the bar width -> the Row overflows (~77px) instead of the slot shrinking + marquee scrolling. - -Fix direction: give the in-pane context slot a flexible/bounded width in the status bar (e.g. wrap it in Flexible(fit: loose), or have the bar allot it a shrinkable share) so it yields width when the bar is tight and ClideMarquee scrolls within the allotted width. Verify the interaction with the Spacer and the right-aligned items (priority>=100), and reconsider the fixed maxWidth:360. Acceptance: at narrow widths the in-pane slot marquee-scrolls and the status bar never shows an overflow stripe; widget test at a constrained width. Introduced by T-150 (marquee) + T-154 (longer line). - ---- -Refinement (2026-05-30, /whats-next): pulled into the T-167/T-168 batch and sequenced FIRST. T-168 adds live cost / permission-mode / context / rate-limit to this same in-pane status slot; the Flexible/marquee fix here must land before T-168 lengthens the line. After this fix, T-168 can safely add fields to the slot.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-24 09:13:42', '2026-05-30 10:48:32', NULL, 'be801b09fd2aeb3a4bb71b782499f9a7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-167', 'task', 'T-164', 'P1: session persistence + /clear + /resume on stream-json (--resume)', 'Move session lifecycle onto the stream-json model. New session: --session-id ; resume an existing one: --resume (T-161 confirmed --session-id refuses an existing id). Re-implement the clide-owned commands on this model: /clear = new fresh session, /resume = picker then resume the picked id. Retire the tmux session lifecycle for Claude (kill/hasSession/reap, new-session -A) and the --session-id-in-use handling once the transport no longer uses tmux. Acceptance: restart resumes the primary''s session; /clear starts empty; /resume switches to a picked session; all without tmux; tests for the new/resume argv selection and the command flows. Supersedes the mechanics of T-156/T-161. Blocked by T-165. Amends D-41 (tmux persistence → --resume).', 'done', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:30', '2026-05-30 11:05:56', NULL, '8da00e6f4b27462157ed80d908b91a6b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-160', 'bug', 'T-132', 'Status bar overflows instead of marquee-scrolling the in-pane slot', 'Observed via make run (2026-05-24): the bottom status bar shows a RenderFlex ''overflowed by 77 PIXELS'' stripe after the in-pane context line (opus 4.7 · default · 21k ctx · 10 skills); the ClideMarquee never engages. - -Cause: StatusbarHost (lib/app.dart ~1143) lays items in a Row [left items..., Spacer(), right items...], each at INTRINSIC width with no Flexible. PaneContextStatusItem wraps its text in ConstrainedBox(maxWidth:360) + ClideMarquee, but because the text (~280px) is under 360 the slot takes content width, so the marquee''s viewport == its content and it never scrolls. T-154 appended ''· N skills'', lengthening the line enough that git-branch + context slot + right items exceed the bar width -> the Row overflows (~77px) instead of the slot shrinking + marquee scrolling. - -Fix direction: give the in-pane context slot a flexible/bounded width in the status bar (e.g. wrap it in Flexible(fit: loose), or have the bar allot it a shrinkable share) so it yields width when the bar is tight and ClideMarquee scrolls within the allotted width. Verify the interaction with the Spacer and the right-aligned items (priority>=100), and reconsider the fixed maxWidth:360. Acceptance: at narrow widths the in-pane slot marquee-scrolls and the status bar never shows an overflow stripe; widget test at a constrained width. Introduced by T-150 (marquee) + T-154 (longer line). - ---- -Refinement (2026-05-30, /whats-next): pulled into the T-167/T-168 batch and sequenced FIRST. T-168 adds live cost / permission-mode / context / rate-limit to this same in-pane status slot; the Flexible/marquee fix here must land before T-168 lengthens the line. After this fix, T-168 can safely add fields to the slot.', 'done', 'medium', NULL, NULL, NULL, '2026-05-24 09:13:42', '2026-05-30 11:05:56', NULL, 'c9c1801d8523ed0af1aff7da39393a0c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-184', 'task', 'T-164', 'Validate partial-message wire shape from --include-partial-messages against a live capture', 'T-168 follow-up. T-168 wired --include-partial-messages into the interactive stream-json session (stream_json_session.dart) and added a partial-streaming handler that ASSUMES claude emits incremental assistant events carrying partial:true sharing one message.id. That shape is UNVERIFIED: the 2.1.150 spike (docs/spikes/cc-stream-json-control-protocol-2.1.150.md) only documents non-partial assistant events (one per content block, no partial flag), and the flag''s --help says it ''only works with --print'' while clide runs the interactive control protocol (--input-format/--output-format stream-json, not --print). The handler degrades gracefully (unmatched shapes fall through to parseTranscriptChunk and are ignored — token streaming is inert but nothing breaks), and the T-168 fixtures are synthetic so they pass regardless of reality. Do: capture a real claude turn over the interactive stream-json transport with --include-partial-messages and record the actual partial event envelope (likely a stream_event content_block_delta, not assistant+partial:true). Then either (a) fix the handler in stream_json_session.dart::_onLine to match the real shape and add a fixture from the live capture, or (b) if the flag is a true no-op in the interactive (non-print) mode, drop --include-partial-messages from the argv and the dead partial-handler branch, and pin the finding in the spike doc. Either way, update the UNVERIFIED-WIRE-SHAPE comment in stream_json_session.dart and the spike. Acceptance: the partial path is validated against a live capture, the handler matches the real shape (or is removed), a fixture derived from the capture covers it, and the spike + code comment reflect the confirmed shape.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-05-30 11:42:53', '2026-05-30 11:42:53', NULL, '38dccbc403b8ac945bd2ad01d9ac1640', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-168', 'task', 'T-164', 'P1: native rendering leveled up — typed event cards + streaming + live status', 'Exploit the structured stream the transcript-tail never cleanly gave. Per-tool cards from tool_use input (Edit/Write → diff, Bash → command+output, Read/Grep → file/query); pair each tool_result to its tool_use (with is_error) so diffs/failures render in place; stream assistant text + thinking blocks token-by-token via --include-partial-messages; surface live model / permission-mode / context-tokens / cost and rate_limit_event straight off the init + result events (retires the ClaudeConfig probe + status-bar guessing for the active pane); render structured errors as events. Acceptance: a session with edits/bash/reads shows the right card per tool with paired results; replies stream; the status line reflects live model/mode/context/cost from events; widget + fixture tests. Blocked by T-165. - ---- -Refinement (2026-05-30, /whats-next) — five seam decisions resolved: - -1. tool_use/result pairing: add a Map toolUseById index to ConversationController. The result card looks up its tool_use and renders the Edit/Write diff (reuse prompt_card''s _editBody style) or the is_error failure IN PLACE in the result card. Files: conversation_controller.dart (index), conversation_view.dart (_toolUse / _toolResult bodies — they currently render indented JSON only and can''t see each other). - -2. Per-tool card bodies: Edit/Write → diff; Bash → command+output; Read/Grep → file/query. Selected by tool name off the paired tool_use input. - -3. Partial-message streaming: pass --include-partial-messages in ClaudeStreamJsonProcess.start(); accumulate partials keyed by message.id in StreamJsonSession._onLine BEFORE emitting to the controller, so one item updates in place (no duplicate AssistantTextMessage items). Thinking blocks stream the same way. Do NOT add a new streaming item type in the controller. - -4. Live cost/context (was an open gap, defaulted): extend SessionStatus (transcript_reader.dart) with cost + contextWindow fields; extract them from result events in stream_json_session.dart::_statusFromEvent (currently only handles system/init) — total_cost_usd + modelUsage..contextWindow per the spike doc docs/spikes/cc-stream-json-control-protocol-2.1.150.md. - -5. rate_limit_event + the focus status line: surface live model/mode/context/cost/rate-limit in the in-pane status slot. This DEPENDS ON T-160 (status-bar overflow / marquee) which is pulled into this batch and sequenced FIRST — do not lengthen the status line until T-160''s Flexible/marquee fix lands. ClaudeConfig.ensureProbe() is retired only for the ACTIVE-pane status; the probe survives for slash-command typeahead. - -Files an implementer touches: conversation_view.dart, conversation_controller.dart, transcript_reader.dart, stream_json_session.dart, claude_pane.dart, plus test/builtin/claude/*. Commit the in-flight prompt_card.dart change first (disjoint surface — permission-card Write-description dedup). - -Sequencing within the batch: T-160 → T-168.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:39', '2026-05-30 11:44:28', NULL, 'd2358f27340e318a3804a330dc1bc615', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-172', 'task', 'T-164', 'P2: fork-into-a-pane (--fork-session)', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator). - ---- -Refinement (2026-05-30, /whats-next): --fork-session CONFIRMED against the live binary (claude --help: "When resuming, create a new session ID instead of reusing the original (use with --resume or --continue)"). Fork argv = [''--resume'', , ''--fork-session''] (no --session-id; the new branch gets its own id from the init event). Add forkSourceSessionId to SpawnSpec (session_orchestrator.dart); build argv via a forkSessionArgs helper in session_naming.dart; add ''fork'' to kClideOwnedCommands + a case in claude_pane._send; add a fork button to _memberRow in claude_meta_sidebar.dart. SEQUENCE AFTER T-171 (shares _memberRow + orchestrator).', 'backlog', 'low', NULL, NULL, 'D-77', '2026-05-24 16:27:16', '2026-05-30 17:12:17', NULL, '6cc2eaeeeeb21fec55cc179fd8faa6af', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-171', 'task', 'T-164', 'P2: sidebar cockpit — task list + messages + roster controls', 'Evolve the Claude meta sidebar (T-141/T-157) from a read-only roster into the CONTROL surface for clide-managed agents. Because clide owns the broker, the task list and inter-agent messages are local data — so the sidebar both shows them live and lets the user ACT: spawn / show / hide / mute an agent, reassign a task, inject or redirect a message. Roster rows keep the live per-member status (T-157) but gain controls. Acceptance: from the sidebar the user can spawn/show/hide an agent, watch the shared task list update, see the message flow, and intervene (reassign/inject); widget tests for the controls wired to the orchestrator + broker. Blocked by T-169 (orchestrator) and T-170 (broker).', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:27:09', '2026-05-30 17:12:46', NULL, '0e369ad03149a1f4cef4ec12bbc05d0a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-174', 'task', 'T-164', 'ConversationCard actions: keyboard-focusable + always-reachable (not hover-only)', 'Follow-up split out of T-173. The ConversationCard action bar (copy button + custom MessageActions) currently renders only while the card is hovered (lib/builtin/claude/src/conversation_card.dart, _header: ''if (_hover) ..._actions''). The collapse/expand caret is always visible, but the action buttons are not keyboard/AT reachable when not hovering. +Sequencing within the batch: T-160 → T-168.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:26:39', '2026-05-30 11:44:28', NULL, '08c65c3f0a3b1642252fb715f1d7f4e0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZB63YYNFXM9DQNRR', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'ConversationCard actions: keyboard-focusable + always-reachable (not hover-only)', 'Follow-up split out of T-173. The ConversationCard action bar (copy button + custom MessageActions) currently renders only while the card is hovered (lib/builtin/claude/src/conversation_card.dart, _header: ''if (_hover) ..._actions''). The collapse/expand caret is always visible, but the action buttons are not keyboard/AT reachable when not hovering. Make the actions always reachable per T-173''s acceptance: keep them in the tree always (reveal visually on hover OR focus via opacity), add Semantics onTap so assistive tech can activate them, and wire keyboard focus traversal + Enter/Space activation through clide''s keyboard-operability framework (the _tap helper uses a bare GestureDetector today, which is mouse/AT-tap only, not focus-traversable). Applies to both the action buttons and the caret. -Acceptance: copy + custom actions are reachable and activatable by keyboard alone (Tab to focus, Enter/Space to invoke) with no mouse; Semantics button labels present; widget test drives a copy via focus+activation (no hover); a11y gate green.', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-24 17:11:17', '2026-05-30 17:12:46', NULL, '5294925aae67ed738dd8f2096469f56c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-174', 'task', 'T-164', 'ConversationCard actions: keyboard-focusable + always-reachable (not hover-only)', 'Follow-up split out of T-173. The ConversationCard action bar (copy button + custom MessageActions) currently renders only while the card is hovered (lib/builtin/claude/src/conversation_card.dart, _header: ''if (_hover) ..._actions''). The collapse/expand caret is always visible, but the action buttons are not keyboard/AT reachable when not hovering. - -Make the actions always reachable per T-173''s acceptance: keep them in the tree always (reveal visually on hover OR focus via opacity), add Semantics onTap so assistive tech can activate them, and wire keyboard focus traversal + Enter/Space activation through clide''s keyboard-operability framework (the _tap helper uses a bare GestureDetector today, which is mouse/AT-tap only, not focus-traversable). Applies to both the action buttons and the caret. - -Acceptance: copy + custom actions are reachable and activatable by keyboard alone (Tab to focus, Enter/Space to invoke) with no mouse; Semantics button labels present; widget test drives a copy via focus+activation (no hover); a11y gate green.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 17:11:17', '2026-05-30 21:11:26', NULL, '2eb96628ffebd214dca72ffcd5f5da69', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-171', 'task', 'T-164', 'P2: sidebar cockpit — task list + messages + roster controls', 'Evolve the Claude meta sidebar (T-141/T-157) from a read-only roster into the CONTROL surface for clide-managed agents. Because clide owns the broker, the task list and inter-agent messages are local data — so the sidebar both shows them live and lets the user ACT: spawn / show / hide / mute an agent, reassign a task, inject or redirect a message. Roster rows keep the live per-member status (T-157) but gain controls. Acceptance: from the sidebar the user can spawn/show/hide an agent, watch the shared task list update, see the message flow, and intervene (reassign/inject); widget tests for the controls wired to the orchestrator + broker. Blocked by T-169 (orchestrator) and T-170 (broker).', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:27:09', '2026-05-30 21:11:26', NULL, '7d186872f15ef055161e51f2ae3f7479', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'task', 'T-164', 'Per-agent permission-mode cycle badge in the cockpit roster', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator).', 'in_progress', 'low', NULL, NULL, 'D-77', '2026-05-25 09:16:05', '2026-05-30 21:14:56', NULL, '3e4c5116d77bfc451e05eb54d208e83d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'task', 'T-164', 'Per-agent permission-mode cycle badge in the cockpit roster', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator).', 'done', 'low', NULL, NULL, 'D-77', '2026-05-25 09:16:05', '2026-05-30 21:25:11', NULL, '5742b99a6d5b4315dcc85bc5f9e0ed39', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-172', 'task', 'T-164', 'P2: fork-into-a-pane (--fork-session)', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator). +Acceptance: copy + custom actions are reachable and activatable by keyboard alone (Tab to focus, Enter/Space to invoke) with no mouse; Semantics button labels present; widget test drives a copy via focus+activation (no hover); a11y gate green.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 17:11:17', '2026-05-30 21:11:26', NULL, '7fab4b20b3a0970593130b0f110ce80d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6FMAHXM1XAHHCQC60', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'P2: sidebar cockpit — task list + messages + roster controls', 'Evolve the Claude meta sidebar (T-141/T-157) from a read-only roster into the CONTROL surface for clide-managed agents. Because clide owns the broker, the task list and inter-agent messages are local data — so the sidebar both shows them live and lets the user ACT: spawn / show / hide / mute an agent, reassign a task, inject or redirect a message. Roster rows keep the live per-member status (T-157) but gain controls. Acceptance: from the sidebar the user can spawn/show/hide an agent, watch the shared task list update, see the message flow, and intervene (reassign/inject); widget tests for the controls wired to the orchestrator + broker. Blocked by T-169 (orchestrator) and T-170 (broker).', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-24 16:27:09', '2026-05-30 21:11:26', NULL, 'cbd95c04b6003ab75929a3864a1ea5cc', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71N3YMS82BAYD3M9R', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'P2: fork-into-a-pane (--fork-session)', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator). --- -Refinement (2026-05-30, /whats-next): --fork-session CONFIRMED against the live binary (claude --help: "When resuming, create a new session ID instead of reusing the original (use with --resume or --continue)"). Fork argv = [''--resume'', , ''--fork-session''] (no --session-id; the new branch gets its own id from the init event). Add forkSourceSessionId to SpawnSpec (session_orchestrator.dart); build argv via a forkSessionArgs helper in session_naming.dart; add ''fork'' to kClideOwnedCommands + a case in claude_pane._send; add a fork button to _memberRow in claude_meta_sidebar.dart. SEQUENCE AFTER T-171 (shares _memberRow + orchestrator).', 'in_progress', 'low', NULL, NULL, 'D-77', '2026-05-24 16:27:16', '2026-05-30 21:25:12', NULL, '1aaeba013d95c3d2baa5488f20183775', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-185', 'task', 'T-164', 'Capture the forked session''s real claude session-id from its init event', 'T-172 follow-up. A forked session is spawned with --resume --fork-session and no --session-id, so claude assigns the branch a NEW session-id that only arrives in the stream-json init event. Today ManagedSession.sessionId for a fork holds a placeholder UUID (the orchestrator key), and the real claude session-id is never captured back. Consequence: the fork can''t itself be resumed/forked by id later, and any UI showing the session-id is wrong for forks. Do: capture session_id from the init/system event (StreamJsonSession already parses init for status — surface the session_id) and update the ManagedSession (make sessionId mutable or store a separate claudeSessionId that the orchestrator fills on init). Acceptance: after a fork''s first init event, the ManagedSession exposes the branch''s real claude session-id; a test drives an init event through a fake process and asserts the captured id. Surfaced by T-172; the fork argv/spawn/roster/original-unaffected behaviour already works without it.', 'backlog', 'low', NULL, NULL, 'D-77', '2026-05-30 21:34:26', '2026-05-30 21:34:26', NULL, 'f041094e4dea938f3e00997226e21edf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-172', 'task', 'T-164', 'P2: fork-into-a-pane (--fork-session)', 'Branch a conversation into a new managed session/pane, reusing the orchestrator''s spawn plumbing. ''Fork'' = spawn a managed session seeded from another session''s context via --resume + --fork-session (resume the source, but create a new session-id so the branch diverges without touching the original). Surface it as a slash action and a sidebar/roster button on a session. Acceptance: forking an active session opens a new pane continuing from its context, independent thereafter; the original is unaffected; tests for the fork argv + the new managed session appearing in the roster. Blocked by T-169 (orchestrator). - ---- -Refinement (2026-05-30, /whats-next): --fork-session CONFIRMED against the live binary (claude --help: "When resuming, create a new session ID instead of reusing the original (use with --resume or --continue)"). Fork argv = [''--resume'', , ''--fork-session''] (no --session-id; the new branch gets its own id from the init event). Add forkSourceSessionId to SpawnSpec (session_orchestrator.dart); build argv via a forkSessionArgs helper in session_naming.dart; add ''fork'' to kClideOwnedCommands + a case in claude_pane._send; add a fork button to _memberRow in claude_meta_sidebar.dart. SEQUENCE AFTER T-171 (shares _memberRow + orchestrator).', 'done', 'low', NULL, NULL, 'D-77', '2026-05-24 16:27:16', '2026-05-30 21:34:56', NULL, '8e8e43c735d6376e3eb1531a4f682b52', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-180', 'task', 'T-164', 'Team message inbox — broker chat (sidebar widget + poppable pane)', 'A team chat / message inbox: render the clide broker traffic as a chat-client timeline and make the user a first-class participant. (1) Timeline: every inter-agent message (send_message/broadcast) shown as chat rows with a colour-coded sender chip (tyre/gestalt/lead/you/...). (2) User as a broker participant: extend the T-170 broker so send_message(to: user) delivers into the inbox, and the team-awareness roster injected into each agent lists "you" as addressable; the user can post into the channel. (3) @-routing from the user: a recipient picker / @name parses to a broker route, @team (or no tag) broadcasts; reuse the slash-typeahead machinery for @-completion. (4) Interrupt tickbox on the composer: unchecked queues the message into the target next turn (normal broker delivery); checked sends an interrupt control_request to the target session (cancels its current turn) then delivers; default OFF. (5) One chat model, two surfaces: a compact sidebar widget in the cockpit (live feed + quick @-post) that pops out into a full chat pane. Wireframes: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png (widget) + 06-team-chat-pane.png (pane). Acceptance: broker messages render as chat; user posts route by @tag (one agent) or broadcast; interrupt tickbox cancels+delivers vs queues; widget and pane share one model; tests for routing + interrupt + render. Resolves the user-participation gaps from the team-mode screen analysis. Blocked by T-170 (broker).', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:15:58', '2026-05-30 21:34:57', NULL, 'caa8f38a5ae02650d2b3552a00ab796e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-180', 'task', 'T-164', 'Team message inbox — broker chat (sidebar widget + poppable pane)', 'A team chat / message inbox: render the clide broker traffic as a chat-client timeline and make the user a first-class participant. (1) Timeline: every inter-agent message (send_message/broadcast) shown as chat rows with a colour-coded sender chip (tyre/gestalt/lead/you/...). (2) User as a broker participant: extend the T-170 broker so send_message(to: user) delivers into the inbox, and the team-awareness roster injected into each agent lists "you" as addressable; the user can post into the channel. (3) @-routing from the user: a recipient picker / @name parses to a broker route, @team (or no tag) broadcasts; reuse the slash-typeahead machinery for @-completion. (4) Interrupt tickbox on the composer: unchecked queues the message into the target next turn (normal broker delivery); checked sends an interrupt control_request to the target session (cancels its current turn) then delivers; default OFF. (5) One chat model, two surfaces: a compact sidebar widget in the cockpit (live feed + quick @-post) that pops out into a full chat pane. Wireframes: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png (widget) + 06-team-chat-pane.png (pane). Acceptance: broker messages render as chat; user posts route by @tag (one agent) or broadcast; interrupt tickbox cancels+delivers vs queues; widget and pane share one model; tests for routing + interrupt + render. Resolves the user-participation gaps from the team-mode screen analysis. Blocked by T-170 (broker).', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:15:58', '2026-05-31 08:49:07', NULL, 'b81c7bf1be527e38ef37925f2debd1c5', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'task', 'T-164', 'Claude Config sidebar tab — skills/agents/commands/settings/permissions/hooks/MCP', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered, local wins) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Layout: SETTINGS is a FIXED key->value table pinned at the top (not collapsible) — model, output style, permission mode, source. Everything variable-length is an EXPANDABLE accordion section showing the FULL list when expanded, never truncated (truncating to first-N + ellipsis falsely prioritises the first entries and makes the list pointless): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS (grouped allow/ask/deny, colour-coded, each rule on its own line), MCP SERVERS — each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what it says. Watched/live on .claude changes. Acceptance: settings render as a top table; every variable section expands to its complete list (no ellipsis); clicking a file-backed item opens its .md in the reader; permissions colour-coded by kind; widget tests for table + expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', 'in_progress', 'medium', NULL, NULL, 'D-76', '2026-05-25 09:26:40', '2026-05-31 08:49:08', NULL, '4c86cfc6e81700b9b94df5420705d849', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-183', 'task', 'T-164', 'Claude Config sidebar tab — skills/agents/commands/settings/permissions/hooks/MCP', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered, local wins) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Layout: SETTINGS is a FIXED key->value table pinned at the top (not collapsible) — model, output style, permission mode, source. Everything variable-length is an EXPANDABLE accordion section showing the FULL list when expanded, never truncated (truncating to first-N + ellipsis falsely prioritises the first entries and makes the list pointless): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS (grouped allow/ask/deny, colour-coded, each rule on its own line), MCP SERVERS — each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what it says. Watched/live on .claude changes. Acceptance: settings render as a top table; every variable section expands to its complete list (no ellipsis); clicking a file-backed item opens its .md in the reader; permissions colour-coded by kind; widget tests for table + expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', 'done', 'medium', NULL, NULL, 'D-76', '2026-05-25 09:26:40', '2026-05-31 09:30:11', NULL, '9589b5d69a790d02598466697638dde6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-184', 'task', 'T-164', 'Validate partial-message wire shape from --include-partial-messages against a live capture', 'T-168 follow-up. T-168 wired --include-partial-messages into the interactive stream-json session (stream_json_session.dart) and added a partial-streaming handler that ASSUMES claude emits incremental assistant events carrying partial:true sharing one message.id. That shape is UNVERIFIED: the 2.1.150 spike (docs/spikes/cc-stream-json-control-protocol-2.1.150.md) only documents non-partial assistant events (one per content block, no partial flag), and the flag''s --help says it ''only works with --print'' while clide runs the interactive control protocol (--input-format/--output-format stream-json, not --print). The handler degrades gracefully (unmatched shapes fall through to parseTranscriptChunk and are ignored — token streaming is inert but nothing breaks), and the T-168 fixtures are synthetic so they pass regardless of reality. Do: capture a real claude turn over the interactive stream-json transport with --include-partial-messages and record the actual partial event envelope (likely a stream_event content_block_delta, not assistant+partial:true). Then either (a) fix the handler in stream_json_session.dart::_onLine to match the real shape and add a fixture from the live capture, or (b) if the flag is a true no-op in the interactive (non-print) mode, drop --include-partial-messages from the argv and the dead partial-handler branch, and pin the finding in the spike doc. Either way, update the UNVERIFIED-WIRE-SHAPE comment in stream_json_session.dart and the spike. Acceptance: the partial path is validated against a live capture, the handler matches the real shape (or is removed), a fixture derived from the capture covers it, and the spike + code comment reflect the confirmed shape.', 'in_progress', 'medium', NULL, NULL, 'D-77', '2026-05-30 11:42:53', '2026-05-31 09:49:55', NULL, 'ef7ba71512bcda769d81f79858afab05', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-184', 'task', 'T-164', 'Validate partial-message wire shape from --include-partial-messages against a live capture', 'T-168 follow-up. T-168 wired --include-partial-messages into the interactive stream-json session (stream_json_session.dart) and added a partial-streaming handler that ASSUMES claude emits incremental assistant events carrying partial:true sharing one message.id. That shape is UNVERIFIED: the 2.1.150 spike (docs/spikes/cc-stream-json-control-protocol-2.1.150.md) only documents non-partial assistant events (one per content block, no partial flag), and the flag''s --help says it ''only works with --print'' while clide runs the interactive control protocol (--input-format/--output-format stream-json, not --print). The handler degrades gracefully (unmatched shapes fall through to parseTranscriptChunk and are ignored — token streaming is inert but nothing breaks), and the T-168 fixtures are synthetic so they pass regardless of reality. Do: capture a real claude turn over the interactive stream-json transport with --include-partial-messages and record the actual partial event envelope (likely a stream_event content_block_delta, not assistant+partial:true). Then either (a) fix the handler in stream_json_session.dart::_onLine to match the real shape and add a fixture from the live capture, or (b) if the flag is a true no-op in the interactive (non-print) mode, drop --include-partial-messages from the argv and the dead partial-handler branch, and pin the finding in the spike doc. Either way, update the UNVERIFIED-WIRE-SHAPE comment in stream_json_session.dart and the spike. Acceptance: the partial path is validated against a live capture, the handler matches the real shape (or is removed), a fixture derived from the capture covers it, and the spike + code comment reflect the confirmed shape.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-30 11:42:53', '2026-05-31 10:17:09', NULL, '520d6f67b1305ab3db201ddb2d4ec813', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-185', 'task', 'T-164', 'Capture the forked session''s real claude session-id from its init event', 'T-172 follow-up. A forked session is spawned with --resume --fork-session and no --session-id, so claude assigns the branch a NEW session-id that only arrives in the stream-json init event. Today ManagedSession.sessionId for a fork holds a placeholder UUID (the orchestrator key), and the real claude session-id is never captured back. Consequence: the fork can''t itself be resumed/forked by id later, and any UI showing the session-id is wrong for forks. Do: capture session_id from the init/system event (StreamJsonSession already parses init for status — surface the session_id) and update the ManagedSession (make sessionId mutable or store a separate claudeSessionId that the orchestrator fills on init). Acceptance: after a fork''s first init event, the ManagedSession exposes the branch''s real claude session-id; a test drives an init event through a fake process and asserts the captured id. Surfaced by T-172; the fork argv/spawn/roster/original-unaffected behaviour already works without it.', 'done', 'low', NULL, NULL, 'D-77', '2026-05-30 21:34:26', '2026-05-31 10:22:02', NULL, '82468ac302541ae03717d48b71e36a1c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-164', 'epic', NULL, 'Claude via stream-json + unified clide-managed agent/session model', 'Per D-77: pivot the Claude pane from the interactive tmux TUI to Claude Code''s stream-json control protocol, and build a unified clide-managed agent/session model on top. +Refinement (2026-05-30, /whats-next): --fork-session CONFIRMED against the live binary (claude --help: "When resuming, create a new session ID instead of reusing the original (use with --resume or --continue)"). Fork argv = [''--resume'', , ''--fork-session''] (no --session-id; the new branch gets its own id from the init event). Add forkSourceSessionId to SpawnSpec (session_orchestrator.dart); build argv via a forkSessionArgs helper in session_naming.dart; add ''fork'' to kClideOwnedCommands + a case in claude_pane._send; add a fork button to _memberRow in claude_meta_sidebar.dart. SEQUENCE AFTER T-171 (shares _memberRow + orchestrator).', 'done', 'low', NULL, NULL, 'D-77', '2026-05-24 16:27:16', '2026-05-30 21:34:56', NULL, '7b902e0081c0ce980ba594c767edeb3e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6V6TKRPA106D5SMMM', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Team message inbox — broker chat (sidebar widget + poppable pane)', 'A team chat / message inbox: render the clide broker traffic as a chat-client timeline and make the user a first-class participant. (1) Timeline: every inter-agent message (send_message/broadcast) shown as chat rows with a colour-coded sender chip (tyre/gestalt/lead/you/...). (2) User as a broker participant: extend the T-170 broker so send_message(to: user) delivers into the inbox, and the team-awareness roster injected into each agent lists "you" as addressable; the user can post into the channel. (3) @-routing from the user: a recipient picker / @name parses to a broker route, @team (or no tag) broadcasts; reuse the slash-typeahead machinery for @-completion. (4) Interrupt tickbox on the composer: unchecked queues the message into the target next turn (normal broker delivery); checked sends an interrupt control_request to the target session (cancels its current turn) then delivers; default OFF. (5) One chat model, two surfaces: a compact sidebar widget in the cockpit (live feed + quick @-post) that pops out into a full chat pane. Wireframes: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png (widget) + 06-team-chat-pane.png (pane). Acceptance: broker messages render as chat; user posts route by @tag (one agent) or broadcast; interrupt tickbox cancels+delivers vs queues; widget and pane share one model; tests for routing + interrupt + render. Resolves the user-participation gaps from the team-mode screen analysis. Blocked by T-170 (broker).', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:15:58', '2026-05-31 08:49:07', NULL, '806fbf94bf043e95c44e353cae0847ee', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WE0SD9N4HCP9BB64', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Claude Config sidebar tab — skills/agents/commands/settings/permissions/hooks/MCP', 'The Config sub-tab (T-182): a browsable view of the Claude environment over ClaudeConfig (D-76 / T-151 — skills, commands, settings, permissions from ~/.claude + .claude, layered, local wins) plus the stream-json init/initialize payloads (agents, output styles, MCP servers, models, plugins). Layout: SETTINGS is a FIXED key->value table pinned at the top (not collapsible) — model, output style, permission mode, source. Everything variable-length is an EXPANDABLE accordion section showing the FULL list when expanded, never truncated (truncating to first-N + ellipsis falsely prioritises the first entries and makes the list pointless): SKILLS, AGENTS, COMMANDS, HOOKS, PERMISSIONS (grouped allow/ask/deny, colour-coded, each rule on its own line), MCP SERVERS — each with a count. File-backed entries are CLICKABLE: a skill (SKILL.md), agent (.claude/agents/*.md), or command (.claude/commands/*.md) — clicking fires an open-file action into the right-side markdown reader/editor pane (reusing the existing file-open plumbing, D-6 parity) to show what it says. Watched/live on .claude changes. Acceptance: settings render as a top table; every variable section expands to its complete list (no ellipsis); clicking a file-backed item opens its .md in the reader; permissions colour-coded by kind; widget tests for table + expand + click-to-open. Wireframe: docs/design/wireframes/claude-prompts/09-sidebar-config-tab.png. Blocked by T-182.', 'done', 'medium', NULL, NULL, 'D-76', '2026-05-25 09:26:40', '2026-05-31 09:30:11', NULL, '91685d0b5a2b70f90746cd126ec023f1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55DZN2RFEVP6CG48G', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Validate partial-message wire shape from --include-partial-messages against a live capture', 'T-168 follow-up. T-168 wired --include-partial-messages into the interactive stream-json session (stream_json_session.dart) and added a partial-streaming handler that ASSUMES claude emits incremental assistant events carrying partial:true sharing one message.id. That shape is UNVERIFIED: the 2.1.150 spike (docs/spikes/cc-stream-json-control-protocol-2.1.150.md) only documents non-partial assistant events (one per content block, no partial flag), and the flag''s --help says it ''only works with --print'' while clide runs the interactive control protocol (--input-format/--output-format stream-json, not --print). The handler degrades gracefully (unmatched shapes fall through to parseTranscriptChunk and are ignored — token streaming is inert but nothing breaks), and the T-168 fixtures are synthetic so they pass regardless of reality. Do: capture a real claude turn over the interactive stream-json transport with --include-partial-messages and record the actual partial event envelope (likely a stream_event content_block_delta, not assistant+partial:true). Then either (a) fix the handler in stream_json_session.dart::_onLine to match the real shape and add a fixture from the live capture, or (b) if the flag is a true no-op in the interactive (non-print) mode, drop --include-partial-messages from the argv and the dead partial-handler branch, and pin the finding in the spike doc. Either way, update the UNVERIFIED-WIRE-SHAPE comment in stream_json_session.dart and the spike. Acceptance: the partial path is validated against a live capture, the handler matches the real shape (or is removed), a fixture derived from the capture covers it, and the spike + code comment reflect the confirmed shape.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-30 11:42:53', '2026-05-31 10:17:09', NULL, '460a2c17c6509d145b8ee8870347ec23', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM52PRNJM86A1DZTQ1C', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Capture the forked session''s real claude session-id from its init event', 'T-172 follow-up. A forked session is spawned with --resume --fork-session and no --session-id, so claude assigns the branch a NEW session-id that only arrives in the stream-json init event. Today ManagedSession.sessionId for a fork holds a placeholder UUID (the orchestrator key), and the real claude session-id is never captured back. Consequence: the fork can''t itself be resumed/forked by id later, and any UI showing the session-id is wrong for forks. Do: capture session_id from the init/system event (StreamJsonSession already parses init for status — surface the session_id) and update the ManagedSession (make sessionId mutable or store a separate claudeSessionId that the orchestrator fills on init). Acceptance: after a fork''s first init event, the ManagedSession exposes the branch''s real claude session-id; a test drives an init event through a fake process and asserts the captured id. Surfaced by T-172; the fork argv/spawn/roster/original-unaffected behaviour already works without it.', 'done', 'low', NULL, NULL, 'D-77', '2026-05-30 21:34:26', '2026-05-31 10:22:02', NULL, '3286b15f285b1297144a350967eca43c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5HM3N07VB3RPFJCFM', 'epic', NULL, 'Claude via stream-json + unified clide-managed agent/session model', 'Per D-77: pivot the Claude pane from the interactive tmux TUI to Claude Code''s stream-json control protocol, and build a unified clide-managed agent/session model on top. Why: the interactive TUI can''t surface AskUserQuestion / permission prompts (they never reach the transcript), transcript-file tailing is lossy + caused the session-fork/--session-id bug class, and Claude''s agent-team mode is tmux/interactive-only. stream-json fixes all three: structured events, native canUseTool prompt handling, --resume persistence — and lets clide OWN orchestration (teammate / secondary tab / forked branch / inline subagent all collapse into one primitive: a clide-managed session rendered as a pane; the sidebar becomes its cockpit). Phase 1 (single-agent, ships the prompt-gap + lifecycle fix): stream-json transport, native permission/AskUserQuestion UI, --resume persistence, leveled-up native rendering. Phase 2 (unified model): session orchestrator (spawn/show/hide), clide-hosted MCP coordination broker + injected team-awareness, sidebar cockpit (tasks + messages + roster controls), fork-into-a-pane. -Supersedes the transcript-tail + observe-tmux-team approach of epic T-132. Acceptance: Claude runs over stream-json with native prompts + --resume; teams are clide-orchestrated managed sessions controlled from the sidebar; the tmux dependency for Claude is gone (terminal builtin retained for general shell).', 'done', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:05', '2026-05-31 10:22:03', NULL, 'dcad2cbbc0c36eab5c9787ed5fb30be1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-95', 'bug', NULL, 'TerminalGestureHandler tertiary-tap wired to secondary; middle-button mis-typed', '**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). +Supersedes the transcript-tail + observe-tmux-team approach of epic T-132. Acceptance: Claude runs over stream-json with native prompts + --resume; teams are clide-orchestrated managed sessions controlled from the sidebar; the tmux dependency for Claude is gone (terminal builtin retained for general shell).', 'done', 'high', NULL, NULL, 'D-77', '2026-05-24 16:26:05', '2026-05-31 10:22:03', NULL, 'aeda893b3e33e74a3f68444835957b45', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TDSA2PDCV814E3YW', 'bug', NULL, 'TerminalGestureHandler tertiary-tap wired to secondary; middle-button mis-typed', '**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). **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. @@ -2936,54 +1071,20 @@ The `PanGestureRecognizer` in `TerminalGestureDetector` is registered with `supp **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. **Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic). -', 'done', 'medium', NULL, NULL, NULL, '2026-05-08 10:48:59', '2026-05-31 11:20:21', NULL, 'e4f5f6de2aab279291c19bebe5f57224', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-186', 'epic', NULL, 'Sidebar readers: fix message-bus open + add back/forward + pin', 'The right-side sidebar READERS — the markdown reader (builtin.markdown, lib/builtin/markdown/src/markdown_viewer.dart) and the decision reader (builtin.decisions, lib/builtin/decisions/src/decision_detail_view.dart) — are separate from the editor and open via the kernel MessageBus, the same pattern as the still-working ticket panel (lib/builtin/tickets): a click publishes (publisher, channel ''selection''); an extension activates the context-panel tab; the reader widget subscribes and renders. Two open paths are currently broken and we also want reading-navigation (back/forward) plus a single-slot pin on BOTH readers. Grouped as one set because all four tickets touch the same two reader widgets. Root-cause investigation done 2026-05-31 (see child tickets).', 'backlog', 'high', NULL, NULL, NULL, '2026-05-31 11:37:34', '2026-05-31 11:37:34', NULL, '7f69e3d9e0377bb4ed300dc537e8411d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-189', 'story', 'T-186', 'Back/forward navigation on the markdown & decision readers', 'Add Back/Forward buttons to both sidebar readers so you can return to what you were reading. Neither reader keeps history today: _MarkdownViewerState (markdown_viewer.dart) holds only _path/_content; _DecisionDetailViewState (decision_detail_view.dart) holds only _decision. Add a history stack + index to each, pushing on each new open; Back/Forward walk the stack and load the entry WITHOUT re-publishing a ''selection'' (in-widget navigation). For the decision reader, back/forward must not trigger the extension tab re-contribution — coordinate with the decision-open bug fix (T-188), which should stop re-contributing per selection. Custom chrome only (no Material). Acceptance: Back/Forward controls on both readers walk reading history and disable at the ends; widget tests. Sequence after the reader-open bug fixes (T-187/T-188) since it shares those widgets.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 11:38:36', '2026-05-31 11:38:36', NULL, 'ca447bc78eac102564ab57c097a562fb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-190', 'story', 'T-186', 'Pin a doc in the markdown & decision readers (single-slot quick-return)', 'Add a one-click PIN to both sidebar readers. Semantics per the reporter: a SINGLE pin slot per reader — clicking pin remembers the CURRENT doc; setting a new pin REPLACES the previous one (no list to manage); the pinned doc is always one click away to return to, so it is easy to use inline as you work. It is a quick-return bookmark, NOT a freeze-incoming toggle. Add a pinned-doc ref to _MarkdownViewerState and _DecisionDetailViewState; a pin button (set/replace) plus a jump-to-pin affordance in the reader chrome; persist for at least the session. Custom chrome only (no Material). Acceptance: pinning a doc lets you one-click return to it later; pinning another replaces it; works on both readers; widget tests. Sequence after the reader-open bug fixes (T-187/T-188); pairs naturally with back/forward (T-189).', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 11:38:42', '2026-05-31 11:38:42', NULL, 'dc5690ae5f2916336ceb3ec4d112bf7b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-188', 'bug', 'T-186', 'Decision reader no longer opens on click', 'Clicking a decision (in the decisions list, or a D-ref from a ticket/markdown) no longer opens it in the decision reader. The channels are CORRECT — publishers emit (''builtin.decisions'',''selection'') and DecisionDetailView subscribes to the same (decision_detail_view.dart ~line 26). Suspected cause (confirm at fix time): DecisionsExtension.activate (decisions/src/extension.dart ~23-37) tears down and re-contributes the decisions.detail tab on EVERY selection (uncontribute then contribute with initialId), and the context-panel slot may not be surfaced (setVisible/setCollapsed not always succeeding) — unlike the still-working ticket panel, which uses a STATIC tab + activateTab + load (ticket_detail_controller.dart). Fix direction: align the decision reader with the ticket panel pattern (static tab; activateTab + load on selection; no per-click re-contribution) and ensure the context panel is revealed. Acceptance: clicking a decision anywhere reliably opens it in the decision reader, including same-decision-twice and rapid-sequential clicks; widget test.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-31 11:38:28', '2026-05-31 11:42:11', NULL, '64083356b2ba4b989b8c1e902c438316', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-187', 'bug', 'T-186', 'Markdown reader no longer opens on click (files panel, Claude Config tab, wiki links)', 'Clicking a .md no longer opens it in the right-side markdown reader; only the pql markdown tab still works (it is the one surface that correctly publishes (''builtin.markdown'',''selection'')). Broken surfaces: the files panel (file_tree_view.dart _openFile / _FilteredFileRow, ~lines 213-222 and 284-285) and the Claude Config tab (claude_meta_sidebar.dart _configFileRow, ~553-556, shipped in T-183) both call ipc.request(''editor.open'',...) which targets the EDITOR, not the reader; the in-markdown wiki-link handler (markdown_viewer.dart _navigateToRecord) does not handle .md links; and a dead fallback subscribes to DaemonEvent kind ''editor.buffer_activated'' which the EditorRegistry never emits (it emits ''editor.active-changed''). Working pattern: publish (''builtin.markdown'',''selection'') -> the markdown extension activates the context-panel tab and bridges to channel ''load'' after mount -> MarkdownViewer renders. Fix: route .md clicks from the files panel, the Claude Config tab, and .md wiki links to publish (''builtin.markdown'',''selection''); fix or remove the dead editor-event fallback. Design note: per the reporter, clicking a .md should open the READER (non-.md files stay in the editor). Regression: files panel at 681ce3b (2026-04-22); Config tab born-broken at 494a6f1 (T-183). Acceptance: clicking a .md in the files panel, the Claude Config tab, and a wiki .md link each open it in the markdown reader; the dead fallback is gone; widget tests.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-31 11:38:20', '2026-05-31 11:42:11', NULL, '9b3f89015a2fd3d29f0ed0b34bcf369f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-162', 'bug', 'T-132', 'Slash typeahead omits clide-owned commands (/resume)', '/resume works when typed but doesn''t appear in the composer slash typeahead. Cause: the typeahead sources its command list from activeClaudeConfig.slashCommands (the CLI stream-json probe), which does not include ''resume'' (it''s not an in-session slash command the CLI advertises; clide owns/intercepts it). /clear appears because ''clear'' IS in the probe list. Fix: union kClideOwnedCommands (slash_commands.dart) into the typeahead''s command source — e.g. in ClaudeComposer''s default slashCommandsResolver return {...?activeClaudeConfig?.slashCommands, ...kClideOwnedCommands}. Acceptance: typing /res suggests /resume; /clear still suggested; widget test asserting clide-owned commands appear even when absent from the probe list. Follow-up to T-152/T-156.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-24 09:29:07', '2026-05-31 11:42:11', NULL, 'b50d70ae11b092f80652fb4dcf2e36b6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-191', 'story', 'T-186', 'Edit pencil in the reader action bar — open the current doc in the editor', 'Since clicking a .md now opens the READER (T-187), give the user a deliberate one-click way to open the SAME doc in the editor in edit mode. Add an edit pencil to the action bar of BOTH sidebar readers (markdown reader, decision reader). The pencil fires the editor path the readers moved away from for plain clicks: kernel.ipc.request(''editor.open'', {path}) on the reader''s current doc. Markdown reader: its current file path. Decision reader: the D-record''s backing file (decisions expose file_path = governance/decisions/.md via pql decisions read; resolve and open that — note it opens the whole domain file, since a decision is a section within it, unless we later add anchor support). Lives in the same reader chrome as back/forward (T-189) and pin (T-190). Custom chrome only (no Material). Acceptance: a pencil button in each reader opens the currently-shown doc in the editor; widget tests. Sequence with the other reader-chrome work (Wave B).', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 11:58:19', '2026-05-31 11:58:19', NULL, '942200e1435cac298f011e2e3973a0c8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-192', 'task', NULL, 'Speed up the test gate: dedupe the a11y run + decouple coverage from the dev loop', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow. - -Add a per-test timeout to the gate''s flutter test (e.g. ci/test.sh: ''flutter test --coverage --timeout 60s''). Motivation discovered 2026-05-31: a hung pumpAndSettle has a 10-MINUTE default timeout, so a few hanging widget tests wedge make push-check for 30+ minutes — which is almost certainly why a MacBook session resorted to ''git push --no-verify'' (the gate never returned; it was not disabled, it was hung). A tight per-test timeout makes any future hang fail fast (60s) instead of wedging a pre-push, so the gate stays usable and nobody is tempted to bypass it. Pair with: keep writing pump()-bounded tests instead of pumpAndSettle() where a view has overlapping async loads (see the T-188 decision_reader_test hang).', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 12:08:42', '2026-05-31 12:31:58', NULL, '015fe328db1b9ba7193301a809129dc6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-188', 'bug', 'T-186', 'Decision reader no longer opens on click', 'Clicking a decision (in the decisions list, or a D-ref from a ticket/markdown) no longer opens it in the decision reader. The channels are CORRECT — publishers emit (''builtin.decisions'',''selection'') and DecisionDetailView subscribes to the same (decision_detail_view.dart ~line 26). Suspected cause (confirm at fix time): DecisionsExtension.activate (decisions/src/extension.dart ~23-37) tears down and re-contributes the decisions.detail tab on EVERY selection (uncontribute then contribute with initialId), and the context-panel slot may not be surfaced (setVisible/setCollapsed not always succeeding) — unlike the still-working ticket panel, which uses a STATIC tab + activateTab + load (ticket_detail_controller.dart). Fix direction: align the decision reader with the ticket panel pattern (static tab; activateTab + load on selection; no per-click re-contribution) and ensure the context panel is revealed. Acceptance: clicking a decision anywhere reliably opens it in the decision reader, including same-decision-twice and rapid-sequential clicks; widget test.', 'done', 'high', NULL, NULL, NULL, '2026-05-31 11:38:28', '2026-05-31 14:17:22', NULL, '0cffcc7074b9b3aee5b4404ad9077c96', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-187', 'bug', 'T-186', 'Markdown reader no longer opens on click (files panel, Claude Config tab, wiki links)', 'Clicking a .md no longer opens it in the right-side markdown reader; only the pql markdown tab still works (it is the one surface that correctly publishes (''builtin.markdown'',''selection'')). Broken surfaces: the files panel (file_tree_view.dart _openFile / _FilteredFileRow, ~lines 213-222 and 284-285) and the Claude Config tab (claude_meta_sidebar.dart _configFileRow, ~553-556, shipped in T-183) both call ipc.request(''editor.open'',...) which targets the EDITOR, not the reader; the in-markdown wiki-link handler (markdown_viewer.dart _navigateToRecord) does not handle .md links; and a dead fallback subscribes to DaemonEvent kind ''editor.buffer_activated'' which the EditorRegistry never emits (it emits ''editor.active-changed''). Working pattern: publish (''builtin.markdown'',''selection'') -> the markdown extension activates the context-panel tab and bridges to channel ''load'' after mount -> MarkdownViewer renders. Fix: route .md clicks from the files panel, the Claude Config tab, and .md wiki links to publish (''builtin.markdown'',''selection''); fix or remove the dead editor-event fallback. Design note: per the reporter, clicking a .md should open the READER (non-.md files stay in the editor). Regression: files panel at 681ce3b (2026-04-22); Config tab born-broken at 494a6f1 (T-183). Acceptance: clicking a .md in the files panel, the Claude Config tab, and a wiki .md link each open it in the markdown reader; the dead fallback is gone; widget tests.', 'done', 'high', NULL, NULL, NULL, '2026-05-31 11:38:20', '2026-05-31 14:17:22', NULL, '40eb07a9d378b5a067fcc9488dad3bb9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-162', 'bug', 'T-132', 'Slash typeahead omits clide-owned commands (/resume)', '/resume works when typed but doesn''t appear in the composer slash typeahead. Cause: the typeahead sources its command list from activeClaudeConfig.slashCommands (the CLI stream-json probe), which does not include ''resume'' (it''s not an in-session slash command the CLI advertises; clide owns/intercepts it). /clear appears because ''clear'' IS in the probe list. Fix: union kClideOwnedCommands (slash_commands.dart) into the typeahead''s command source — e.g. in ClaudeComposer''s default slashCommandsResolver return {...?activeClaudeConfig?.slashCommands, ...kClideOwnedCommands}. Acceptance: typing /res suggests /resume; /clear still suggested; widget test asserting clide-owned commands appear even when absent from the probe list. Follow-up to T-152/T-156.', 'done', 'low', NULL, NULL, NULL, '2026-05-24 09:29:07', '2026-05-31 14:17:22', NULL, '7eb767d4a8271738e418f6bddd7425bb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-192', 'task', NULL, 'Speed up the test gate: dedupe the a11y run + decouple coverage from the dev loop', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow. +', 'done', 'medium', NULL, NULL, NULL, '2026-05-08 10:48:59', '2026-05-31 11:20:21', NULL, '6b80c66853b9ba73dff37b608e0a62bb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM514YJBF8AY8Q4N7Q4', 'bug', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Decision reader no longer opens on click', 'Clicking a decision (in the decisions list, or a D-ref from a ticket/markdown) no longer opens it in the decision reader. The channels are CORRECT — publishers emit (''builtin.decisions'',''selection'') and DecisionDetailView subscribes to the same (decision_detail_view.dart ~line 26). Suspected cause (confirm at fix time): DecisionsExtension.activate (decisions/src/extension.dart ~23-37) tears down and re-contributes the decisions.detail tab on EVERY selection (uncontribute then contribute with initialId), and the context-panel slot may not be surfaced (setVisible/setCollapsed not always succeeding) — unlike the still-working ticket panel, which uses a STATIC tab + activateTab + load (ticket_detail_controller.dart). Fix direction: align the decision reader with the ticket panel pattern (static tab; activateTab + load on selection; no per-click re-contribution) and ensure the context panel is revealed. Acceptance: clicking a decision anywhere reliably opens it in the decision reader, including same-decision-twice and rapid-sequential clicks; widget test.', 'done', 'high', NULL, NULL, NULL, '2026-05-31 11:38:28', '2026-05-31 14:17:22', NULL, '9b578034685a5745837ef90a1463e874', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5316HVGDYD2W0DC50', 'bug', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Slash typeahead omits clide-owned commands (/resume)', '/resume works when typed but doesn''t appear in the composer slash typeahead. Cause: the typeahead sources its command list from activeClaudeConfig.slashCommands (the CLI stream-json probe), which does not include ''resume'' (it''s not an in-session slash command the CLI advertises; clide owns/intercepts it). /clear appears because ''clear'' IS in the probe list. Fix: union kClideOwnedCommands (slash_commands.dart) into the typeahead''s command source — e.g. in ClaudeComposer''s default slashCommandsResolver return {...?activeClaudeConfig?.slashCommands, ...kClideOwnedCommands}. Acceptance: typing /res suggests /resume; /clear still suggested; widget test asserting clide-owned commands appear even when absent from the probe list. Follow-up to T-152/T-156.', 'done', 'low', NULL, NULL, NULL, '2026-05-24 09:29:07', '2026-05-31 14:17:22', NULL, 'db998b28efb4e9facbe279d05db65277', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GVWHMRTMQYRTRKD4', 'bug', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Markdown reader no longer opens on click (files panel, Claude Config tab, wiki links)', 'Clicking a .md no longer opens it in the right-side markdown reader; only the pql markdown tab still works (it is the one surface that correctly publishes (''builtin.markdown'',''selection'')). Broken surfaces: the files panel (file_tree_view.dart _openFile / _FilteredFileRow, ~lines 213-222 and 284-285) and the Claude Config tab (claude_meta_sidebar.dart _configFileRow, ~553-556, shipped in T-183) both call ipc.request(''editor.open'',...) which targets the EDITOR, not the reader; the in-markdown wiki-link handler (markdown_viewer.dart _navigateToRecord) does not handle .md links; and a dead fallback subscribes to DaemonEvent kind ''editor.buffer_activated'' which the EditorRegistry never emits (it emits ''editor.active-changed''). Working pattern: publish (''builtin.markdown'',''selection'') -> the markdown extension activates the context-panel tab and bridges to channel ''load'' after mount -> MarkdownViewer renders. Fix: route .md clicks from the files panel, the Claude Config tab, and .md wiki links to publish (''builtin.markdown'',''selection''); fix or remove the dead editor-event fallback. Design note: per the reporter, clicking a .md should open the READER (non-.md files stay in the editor). Regression: files panel at 681ce3b (2026-04-22); Config tab born-broken at 494a6f1 (T-183). Acceptance: clicking a .md in the files panel, the Claude Config tab, and a wiki .md link each open it in the markdown reader; the dead fallback is gone; widget tests.', 'done', 'high', NULL, NULL, NULL, '2026-05-31 11:38:20', '2026-05-31 14:17:22', NULL, 'f47a85905c0df0b4b26b0ee1606fad27', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4DAQ2CEFESG9JGMZ8', 'task', NULL, 'Speed up the test gate: dedupe the a11y run + decouple coverage from the dev loop', 'The pre-push gate (make push-check) and the inner-loop make test are slow; cost is dominated by ''flutter test --coverage --exclude-tags pty'' running the whole flutter suite WITH coverage instrumentation (ci/test.sh). Two concrete levers found by reading the pipeline: (1) FREE WIN — a11y tests run TWICE in push-check: the coverage run in ci/test.sh has no path filter so it already includes test/a11y/, then ci/test_a11y.sh (''flutter test test/a11y/'') runs them again in a separate flutter invocation, chained AFTER test (so not even fast-fail). Drop test-a11y from the push-check chain (keep ''make test-a11y'' as a standalone dev target) to remove a whole redundant flutter compile+boot+run; same tests still execute under test. (2) Coverage is on the dev inner loop — make test always runs --coverage, which is only needed where coverage-gate reads lcov (push-check/CI). Split a fast no-coverage ''make test'' for the edit loop from a coverage run used by push-check/CI; the script even claims <60s warm but coverage blows past that. MEASURE FIRST (cheap): time each ci/*.sh step, check ''flutter test --concurrency'' vs nproc, and look at per-file timing for golden/alchemist tests before further changes. Acceptance: dev ''make test'' runs without coverage and is meaningfully faster; push-check no longer double-runs the a11y suite; coverage is still gated (coverage-gate unchanged, floor 95); all gates stay green; the fast-vs-coverage split is documented in the Makefile/CONTRIBUTING. Domain: tooling/CI. Raised 2026-05-31 while the push gate kept interrupting flow. Add a per-test timeout to the gate''s flutter test (e.g. ci/test.sh: ''flutter test --coverage --timeout 60s''). Motivation discovered 2026-05-31: a hung pumpAndSettle has a 10-MINUTE default timeout, so a few hanging widget tests wedge make push-check for 30+ minutes — which is almost certainly why a MacBook session resorted to ''git push --no-verify'' (the gate never returned; it was not disabled, it was hung). A tight per-test timeout makes any future hang fail fast (60s) instead of wedging a pre-push, so the gate stays usable and nobody is tempted to bypass it. Pair with: keep writing pump()-bounded tests instead of pumpAndSettle() where a view has overlapping async loads (see the T-188 decision_reader_test hang). -Measured 2026-05-31 (16-core box, warm cache, full flutter suite --exclude-tags pty): coverage default-concurrency = ~36s; coverage --concurrency=12 = ~37s (NO improvement — coverage runs are concurrency-insensitive, the instrumentation/collection dominates); NO-coverage --concurrency=12 = ~21s. Conclusions: (1) Concurrency only helps the NON-coverage path — do NOT bother adding --concurrency to the coverage gate, it buys nothing. (2) The dev inner-loop win is concrete: a no-coverage ''make test'' with --concurrency=12 runs ~21s vs ~36s today (~40% faster) — this is how we ''beat 40s'' for the edit loop. (3) The push-check coverage run is floored at ~36s by coverage itself; getting below that needs a different coverage approach (package:coverage via VM service, or coverage-on-changed-files) — out of scope for the quick win. So the actionable set is unchanged: no-coverage+concurrency dev target, coverage-only-in-push, drop the redundant a11y pass, add a per-test --timeout for hang-safety.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 12:08:42', '2026-05-31 14:46:39', NULL, 'baf9357d919ab245d6e939737a44695a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-193', 'bug', NULL, 'Flaky: transcript_publisher_test ''republishes reader items onto the bus'' fails under the full parallel run', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 16:12:42', '2026-05-31 16:12:42', NULL, '2ef560fd740561a887c132e80b32e27d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-191', 'story', 'T-186', 'Edit pencil in the reader action bar — open the current doc in the editor', 'Since clicking a .md now opens the READER (T-187), give the user a deliberate one-click way to open the SAME doc in the editor in edit mode. Add an edit pencil to the action bar of BOTH sidebar readers (markdown reader, decision reader). The pencil fires the editor path the readers moved away from for plain clicks: kernel.ipc.request(''editor.open'', {path}) on the reader''s current doc. Markdown reader: its current file path. Decision reader: the D-record''s backing file (decisions expose file_path = governance/decisions/.md via pql decisions read; resolve and open that — note it opens the whole domain file, since a decision is a section within it, unless we later add anchor support). Lives in the same reader chrome as back/forward (T-189) and pin (T-190). Custom chrome only (no Material). Acceptance: a pencil button in each reader opens the currently-shown doc in the editor; widget tests. Sequence with the other reader-chrome work (Wave B).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-31 11:58:19', '2026-05-31 16:13:39', NULL, '46ee324c0653868dcdd3f20ab6e21384', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-189', 'story', 'T-186', 'Back/forward navigation on the markdown & decision readers', 'Add Back/Forward buttons to both sidebar readers so you can return to what you were reading. Neither reader keeps history today: _MarkdownViewerState (markdown_viewer.dart) holds only _path/_content; _DecisionDetailViewState (decision_detail_view.dart) holds only _decision. Add a history stack + index to each, pushing on each new open; Back/Forward walk the stack and load the entry WITHOUT re-publishing a ''selection'' (in-widget navigation). For the decision reader, back/forward must not trigger the extension tab re-contribution — coordinate with the decision-open bug fix (T-188), which should stop re-contributing per selection. Custom chrome only (no Material). Acceptance: Back/Forward controls on both readers walk reading history and disable at the ends; widget tests. Sequence after the reader-open bug fixes (T-187/T-188) since it shares those widgets.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-31 11:38:36', '2026-05-31 16:13:39', NULL, '8e8e53cd7d4bb35535281c433e8d122d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-190', 'story', 'T-186', 'Pin a doc in the markdown & decision readers (single-slot quick-return)', 'Add a one-click PIN to both sidebar readers. Semantics per the reporter: a SINGLE pin slot per reader — clicking pin remembers the CURRENT doc; setting a new pin REPLACES the previous one (no list to manage); the pinned doc is always one click away to return to, so it is easy to use inline as you work. It is a quick-return bookmark, NOT a freeze-incoming toggle. Add a pinned-doc ref to _MarkdownViewerState and _DecisionDetailViewState; a pin button (set/replace) plus a jump-to-pin affordance in the reader chrome; persist for at least the session. Custom chrome only (no Material). Acceptance: pinning a doc lets you one-click return to it later; pinning another replaces it; works on both readers; widget tests. Sequence after the reader-open bug fixes (T-187/T-188); pairs naturally with back/forward (T-189).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-31 11:38:42', '2026-05-31 16:13:39', NULL, 'd0ec78c056f0d7f8e35e521f13d229ed', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-186', 'epic', NULL, 'Sidebar readers: fix message-bus open + add back/forward + pin', 'The right-side sidebar READERS — the markdown reader (builtin.markdown, lib/builtin/markdown/src/markdown_viewer.dart) and the decision reader (builtin.decisions, lib/builtin/decisions/src/decision_detail_view.dart) — are separate from the editor and open via the kernel MessageBus, the same pattern as the still-working ticket panel (lib/builtin/tickets): a click publishes (publisher, channel ''selection''); an extension activates the context-panel tab; the reader widget subscribes and renders. Two open paths are currently broken and we also want reading-navigation (back/forward) plus a single-slot pin on BOTH readers. Grouped as one set because all four tickets touch the same two reader widgets. Root-cause investigation done 2026-05-31 (see child tickets).', 'done', 'high', NULL, NULL, NULL, '2026-05-31 11:37:34', '2026-05-31 16:40:19', NULL, 'f32bbd391b6fd1d8448af994fae28a13', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-193', 'bug', NULL, 'Flaky: transcript_publisher_test ''republishes reader items onto the bus'' fails under the full parallel run', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified. +Measured 2026-05-31 (16-core box, warm cache, full flutter suite --exclude-tags pty): coverage default-concurrency = ~36s; coverage --concurrency=12 = ~37s (NO improvement — coverage runs are concurrency-insensitive, the instrumentation/collection dominates); NO-coverage --concurrency=12 = ~21s. Conclusions: (1) Concurrency only helps the NON-coverage path — do NOT bother adding --concurrency to the coverage gate, it buys nothing. (2) The dev inner-loop win is concrete: a no-coverage ''make test'' with --concurrency=12 runs ~21s vs ~36s today (~40% faster) — this is how we ''beat 40s'' for the edit loop. (3) The push-check coverage run is floored at ~36s by coverage itself; getting below that needs a different coverage approach (package:coverage via VM service, or coverage-on-changed-files) — out of scope for the quick win. So the actionable set is unchanged: no-coverage+concurrency dev target, coverage-only-in-push, drop the redundant a11y pass, add a per-test --timeout for hang-safety.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 12:08:42', '2026-05-31 14:46:39', NULL, '650abf0ff970502eb94e4780b6d69158', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6S7FDAXQX32JNCSFG', 'epic', NULL, 'Sidebar readers: fix message-bus open + add back/forward + pin', 'The right-side sidebar READERS — the markdown reader (builtin.markdown, lib/builtin/markdown/src/markdown_viewer.dart) and the decision reader (builtin.decisions, lib/builtin/decisions/src/decision_detail_view.dart) — are separate from the editor and open via the kernel MessageBus, the same pattern as the still-working ticket panel (lib/builtin/tickets): a click publishes (publisher, channel ''selection''); an extension activates the context-panel tab; the reader widget subscribes and renders. Two open paths are currently broken and we also want reading-navigation (back/forward) plus a single-slot pin on BOTH readers. Grouped as one set because all four tickets touch the same two reader widgets. Root-cause investigation done 2026-05-31 (see child tickets).', 'done', 'high', NULL, NULL, NULL, '2026-05-31 11:37:34', '2026-05-31 16:40:19', NULL, '642752efce8c4d5223236fc6f0e68116', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM709YPWECH1E66XXD4', 'bug', NULL, 'Flaky: transcript_publisher_test ''republishes reader items onto the bus'' fails under the full parallel run', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified. -MECHANISM (the systemic answer, per discussion): tests are parallel by default (--concurrency global = invisible parallel=true); express ''parallel=false'' for a vulnerable test via a TAG + a serial pass. The ''pty'' tag is the half-built precedent: it splits PTY tests into a separate ''dart test'' pass but did NOT serialize it (and leaned on retry:2). DONE 2026-05-31: that pty pass now runs --concurrency=1 and the retry:2 band-aids are removed — PTY/registry flake verified stable 4/4 runs. REMAINING: generalize to a ''serial'' tag for FLUTTER-side vulnerable tests (transcript_publisher''s ''republishes reader items onto the bus''): declare ''serial'' in dart_test.yaml; the parallel flutter run adds --exclude-tags serial; add a serial flutter pass (flutter test --tags serial --concurrency=1). WRINKLE for the gate: two flutter --coverage passes can''t just concatenate lcov (duplicate SF records double-count) — must real-merge (package:coverage format_coverage / lcov -a), OR run the serial pass without coverage if those tests'' source lines are covered elsewhere (check transcript_publisher.dart coverage from other tests first). ALTERNATIVE for the single transcript_publisher case: fix its await to be deterministic (await the bus delivery / expectLater on the stream) rather than quarantine — cheaper if it''s a test-timing bug vs a real bus race. Acceptance: no flaky test runs in the parallel pool; vulnerable tests are tagged + run serially; gate is deterministic across repeated full runs.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 16:12:42', '2026-05-31 17:01:58', NULL, 'a53bd052ae3024be526c4a6d54da23ef', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-193', 'bug', NULL, 'Flaky: transcript_publisher_test ''republishes reader items onto the bus'' fails under the full parallel run', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified. - -MECHANISM (the systemic answer, per discussion): tests are parallel by default (--concurrency global = invisible parallel=true); express ''parallel=false'' for a vulnerable test via a TAG + a serial pass. The ''pty'' tag is the half-built precedent: it splits PTY tests into a separate ''dart test'' pass but did NOT serialize it (and leaned on retry:2). DONE 2026-05-31: that pty pass now runs --concurrency=1 and the retry:2 band-aids are removed — PTY/registry flake verified stable 4/4 runs. REMAINING: generalize to a ''serial'' tag for FLUTTER-side vulnerable tests (transcript_publisher''s ''republishes reader items onto the bus''): declare ''serial'' in dart_test.yaml; the parallel flutter run adds --exclude-tags serial; add a serial flutter pass (flutter test --tags serial --concurrency=1). WRINKLE for the gate: two flutter --coverage passes can''t just concatenate lcov (duplicate SF records double-count) — must real-merge (package:coverage format_coverage / lcov -a), OR run the serial pass without coverage if those tests'' source lines are covered elsewhere (check transcript_publisher.dart coverage from other tests first). ALTERNATIVE for the single transcript_publisher case: fix its await to be deterministic (await the bus delivery / expectLater on the stream) rather than quarantine — cheaper if it''s a test-timing bug vs a real bus race. Acceptance: no flaky test runs in the parallel pool; vulnerable tests are tagged + run serially; gate is deterministic across repeated full runs.', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 16:12:42', '2026-05-31 17:26:25', NULL, 'f7daf684c390b57a474aadb2fcf6f532', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'story', NULL, 'search and replace across files', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config. - -Refinement (2026-05-31): - -Depends on T-52 — consumes its match model (path, line, start/end offset per match, capture groups) as the replacement input. Sequence after T-52 (T-52 blocks T-53). - -Write path — no files.write IPC exists today (only files.root/read/ls/watch; the sole write path is EditorRegistry.save on an open buffer). Add a `files.write` verb gated by resolveUnderRootFollowingSymlinks (lib/src/files/path_safety.dart). Do NOT route through the editor (would pollute the open-buffer list with dozens of temp buffers). - -Regex capture groups — Dart replaceAllMapped / $1; the T-52 engine must surface match groups. - -Preview — the existing diff view (lib/builtin/diff) is git-only (hardwired to git.diff). Build a ReplacementPreviewController/view that reuses the diff RENDER primitives (DiffLine / _HunkView styling) fed a computed in-memory before/after set. Do NOT generalize/entangle the git DiffController. - -Safety (user decision) — REQUIRE A CLEAN GIT WORKING TREE before apply: refuse with "commit or stash your changes first" if there are unstaged changes, making git the lossless undo layer. Preview shown first; final confirmation via the existing DialogRouter (as used by git discard-confirm). - -Ignore — same full ignore_files: layering as T-52 (D-4). - -Files: lib/src/daemon/files_commands.dart (files.write); new replacement-preview controller/view under lib/builtin/search/; reuse path_safety + diff render primitives; DialogRouter confirm; IPC schema + CLI parity verbs.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:01:14', NULL, '8d258948527bb868dbd6c5b54038297b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-51', 'story', NULL, 'quick file open (Ctrl+P)', '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. +MECHANISM (the systemic answer, per discussion): tests are parallel by default (--concurrency global = invisible parallel=true); express ''parallel=false'' for a vulnerable test via a TAG + a serial pass. The ''pty'' tag is the half-built precedent: it splits PTY tests into a separate ''dart test'' pass but did NOT serialize it (and leaned on retry:2). DONE 2026-05-31: that pty pass now runs --concurrency=1 and the retry:2 band-aids are removed — PTY/registry flake verified stable 4/4 runs. REMAINING: generalize to a ''serial'' tag for FLUTTER-side vulnerable tests (transcript_publisher''s ''republishes reader items onto the bus''): declare ''serial'' in dart_test.yaml; the parallel flutter run adds --exclude-tags serial; add a serial flutter pass (flutter test --tags serial --concurrency=1). WRINKLE for the gate: two flutter --coverage passes can''t just concatenate lcov (duplicate SF records double-count) — must real-merge (package:coverage format_coverage / lcov -a), OR run the serial pass without coverage if those tests'' source lines are covered elsewhere (check transcript_publisher.dart coverage from other tests first). ALTERNATIVE for the single transcript_publisher case: fix its await to be deterministic (await the bus delivery / expectLater on the stream) rather than quarantine — cheaper if it''s a test-timing bug vs a real bus race. Acceptance: no flaky test runs in the parallel pool; vulnerable tests are tagged + run serially; gate is deterministic across repeated full runs.', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 16:12:42', '2026-05-31 17:26:25', NULL, '90c3ac0bae00567816b15cf6a2c54167', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QPGVP54EKJPCNQQM', 'story', NULL, 'quick file open (Ctrl+P)', '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. Refinement (2026-05-31): @@ -2995,8 +1096,8 @@ Recent files — none exists today (ProjectManager.recents tracks projects, not Overlay mirrors the ClidePalette chrome and reuses the 200ms-debounced ClideFilterBox. New QuickOpenIntent bound to ctrl+p / meta+p, guarded so it does not fire while the command palette is open (when: !palette.open — verify the keymap when-parser supports negation; otherwise pick a non-conflicting default). ctrl+p is currently palette.selectPrevious. -Files: new lib/builtin/files/src/quick_open_overlay.dart + extension registration; new lib/kernel/src/recent_files.dart; files_commands.dart (files.walk); keymap intents + default.yaml; app.dart Stack mount alongside ClidePalette.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:01:23', NULL, '40a056ccb71d46695c1aba494f5e4eaa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'story', NULL, 'find in files (Ctrl+Shift+F)', '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. +Files: new lib/builtin/files/src/quick_open_overlay.dart + extension registration; new lib/kernel/src/recent_files.dart; files_commands.dart (files.walk); keymap intents + default.yaml; app.dart Stack mount alongside ClidePalette.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:54:02', NULL, '318afbcd201a6a6fd559afd2174044e6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6YYG4X5R1GX97TJB4', 'story', NULL, 'find in files (Ctrl+Shift+F)', '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. Refinement (2026-05-31) — see D-79: @@ -3012,38 +1113,8 @@ Open-at-line — editor.open takes only {path} today. Extend it with an optional Sidebar panel follows the files/git/decisions TabContribution(slot: Slots.sidebar) pattern; custom widgets, no Material/Cupertino. -Files: new lib/builtin/search/{extension,search_controller,search_panel_view}.dart; new lib/src/daemon/search_commands.dart (search.grep + isolate pool); lib/src/files ignore layering; editor_commands.dart + registry.dart (line param); IPC schema + dispatcher registration; CLI `clide search grep` verb.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:01:23', NULL, '7a80f9a305e2e93f33820bea9cb68740', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-51', 'story', NULL, 'quick file open (Ctrl+P)', '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. - -Refinement (2026-05-31): - -File source — no repo-wide listing exists today (files.ls is per-directory). Add a `files.walk` daemon IPC verb that recursively walks the workspace honoring IgnoreSet (per D-4) and returns a flat file list. - -Open routing mirrors the files panel after T-187: a `.md` path publishes (''builtin.markdown'',''selection'',{path}) to open the markdown reader; any other path uses editor.open. (The ticket''s "editor or markdown viewer depending on file type" IS this split.) - -Recent files — none exists today (ProjectManager.recents tracks projects, not files). Add a small kernel RecentFilesService (bounded in-memory list of repo-relative paths) pushed from the shared open dispatch in file_tree_view.dart; empty-input state shows it. - -Overlay mirrors the ClidePalette chrome and reuses the 200ms-debounced ClideFilterBox. New QuickOpenIntent bound to ctrl+p / meta+p, guarded so it does not fire while the command palette is open (when: !palette.open — verify the keymap when-parser supports negation; otherwise pick a non-conflicting default). ctrl+p is currently palette.selectPrevious. - -Files: new lib/builtin/files/src/quick_open_overlay.dart + extension registration; new lib/kernel/src/recent_files.dart; files_commands.dart (files.walk); keymap intents + default.yaml; app.dart Stack mount alongside ClidePalette.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:54:02', NULL, '24320514183313ddd9f775d8f63eed3e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'story', NULL, 'find in files (Ctrl+Shift+F)', '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. - -Refinement (2026-05-31) — see D-79: - -Backend = pure-Dart in-process grep engine. An isolate worker pool fans non-ignored files across cores; match with RegExp, with a literal indexOf fast-path when the regex toggle is off. Results STREAM over a new engine-agnostic IPC verb `search.grep` (args: terms, regex, ignoreCase, include/exclude globs), with cancellation when the query changes. NOT ripgrep/grep (unvendored external binary, not guaranteed cross-platform — against prefer-zero-deps). NOT pql: pql search is a ranked document index (path/score/connections) with no line numbers, snippets, regex, case, or glob, so it structurally cannot satisfy find-in-files. - -Future work (note only, do not build): optional "use rg when on PATH, else the built-in engine" accelerator behind the same search.grep verb — no caller changes (the contract is engine-agnostic). - -Tree-sitter is NOT used here — it is a parser for structural/semantic search (find usages, go-to-def), a separate future feature, not text grep. - -Ignore handling — implement the FULL ignore_files: ordered layering from .pql/config.yaml (D-4) in-scope. This closes the never-filed "Q-024 (to be recorded)" placeholder comment at lib/src/daemon/files_commands.dart:161 — remove that comment. (Current _defaultIgnore only reads .gitignore + .clideignore.) - -Open-at-line — editor.open takes only {path} today. Extend it with an optional 1-based `line` param (daemon converts line to byte offset and sets the initial Selection); backward compatible (line null = open at top). Enables CLI parity: clide editor open --line N. - -Sidebar panel follows the files/git/decisions TabContribution(slot: Slots.sidebar) pattern; custom widgets, no Material/Cupertino. - -Files: new lib/builtin/search/{extension,search_controller,search_panel_view}.dart; new lib/src/daemon/search_commands.dart (search.grep + isolate pool); lib/src/files ignore layering; editor_commands.dart + registry.dart (line param); IPC schema + dispatcher registration; CLI `clide search grep` verb.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:54:02', NULL, '59534ff6067343e354f4946fe7931d2f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'story', NULL, 'search and replace across files', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config. +Files: new lib/builtin/search/{extension,search_controller,search_panel_view}.dart; new lib/src/daemon/search_commands.dart (search.grep + isolate pool); lib/src/files ignore layering; editor_commands.dart + registry.dart (line param); IPC schema + dispatcher registration; CLI `clide search grep` verb.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:54:02', NULL, '49267e45bcfbbd443fbb1c969ceef890', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM452XPSG99PA60ZZ48', 'story', NULL, 'search and replace across files', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config. Refinement (2026-05-31): @@ -3059,21 +1130,4 @@ Safety (user decision) — REQUIRE A CLEAN GIT WORKING TREE before apply: refuse Ignore — same full ignore_files: layering as T-52 (D-4). -Files: lib/src/daemon/files_commands.dart (files.write); new replacement-preview controller/view under lib/builtin/search/; reuse path_safety + diff render primitives; DialogRouter confirm; IPC schema + CLI parity verbs.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 19:28:39', NULL, 'fb6ccea7b9f4b2a2f403f7ade153cfeb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-53', 'story', NULL, 'search and replace across files', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config. - -Refinement (2026-05-31): - -Depends on T-52 — consumes its match model (path, line, start/end offset per match, capture groups) as the replacement input. Sequence after T-52 (T-52 blocks T-53). - -Write path — no files.write IPC exists today (only files.root/read/ls/watch; the sole write path is EditorRegistry.save on an open buffer). Add a `files.write` verb gated by resolveUnderRootFollowingSymlinks (lib/src/files/path_safety.dart). Do NOT route through the editor (would pollute the open-buffer list with dozens of temp buffers). - -Regex capture groups — Dart replaceAllMapped / $1; the T-52 engine must surface match groups. - -Preview — the existing diff view (lib/builtin/diff) is git-only (hardwired to git.diff). Build a ReplacementPreviewController/view that reuses the diff RENDER primitives (DiffLine / _HunkView styling) fed a computed in-memory before/after set. Do NOT generalize/entangle the git DiffController. - -Safety (user decision) — REQUIRE A CLEAN GIT WORKING TREE before apply: refuse with "commit or stash your changes first" if there are unstaged changes, making git the lossless undo layer. Preview shown first; final confirmation via the existing DialogRouter (as used by git discard-confirm). - -Ignore — same full ignore_files: layering as T-52 (D-4). - -Files: lib/src/daemon/files_commands.dart (files.write); new replacement-preview controller/view under lib/builtin/search/; reuse path_safety + diff render primitives; DialogRouter confirm; IPC schema + CLI parity verbs.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 19:41:13', NULL, 'd3de16341cfef833829cc83c5ff76995', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +Files: lib/src/daemon/files_commands.dart (files.write); new replacement-preview controller/view under lib/builtin/search/; reuse path_safety + diff render primitives; DialogRouter confirm; IPC schema + CLI parity verbs.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 19:41:13', NULL, '4180fad513063f05c53b8724d045e1e2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index 9579fe16..43a7f201 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -1,124 +1,30 @@ -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-194', 'bug', 'T-132', 'Markdown reader 404s on absolute paths handed to files.read', 'The sidebar markdown reader (and any files.read caller) was handed an absolute path (e.g. the Claude Config tab publishing a skill''s absolute SKILL.md manifestPath). resolveUnderRoot joined the absolute path onto the workspace root (/repo + /repo/x = /repo/repo/x), which does not exist, so the reader showed ''file not found'' for a file that exists. Fix: resolveUnderRoot normalizes an absolute input as-is and relies on the existing containment check (so absolute-under-root resolves, absolute-outside still throws PathOutsideRoot). Found live opening .claude/skills/frame0-wireframe/SKILL.md.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 06:49:25', '2026-06-01 06:49:47', NULL, '01a3a891c9786f7afb7a1b95e8ab0b2f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-195', 'story', 'T-132', 'Reader can''t open user-scope Claude config files (~/.claude skills/agents/commands)', 'Clicking a user-scope skill in the Claude Config tab (e.g. /home//.claude/skills/peon-ping-toggle/SKILL.md) shows ''path outside workspace''. The markdown reader loads via files.read, which is workspace-confined for security (T-102); but user-scope Claude config files legitimately live under ~/.claude, outside the repo. Per D-76, Claude''s config surface (skills/agents/commands under ~/.claude AND the repo .claude) is clide-managed and safe to surface. Proposed: a scoped trusted-read — a new IPC verb (e.g. claude.read-config-file) that permits reads ONLY under the resolved Claude config roots (~/.claude + /.claude), still rejecting arbitrary paths. Route the Config tab''s skill/agent/command markdown clicks through it, or have the markdown reader fall back to it when the path is under a Claude config root but outside the workspace. Alternative: the Config tab already reads SKILL.md during scan (claude_config.dart) and could publish content directly via a load-by-content channel rather than a path (the reader currently only reads by path). Repo-scope skills already work after T-194; this covers the user-scope half.', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 06:56:05', '2026-06-01 07:08:16', NULL, '7b46d0491c01bcc056c430a7af48f734', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-197', 'bug', NULL, 'Editor pane never reveals on editor.open (edit button, file-tree open)', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 08:58:19', '2026-06-01 10:45:14', NULL, '05ae54e31d0c9befdbfef2fe096737c0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-196', 'bug', 'T-186', 'Decision reader: first click switches tab but doesn''t load (lost on tab mount)', 'Clicking a D-record activates the decisions.detail context-panel tab but does not load the record; only the SECOND click (any record) works. Root cause: DecisionDetailView subscribes to the ''selection'' MessageBus channel in didChangeDependencies, which runs only AFTER the extension''s activateTab mounts the view. The bus is broadcast (no replay), so the ''selection'' message that triggered the reveal is already gone — the view subscribes too late and misses it. The markdown extension already solved this: activateTab, then addPostFrameCallback -> re-publish on a separate ''load'' channel the viewer subscribes to, so the load fires after the viewer mounts+subscribes. Fix: mirror that — DecisionDetailView subscribes to ''load''; DecisionsExtension forwards ''selection'' -> post-frame ''load'' after activateTab.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 08:58:10', '2026-06-01 10:45:14', NULL, 'c87a8726edd727831291bd1055e39ae1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-198', 'task', 'T-186', 'Reader pin UX: pin icon, toggle, left-toggle/right-navigate', 'Polish on the reader action bar (follows T-190). (1) push-pin glyph instead of the chain/link icon; (2) the pin button TOGGLES pinned state (tap to pin current, tap again to unpin) rather than set/replace; (3) layout split — pin/unpin toggle on the LEFT, jump-to-pin moved into the navigator (back/forward) on the right. Added ReaderNav.togglePin(), PhosphorIcons.pushPin (0xe3e2) + arrowUUpLeft (0xe08a), and an active state on the action button.', 'done', 'low', NULL, NULL, NULL, '2026-06-01 11:11:32', '2026-06-01 11:31:39', NULL, 'd2392d2e56ffc552c8f4f8993350d939', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-199', 'task', 'T-186', 'Tickets detail: adopt retained ReaderNav (back/forward + pin) + static tab', 'Bring the tickets detail reader in line with markdown/decisions (T-196/D-81). Adopt the retained ReaderNav(''builtin.tickets'', dataKey ''id''): the nav records selections + emits ''load''; the controller loads on ''load'' (not ''selection''); the view grabs nav.current on mount and wraps in ClidePaneChrome with a ReaderActionBar (pin toggle left; back/forward + jump-to-pin right; NO edit pencil since tickets are pql records, not files). Also drop the per-click uncontribute/contribute churn in TicketsExtension (the T-188 anti-pattern) — use the static tickets.detail tab + reveal on selection. TicketDetailController loses its panels dependency (reveal moves to the extension).', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 11:38:32', '2026-06-01 12:35:04', NULL, '82fe1aefd26d3f53872cc6983d1ae52d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-200', 'bug', NULL, 'Sidebar icon rail overflows (RenderFlex) when tab count exceeds width', 'Adding the Search sidebar tab (T-52) pushed the bottom icon rail past its width: ''A RenderFlex overflowed by 54 pixels'' from clide_icon_rail.dart — a fixed Row(center, max) with one _RailButton per tab. Fix: ClideIconRail centers when the icons fit and scrolls horizontally when they don''t (LayoutBuilder + SingleChildScrollView + ConstrainedBox(minWidth: maxWidth)). Robust to any tab count. (Related: T-201 will reduce tab count by merging the pql + find-in-files search surfaces.)', 'done', 'high', NULL, NULL, NULL, '2026-06-01 14:04:00', '2026-06-01 14:04:24', NULL, '59e5ac635342f5994afcc76c0fb3cd1e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-201', 'task', 'T-186', 'Merge pql search into the find-in-files Search tab (one search surface)', 'The pql sidebar panel (ranked text search + PQL DSL query + synced markdown-file listing) and the find-in-files Search tab (content grep) are duplicate search surfaces. Consolidate into ONE Search tab with a mode switch (option B): Find (content grep, existing), Vault (pql ranked search), Query (PQL DSL), Markdown (synced md-file listing, keeps focus-highlight + live refresh). Remove the builtin.pql SIDEBAR tab; keep the pql extension''s Backlinks context-panel tab + PqlController. Implementation: SearchPanelView gains a 4-mode switcher and holds both FindInFilesController + PqlController; extract the pql body/result rows into a reusable widget (they''re private in pql_panel_view.dart today); move the md-listing focus-sync + files.changed refresh into the merged panel; delete PqlPanelView. Test/coverage note: pql_panel_view + pql_controller are currently untested (not in the coverage denominator); folding them into the tested Search panel pulls them in, so the merge must cover the Vault/Query/Markdown modes too (same dynamic that bit T-199). Also reduces sidebar tab count (helps the rail, T-200). No D-79 conflict: grep vs ranked-search remain distinct backends; this is UI consolidation only.', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 14:25:59', '2026-06-01 14:45:39', NULL, '4101a253d4893d6ea9b994332645dfdb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-197', 'bug', NULL, 'Editor pane never reveals on editor.open (edit button, file-tree open)', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race. +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7MFNNCC0MMY2TCNRM', 'bug', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Markdown reader 404s on absolute paths handed to files.read', 'The sidebar markdown reader (and any files.read caller) was handed an absolute path (e.g. the Claude Config tab publishing a skill''s absolute SKILL.md manifestPath). resolveUnderRoot joined the absolute path onto the workspace root (/repo + /repo/x = /repo/repo/x), which does not exist, so the reader showed ''file not found'' for a file that exists. Fix: resolveUnderRoot normalizes an absolute input as-is and relies on the existing containment check (so absolute-under-root resolves, absolute-outside still throws PathOutsideRoot). Found live opening .claude/skills/frame0-wireframe/SKILL.md.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 06:49:25', '2026-06-01 06:49:47', NULL, '22ea906d212d808ba7af9cb83060b195', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4M2Z9YZ0SA81E5Q08', 'story', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Reader can''t open user-scope Claude config files (~/.claude skills/agents/commands)', 'Clicking a user-scope skill in the Claude Config tab (e.g. /home//.claude/skills/peon-ping-toggle/SKILL.md) shows ''path outside workspace''. The markdown reader loads via files.read, which is workspace-confined for security (T-102); but user-scope Claude config files legitimately live under ~/.claude, outside the repo. Per D-76, Claude''s config surface (skills/agents/commands under ~/.claude AND the repo .claude) is clide-managed and safe to surface. Proposed: a scoped trusted-read — a new IPC verb (e.g. claude.read-config-file) that permits reads ONLY under the resolved Claude config roots (~/.claude + /.claude), still rejecting arbitrary paths. Route the Config tab''s skill/agent/command markdown clicks through it, or have the markdown reader fall back to it when the path is under a Claude config root but outside the workspace. Alternative: the Config tab already reads SKILL.md during scan (claude_config.dart) and could publish content directly via a load-by-content channel rather than a path (the reader currently only reads by path). Repo-scope skills already work after T-194; this covers the user-scope half.', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 06:56:05', '2026-06-01 07:08:16', NULL, '12a1aa9958f3b375c19a5c03f694fc00', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6NTYAB8K6T50F8W00', 'bug', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Decision reader: first click switches tab but doesn''t load (lost on tab mount)', 'Clicking a D-record activates the decisions.detail context-panel tab but does not load the record; only the SECOND click (any record) works. Root cause: DecisionDetailView subscribes to the ''selection'' MessageBus channel in didChangeDependencies, which runs only AFTER the extension''s activateTab mounts the view. The bus is broadcast (no replay), so the ''selection'' message that triggered the reveal is already gone — the view subscribes too late and misses it. The markdown extension already solved this: activateTab, then addPostFrameCallback -> re-publish on a separate ''load'' channel the viewer subscribes to, so the load fires after the viewer mounts+subscribes. Fix: mirror that — DecisionDetailView subscribes to ''load''; DecisionsExtension forwards ''selection'' -> post-frame ''load'' after activateTab.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 08:58:10', '2026-06-01 10:45:14', NULL, '818668b41eab83cdb411ccd17195c5c1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6HQXNNF05GXWK7BWM', 'task', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Reader pin UX: pin icon, toggle, left-toggle/right-navigate', 'Polish on the reader action bar (follows T-190). (1) push-pin glyph instead of the chain/link icon; (2) the pin button TOGGLES pinned state (tap to pin current, tap again to unpin) rather than set/replace; (3) layout split — pin/unpin toggle on the LEFT, jump-to-pin moved into the navigator (back/forward) on the right. Added ReaderNav.togglePin(), PhosphorIcons.pushPin (0xe3e2) + arrowUUpLeft (0xe08a), and an active state on the action button.', 'done', 'low', NULL, NULL, NULL, '2026-06-01 11:11:32', '2026-06-01 11:31:39', NULL, 'aef79fd9dbfa2f9197f450552ce72377', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM68APBCQTMGJAZ2B9C', 'task', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Tickets detail: adopt retained ReaderNav (back/forward + pin) + static tab', 'Bring the tickets detail reader in line with markdown/decisions (T-196/D-81). Adopt the retained ReaderNav(''builtin.tickets'', dataKey ''id''): the nav records selections + emits ''load''; the controller loads on ''load'' (not ''selection''); the view grabs nav.current on mount and wraps in ClidePaneChrome with a ReaderActionBar (pin toggle left; back/forward + jump-to-pin right; NO edit pencil since tickets are pql records, not files). Also drop the per-click uncontribute/contribute churn in TicketsExtension (the T-188 anti-pattern) — use the static tickets.detail tab + reveal on selection. TicketDetailController loses its panels dependency (reveal moves to the extension).', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 11:38:32', '2026-06-01 12:35:04', NULL, '999c432fcd16538d949eecc86bb61a05', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4RAKSX59W1HJCZ1M8', 'bug', NULL, 'Sidebar icon rail overflows (RenderFlex) when tab count exceeds width', 'Adding the Search sidebar tab (T-52) pushed the bottom icon rail past its width: ''A RenderFlex overflowed by 54 pixels'' from clide_icon_rail.dart — a fixed Row(center, max) with one _RailButton per tab. Fix: ClideIconRail centers when the icons fit and scrolls horizontally when they don''t (LayoutBuilder + SingleChildScrollView + ConstrainedBox(minWidth: maxWidth)). Robust to any tab count. (Related: T-201 will reduce tab count by merging the pql + find-in-files search surfaces.)', 'done', 'high', NULL, NULL, NULL, '2026-06-01 14:04:00', '2026-06-01 14:04:24', NULL, '5c3606e7985e8621109b0f8ca2e5cbe7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM57GY6PTG78SD3961M', 'task', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Merge pql search into the find-in-files Search tab (one search surface)', 'The pql sidebar panel (ranked text search + PQL DSL query + synced markdown-file listing) and the find-in-files Search tab (content grep) are duplicate search surfaces. Consolidate into ONE Search tab with a mode switch (option B): Find (content grep, existing), Vault (pql ranked search), Query (PQL DSL), Markdown (synced md-file listing, keeps focus-highlight + live refresh). Remove the builtin.pql SIDEBAR tab; keep the pql extension''s Backlinks context-panel tab + PqlController. Implementation: SearchPanelView gains a 4-mode switcher and holds both FindInFilesController + PqlController; extract the pql body/result rows into a reusable widget (they''re private in pql_panel_view.dart today); move the md-listing focus-sync + files.changed refresh into the merged panel; delete PqlPanelView. Test/coverage note: pql_panel_view + pql_controller are currently untested (not in the coverage denominator); folding them into the tested Search panel pulls them in, so the merge must cover the Vault/Query/Markdown modes too (same dynamic that bit T-199). Also reduces sidebar tab count (helps the rail, T-200). No D-79 conflict: grep vs ranked-search remain distinct backends; this is UI consolidation only.', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 14:25:59', '2026-06-01 14:45:39', NULL, '9ae4d2691295abc84d8c3489ca1e3825', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7G8QBJ2PNQ0VREN24', 'bug', NULL, 'Editor pane never reveals on editor.open (edit button, file-tree open)', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race. -Reopened fix (2026-06-01): the original fix called panels.activateTab(Slots.workspace, ''editor.active''), but _WorkspaceSlot renders its editor split off arrangement.editorOpen, NOT the active tab — so the editor never appeared. Real fix: EditorExtension.activate now calls arrangement.openEditor() on editor.opened / active-changed(non-null) and closeEditor() on active-changed(null). Test asserts arrangement.editorOpen.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 08:58:19', '2026-06-01 16:36:38', NULL, '01039a9e83c3112ca93e33c228763b50', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-202', 'bug', NULL, 'rate_limit_event with numeric resetsAt crashes the Claude session', 'stream_json_session._statusFromEvent cast info[''resetsAt''] as String?, but claude sends resetsAt as a unix-epoch NUMBER → ''int is not a subtype of String?'' unhandled in _onLine, killing the session''s line parsing. Fix: accept num (epoch seconds → DateTime.fromMillisecondsSinceEpoch) or String (ISO). Surfaced in a live session log.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 16:48:31', '2026-06-01 16:48:56', NULL, '589320cb1ad6a97a3d91c090ce4f2112', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-203', 'bug', 'T-197', 'Editor split throws semantics + SelectableRegion exceptions on open', 'Opening the editor split (now that T-197 works) flooded two exceptions. (1) The _EditorDragHandle Semantics had slider:true + value + onIncrease/onDecrease but no increasedValue/decreasedValue → ''SemanticsNode with action increase needs both value and increasedValue'' on every flush; added increasedValue/decreasedValue. (2) Reparenting the Claude pane (direct child → Column/Expanded) when the split opens disposed its ClideSelectionArea/SelectableRegion subtree mid selection-update microtask → ''selectable not in this registrar'' + ''renderObject of inactive element''; fixed with a stable GlobalKey on the workspace primary so Flutter moves the element instead of disposing it.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 16:48:31', '2026-06-01 16:48:56', NULL, '77c284e1d35358783bda17955abba6b8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'story', 'T-65', 'Key-sequence resolution in the keymap layer (Vim motions, counts)', 'The resolver is single-chord: KeyChord.fromKeyEvent -> Keymap.resolve fires and forgets one keystroke. Real Vim needs multi-key sequences (dd, gg, dw, ciw, yy) and count prefixes (5j, 3dd). Add a pending-sequence model to KeymapService: accumulate chords into a buffer, match against sequence bindings by prefix, fire on full match, reset on no-prefix-match or timeout. Add a KeySequence type (ordered list of KeyChord) alongside KeyChord. Extend the YAML schema to express sequences distinctly from alias-lists: keys:[d,d] today means ''either d or d'' (alias) — need a ''sequence:'' key or a chord-string notation (e.g. ''d d'' space-separated) for ordered sequences. Add count-prefix capture so a leading digit run is parsed as a repeat count passed to the intent. Keep single-chord resolution unchanged (fast path). This also unblocks JetBrains shift+shift (T-66) via a double-tap special-case. Decision-worthy: the sequence/notation choice may warrant a D-record.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-01 18:48:27', '2026-06-01 18:48:27', NULL, '50a4ef00ac4ea54e4d83d0991fc9905b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-206', 'story', 'T-65', 'Modal editor: Vim motion & edit intents', 'The editor (lib/builtin/editor/src/editor_view.dart) is a raw Flutter EditableText that only handles Cmd/Ctrl+S; in normal mode, letter keys must NOT insert text and must instead drive motions/edits. Add typed editor Intents + Action handlers wired into the editor that operate on the SyntaxTextController/TextEditingValue: cursor motions (cursorLeft/Down/Up/Right for h/j/k/l), word motions (wordForward/wordBackward/wordEnd for w/b/e), line motions (lineStart/lineEnd/firstNonBlank for 0/$/^), document motions (documentStart/End for gg/G), edits (deleteChar x, deleteLine dd, deleteWord dw, changeLine cc, changeWord cw, yankLine yy, paste p/P, openLineBelow/Above o/O), and insert-entry (enterInsert i/a/I/A/o). Motions+edits must compose with the count prefix (T-205) and operator+motion (dw, cw). The editor must read the active Vim mode (T-206 service) to decide whether a bare key inserts or commands — key interception must win over EditableText''s text input in normal mode. Acceptance: each intent has an Action that mutates the buffer correctly; regression tests cover j (cursor down), x (delete char), dd (delete line), i (enter insert).', 'backlog', 'high', NULL, NULL, NULL, '2026-06-01 18:48:40', '2026-06-01 18:48:40', NULL, '752546ab98b527cd6abcb1232f578aca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'story', 'T-65', 'Vim mode service + status-bar mode indicator', 'A small mode-tracking service (ChangeNotifier) that owns the current Vim mode (normal/insert/visual), publishes vim.normal / vim.insert / vim.visual scope flags via KeymapService.setScopeFlag, and exposes the mode to the editor (T-206) and a status-bar indicator (-- NORMAL -- / -- INSERT -- / -- VISUAL --). Handles transitions: Esc -> normal (from any mode); i/a/o/I/A -> insert (from normal); v -> visual (from normal); colon -> command-line indicator (stretch). Resets to normal on editor focus by default (configurable). The when-clause grammar already supports dotted flags (vim.normal && editor.focused), confirmed in when_clause.dart. Service is a pure ChangeNotifier with no deps; fits in lib/kernel/src/keymap/ or as an editor builtin piece. Acceptance: setPreset(''vim'') + this service produce correct mode transitions; a regression test asserts i->insert and Esc->normal flip the scope flags; the status bar reflects mode.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-01 18:48:47', '2026-06-01 18:48:47', NULL, '337aed65495c6d858c5ce205b54d5b06', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', '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. +Reopened fix (2026-06-01): the original fix called panels.activateTab(Slots.workspace, ''editor.active''), but _WorkspaceSlot renders its editor split off arrangement.editorOpen, NOT the active tab — so the editor never appeared. Real fix: EditorExtension.activate now calls arrangement.openEditor() on editor.opened / active-changed(non-null) and closeEditor() on active-changed(null). Test asserts arrangement.editorOpen.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 08:58:19', '2026-06-01 16:36:38', NULL, '7c752b0b9c4b83d17bcf896dfc6b15e4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5A3984KFPD4NKRG6M', 'bug', NULL, 'rate_limit_event with numeric resetsAt crashes the Claude session', 'stream_json_session._statusFromEvent cast info[''resetsAt''] as String?, but claude sends resetsAt as a unix-epoch NUMBER → ''int is not a subtype of String?'' unhandled in _onLine, killing the session''s line parsing. Fix: accept num (epoch seconds → DateTime.fromMillisecondsSinceEpoch) or String (ISO). Surfaced in a live session log.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 16:48:31', '2026-06-01 16:48:56', NULL, '1748dedeac6b6e160d609c3336488687', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM643F7Z648KNFPT7GR', 'bug', '06FB0TNQM7G8QBJ2PNQ0VREN24', 'Editor split throws semantics + SelectableRegion exceptions on open', 'Opening the editor split (now that T-197 works) flooded two exceptions. (1) The _EditorDragHandle Semantics had slider:true + value + onIncrease/onDecrease but no increasedValue/decreasedValue → ''SemanticsNode with action increase needs both value and increasedValue'' on every flush; added increasedValue/decreasedValue. (2) Reparenting the Claude pane (direct child → Column/Expanded) when the split opens disposed its ClideSelectionArea/SelectableRegion subtree mid selection-update microtask → ''selectable not in this registrar'' + ''renderObject of inactive element''; fixed with a stable GlobalKey on the workspace primary so Flutter moves the element instead of disposing it.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 16:48:31', '2026-06-01 16:48:56', NULL, '1c5aeeaaf738cf15cbb4b1ef3b2f57fe', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM65R8CNFNE1MA5GGR0', 'task', '06FB0TNQM5V7GHH42RHV04DPTR', 'Fix dead default keymap: focus.next/previous undefined drops whole preset', 'The keymap loader catches FormatException from preset parsing and silently sets _preset=null (keymap_service.dart load()). default.yaml binds tab->focus.next and shift+tab->focus.previous, but neither id exists in builtinIntents (only focus.nextPanel/previousPanel). parseIntentId returns null -> KeymapLayer.fromYaml throws -> the ENTIRE default preset is dropped at boot. So palette (ctrl+shift+p), quick-open (ctrl+p), find-in-files, and text-scale bindings are all dead at runtime. Uncaught because every keymap_service_test injects a synthetic bundle; the real asset is never parsed. Fix: add focus.next->NextFocusIntent and focus.previous->PreviousFocusIntent (Flutter-provided, like activate/dismiss) to builtinIntents so Tab does correct widget focus traversal; add a test that loads the REAL assets/keymaps/default.yaml and asserts it parses with the expected binding count, as a regression guard for every shipped preset.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:19', '2026-06-01 18:51:08', NULL, '4c8a15d72af7462d3c444373e7e1bdcb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6M83TTXHRXESFZY14', 'story', '06FB0TNQM5V7GHH42RHV04DPTR', 'Vim mode service + status-bar mode indicator', 'A small mode-tracking service (ChangeNotifier) that owns the current Vim mode (normal/insert/visual), publishes vim.normal / vim.insert / vim.visual scope flags via KeymapService.setScopeFlag, and exposes the mode to the editor (T-206) and a status-bar indicator (-- NORMAL -- / -- INSERT -- / -- VISUAL --). Handles transitions: Esc -> normal (from any mode); i/a/o/I/A -> insert (from normal); v -> visual (from normal); colon -> command-line indicator (stretch). Resets to normal on editor focus by default (configurable). The when-clause grammar already supports dotted flags (vim.normal && editor.focused), confirmed in when_clause.dart. Service is a pure ChangeNotifier with no deps; fits in lib/kernel/src/keymap/ or as an editor builtin piece. Acceptance: setPreset(''vim'') + this service produce correct mode transitions; a regression test asserts i->insert and Esc->normal flip the scope flags; the status bar reflects mode.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:47', '2026-06-01 18:59:19', NULL, 'e0e01b13b1d2cc833f4f1ef79aa1663e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4PH1J6ENYT4PJZRHG', 'story', '06FB0TNQM5V7GHH42RHV04DPTR', 'Key-sequence resolution in the keymap layer (Vim motions, counts)', 'The resolver is single-chord: KeyChord.fromKeyEvent -> Keymap.resolve fires and forgets one keystroke. Real Vim needs multi-key sequences (dd, gg, dw, ciw, yy) and count prefixes (5j, 3dd). Add a pending-sequence model to KeymapService: accumulate chords into a buffer, match against sequence bindings by prefix, fire on full match, reset on no-prefix-match or timeout. Add a KeySequence type (ordered list of KeyChord) alongside KeyChord. Extend the YAML schema to express sequences distinctly from alias-lists: keys:[d,d] today means ''either d or d'' (alias) — need a ''sequence:'' key or a chord-string notation (e.g. ''d d'' space-separated) for ordered sequences. Add count-prefix capture so a leading digit run is parsed as a repeat count passed to the intent. Keep single-chord resolution unchanged (fast path). This also unblocks JetBrains shift+shift (T-66) via a double-tap special-case. Decision-worthy: the sequence/notation choice may warrant a D-record.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:27', '2026-06-01 19:12:01', NULL, 'b615f886c304b96668db19a2a295cbd2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5REFZXMY8ESWN0Z90', 'story', '06FB0TNQM5V7GHH42RHV04DPTR', 'Modal editor: Vim motion & edit intents', 'The editor (lib/builtin/editor/src/editor_view.dart) is a raw Flutter EditableText that only handles Cmd/Ctrl+S; in normal mode, letter keys must NOT insert text and must instead drive motions/edits. Add typed editor Intents + Action handlers wired into the editor that operate on the SyntaxTextController/TextEditingValue: cursor motions (cursorLeft/Down/Up/Right for h/j/k/l), word motions (wordForward/wordBackward/wordEnd for w/b/e), line motions (lineStart/lineEnd/firstNonBlank for 0/$/^), document motions (documentStart/End for gg/G), edits (deleteChar x, deleteLine dd, deleteWord dw, changeLine cc, changeWord cw, yankLine yy, paste p/P, openLineBelow/Above o/O), and insert-entry (enterInsert i/a/I/A/o). Motions+edits must compose with the count prefix (T-205) and operator+motion (dw, cw). The editor must read the active Vim mode (T-206 service) to decide whether a bare key inserts or commands — key interception must win over EditableText''s text input in normal mode. Acceptance: each intent has an Action that mutates the buffer correctly; regression tests cover j (cursor down), x (delete char), dd (delete line), i (enter insert).', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:40', '2026-06-01 19:27:18', NULL, '44c8001114ba7c63d5d2ab6ba13223a6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM48QJV4N7J62436GC0', 'task', '06FB0TNQM4QYYKHPQ03H3QD61W', 'Regression test: clideVersion matches pubspec version', 'Gap 6 version-drift guard. clideVersion in lib/src/build_info.g.dart (generated by gen-build-info) currently matches pubspec.yaml `version:` (both 2.1.0). Add a test asserting they stay equal so an agent keying off `clide version` is never misled.', 'done', 'low', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, '1a10d96abef4a38149f02ec60b4a2d5d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4C2PSN2GVJY9JDSZG', 'task', '06FB0TNQM4QYYKHPQ03H3QD61W', 'Build the C clide client by default', 'Make the `clide-cli` target (Makefile:284) a dependency of `build` (Makefile:169) and `install` (Makefile:195) so the C client is always produced. Today it builds only via the explicit, non-default `make clide-cli`. Acceptance: a plain `make build` / `make install` produces the C client binary for the current OS.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, '8d614f61e2893184e1e6792acf92c64b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7171NRB1T82TGP5RG', 'task', '06FB0TNQM4QYYKHPQ03H3QD61W', 'install delivers the C client to PATH on macOS and Linux', 'Fix `make install` so $(INSTALL_DIR)/clide (default ~/.local/bin/clide) IS the compiled C client, not the Flutter GUI binary. Today Makefile:200 symlinks the GUI bundle runner on both platforms. Acceptance: after `make install`, `which clide` resolves and `clide ping` returns {"pong":true} against a running instance on macOS and Linux. Blocked by ''Build the C client by default''.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, 'a1978f7f0fec1c0b0c0bd4b7d1f8f65e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM54WTNQDMYT0AVR4WM', 'task', '06FB0TNQM7XMQ44AVCAARQZX14', 'Decide the dogfood agent model (governance Q/D record)', 'Gap 5 from self-analysis.md — foundational. Two distinct ''Claude inside clide'' stories with different gaps: (A) the clide-HOSTED stream-json session (D-77/D-78) — the user''s primary Claude pane, which itself needs `clide` on PATH to drive the surrounding IDE; (B) an EXTERNAL agent (e.g. a Claude Code harness, TERM_PROGRAM=zed as observed) driving via `clide ...` IPC, which clide cannot fully observe because plain file reads / `make test` / `git` bypass clide. Decide the intended model — hosted, external, or both — and record it under governance/ (claim via `pql decisions claim`). Gates how Epic B bootstraps and how Epic C frames observability. -**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place; modal Vim presets are more involved than the VS Code preset (T-64) because modes need to be expressed as scope flags (`vim.normal`, `vim.insert`, `vim.visual`) that the when-clause grammar can branch on. Implementation work: - -1. Author `assets/keymaps/vim.yaml` using the typed Intents + `command:` bindings. -2. Add a small mode-tracking service that publishes `vim.` scope flags via `KeymapService.setScopeFlag`. -3. Bind `Esc` to mode-reset → normal; `i` (when `vim.normal`) → enter insert; etc. - -**Acceptance:** -1. `assets/keymaps/vim.yaml` ships covering the documented Vim default keybindings for editor / navigation / panes. -2. `KeymapService.setPreset("vim")` + the mode-tracking service together produce correct mode transitions. -3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert). - -Refinement (2026-06-01): scoped Vim-first for a Vim-power-user demo this weekend. ''Author a YAML file'' was wrong — T-117 shipped single-chord resolution + scope flags + when-clauses only. Decomposed into children: T-204 (fix dead default preset — live bug), T-205 (key-sequence + count resolution), T-206 (modal editor motion/edit intents), T-207 (Vim mode service + status indicator). T-65 itself becomes assets/keymaps/vim.yaml + regression tests once children land. T-64/T-66 deferred (single-chord, easy; T-205 hands JetBrains shift+shift later). Foundation is shared, not Vim-only.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-01 18:49:02', NULL, '8a7f2eee186a7824893e7f2d22604156', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-204', 'task', 'T-65', 'Fix dead default keymap: focus.next/previous undefined drops whole preset', 'The keymap loader catches FormatException from preset parsing and silently sets _preset=null (keymap_service.dart load()). default.yaml binds tab->focus.next and shift+tab->focus.previous, but neither id exists in builtinIntents (only focus.nextPanel/previousPanel). parseIntentId returns null -> KeymapLayer.fromYaml throws -> the ENTIRE default preset is dropped at boot. So palette (ctrl+shift+p), quick-open (ctrl+p), find-in-files, and text-scale bindings are all dead at runtime. Uncaught because every keymap_service_test injects a synthetic bundle; the real asset is never parsed. Fix: add focus.next->NextFocusIntent and focus.previous->PreviousFocusIntent (Flutter-provided, like activate/dismiss) to builtinIntents so Tab does correct widget focus traversal; add a test that loads the REAL assets/keymaps/default.yaml and asserts it parses with the expected binding count, as a regression guard for every shipped preset.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-01 18:48:19', '2026-06-01 18:49:17', NULL, '01fc224dd1bef50e6c42340ec3a3d81f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'story', 'T-65', 'Vim mode service + status-bar mode indicator', 'A small mode-tracking service (ChangeNotifier) that owns the current Vim mode (normal/insert/visual), publishes vim.normal / vim.insert / vim.visual scope flags via KeymapService.setScopeFlag, and exposes the mode to the editor (T-206) and a status-bar indicator (-- NORMAL -- / -- INSERT -- / -- VISUAL --). Handles transitions: Esc -> normal (from any mode); i/a/o/I/A -> insert (from normal); v -> visual (from normal); colon -> command-line indicator (stretch). Resets to normal on editor focus by default (configurable). The when-clause grammar already supports dotted flags (vim.normal && editor.focused), confirmed in when_clause.dart. Service is a pure ChangeNotifier with no deps; fits in lib/kernel/src/keymap/ or as an editor builtin piece. Acceptance: setPreset(''vim'') + this service produce correct mode transitions; a regression test asserts i->insert and Esc->normal flip the scope flags; the status bar reflects mode.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-01 18:48:47', '2026-06-01 18:51:08', NULL, '387db5a29a081da46a96dcb8672ccb79', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-204', 'task', 'T-65', 'Fix dead default keymap: focus.next/previous undefined drops whole preset', 'The keymap loader catches FormatException from preset parsing and silently sets _preset=null (keymap_service.dart load()). default.yaml binds tab->focus.next and shift+tab->focus.previous, but neither id exists in builtinIntents (only focus.nextPanel/previousPanel). parseIntentId returns null -> KeymapLayer.fromYaml throws -> the ENTIRE default preset is dropped at boot. So palette (ctrl+shift+p), quick-open (ctrl+p), find-in-files, and text-scale bindings are all dead at runtime. Uncaught because every keymap_service_test injects a synthetic bundle; the real asset is never parsed. Fix: add focus.next->NextFocusIntent and focus.previous->PreviousFocusIntent (Flutter-provided, like activate/dismiss) to builtinIntents so Tab does correct widget focus traversal; add a test that loads the REAL assets/keymaps/default.yaml and asserts it parses with the expected binding count, as a regression guard for every shipped preset.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:19', '2026-06-01 18:51:08', NULL, 'b335e35e5d0f4c3cb267d1b9573c1434', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'story', 'T-65', 'Vim mode service + status-bar mode indicator', 'A small mode-tracking service (ChangeNotifier) that owns the current Vim mode (normal/insert/visual), publishes vim.normal / vim.insert / vim.visual scope flags via KeymapService.setScopeFlag, and exposes the mode to the editor (T-206) and a status-bar indicator (-- NORMAL -- / -- INSERT -- / -- VISUAL --). Handles transitions: Esc -> normal (from any mode); i/a/o/I/A -> insert (from normal); v -> visual (from normal); colon -> command-line indicator (stretch). Resets to normal on editor focus by default (configurable). The when-clause grammar already supports dotted flags (vim.normal && editor.focused), confirmed in when_clause.dart. Service is a pure ChangeNotifier with no deps; fits in lib/kernel/src/keymap/ or as an editor builtin piece. Acceptance: setPreset(''vim'') + this service produce correct mode transitions; a regression test asserts i->insert and Esc->normal flip the scope flags; the status bar reflects mode.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:47', '2026-06-01 18:59:19', NULL, 'bb6b26be440d7850e55bf9ab138263bd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'story', 'T-65', 'Key-sequence resolution in the keymap layer (Vim motions, counts)', 'The resolver is single-chord: KeyChord.fromKeyEvent -> Keymap.resolve fires and forgets one keystroke. Real Vim needs multi-key sequences (dd, gg, dw, ciw, yy) and count prefixes (5j, 3dd). Add a pending-sequence model to KeymapService: accumulate chords into a buffer, match against sequence bindings by prefix, fire on full match, reset on no-prefix-match or timeout. Add a KeySequence type (ordered list of KeyChord) alongside KeyChord. Extend the YAML schema to express sequences distinctly from alias-lists: keys:[d,d] today means ''either d or d'' (alias) — need a ''sequence:'' key or a chord-string notation (e.g. ''d d'' space-separated) for ordered sequences. Add count-prefix capture so a leading digit run is parsed as a repeat count passed to the intent. Keep single-chord resolution unchanged (fast path). This also unblocks JetBrains shift+shift (T-66) via a double-tap special-case. Decision-worthy: the sequence/notation choice may warrant a D-record.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-01 18:48:27', '2026-06-01 19:07:16', NULL, '63396c8b4d6ae568bde2b7465001c3ad', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'story', 'T-65', 'Key-sequence resolution in the keymap layer (Vim motions, counts)', 'The resolver is single-chord: KeyChord.fromKeyEvent -> Keymap.resolve fires and forgets one keystroke. Real Vim needs multi-key sequences (dd, gg, dw, ciw, yy) and count prefixes (5j, 3dd). Add a pending-sequence model to KeymapService: accumulate chords into a buffer, match against sequence bindings by prefix, fire on full match, reset on no-prefix-match or timeout. Add a KeySequence type (ordered list of KeyChord) alongside KeyChord. Extend the YAML schema to express sequences distinctly from alias-lists: keys:[d,d] today means ''either d or d'' (alias) — need a ''sequence:'' key or a chord-string notation (e.g. ''d d'' space-separated) for ordered sequences. Add count-prefix capture so a leading digit run is parsed as a repeat count passed to the intent. Keep single-chord resolution unchanged (fast path). This also unblocks JetBrains shift+shift (T-66) via a double-tap special-case. Decision-worthy: the sequence/notation choice may warrant a D-record.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:27', '2026-06-01 19:12:01', NULL, '7cc1e3c9a5249e5d352b0107b1936327', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-206', 'story', 'T-65', 'Modal editor: Vim motion & edit intents', 'The editor (lib/builtin/editor/src/editor_view.dart) is a raw Flutter EditableText that only handles Cmd/Ctrl+S; in normal mode, letter keys must NOT insert text and must instead drive motions/edits. Add typed editor Intents + Action handlers wired into the editor that operate on the SyntaxTextController/TextEditingValue: cursor motions (cursorLeft/Down/Up/Right for h/j/k/l), word motions (wordForward/wordBackward/wordEnd for w/b/e), line motions (lineStart/lineEnd/firstNonBlank for 0/$/^), document motions (documentStart/End for gg/G), edits (deleteChar x, deleteLine dd, deleteWord dw, changeLine cc, changeWord cw, yankLine yy, paste p/P, openLineBelow/Above o/O), and insert-entry (enterInsert i/a/I/A/o). Motions+edits must compose with the count prefix (T-205) and operator+motion (dw, cw). The editor must read the active Vim mode (T-206 service) to decide whether a bare key inserts or commands — key interception must win over EditableText''s text input in normal mode. Acceptance: each intent has an Action that mutates the buffer correctly; regression tests cover j (cursor down), x (delete char), dd (delete line), i (enter insert).', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-01 18:48:40', '2026-06-01 19:16:58', NULL, '4627c4a8eb9257df10d0b3e82c7c1cc6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-206', 'story', 'T-65', 'Modal editor: Vim motion & edit intents', 'The editor (lib/builtin/editor/src/editor_view.dart) is a raw Flutter EditableText that only handles Cmd/Ctrl+S; in normal mode, letter keys must NOT insert text and must instead drive motions/edits. Add typed editor Intents + Action handlers wired into the editor that operate on the SyntaxTextController/TextEditingValue: cursor motions (cursorLeft/Down/Up/Right for h/j/k/l), word motions (wordForward/wordBackward/wordEnd for w/b/e), line motions (lineStart/lineEnd/firstNonBlank for 0/$/^), document motions (documentStart/End for gg/G), edits (deleteChar x, deleteLine dd, deleteWord dw, changeLine cc, changeWord cw, yankLine yy, paste p/P, openLineBelow/Above o/O), and insert-entry (enterInsert i/a/I/A/o). Motions+edits must compose with the count prefix (T-205) and operator+motion (dw, cw). The editor must read the active Vim mode (T-206 service) to decide whether a bare key inserts or commands — key interception must win over EditableText''s text input in normal mode. Acceptance: each intent has an Action that mutates the buffer correctly; regression tests cover j (cursor down), x (delete char), dd (delete line), i (enter insert).', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:40', '2026-06-01 19:27:18', NULL, '2810354fde44fa08199b38acb9c017a4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', '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. - -**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place; modal Vim presets are more involved than the VS Code preset (T-64) because modes need to be expressed as scope flags (`vim.normal`, `vim.insert`, `vim.visual`) that the when-clause grammar can branch on. Implementation work: - -1. Author `assets/keymaps/vim.yaml` using the typed Intents + `command:` bindings. -2. Add a small mode-tracking service that publishes `vim.` scope flags via `KeymapService.setScopeFlag`. -3. Bind `Esc` to mode-reset → normal; `i` (when `vim.normal`) → enter insert; etc. - -**Acceptance:** -1. `assets/keymaps/vim.yaml` ships covering the documented Vim default keybindings for editor / navigation / panes. -2. `KeymapService.setPreset("vim")` + the mode-tracking service together produce correct mode transitions. -3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert). - -Refinement (2026-06-01): scoped Vim-first for a Vim-power-user demo this weekend. ''Author a YAML file'' was wrong — T-117 shipped single-chord resolution + scope flags + when-clauses only. Decomposed into children: T-204 (fix dead default preset — live bug), T-205 (key-sequence + count resolution), T-206 (modal editor motion/edit intents), T-207 (Vim mode service + status indicator). T-65 itself becomes assets/keymaps/vim.yaml + regression tests once children land. T-64/T-66 deferred (single-chord, easy; T-205 hands JetBrains shift+shift later). Foundation is shared, not Vim-only.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-01 19:29:15', NULL, 'efc76aa91cfc889968d5bda60f897afa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-220', 'task', 'T-218', 'Reflect active editor buffer + focused file + selection into EditorRegistry', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '024c50b98b31cb66da1a7361f068415d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-216', 'task', 'T-214', 'Inject an agent context note describing the CLI surface + parity contract', 'Give the hosted agent a short note (CLAUDE.md fragment / system reminder) stating it is inside clide and how to drive the editor / git / file tree / panes via `clide ...`, plus the D-6 parity contract. Acceptance: a hosted agent is told, without the user having to explain it.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '032f132431556cd5b9e478ce052abfff', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-208', 'initiative', NULL, 'Give Claude hands', 'Make a Claude agent able to actually WORK inside clide: reach the CLI, know clide is hosting it, and observe the same workspace the user sees. Source: self-analysis.md (2026-06-02), a live dogfood probe by a Claude (Opus 4.8) running against a live instance (socket + MCP SSE up, version 2.1.0). - -Finding: the IPC/CLI foundation (D-1, D-6, D-56; built under T-99 incl. the C client T-126 and the `clide tail --events` stream T-129) works end-to-end at the socket level. But a fresh agent cannot REACH or OBSERVE it. Three blockers: -1. No `clide` binary on PATH, and no install path that delivers one (Epic A — blocker). -2. No bootstrap: nothing tells a fresh agent clide is running or where its socket is (Epic B). -3. The live UI panes / active file are not reflected in the CLI registries, so the D-6 parity premise (agent sees what the user sees) breaks (Epic C). - -Plus: a governance decision on WHICH agent model is the dogfood target (hosted stream-json session vs external CLI driver vs both) and an MCP follow-up. The pleasant surprise per the probe: the hard part (a live, correct, single-process IPC contract with proper 0/1/3 exit codes) is done; what''s missing is the last mile that puts the tool in the agent''s hands. NOTE: the report says ''daemon'' but D-56 dissolved it — this is the in-process IPC server; the socket contract is unchanged.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '0c8826a602ec3d1f5fd9cb24d57cb4df', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-218', 'epic', 'T-208', 'Make the live workspace observable to the CLI (D-6 parity)', 'Gap 3 from self-analysis.md, verified in code. PaneRegistry (lib/src/panes/registry.dart) is written ONLY by the pane.spawn IPC path, so the built-in TabContribution panes — Claude (builtin/claude/src/extension.dart:42), Files (builtin/files/.../extension.dart:22), Editor (builtin/editor/.../extension.dart:52) — render in the kernel layout tree but never appear in `pane list` (read at pane_commands.dart:121). EditorRegistry is written by editor.open IPC (the Files tree uses it) but the active Claude pane / focused file / selection are not reflected. Result while the UI is in use: `clide pane list` -> {"panes":[]}, `clide editor active` -> {"active":null}. The ''agent sees what the user sees'' half of D-6 is missing.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '1abeb011393b813cc9810eab72c56d15', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-222', 'epic', 'T-208', 'Agent-friendly event observation (pull-based)', 'Gap 4 from self-analysis.md. `clide tail --events` (T-129) is a never-returning stream — awkward for a request/response agent loop; the agent must background it and poll a file or it misses events between polls. Provide a pull-based form that fits an agent loop natively.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '52bfa2fc2cd53bf08cd94573643cdf15', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-221', 'task', 'T-218', 'Add a clide status one-shot snapshot command', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '5ba39937538d85c2475c0e706445bd07', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-209', 'epic', 'T-208', 'Ship the clide CLI on PATH', 'Gap 1 (BLOCKER) from self-analysis.md. The C client native/clide-cli/clide.c (T-126, done) is the real CLI, but there is no supported way for `clide` to land on an agent''s PATH: -- `make clide-cli` (Makefile:284) is a non-default target; `build` (Makefile:169) does not depend on it, so the binary is never produced by a normal build (native/macos-arm64/clide had never been built). -- `make install` (Makefile:195) copies the GUI bundle and `ln -sf $(INSTALL_PREFIX)/clide/clide $(INSTALL_DIR)/clide` (Makefile:200) symlinks the FLUTTER GUI runner — not the C client — on BOTH macOS and Linux (on macOS that path doesn''t even resolve inside clide.app). - -Net: `which clide` -> not found, `clide ping` -> exit 127. The CLI-first contract (D-1) has no delivery mechanism. Related to the broader installer epic T-46, but that is the GUI/runtime installer; this epic is specifically C-client-on-PATH delivery.', 'backlog', 'high', NULL, NULL, 'D-1', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '7b9a66f2daa4a692c7eb08cad919eb71', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-223', 'task', 'T-222', 'Add cursor-based pull events: clide events --since ', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '9c1c0c333670cd884794f5e8ccd521e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-212', 'task', 'T-209', 'In-app ''Install clide command in PATH'' affordance', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '9cd1982fd89036a7fd9c8aa181c73cca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-219', 'task', 'T-218', 'Register built-in panes into the PaneRegistry the CLI reads', 'Make the kernel layout''s TabContribution panes (Claude / Files / Editor and any viewer) visible to `pane list` / `pane focus` by registering or mirroring them into PaneRegistry (lib/src/panes/registry.dart, read by pane_commands.dart:121). Acceptance: with the GUI open, `clide pane list` enumerates the live panes the user sees, with stable ids and focus state.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'a81479a52653c4e644baa58121c17f01', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-213', 'task', 'T-209', 'Regression test: clideVersion matches pubspec version', 'Gap 6 version-drift guard. clideVersion in lib/src/build_info.g.dart (generated by gen-build-info) currently matches pubspec.yaml `version:` (both 2.1.0). Add a test asserting they stay equal so an agent keying off `clide version` is never misled.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'af3253c0d2ad462a32f5ff31ba688d77', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-211', 'task', 'T-209', 'install delivers the C client to PATH on macOS and Linux', 'Fix `make install` so $(INSTALL_DIR)/clide (default ~/.local/bin/clide) IS the compiled C client, not the Flutter GUI binary. Today Makefile:200 symlinks the GUI bundle runner on both platforms. Acceptance: after `make install`, `which clide` resolves and `clide ping` returns {"pong":true} against a running instance on macOS and Linux. Blocked by ''Build the C client by default''.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'b9771b279b7249b8efe59eb702c5c05f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-217', 'task', 'T-214', 'Pre-seed a Bash(clide *) permission allow rule for hosted agents', 'So the hosted agent is not prompted on every `clide ...` call. Acceptance: clide-hosted sessions get a Bash(clide *) allow rule in their effective settings.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'bdcb7506945369bb6a84bd68583274b4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-224', 'task', 'T-208', 'Decide the dogfood agent model (governance Q/D record)', 'Gap 5 from self-analysis.md — foundational. Two distinct ''Claude inside clide'' stories with different gaps: (A) the clide-HOSTED stream-json session (D-77/D-78) — the user''s primary Claude pane, which itself needs `clide` on PATH to drive the surrounding IDE; (B) an EXTERNAL agent (e.g. a Claude Code harness, TERM_PROGRAM=zed as observed) driving via `clide ...` IPC, which clide cannot fully observe because plain file reads / `make test` / `git` bypass clide. Decide the intended model — hosted, external, or both — and record it under governance/ (claim via `pql decisions claim`). Gates how Epic B bootstraps and how Epic C frames observability.', 'backlog', 'high', NULL, NULL, 'D-77', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'c220bfde67438d19824eef10006a8c1a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-214', 'epic', 'T-208', 'Bootstrap the clide-hosted/launched agent', 'Gap 2 from self-analysis.md. Even with the binary installed, a fresh session has no signal it is hosted by clide. There IS an MCP discovery lock (~/.claude/ide/.lock pointing at workspace + SSE URL) but NOTHING for the Bash/CLI path: no CLIDE_SOCK/CLIDE_WORKSPACE env var, no injected context note, no pre-seeded Bash(clide *) allow rule. Discovery is the difference between a capability existing and getting used — the probing agent had to be told ''you''re inside clide'' and then hunt for the socket.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'd95247263694e8a70fbeae3570b9579c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-210', 'task', 'T-209', 'Build the C clide client by default', 'Make the `clide-cli` target (Makefile:284) a dependency of `build` (Makefile:169) and `install` (Makefile:195) so the C client is always produced. Today it builds only via the explicit, non-default `make clide-cli`. Acceptance: a plain `make build` / `make install` produces the C client binary for the current OS.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'e2bead8a722cf0375adf79d22050b30e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-225', 'task', 'T-208', 'MCP transport reachable by an external agent', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface).', 'backlog', 'low', NULL, NULL, 'D-68', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'ebc32a0ac23296877246ac17f50d15b5', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-215', 'task', 'T-214', 'Export CLIDE_SOCK/CLIDE_WORKSPACE + ensure clide on the child PATH', 'When clide spawns/hosts an agent (the stream-json session per D-77, and any launched agent), inject CLIDE_SOCK (the per-workspace deterministic socket, D-70) and CLIDE_WORKSPACE into the child env, and guarantee `clide` is on the child PATH. Acceptance: a hosted agent''s shell has both vars and `clide ping` works with zero manual socket discovery. Blocked by Epic A delivery.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'fed9697da84ca9013659b9a882475c3a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-213', 'task', 'T-209', 'Regression test: clideVersion matches pubspec version', 'Gap 6 version-drift guard. clideVersion in lib/src/build_info.g.dart (generated by gen-build-info) currently matches pubspec.yaml `version:` (both 2.1.0). Add a test asserting they stay equal so an agent keying off `clide version` is never misled.', 'done', 'low', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, '1cc6925922c30c6f7e1dc9ee356de8e3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-211', 'task', 'T-209', 'install delivers the C client to PATH on macOS and Linux', 'Fix `make install` so $(INSTALL_DIR)/clide (default ~/.local/bin/clide) IS the compiled C client, not the Flutter GUI binary. Today Makefile:200 symlinks the GUI bundle runner on both platforms. Acceptance: after `make install`, `which clide` resolves and `clide ping` returns {"pong":true} against a running instance on macOS and Linux. Blocked by ''Build the C client by default''.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, '2e1f0a48393ff4f04079124fd2a1f579', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-210', 'task', 'T-209', 'Build the C clide client by default', 'Make the `clide-cli` target (Makefile:284) a dependency of `build` (Makefile:169) and `install` (Makefile:195) so the C client is always produced. Today it builds only via the explicit, non-default `make clide-cli`. Acceptance: a plain `make build` / `make install` produces the C client binary for the current OS.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:00:37', NULL, '738656b25288166130ca516a88b94784', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-224', 'task', 'T-208', 'Decide the dogfood agent model (governance Q/D record)', 'Gap 5 from self-analysis.md — foundational. Two distinct ''Claude inside clide'' stories with different gaps: (A) the clide-HOSTED stream-json session (D-77/D-78) — the user''s primary Claude pane, which itself needs `clide` on PATH to drive the surrounding IDE; (B) an EXTERNAL agent (e.g. a Claude Code harness, TERM_PROGRAM=zed as observed) driving via `clide ...` IPC, which clide cannot fully observe because plain file reads / `make test` / `git` bypass clide. Decide the intended model — hosted, external, or both — and record it under governance/ (claim via `pql decisions claim`). Gates how Epic B bootstraps and how Epic C frames observability. - -Resolved (2026-06-03): recorded as D-83. Decision — clide commits to BOTH agent models with the clide-HOSTED stream-json session (D-77/D-78) as the PRIMARY dogfood target (the one clide spawns, so the one Epic B/T-214 bootstraps: CLIDE_SOCK/CLIDE_WORKSPACE + PATH + context note + Bash(clide *) allow rule), and the EXTERNAL CLI driver (D-68) as a first-class but SECONDARY, best-effort integration (manual install via T-212; no promise to observe its non-clide tool use). Epic C/T-218 parity is scoped to clide''s own surfaces reflected through the CLI in both directions; an external agent''s side-channel reads/tests/git are out of parity scope.', 'done', 'high', NULL, NULL, 'D-77', '2026-06-02 18:13:52', '2026-06-03 09:05:50', NULL, '8ba80265666e3103ea82c02fdf48e632', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-215', 'task', 'T-214', 'Export CLIDE_SOCK/CLIDE_WORKSPACE + ensure clide on the child PATH', 'When clide spawns/hosts an agent (the stream-json session per D-77, and any launched agent), inject CLIDE_SOCK (the per-workspace deterministic socket, D-70) and CLIDE_WORKSPACE into the child env, and guarantee `clide` is on the child PATH. Acceptance: a hosted agent''s shell has both vars and `clide ping` works with zero manual socket discovery. Blocked by Epic A delivery.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, '220432969915ff7b84a64a3ef880399f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-217', 'task', 'T-214', 'Pre-seed a Bash(clide *) permission allow rule for hosted agents', 'So the hosted agent is not prompted on every `clide ...` call. Acceptance: clide-hosted sessions get a Bash(clide *) allow rule in their effective settings.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, 'e592d53b85d813172fc1eab377c407ba', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-216', 'task', 'T-214', 'Inject an agent context note describing the CLI surface + parity contract', 'Give the hosted agent a short note (CLAUDE.md fragment / system reminder) stating it is inside clide and how to drive the editor / git / file tree / panes via `clide ...`, plus the D-6 parity contract. Acceptance: a hosted agent is told, without the user having to explain it.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, 'ec64cf891f2150b6b6421a85e842899f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-214', 'epic', 'T-208', 'Bootstrap the clide-hosted/launched agent', 'Gap 2 from self-analysis.md. Even with the binary installed, a fresh session has no signal it is hosted by clide. There IS an MCP discovery lock (~/.claude/ide/.lock pointing at workspace + SSE URL) but NOTHING for the Bash/CLI path: no CLIDE_SOCK/CLIDE_WORKSPACE env var, no injected context note, no pre-seeded Bash(clide *) allow rule. Discovery is the difference between a capability existing and getting used — the probing agent had to be told ''you''re inside clide'' and then hunt for the socket.', 'done', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-03 09:18:38', NULL, '5c952e43be93015d72028c67267cc608', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-219', 'task', 'T-218', 'Register built-in panes into the PaneRegistry the CLI reads', 'Make the kernel layout''s TabContribution panes (Claude / Files / Editor and any viewer) visible to `pane list` / `pane focus` by registering or mirroring them into PaneRegistry (lib/src/panes/registry.dart, read by pane_commands.dart:121). Acceptance: with the GUI open, `clide pane list` enumerates the live panes the user sees, with stable ids and focus state.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:27:42', NULL, 'dd0ea802eb1ddb83861c55b6fa6e7558', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-220', 'task', 'T-218', 'Reflect active editor buffer + focused file + selection into EditorRegistry', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:27:42', NULL, 'f8be4d7da470d879f0c8e639261afbfa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'task', NULL, 'Shift+Tab work-mode cycler on the focused Claude pane', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-03 09:37:54', '2026-06-03 09:37:54', NULL, '52f082ae878e8a59995084b25c70086d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'task', NULL, 'Shift+Tab work-mode cycler on the focused Claude pane', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82.', 'ready', 'medium', NULL, NULL, 'D-78', '2026-06-03 09:37:54', '2026-06-03 09:45:20', NULL, '066af028173ddba082553e1ea4d17cd0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-229', 'epic', NULL, 'Claude composer ergonomics: focus, persistence, and history', 'Make the Claude composer (the prompt input at the bottom of the Claude pane) feel effortless to use. Groups the input-box ergonomics work: - -- T-227 — click empty pane area to focus the composer -- T-228 — preserve the in-progress draft across UI changes (permission prompts etc.) -- T-163 — Up/Down arrow recall of prompt history (Claude-CLI-style), triggered when the caret hits the top/bottom edge of the field - -Theme: the composer should be easy to focus, never lose what you typed, and let you recall and re-send prior prompts the way the Claude CLI does. These are independent leaves that share the composer surface and per-session draft/history state, so they''re worth coordinating.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-03 09:51:28', '2026-06-03 09:51:28', NULL, '5486c56c01d5959e253e644daa05abb9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-163', 'task', 'T-229', 'Composer prompt history via Up/Down (Claude-CLI-style)', 'Arrow Up/Down in the Claude composer should scroll through previously-sent prompts, matching the Claude Code CLI UX: -- Up: if the caret isn''t on the first visual line, move up one line (normal editing); once on the first line, Up steps to the previous prompt in history. Down mirrors it (move down within multiline; at the last line, step to the next/newer entry). -- When entering history, stash the current draft as the newest entry so Down past the most recent restores exactly what the user was typing. -- History is the sequence of prompts submitted from this composer (this session at least; persisting across restarts is a nice-to-have). -- Plays well with the existing Enter-submit / Shift+Enter-newline and the slash typeahead (Up/Down already drive the typeahead when it''s open — history nav only applies when the typeahead is closed). - -Implementation: a small history controller + caret-line detection in ClaudeComposer (EditableText); the Up/Down handling lives alongside the existing _onKey/Shortcuts. Acceptance: send a few prompts, Up walks back through them, Down returns through them to the restored draft; multiline prompts navigate line-by-line before crossing into history; widget tests for the bound conditions (first/last line) and draft stash/restore.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-24 09:42:44', '2026-06-03 09:51:40', NULL, '6782b760791a181507df5c3f3d9f6847', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-227', 'story', 'T-229', 'Click empty Claude-pane area to focus the composer', 'Clicking anywhere in the Claude pane that is NOT an interactive control (copy button, approve/reject, AskUserQuestion options, links, selectable transcript text, scrollbars, tabs) should move keyboard focus to the composer text field at the bottom. Today focusing the composer requires precisely clicking the field; a click on the conversation background does nothing, so the input is fiddly to focus. - -Goal: make the composer easy to focus — a click on dead space in the pane lands the cursor in the input, like clicking an empty area of a chat app focuses its reply box. - -Acceptance: -1. A tap on non-interactive Claude-pane background focuses the composer input (cursor ready to type). -2. Taps on actual controls keep their behavior (copy, approve/reject, AskUserQuestion choices, tab strip, links) — the focus-grab must not swallow or pre-empt their gestures. -3. Text selection in the transcript still works: a click-drag to select must NOT steal focus to the composer (only a plain tap/click-release with no selection focuses it). -4. Interaction-zone nuance (D-78): when a permission / AskUserQuestion prompt has replaced the composer in the interaction zone, a background click should focus that prompt''s primary control instead of the (absent) composer — or do nothing — never resurrect the composer. - -Implementation sketch: wrap the pane body in a tap recognizer (behavior: translucent / deferToChild) that, on a tap that did not hit a child gesture and produced no text selection, requests focus on the composer FocusNode. Mind the SelectableRegion so selection drags aren''t hijacked.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-03 09:48:23', '2026-06-03 09:51:40', NULL, '77761c0034f51145c210cecb75a104cb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-228', 'bug', 'T-229', 'Composer draft lost when the Claude pane UI changes', 'Text typed into the Claude composer is lost whenever the pane UI changes underneath it. The clearest trigger: a permission prompt / AskUserQuestion arrives and the interaction zone replaces the composer; when it resolves and the composer returns, the half-typed message is gone. It can also drop on any pane rebuild/refresh (new transcript message, status change, tab re-render) if the draft lives only in ephemeral widget State. +Resolved (2026-06-03): recorded as D-83. Decision — clide commits to BOTH agent models with the clide-HOSTED stream-json session (D-77/D-78) as the PRIMARY dogfood target (the one clide spawns, so the one Epic B/T-214 bootstraps: CLIDE_SOCK/CLIDE_WORKSPACE + PATH + context note + Bash(clide *) allow rule), and the EXTERNAL CLI driver (D-68) as a first-class but SECONDARY, best-effort integration (manual install via T-212; no promise to observe its non-clide tool use). Epic C/T-218 parity is scoped to clide''s own surfaces reflected through the CLI in both directions; an external agent''s side-channel reads/tests/git are out of parity scope.', 'done', 'high', NULL, NULL, 'D-77', '2026-06-02 18:13:52', '2026-06-03 09:05:50', NULL, 'f7b7cda4651e06371d642fe86313466a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM51CNEJP52P79RDVJW', 'task', '06FB0TNQM78J2H2S0R79SSSPW8', 'Inject an agent context note describing the CLI surface + parity contract', 'Give the hosted agent a short note (CLAUDE.md fragment / system reminder) stating it is inside clide and how to drive the editor / git / file tree / panes via `clide ...`, plus the D-6 parity contract. Acceptance: a hosted agent is told, without the user having to explain it.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, '1a21a4b790c9cbb2c45c26a87688ec22', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7WYF8ESBSGAQF1S48', 'task', '06FB0TNQM78J2H2S0R79SSSPW8', 'Pre-seed a Bash(clide *) permission allow rule for hosted agents', 'So the hosted agent is not prompted on every `clide ...` call. Acceptance: clide-hosted sessions get a Bash(clide *) allow rule in their effective settings.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, '5426b5365b2d54a9ad55cc5338f748c6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4TP6KXTB1GHKBW1G4', 'task', '06FB0TNQM78J2H2S0R79SSSPW8', 'Export CLIDE_SOCK/CLIDE_WORKSPACE + ensure clide on the child PATH', 'When clide spawns/hosts an agent (the stream-json session per D-77, and any launched agent), inject CLIDE_SOCK (the per-workspace deterministic socket, D-70) and CLIDE_WORKSPACE into the child env, and guarantee `clide` is on the child PATH. Acceptance: a hosted agent''s shell has both vars and `clide ping` works with zero manual socket discovery. Blocked by Epic A delivery.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 09:18:28', NULL, 'fa6e599cfdd6165043ab54427f16d298', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78J2H2S0R79SSSPW8', 'epic', '06FB0TNQM7XMQ44AVCAARQZX14', 'Bootstrap the clide-hosted/launched agent', 'Gap 2 from self-analysis.md. Even with the binary installed, a fresh session has no signal it is hosted by clide. There IS an MCP discovery lock (~/.claude/ide/.lock pointing at workspace + SSE URL) but NOTHING for the Bash/CLI path: no CLIDE_SOCK/CLIDE_WORKSPACE env var, no injected context note, no pre-seeded Bash(clide *) allow rule. Discovery is the difference between a capability existing and getting used — the probing agent had to be told ''you''re inside clide'' and then hunt for the socket.', 'done', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-03 09:18:38', NULL, 'f6131db7ead26edd61619c18cc816e72', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XZQJAK31WRVQ0HGW', 'bug', '06FB0TNQM68T660K07YH3X5DH0', 'Composer draft lost when the Claude pane UI changes', 'Text typed into the Claude composer is lost whenever the pane UI changes underneath it. The clearest trigger: a permission prompt / AskUserQuestion arrives and the interaction zone replaces the composer; when it resolves and the composer returns, the half-typed message is gone. It can also drop on any pane rebuild/refresh (new transcript message, status change, tab re-render) if the draft lives only in ephemeral widget State. Make the draft persistent: an unsent composer message must survive UI changes and come back intact. @@ -130,35 +36,15 @@ Acceptance: 3. Draft is per-session: switching between Claude sessions/panes preserves each session''s own unsent text; sending or explicitly clearing empties it. 4. Selection/caret position preserved, not just the string (nice-to-have). -Implementation sketch: store the draft (text + selection) on the per-session model/controller (kernel- or session-scoped), not in the composer widget''s State. Seed the TextEditingController from it on mount and write back on change, so a remount after the interaction zone closes restores it. Relates to T-227 (focusing the composer) and the interaction-zone behavior where prompts replace the composer rather than rendering inline.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-03 09:49:35', '2026-06-03 09:54:14', NULL, '768ef9874198e50fa92f208e4ada79e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-163', 'task', 'T-229', 'Composer prompt history via Up/Down (Claude-CLI-style)', 'Arrow Up/Down in the Claude composer should scroll through previously-sent prompts, matching the Claude Code CLI UX: +Implementation sketch: store the draft (text + selection) on the per-session model/controller (kernel- or session-scoped), not in the composer widget''s State. Seed the TextEditingController from it on mount and write back on change, so a remount after the interaction zone closes restores it. Relates to T-227 (focusing the composer) and the interaction-zone behavior where prompts replace the composer rather than rendering inline.', 'done', 'high', NULL, NULL, NULL, '2026-06-03 09:49:35', '2026-06-03 10:02:02', NULL, '63ae2f059c866728f67f7009b420657b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7SA9TEX5CNMTG67MW', 'task', '06FB0TNQM68T660K07YH3X5DH0', 'Composer prompt history via Up/Down (Claude-CLI-style)', 'Arrow Up/Down in the Claude composer should scroll through previously-sent prompts, matching the Claude Code CLI UX: - Up: if the caret isn''t on the first visual line, move up one line (normal editing); once on the first line, Up steps to the previous prompt in history. Down mirrors it (move down within multiline; at the last line, step to the next/newer entry). - When entering history, stash the current draft as the newest entry so Down past the most recent restores exactly what the user was typing. - History is the sequence of prompts submitted from this composer (this session at least; persisting across restarts is a nice-to-have). - Plays well with the existing Enter-submit / Shift+Enter-newline and the slash typeahead (Up/Down already drive the typeahead when it''s open — history nav only applies when the typeahead is closed). -Implementation: a small history controller + caret-line detection in ClaudeComposer (EditableText); the Up/Down handling lives alongside the existing _onKey/Shortcuts. Acceptance: send a few prompts, Up walks back through them, Down returns through them to the restored draft; multiline prompts navigate line-by-line before crossing into history; widget tests for the bound conditions (first/last line) and draft stash/restore.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-24 09:42:44', '2026-06-03 10:02:02', NULL, '12f258c45979300ed8c0ecc52ebbe87e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-228', 'bug', 'T-229', 'Composer draft lost when the Claude pane UI changes', 'Text typed into the Claude composer is lost whenever the pane UI changes underneath it. The clearest trigger: a permission prompt / AskUserQuestion arrives and the interaction zone replaces the composer; when it resolves and the composer returns, the half-typed message is gone. It can also drop on any pane rebuild/refresh (new transcript message, status change, tab re-render) if the draft lives only in ephemeral widget State. - -Make the draft persistent: an unsent composer message must survive UI changes and come back intact. - -Likely cause: the composer''s TextEditingController lives in widget State that is disposed/recreated when the pane rebuilds or the interaction zone swaps the composer out. The draft (text + selection) needs to be hoisted somewhere that outlives the widget. - -Acceptance: -1. Type in the composer, trigger a permission prompt (composer replaced by the interaction zone), resolve it -> the composer returns with the typed text and caret intact. -2. The draft survives an arbitrary Claude-pane rebuild (incoming transcript message, status update, sub-tab switch) without loss. -3. Draft is per-session: switching between Claude sessions/panes preserves each session''s own unsent text; sending or explicitly clearing empties it. -4. Selection/caret position preserved, not just the string (nice-to-have). - -Implementation sketch: store the draft (text + selection) on the per-session model/controller (kernel- or session-scoped), not in the composer widget''s State. Seed the TextEditingController from it on mount and write back on change, so a remount after the interaction zone closes restores it. Relates to T-227 (focusing the composer) and the interaction-zone behavior where prompts replace the composer rather than rendering inline.', 'done', 'high', NULL, NULL, NULL, '2026-06-03 09:49:35', '2026-06-03 10:02:02', NULL, 'bdd509507b199031cb2b368a2813245a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-163', 'task', 'T-229', 'Composer prompt history via Up/Down (Claude-CLI-style)', 'Arrow Up/Down in the Claude composer should scroll through previously-sent prompts, matching the Claude Code CLI UX: -- Up: if the caret isn''t on the first visual line, move up one line (normal editing); once on the first line, Up steps to the previous prompt in history. Down mirrors it (move down within multiline; at the last line, step to the next/newer entry). -- When entering history, stash the current draft as the newest entry so Down past the most recent restores exactly what the user was typing. -- History is the sequence of prompts submitted from this composer (this session at least; persisting across restarts is a nice-to-have). -- Plays well with the existing Enter-submit / Shift+Enter-newline and the slash typeahead (Up/Down already drive the typeahead when it''s open — history nav only applies when the typeahead is closed). - -Implementation: a small history controller + caret-line detection in ClaudeComposer (EditableText); the Up/Down handling lives alongside the existing _onKey/Shortcuts. Acceptance: send a few prompts, Up walks back through them, Down returns through them to the restored draft; multiline prompts navigate line-by-line before crossing into history; widget tests for the bound conditions (first/last line) and draft stash/restore.', 'done', 'medium', NULL, NULL, NULL, '2026-05-24 09:42:44', '2026-06-03 10:05:55', NULL, '267d50ea429ba291e70a76201387bff7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-227', 'story', 'T-229', 'Click empty Claude-pane area to focus the composer', 'Clicking anywhere in the Claude pane that is NOT an interactive control (copy button, approve/reject, AskUserQuestion options, links, selectable transcript text, scrollbars, tabs) should move keyboard focus to the composer text field at the bottom. Today focusing the composer requires precisely clicking the field; a click on the conversation background does nothing, so the input is fiddly to focus. +Implementation: a small history controller + caret-line detection in ClaudeComposer (EditableText); the Up/Down handling lives alongside the existing _onKey/Shortcuts. Acceptance: send a few prompts, Up walks back through them, Down returns through them to the restored draft; multiline prompts navigate line-by-line before crossing into history; widget tests for the bound conditions (first/last line) and draft stash/restore.', 'done', 'medium', NULL, NULL, NULL, '2026-05-24 09:42:44', '2026-06-03 10:05:55', NULL, '4d0babaecb6b5cb3f77222e9ed786f3b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5PFAYE4XH7WV6VDW8', 'story', '06FB0TNQM68T660K07YH3X5DH0', 'Click empty Claude-pane area to focus the composer', 'Clicking anywhere in the Claude pane that is NOT an interactive control (copy button, approve/reject, AskUserQuestion options, links, selectable transcript text, scrollbars, tabs) should move keyboard focus to the composer text field at the bottom. Today focusing the composer requires precisely clicking the field; a click on the conversation background does nothing, so the input is fiddly to focus. Goal: make the composer easy to focus — a click on dead space in the pane lands the cursor in the input, like clicking an empty area of a chat app focuses its reply box. @@ -168,106 +54,33 @@ Acceptance: 3. Text selection in the transcript still works: a click-drag to select must NOT steal focus to the composer (only a plain tap/click-release with no selection focuses it). 4. Interaction-zone nuance (D-78): when a permission / AskUserQuestion prompt has replaced the composer in the interaction zone, a background click should focus that prompt''s primary control instead of the (absent) composer — or do nothing — never resurrect the composer. -Implementation sketch: wrap the pane body in a tap recognizer (behavior: translucent / deferToChild) that, on a tap that did not hit a child gesture and produced no text selection, requests focus on the composer FocusNode. Mind the SelectableRegion so selection drags aren''t hijacked.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-03 09:48:23', '2026-06-03 10:05:55', NULL, 'f8cede4c62e453b421cae5ba9a54af2b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-227', 'story', 'T-229', 'Click empty Claude-pane area to focus the composer', 'Clicking anywhere in the Claude pane that is NOT an interactive control (copy button, approve/reject, AskUserQuestion options, links, selectable transcript text, scrollbars, tabs) should move keyboard focus to the composer text field at the bottom. Today focusing the composer requires precisely clicking the field; a click on the conversation background does nothing, so the input is fiddly to focus. - -Goal: make the composer easy to focus — a click on dead space in the pane lands the cursor in the input, like clicking an empty area of a chat app focuses its reply box. - -Acceptance: -1. A tap on non-interactive Claude-pane background focuses the composer input (cursor ready to type). -2. Taps on actual controls keep their behavior (copy, approve/reject, AskUserQuestion choices, tab strip, links) — the focus-grab must not swallow or pre-empt their gestures. -3. Text selection in the transcript still works: a click-drag to select must NOT steal focus to the composer (only a plain tap/click-release with no selection focuses it). -4. Interaction-zone nuance (D-78): when a permission / AskUserQuestion prompt has replaced the composer in the interaction zone, a background click should focus that prompt''s primary control instead of the (absent) composer — or do nothing — never resurrect the composer. - -Implementation sketch: wrap the pane body in a tap recognizer (behavior: translucent / deferToChild) that, on a tap that did not hit a child gesture and produced no text selection, requests focus on the composer FocusNode. Mind the SelectableRegion so selection drags aren''t hijacked.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 09:48:23', '2026-06-03 10:09:26', NULL, '173f374747e44ab8b989e68e1cace427', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-229', 'epic', NULL, 'Claude composer ergonomics: focus, persistence, and history', 'Make the Claude composer (the prompt input at the bottom of the Claude pane) feel effortless to use. Groups the input-box ergonomics work: +Implementation sketch: wrap the pane body in a tap recognizer (behavior: translucent / deferToChild) that, on a tap that did not hit a child gesture and produced no text selection, requests focus on the composer FocusNode. Mind the SelectableRegion so selection drags aren''t hijacked.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 09:48:23', '2026-06-03 10:09:26', NULL, '68310a50972a774abfeadd32f221a140', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM68T660K07YH3X5DH0', 'epic', NULL, 'Claude composer ergonomics: focus, persistence, and history', 'Make the Claude composer (the prompt input at the bottom of the Claude pane) feel effortless to use. Groups the input-box ergonomics work: - T-227 — click empty pane area to focus the composer - T-228 — preserve the in-progress draft across UI changes (permission prompts etc.) - T-163 — Up/Down arrow recall of prompt history (Claude-CLI-style), triggered when the caret hits the top/bottom edge of the field -Theme: the composer should be easy to focus, never lose what you typed, and let you recall and re-send prior prompts the way the Claude CLI does. These are independent leaves that share the composer surface and per-session draft/history state, so they''re worth coordinating.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 09:51:28', '2026-06-03 10:09:26', NULL, '844cfc2043d37af092a0cde6a31f619c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-219', 'task', 'T-218', 'Register built-in panes into the PaneRegistry the CLI reads', 'Make the kernel layout''s TabContribution panes (Claude / Files / Editor and any viewer) visible to `pane list` / `pane focus` by registering or mirroring them into PaneRegistry (lib/src/panes/registry.dart, read by pane_commands.dart:121). Acceptance: with the GUI open, `clide pane list` enumerates the live panes the user sees, with stable ids and focus state.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 10:31:10', NULL, 'c61622fb5b14bed734e8a3d8dc096be4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-220', 'task', 'T-218', 'Reflect active editor buffer + focused file + selection into EditorRegistry', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`. +Theme: the composer should be easy to focus, never lose what you typed, and let you recall and re-send prior prompts the way the Claude CLI does. These are independent leaves that share the composer surface and per-session draft/history state, so they''re worth coordinating.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 09:51:28', '2026-06-03 10:09:26', NULL, 'ec5d2dcaf4e33b2d4dde1b3d4623110a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6Y5SET5WGSS2W78F8', 'task', '06FB0TNQM7AMEYEBTF8YGJ4BVG', 'Register built-in panes into the PaneRegistry the CLI reads', 'Make the kernel layout''s TabContribution panes (Claude / Files / Editor and any viewer) visible to `pane list` / `pane focus` by registering or mirroring them into PaneRegistry (lib/src/panes/registry.dart, read by pane_commands.dart:121). Acceptance: with the GUI open, `clide pane list` enumerates the live panes the user sees, with stable ids and focus state.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 10:31:10', NULL, '697f1f648cd46893ac5863c139ba4bee', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6EGSD0SB7NYNSP1BM', 'task', '06FB0TNQM7AMEYEBTF8YGJ4BVG', 'Reflect active editor buffer + focused file + selection into EditorRegistry', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`. -Resolved (2026-06-03): done by prior editor work, not new code. Verified by reading the flow: openWorkspaceFile (file_open.dart) routes non-.md opens through the editor.open IPC verb -> EditorRegistry.open -> _setActive, so file-tree/quick-open opens already populate ''editor active''/''editor list''; tab switches go through editor.activate; and editor_view._onTextChanged pushes editor.set-content on caret-only moves too (its guard skips only when BOTH text and selection are unchanged), so selection is reflected. The probe saw editor active=null only because no code file was open (just the Claude pane / a markdown reader) -- correct, not a gap. The genuine remainder -- the read-only markdown/decisions READER''s currently-viewed file (routed via the D-81 ReaderNav bus, not editor.open, and rightly NOT an EditorRegistry buffer) -- is folded into T-221 (clide status: focused file + selection). Editor-reflects-opens claim is code-read, to be confirmed on the next live run.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 10:59:25', NULL, '1c3c6b32c583a0017e1e1bb519e115aa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-221', 'task', 'T-218', 'Add a clide status one-shot snapshot command', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields. +Resolved (2026-06-03): done by prior editor work, not new code. Verified by reading the flow: openWorkspaceFile (file_open.dart) routes non-.md opens through the editor.open IPC verb -> EditorRegistry.open -> _setActive, so file-tree/quick-open opens already populate ''editor active''/''editor list''; tab switches go through editor.activate; and editor_view._onTextChanged pushes editor.set-content on caret-only moves too (its guard skips only when BOTH text and selection are unchanged), so selection is reflected. The probe saw editor active=null only because no code file was open (just the Claude pane / a markdown reader) -- correct, not a gap. The genuine remainder -- the read-only markdown/decisions READER''s currently-viewed file (routed via the D-81 ReaderNav bus, not editor.open, and rightly NOT an EditorRegistry buffer) -- is folded into T-221 (clide status: focused file + selection). Editor-reflects-opens claim is code-read, to be confirmed on the next live run.', 'done', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 10:59:25', NULL, '178db75de081136eebacc44488a52397', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67ET3VQP90WRXVSMG', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Per-agent permission-mode cycle badge in the cockpit roster', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator). -Scope addition (2026-06-03, from T-220): clide status must surface the focused file + selection from BOTH the editor (EditorRegistry.active, already populated) AND the read-only markdown/decisions reader''s current file (D-81 ReaderNav), since viewer files rightly do not live in EditorRegistry. So ''focused file'' = whichever of {active editor buffer, active reader doc} the user is currently looking at. Pane field comes from the T-219 view-pane snapshot; git summary from git.status; layout from the arrangement.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 10:59:31', NULL, '9dc92f17b898784bcae0ed1a21813884', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'story', 'T-132', 'Cluster meta messages into a collapsible activity card', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png. +Verified done (2026-06-03): the cockpit per-agent permission-mode cycle badge is implemented and wired in claude_meta_sidebar.dart — _PermissionModeBadge (safe cycle default->acceptEdits->plan, Shift-click bypass behind a confirm, live mode from SessionStatus). No further work; closing. The primary-pane equivalent is T-226.', 'done', 'low', NULL, NULL, 'D-77', '2026-05-25 09:16:05', '2026-06-03 11:41:17', NULL, 'ccaa859108ef4e5367f2b149ce4f3d28', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM758PXMJH16PA3JHFW', 'task', '06FB0TNQM7AMEYEBTF8YGJ4BVG', 'Add a clide status one-shot snapshot command', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields. -Fold runs of consecutive ''meta'' items into one live, collapsible activity card: -- The card shows the MOST RECENT meta line as a live ticker and updates in place as new ones stream in — a running ''what''s happening now''. A step count (''14 steps'') sits on the right. -- Collapsed by default; click the chevron to expand the full list of folded steps, click to re-collapse. -- ''Sticky'' items never fold — they render first-class. A sticky item SEALS the current card and a new cluster begins after it. A cluster = one unbroken run of foldable items between two sticky items. +Scope addition (2026-06-03, from T-220): clide status must surface the focused file + selection from BOTH the editor (EditorRegistry.active, already populated) AND the read-only markdown/decisions reader''s current file (D-81 ReaderNav), since viewer files rightly do not live in EditorRegistry. So ''focused file'' = whichever of {active editor buffer, active reader doc} the user is currently looking at. Pane field comes from the T-219 view-pane snapshot; git summary from git.status; layout from the arrangement.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 11:50:02', NULL, '9b5887b0dba408afb7721e957cc8c483', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7AMEYEBTF8YGJ4BVG', 'epic', '06FB0TNQM7XMQ44AVCAARQZX14', 'Make the live workspace observable to the CLI (D-6 parity)', 'Gap 3 from self-analysis.md, verified in code. PaneRegistry (lib/src/panes/registry.dart) is written ONLY by the pane.spawn IPC path, so the built-in TabContribution panes — Claude (builtin/claude/src/extension.dart:42), Files (builtin/files/.../extension.dart:22), Editor (builtin/editor/.../extension.dart:52) — render in the kernel layout tree but never appear in `pane list` (read at pane_commands.dart:121). EditorRegistry is written by editor.open IPC (the Files tree uses it) but the active Claude pane / focused file / selection are not reflected. Result while the UI is in use: `clide pane list` -> {"panes":[]}, `clide editor active` -> {"active":null}. The ''agent sees what the user sees'' half of D-6 is missing.', 'done', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-03 11:50:08', NULL, 'e7c51439a2c4e6f291284cf6add77602', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GJRWZVHZF24TPPS4', 'task', NULL, 'Shift+Tab work-mode cycler on the focused Claude pane', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82. -Taxonomy over the sealed ConversationItem set (transcript_reader.dart): UserMessage, AssistantTextMessage, AssistantThinkingMessage, AssistantToolUse, ToolResultMessage. -- Always sticky: UserMessage, AssistantTextMessage. Permission/AskUserQuestion prompts already live in the interaction zone (D-78) and are inherently sticky / cluster-breaking — no change, but note they break the run too. -- Foldable: AssistantToolUse + its paired ToolResultMessage (pairing already exists via toolUseById, T-168). +Refinement (2026-06-03): Shift+Tab is REJECTED as the trigger — Tab/Shift+Tab are real a11y focus-traversal intents (focus.next/focus.previous) since T-204, so hijacking Shift+Tab would break keyboard navigation. Instead: (a) Ctrl/Cmd+M cycles, intercepted at the focused Claude composer (so it targets that pane''s session, no global ''find focused session'' needed); (b) the status-line mode label becomes a FOCUSABLE button — cycles on click and on Enter/Space when focused (a11y-native, Tab reaches it); (c) a ''Claude: Cycle permission mode'' palette command. Safe trio default->acceptEdits->plan->default; bypassPermissions only via explicit confirmed path.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-03 09:37:54', '2026-06-03 11:50:23', NULL, 'ac8c4672be7aa07a7ade499da4acd9a3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM427BVZYN6MD7ZZX74', 'task', '06FB0TNQM7XMQ44AVCAARQZX14', 'CLI verb to open a doc in a UI reader (drive-half of D-6 parity)', 'Dogfood finding (2026-06-03): Epic C gave the CLI the OBSERVE half of D-6 parity (clide status / pane list read live UI state). The DRIVE half is missing: there is no CLI/IPC verb that opens a doc in a UI reader. Opening a ticket/decision/markdown doc happens only inside the Flutter app, by publishing to the kernel MessageBus (e.g. messages.publish(''builtin.tickets'',''selection'',{''id'':id}); see tickets/src/extension.dart:29 which subscribes publisher ''builtin.tickets'' channel ''selection'', and ticket_detail_view.dart). lib/src/daemon/ has NO verb that reaches the MessageBus, so an agent cannot say ''look at this with me''. Proposed: add a bridge verb (e.g. ui.open / view.open ) that publishes a ''selection'' message to the bus, so ''clide ui open tickets T-48'' opens T-48 in the ticket reader (and decisions/markdown analogously). Wiring: thread the kernel MessageBus into the dispatcher the same way panels/readerNav were threaded for T-219/T-221 (daemonClientFactory param or post-boot capture in main.dart). Honor D-6 (every UI action has a CLI verb). Complements Epic C (T-218): observe -> drive.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:17:32', '2026-06-03 13:30:05', NULL, 'f26d2dca0dc0d4d3c7b107347e2f22a5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BJCK5YG3R0NEEF70', 'task', '06FB0TNQM7XMQ44AVCAARQZX14', 'CLI argv args dont reach typed command handlers (positional/flags vs named)', 'Major dogfood finding (2026-06-03), verified live: parameterized subsystem commands are NOT reachable from the clide CLI. The C client sends raw argv; parseArgv (lib/src/cli/argv_to_request.dart) turns ''clide editor open X'' into args={positional:[X]} (also flags:{}, passthrough:[]). But the typed handlers read NAMED top-level keys: editor.open reads args[''path''] (editor_commands.dart:64), files.read reads args[''path''], pane.close/editor.activate read args[''id''], etc. Nothing maps positional/flags -> those names, so every arg-taking verb returns ''X is required'' from the CLI. Confirmed: ''clide editor open pubspec.yaml'' and ''--path=pubspec.yaml'' both -> ''path is required''; ''clide files read pubspec.yaml'' -> ''files.read requires a path''. grep shows ONLY the new ui_command.dart reads args[''positional'']. Net: an external agent can OBSERVE (no-arg reads: status, git status, files root, pane list, editor active) but cannot DRIVE anything parameterized -- undercutting CLI-first (D-1) and the D-6 parity premise behind the whole T-208 initiative. Needs a decision on the mapping contract: most ergonomic is a per-command POSITIONAL/flag schema declared where the handler registers (extends the co-registered schema of D-74) so ''clide editor open '' binds positional[0]->path; alternative is lifting --flags into top-level named args. Then either remap in argv_dispatch/parseArgv before dispatch, or have handlers read a normalized accessor. High priority: this is the gating bug for ''Give Claude hands''. -Refinement decisions (2026-06-03, user): -1. WHAT FOLDS is a 3-level setting (conservative → aggressive); DEFAULT = level 1. Build the foldable predicate so all three are switchable: - - L1 (default): fold tool calls + results only; diffs (Edit/Write results) AND AssistantThinkingMessage stay first-class/sticky. - - L2: also fold thinking; diffs stay sticky. - - L3: fold everything except UserMessage + AssistantTextMessage (incl. diffs + thinking). -2. ERRORS: a failed/error ToolResultMessage SURFACES — treated as sticky, stays visible and breaks the cluster (see wireframe state B). Start here; the user noted some non-fatal errors are themselves clutter, so expect to tune which errors surface vs. fold. -3. DEFAULT/RUN STATE: always collapsed, showing the last line + step count, with the ticker updating live. NO setting. (Rejected auto-expanding the in-flight cluster: it scrolls the last useful sticky message out of view during long runs and only collapses when done — worst timing. Collapsed-with-live-ticker keeps the last sticky message anchored while preserving the motion/dynamism.) - -Implementation home: a ''collapse adjacent foldable items'' grouping pass over ConversationController.items, consumed by ConversationView; the card is a new conversation_card variant. Keep it accessible (keyboard expand/collapse + semantics: announce step count and collapsed/expanded state). - -Acceptance: -1. Consecutive foldable items collapse into one card; the collapsed card shows the latest line + a live-updating step count. -2. A sticky message (user / Claude prose / surfaced error / interaction-zone prompt) seals the card and starts a new cluster after it. -3. Expanding shows every folded step (tool call + result pairs) in order; collapsing returns to the one-line view. -4. The foldable level is a setting (default L1: diffs + thinking stay first-class); switching levels re-groups the transcript. -5. Failed tool results surface as their own (sticky) row and break the cluster. -6. Keyboard + screen-reader accessible (expand/collapse, step count announced).', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-03 11:15:39', '2026-06-03 11:15:39', NULL, '82fe31fc833dad4ac84a09ce00b3a17f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-221', 'task', 'T-218', 'Add a clide status one-shot snapshot command', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields. - -Scope addition (2026-06-03, from T-220): clide status must surface the focused file + selection from BOTH the editor (EditorRegistry.active, already populated) AND the read-only markdown/decisions reader''s current file (D-81 ReaderNav), since viewer files rightly do not live in EditorRegistry. So ''focused file'' = whichever of {active editor buffer, active reader doc} the user is currently looking at. Pane field comes from the T-219 view-pane snapshot; git summary from git.status; layout from the arrangement.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 11:17:49', NULL, 'd14252d59f0932f126f32ee73ff09d69', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'task', NULL, 'Shift+Tab work-mode cycler on the focused Claude pane', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82. - -Refinement (2026-06-03): Shift+Tab is REJECTED as the trigger — Tab/Shift+Tab are real a11y focus-traversal intents (focus.next/focus.previous) since T-204, so hijacking Shift+Tab would break keyboard navigation. Instead: (a) Ctrl/Cmd+M cycles, intercepted at the focused Claude composer (so it targets that pane''s session, no global ''find focused session'' needed); (b) the status-line mode label becomes a FOCUSABLE button — cycles on click and on Enter/Space when focused (a11y-native, Tab reaches it); (c) a ''Claude: Cycle permission mode'' palette command. Safe trio default->acceptEdits->plan->default; bypassPermissions only via explicit confirmed path.', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-03 09:37:54', '2026-06-03 11:38:13', NULL, '5dd59c6cbf6f5586fef7c5f873bdfde4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-181', 'task', 'T-164', 'Per-agent permission-mode cycle badge in the cockpit roster', 'Per-agent permission-mode control in the cockpit roster (extends T-171). Each roster row shows a mode badge (D default / A acceptEdits / P plan). Click cycles the SAFE trio default -> acceptEdits -> plan and sends set_permission_mode to that agent session (a confirmed stream-json control subtype). bypassPermissions is a footgun, so it is reachable only on SHIFT-click (behind a confirm), and the tooltip documents both behaviours. Acceptance: clicking the badge cycles the safe modes and the session receives set_permission_mode; shift-click can reach bypass behind a confirm; the badge reflects the live mode (T-157 status); tooltip explains click vs shift-click; widget + transport tests. Wireframe: docs/design/wireframes/claude-prompts/05-team-cockpit-sidebar.png. Blocked by T-169 (orchestrator). - -Verified done (2026-06-03): the cockpit per-agent permission-mode cycle badge is implemented and wired in claude_meta_sidebar.dart — _PermissionModeBadge (safe cycle default->acceptEdits->plan, Shift-click bypass behind a confirm, live mode from SessionStatus). No further work; closing. The primary-pane equivalent is T-226.', 'done', 'low', NULL, NULL, 'D-77', '2026-05-25 09:16:05', '2026-06-03 11:41:17', NULL, '942902ee2825b610673362e5c0add5f7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-221', 'task', 'T-218', 'Add a clide status one-shot snapshot command', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields. - -Scope addition (2026-06-03, from T-220): clide status must surface the focused file + selection from BOTH the editor (EditorRegistry.active, already populated) AND the read-only markdown/decisions reader''s current file (D-81 ReaderNav), since viewer files rightly do not live in EditorRegistry. So ''focused file'' = whichever of {active editor buffer, active reader doc} the user is currently looking at. Pane field comes from the T-219 view-pane snapshot; git summary from git.status; layout from the arrangement.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 11:50:02', NULL, '976aa2a5126f7d0a7dcfa33eb064fd59', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-218', 'epic', 'T-208', 'Make the live workspace observable to the CLI (D-6 parity)', 'Gap 3 from self-analysis.md, verified in code. PaneRegistry (lib/src/panes/registry.dart) is written ONLY by the pane.spawn IPC path, so the built-in TabContribution panes — Claude (builtin/claude/src/extension.dart:42), Files (builtin/files/.../extension.dart:22), Editor (builtin/editor/.../extension.dart:52) — render in the kernel layout tree but never appear in `pane list` (read at pane_commands.dart:121). EditorRegistry is written by editor.open IPC (the Files tree uses it) but the active Claude pane / focused file / selection are not reflected. Result while the UI is in use: `clide pane list` -> {"panes":[]}, `clide editor active` -> {"active":null}. The ''agent sees what the user sees'' half of D-6 is missing.', 'done', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-03 11:50:08', NULL, 'dd286b771087bfc96d6d3d65e76a6b09', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-226', 'task', NULL, 'Shift+Tab work-mode cycler on the focused Claude pane', 'Gap found while dogfooding (2026-06-03): the Claude pane shows the permission mode in its status line (permissionModeLabel, claude_status.dart:44) but has NO quick way to CHANGE it, unlike the CLI where Shift+Tab cycles work modes (plan / read / edit / yolo == plan, default, acceptEdits, bypassPermissions). Today the only interactive setter is the team meta-sidebar per-agent control (claude_meta_sidebar.dart:342); the primary pane has none. There is a command claude.agent.set-permission-mode (extension.dart:148) but it needs an explicit id + explicit mode, and there is no ''cycle'' verb. setPermissionMode() over the stream-json control channel already works (stream_json_session.dart:671, D-78). Scope: (1) add a mode-cycle action that targets the focused/primary session and advances the safe trio default -> acceptEdits -> plan -> default (bypassPermissions only via a confirmed/explicit path, per the T-181 footgun guard); (2) bind it to Shift+Tab INTERCEPTED at the focused Claude composer -- note shift+tab is globally focus.previous (default.yaml:31), so this needs consumer-level interception like the Vim editor (D-82), falling back to focus traversal elsewhere; (3) make the status-line mode label a clickable badge that cycles on click (the ''cockpit badge'' the stream_json_session comment already promises at line 668-669). Relates to T-181, D-77/D-78, D-82. - -Refinement (2026-06-03): Shift+Tab is REJECTED as the trigger — Tab/Shift+Tab are real a11y focus-traversal intents (focus.next/focus.previous) since T-204, so hijacking Shift+Tab would break keyboard navigation. Instead: (a) Ctrl/Cmd+M cycles, intercepted at the focused Claude composer (so it targets that pane''s session, no global ''find focused session'' needed); (b) the status-line mode label becomes a FOCUSABLE button — cycles on click and on Enter/Space when focused (a11y-native, Tab reaches it); (c) a ''Claude: Cycle permission mode'' palette command. Safe trio default->acceptEdits->plan->default; bypassPermissions only via explicit confirmed path.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-03 09:37:54', '2026-06-03 11:50:23', NULL, '38722e333acfbd8f5b8491624c587c69', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-231', 'task', 'T-208', 'CLI verb to open a doc in a UI reader (drive-half of D-6 parity)', 'Dogfood finding (2026-06-03): Epic C gave the CLI the OBSERVE half of D-6 parity (clide status / pane list read live UI state). The DRIVE half is missing: there is no CLI/IPC verb that opens a doc in a UI reader. Opening a ticket/decision/markdown doc happens only inside the Flutter app, by publishing to the kernel MessageBus (e.g. messages.publish(''builtin.tickets'',''selection'',{''id'':id}); see tickets/src/extension.dart:29 which subscribes publisher ''builtin.tickets'' channel ''selection'', and ticket_detail_view.dart). lib/src/daemon/ has NO verb that reaches the MessageBus, so an agent cannot say ''look at this with me''. Proposed: add a bridge verb (e.g. ui.open / view.open ) that publishes a ''selection'' message to the bus, so ''clide ui open tickets T-48'' opens T-48 in the ticket reader (and decisions/markdown analogously). Wiring: thread the kernel MessageBus into the dispatcher the same way panels/readerNav were threaded for T-219/T-221 (daemonClientFactory param or post-boot capture in main.dart). Honor D-6 (every UI action has a CLI verb). Complements Epic C (T-218): observe -> drive.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:17:32', '2026-06-03 13:17:32', NULL, '9a36072c19b514a56efe7ee7b5970457', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'story', 'T-132', 'Cluster meta messages into a collapsible activity card', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png. - -Fold runs of consecutive ''meta'' items into one live, collapsible activity card: -- The card shows the MOST RECENT meta line as a live ticker and updates in place as new ones stream in — a running ''what''s happening now''. A step count (''14 steps'') sits on the right. -- Collapsed by default; click the chevron to expand the full list of folded steps, click to re-collapse. -- ''Sticky'' items never fold — they render first-class. A sticky item SEALS the current card and a new cluster begins after it. A cluster = one unbroken run of foldable items between two sticky items. - -Taxonomy over the sealed ConversationItem set (transcript_reader.dart): UserMessage, AssistantTextMessage, AssistantThinkingMessage, AssistantToolUse, ToolResultMessage. -- Always sticky: UserMessage, AssistantTextMessage. Permission/AskUserQuestion prompts already live in the interaction zone (D-78) and are inherently sticky / cluster-breaking — no change, but note they break the run too. -- Foldable: AssistantToolUse + its paired ToolResultMessage (pairing already exists via toolUseById, T-168). - -Refinement decisions (2026-06-03, user): -1. WHAT FOLDS is a 3-level setting (conservative → aggressive); DEFAULT = level 1. Build the foldable predicate so all three are switchable: - - L1 (default): fold tool calls + results only; diffs (Edit/Write results) AND AssistantThinkingMessage stay first-class/sticky. - - L2: also fold thinking; diffs stay sticky. - - L3: fold everything except UserMessage + AssistantTextMessage (incl. diffs + thinking). -2. ERRORS: a failed/error ToolResultMessage SURFACES — treated as sticky, stays visible and breaks the cluster (see wireframe state B). Start here; the user noted some non-fatal errors are themselves clutter, so expect to tune which errors surface vs. fold. -3. DEFAULT/RUN STATE: always collapsed, showing the last line + step count, with the ticker updating live. NO setting. (Rejected auto-expanding the in-flight cluster: it scrolls the last useful sticky message out of view during long runs and only collapses when done — worst timing. Collapsed-with-live-ticker keeps the last sticky message anchored while preserving the motion/dynamism.) - -Implementation home: a ''collapse adjacent foldable items'' grouping pass over ConversationController.items, consumed by ConversationView; the card is a new conversation_card variant. Keep it accessible (keyboard expand/collapse + semantics: announce step count and collapsed/expanded state). - -Acceptance: -1. Consecutive foldable items collapse into one card; the collapsed card shows the latest line + a live-updating step count. -2. A sticky message (user / Claude prose / surfaced error / interaction-zone prompt) seals the card and starts a new cluster after it. -3. Expanding shows every folded step (tool call + result pairs) in order; collapsing returns to the one-line view. -4. The foldable level is a setting (default L1: diffs + thinking stay first-class); switching levels re-groups the transcript. -5. Failed tool results surface as their own (sticky) row and break the cluster. -6. Keyboard + screen-reader accessible (expand/collapse, step count announced).', 'ready', 'medium', NULL, NULL, NULL, '2026-06-03 11:15:39', '2026-06-03 13:23:43', NULL, '21cff726143d1c2b3102ee1ef5b6bfdb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'task', NULL, 'add Catppuccin theme to bundled themes', 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). +Resolved (2026-06-03): NO new mechanism or decision needed -- the contract already existed. D-74''s CommandSchema.normalize (lib/src/ipc/command_schema.dart) already folds the argv shape {positional,flags} into named args using a declared positional ordering, and the dispatcher already runs normalize+validate. The arg-taking commands simply never registered a schema (adoption is opt-in per D-74). Fix = adopt it: added positional schemas (non-required, so missing-arg errors stay as handlers produce them; only effect is positional->named mapping + numeric coercion of line/cols/rows) to editor.open/activate/read/save/close, files.read/ls, pane.close/focus/resize/write. Handlers unchanged. Tests: CLI-shape ({positional:[...]}) dispatch now binds (editor/pane/files command tests). DEFERRED (still named-arg only; in-process UI works, CLI-arg niche): editor.insert/replace-selection/set-selection/set-content (content/selection encoding + active-buffer fallback make positional ambiguous), pane.spawn (argv is a list, not scalar positional), git.* arg verbs (agents use plain git; D-83 external-agent scope). NOTE: takes effect on app RESTART, not hot reload -- the dispatcher is built once at boot.', 'done', 'high', NULL, NULL, 'D-6', '2026-06-03 13:29:49', '2026-06-03 13:41:42', NULL, 'fc28f03686962cd26af10e60272af278', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JR6QZDMKE167C4MR', 'task', NULL, 'add Catppuccin theme to bundled themes', 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). **Acceptance:** - 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). @@ -278,17 +91,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, **Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. -**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.', 'ready', 'low', NULL, NULL, NULL, '2026-05-06 08:30:15', '2026-06-03 13:24:18', NULL, 'b8cbb4530197f04a1d5277a9cfee5dbc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-232', 'task', 'T-208', 'CLI argv args dont reach typed command handlers (positional/flags vs named)', 'Major dogfood finding (2026-06-03), verified live: parameterized subsystem commands are NOT reachable from the clide CLI. The C client sends raw argv; parseArgv (lib/src/cli/argv_to_request.dart) turns ''clide editor open X'' into args={positional:[X]} (also flags:{}, passthrough:[]). But the typed handlers read NAMED top-level keys: editor.open reads args[''path''] (editor_commands.dart:64), files.read reads args[''path''], pane.close/editor.activate read args[''id''], etc. Nothing maps positional/flags -> those names, so every arg-taking verb returns ''X is required'' from the CLI. Confirmed: ''clide editor open pubspec.yaml'' and ''--path=pubspec.yaml'' both -> ''path is required''; ''clide files read pubspec.yaml'' -> ''files.read requires a path''. grep shows ONLY the new ui_command.dart reads args[''positional'']. Net: an external agent can OBSERVE (no-arg reads: status, git status, files root, pane list, editor active) but cannot DRIVE anything parameterized -- undercutting CLI-first (D-1) and the D-6 parity premise behind the whole T-208 initiative. Needs a decision on the mapping contract: most ergonomic is a per-command POSITIONAL/flag schema declared where the handler registers (extends the co-registered schema of D-74) so ''clide editor open '' binds positional[0]->path; alternative is lifting --flags into top-level named args. Then either remap in argv_dispatch/parseArgv before dispatch, or have handlers read a normalized accessor. High priority: this is the gating bug for ''Give Claude hands''.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-03 13:29:49', '2026-06-03 13:29:49', NULL, '9b89ce5c134ecd097db7e5bd19f3ff75', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open '' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-03 13:29:59', NULL, 'bf44a02d9e8daba9f5d6a6f486c0c881', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-231', 'task', 'T-208', 'CLI verb to open a doc in a UI reader (drive-half of D-6 parity)', 'Dogfood finding (2026-06-03): Epic C gave the CLI the OBSERVE half of D-6 parity (clide status / pane list read live UI state). The DRIVE half is missing: there is no CLI/IPC verb that opens a doc in a UI reader. Opening a ticket/decision/markdown doc happens only inside the Flutter app, by publishing to the kernel MessageBus (e.g. messages.publish(''builtin.tickets'',''selection'',{''id'':id}); see tickets/src/extension.dart:29 which subscribes publisher ''builtin.tickets'' channel ''selection'', and ticket_detail_view.dart). lib/src/daemon/ has NO verb that reaches the MessageBus, so an agent cannot say ''look at this with me''. Proposed: add a bridge verb (e.g. ui.open / view.open ) that publishes a ''selection'' message to the bus, so ''clide ui open tickets T-48'' opens T-48 in the ticket reader (and decisions/markdown analogously). Wiring: thread the kernel MessageBus into the dispatcher the same way panels/readerNav were threaded for T-219/T-221 (daemonClientFactory param or post-boot capture in main.dart). Honor D-6 (every UI action has a CLI verb). Complements Epic C (T-218): observe -> drive.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:17:32', '2026-06-03 13:30:05', NULL, '78cb72d5be8881a31a0cad37ec138d21', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-212', 'task', 'T-209', 'In-app ''Install clide command in PATH'' affordance', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell. - -Refinement (2026-06-03, from live dogfooding): scope should include PROACTIVE detection on launch, not just a palette command. When the clide IDE starts in a repo, check whether ''clide'' resolves on PATH AND points to the C client (not a stale symlink to the GUI bundle runner) -- we hit exactly this: ~/.local/bin/clide was a May-6 symlink to ~/.local/lib/clide/clide (the Flutter GUI), so a bare ''clide pane list'' launched a second app instead of querying. If missing or stale, prompt/offer to install (copy the bundled C client to a PATH dir, VS Code ''Install code command'' style) and report success. This is what lets a fresh agent actually reach the CLI (D-83 names the hosted session primary, but an external agent benefits too). Detecting ''stale GUI symlink'' specifically: the target should be an ELF/Mach-O executable, not a symlink into the bundle. Alternative path the user raised: instead of/alongside this, make the /ide MCP surface reachable (T-225) -- but CLI is primary per D-68.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-03 13:33:43', NULL, '92777a1e6cc2cb6bbaa9acf783f43275', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-232', 'task', 'T-208', 'CLI argv args dont reach typed command handlers (positional/flags vs named)', 'Major dogfood finding (2026-06-03), verified live: parameterized subsystem commands are NOT reachable from the clide CLI. The C client sends raw argv; parseArgv (lib/src/cli/argv_to_request.dart) turns ''clide editor open X'' into args={positional:[X]} (also flags:{}, passthrough:[]). But the typed handlers read NAMED top-level keys: editor.open reads args[''path''] (editor_commands.dart:64), files.read reads args[''path''], pane.close/editor.activate read args[''id''], etc. Nothing maps positional/flags -> those names, so every arg-taking verb returns ''X is required'' from the CLI. Confirmed: ''clide editor open pubspec.yaml'' and ''--path=pubspec.yaml'' both -> ''path is required''; ''clide files read pubspec.yaml'' -> ''files.read requires a path''. grep shows ONLY the new ui_command.dart reads args[''positional'']. Net: an external agent can OBSERVE (no-arg reads: status, git status, files root, pane list, editor active) but cannot DRIVE anything parameterized -- undercutting CLI-first (D-1) and the D-6 parity premise behind the whole T-208 initiative. Needs a decision on the mapping contract: most ergonomic is a per-command POSITIONAL/flag schema declared where the handler registers (extends the co-registered schema of D-74) so ''clide editor open '' binds positional[0]->path; alternative is lifting --flags into top-level named args. Then either remap in argv_dispatch/parseArgv before dispatch, or have handlers read a normalized accessor. High priority: this is the gating bug for ''Give Claude hands''. - -Resolved (2026-06-03): NO new mechanism or decision needed -- the contract already existed. D-74''s CommandSchema.normalize (lib/src/ipc/command_schema.dart) already folds the argv shape {positional,flags} into named args using a declared positional ordering, and the dispatcher already runs normalize+validate. The arg-taking commands simply never registered a schema (adoption is opt-in per D-74). Fix = adopt it: added positional schemas (non-required, so missing-arg errors stay as handlers produce them; only effect is positional->named mapping + numeric coercion of line/cols/rows) to editor.open/activate/read/save/close, files.read/ls, pane.close/focus/resize/write. Handlers unchanged. Tests: CLI-shape ({positional:[...]}) dispatch now binds (editor/pane/files command tests). DEFERRED (still named-arg only; in-process UI works, CLI-arg niche): editor.insert/replace-selection/set-selection/set-content (content/selection encoding + active-buffer fallback make positional ambiguous), pane.spawn (argv is a list, not scalar positional), git.* arg verbs (agents use plain git; D-83 external-agent scope). NOTE: takes effect on app RESTART, not hot reload -- the dispatcher is built once at boot.', 'done', 'high', NULL, NULL, 'D-6', '2026-06-03 13:29:49', '2026-06-03 13:41:42', NULL, '45205bdca118ccf9f6dd2c613707efa2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'story', NULL, 'Theme-switcher popout in the status bar', 'Add a theme switcher to the far-right corner of the bottom status bar (the empty area to the right of the ''application ok'' / ipc-status item — see the user''s screenshot). Today switching themes is only reachable via the theme.pick command / palette (ThemePickerExtension opens a modal picker_view); there''s no always-visible affordance. +**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.', 'done', 'low', NULL, NULL, NULL, '2026-05-06 08:30:15', '2026-06-03 14:56:07', NULL, 'e09db083ea034b33827a1bf708bd46bd', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM492DTRZJK9CGQPGDC', 'story', NULL, 'Theme-switcher popout in the status bar', 'Add a theme switcher to the far-right corner of the bottom status bar (the empty area to the right of the ''application ok'' / ipc-status item — see the user''s screenshot). Today switching themes is only reachable via the theme.pick command / palette (ThemePickerExtension opens a modal picker_view); there''s no always-visible affordance. Add a compact status-bar control that opens a POPOUT (popover anchored to the control, not the full-screen modal) for quick theme switching: - A small right-aligned status item — a palette/swatch icon (or the current theme name). Contribute via StatusItemContribution at Slots.statusbar with priority > 100 so it sits in the right group, to the right of the ipc/toolchain ''application ok'' item (priority 100). @@ -302,90 +106,8 @@ Acceptance: 2. Clicking/activating it opens a popover of all available themes with the current one marked. 3. Selecting a theme applies it live (ThemeController.select) and closes the popout. 4. Esc / outside-click dismisses without changing the theme; the control is keyboard-operable. -5. theme.pick (palette/command) still works unchanged.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-03 13:49:44', '2026-06-03 13:49:44', NULL, '301e958c679661deb77d1e3ca5d45d72', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'story', 'T-132', 'Cluster meta messages into a collapsible activity card', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png. - -Fold runs of consecutive ''meta'' items into one live, collapsible activity card: -- The card shows the MOST RECENT meta line as a live ticker and updates in place as new ones stream in — a running ''what''s happening now''. A step count (''14 steps'') sits on the right. -- Collapsed by default; click the chevron to expand the full list of folded steps, click to re-collapse. -- ''Sticky'' items never fold — they render first-class. A sticky item SEALS the current card and a new cluster begins after it. A cluster = one unbroken run of foldable items between two sticky items. - -Taxonomy over the sealed ConversationItem set (transcript_reader.dart): UserMessage, AssistantTextMessage, AssistantThinkingMessage, AssistantToolUse, ToolResultMessage. -- Always sticky: UserMessage, AssistantTextMessage. Permission/AskUserQuestion prompts already live in the interaction zone (D-78) and are inherently sticky / cluster-breaking — no change, but note they break the run too. -- Foldable: AssistantToolUse + its paired ToolResultMessage (pairing already exists via toolUseById, T-168). - -Refinement decisions (2026-06-03, user): -1. WHAT FOLDS is a 3-level setting (conservative → aggressive); DEFAULT = level 1. Build the foldable predicate so all three are switchable: - - L1 (default): fold tool calls + results only; diffs (Edit/Write results) AND AssistantThinkingMessage stay first-class/sticky. - - L2: also fold thinking; diffs stay sticky. - - L3: fold everything except UserMessage + AssistantTextMessage (incl. diffs + thinking). -2. ERRORS: a failed/error ToolResultMessage SURFACES — treated as sticky, stays visible and breaks the cluster (see wireframe state B). Start here; the user noted some non-fatal errors are themselves clutter, so expect to tune which errors surface vs. fold. -3. DEFAULT/RUN STATE: always collapsed, showing the last line + step count, with the ticker updating live. NO setting. (Rejected auto-expanding the in-flight cluster: it scrolls the last useful sticky message out of view during long runs and only collapses when done — worst timing. Collapsed-with-live-ticker keeps the last sticky message anchored while preserving the motion/dynamism.) - -Implementation home: a ''collapse adjacent foldable items'' grouping pass over ConversationController.items, consumed by ConversationView; the card is a new conversation_card variant. Keep it accessible (keyboard expand/collapse + semantics: announce step count and collapsed/expanded state). - -Acceptance: -1. Consecutive foldable items collapse into one card; the collapsed card shows the latest line + a live-updating step count. -2. A sticky message (user / Claude prose / surfaced error / interaction-zone prompt) seals the card and starts a new cluster after it. -3. Expanding shows every folded step (tool call + result pairs) in order; collapsing returns to the one-line view. -4. The foldable level is a setting (default L1: diffs + thinking stay first-class); switching levels re-groups the transcript. -5. Failed tool results surface as their own (sticky) row and break the cluster. -6. Keyboard + screen-reader accessible (expand/collapse, step count announced).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-03 11:15:39', '2026-06-03 14:27:26', NULL, '3eb3d504dad69cc0c06e57bce8f10456', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'task', NULL, 'add Catppuccin theme to bundled themes', 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). - -**Acceptance:** -- 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). -- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45). -- WCAG-AA contrast gate (D-22) passes against the theme. -- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution. -- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu. - -**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. - -**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.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-06 08:30:15', '2026-06-03 14:27:26', NULL, '4cb876eb35abc00ee93eb0bced3315da', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'story', NULL, 'Theme-switcher popout in the status bar', 'Add a theme switcher to the far-right corner of the bottom status bar (the empty area to the right of the ''application ok'' / ipc-status item — see the user''s screenshot). Today switching themes is only reachable via the theme.pick command / palette (ThemePickerExtension opens a modal picker_view); there''s no always-visible affordance. - -Add a compact status-bar control that opens a POPOUT (popover anchored to the control, not the full-screen modal) for quick theme switching: -- A small right-aligned status item — a palette/swatch icon (or the current theme name). Contribute via StatusItemContribution at Slots.statusbar with priority > 100 so it sits in the right group, to the right of the ipc/toolchain ''application ok'' item (priority 100). -- Click (or focus + Enter/Space) opens a popover listing ThemeController.available, the current (ThemeController.currentName) marked. -- Selecting an entry applies it immediately via ThemeController.select(name) and closes the popout. -- Esc / click-away dismisses with no change. Keyboard navigable (arrow + Enter), matching the palette/quick-open overlay pattern. -- Natural home: ThemePickerExtension (lib/builtin/theme_picker). Reuse the theme list + select logic from picker_view rather than duplicating; the modal theme.pick command stays as-is for parity (D-6). - -Acceptance: -1. A theme control sits in the far-right of the status bar. -2. Clicking/activating it opens a popover of all available themes with the current one marked. -3. Selecting a theme applies it live (ThemeController.select) and closes the popout. -4. Esc / outside-click dismisses without changing the theme; the control is keyboard-operable. -5. theme.pick (palette/command) still works unchanged.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-03 13:49:44', '2026-06-03 14:27:26', NULL, '9a5ac18d9e54e009f912b9c54584d796', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-82', 'task', NULL, 'add Catppuccin theme to bundled themes', 'User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44). - -**Acceptance:** -- 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). -- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45). -- WCAG-AA contrast gate (D-22) passes against the theme. -- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution. -- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu. - -**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase. - -**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.', 'done', 'low', NULL, NULL, NULL, '2026-05-06 08:30:15', '2026-06-03 14:56:07', NULL, '9cb0ab9e2c94f944809ee5abffdf802b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-234', 'story', NULL, 'Theme-switcher popout in the status bar', 'Add a theme switcher to the far-right corner of the bottom status bar (the empty area to the right of the ''application ok'' / ipc-status item — see the user''s screenshot). Today switching themes is only reachable via the theme.pick command / palette (ThemePickerExtension opens a modal picker_view); there''s no always-visible affordance. - -Add a compact status-bar control that opens a POPOUT (popover anchored to the control, not the full-screen modal) for quick theme switching: -- A small right-aligned status item — a palette/swatch icon (or the current theme name). Contribute via StatusItemContribution at Slots.statusbar with priority > 100 so it sits in the right group, to the right of the ipc/toolchain ''application ok'' item (priority 100). -- Click (or focus + Enter/Space) opens a popover listing ThemeController.available, the current (ThemeController.currentName) marked. -- Selecting an entry applies it immediately via ThemeController.select(name) and closes the popout. -- Esc / click-away dismisses with no change. Keyboard navigable (arrow + Enter), matching the palette/quick-open overlay pattern. -- Natural home: ThemePickerExtension (lib/builtin/theme_picker). Reuse the theme list + select logic from picker_view rather than duplicating; the modal theme.pick command stays as-is for parity (D-6). - -Acceptance: -1. A theme control sits in the far-right of the status bar. -2. Clicking/activating it opens a popover of all available themes with the current one marked. -3. Selecting a theme applies it live (ThemeController.select) and closes the popout. -4. Esc / outside-click dismisses without changing the theme; the control is keyboard-operable. -5. theme.pick (palette/command) still works unchanged.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 13:49:44', '2026-06-03 15:04:07', NULL, '88cfe20315c172dfdb9ba3ed1c25564d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'story', 'T-132', 'Cluster meta messages into a collapsible activity card', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png. +5. theme.pick (palette/command) still works unchanged.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 13:49:44', '2026-06-03 15:04:07', NULL, '0633fbd26056b04c5e542283be91a14e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5GH6VP8TZA95NMYK0', 'story', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Cluster meta messages into a collapsible activity card', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png. Fold runs of consecutive ''meta'' items into one live, collapsible activity card: - The card shows the MOST RECENT meta line as a live ticker and updates in place as new ones stream in — a running ''what''s happening now''. A step count (''14 steps'') sits on the right. @@ -414,68 +136,11 @@ Acceptance: 5. Failed tool results surface as their own (sticky) row and break the cluster. 6. Keyboard + screen-reader accessible (expand/collapse, step count announced). -Implemented (2026-06-03): pure grouping in activity_cluster.dart (groupConversation + FoldLevel none/tools/thinking/everything + RenderGroup StickyItem/FoldedCluster), fully unit-tested. Activity card + grouping wired into conversation_view.dart (collapsed live ticker of the latest step + step count; click/Enter expands to the folded steps in order; Semantics announces count + expanded/collapsed). Default level = L1 (tools): tool calls+results fold; user/Claude prose, FAILED results (sticky, surfaced), diffs (Edit/Write), and thinking stay first-class. Added FoldLevel.none (no folding) used by the existing item-level renderer tests. DEFERRED (acceptance 4''s persistence): the fold level is a switchable ConversationView parameter (proven by tests; none/L1/L2/L3 re-group) but is NOT yet wired to a persisted user setting + a UI control — the live pane uses the L1 default. Follow-up: read it from settings (ctx.settings) + a control to change it. Did NOT match the wireframe pixel-for-pixel; functional shape per the refinement decisions.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-03 11:15:39', '2026-06-03 15:17:10', NULL, '448154a6e3692d943eaa3b03bd914693', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-235', 'task', 'T-132', 'Persist + expose the activity-card fold level (T-230 follow-up)', 'Follow-up to T-230. The activity-card fold level is implemented as a switchable ConversationView.foldLevel parameter (FoldLevel none/tools/thinking/everything; groupConversation re-groups per level, unit-tested), but the live Claude pane currently uses the L1 default with no way for the user to change it. Finish acceptance #4: (1) read the fold level from a persisted user setting (via ctx.settings / the kernel settings service — see how other builtins read settings), defaulting to L1; (2) add a control to change it (e.g. a command ''claude.activity.fold-level'' cycling none->L1->L2->L3, and/or an entry in the Claude/settings UI); (3) thread it into the ConversationView built in claude_pane.dart (and team_panel_host.dart). The grouping + card + a11y are already done in activity_cluster.dart + conversation_view.dart.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-03 15:17:29', '2026-06-03 15:17:29', NULL, '8154cda9c468dd17716f381f4e10e0c1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-230', 'story', 'T-132', 'Cluster meta messages into a collapsible activity card', 'The Claude pane renders every transcript item as its own row, so a heavy agent turn becomes a wall of tool-call/result rows (Bash / Bash · result {…} / ''completed with no output'') that buries the messages that matter (user + Claude prose). Wireframe: docs/design/wireframes/claude/meta-activity-card.png. +Implemented (2026-06-03): pure grouping in activity_cluster.dart (groupConversation + FoldLevel none/tools/thinking/everything + RenderGroup StickyItem/FoldedCluster), fully unit-tested. Activity card + grouping wired into conversation_view.dart (collapsed live ticker of the latest step + step count; click/Enter expands to the folded steps in order; Semantics announces count + expanded/collapsed). Default level = L1 (tools): tool calls+results fold; user/Claude prose, FAILED results (sticky, surfaced), diffs (Edit/Write), and thinking stay first-class. Added FoldLevel.none (no folding) used by the existing item-level renderer tests. DEFERRED (acceptance 4''s persistence): the fold level is a switchable ConversationView parameter (proven by tests; none/L1/L2/L3 re-group) but is NOT yet wired to a persisted user setting + a UI control — the live pane uses the L1 default. Follow-up: read it from settings (ctx.settings) + a control to change it. Did NOT match the wireframe pixel-for-pixel; functional shape per the refinement decisions.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 11:15:39', '2026-06-03 15:17:33', NULL, '2b4264775c71be283f67c22aabc117f1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM46ANXPW02XGD3D3NG', 'task', NULL, 'Theme picker polish: collapse -hc into a checkbox, sort, widen, palette icon', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists. -Fold runs of consecutive ''meta'' items into one live, collapsible activity card: -- The card shows the MOST RECENT meta line as a live ticker and updates in place as new ones stream in — a running ''what''s happening now''. A step count (''14 steps'') sits on the right. -- Collapsed by default; click the chevron to expand the full list of folded steps, click to re-collapse. -- ''Sticky'' items never fold — they render first-class. A sticky item SEALS the current card and a new cluster begins after it. A cluster = one unbroken run of foldable items between two sticky items. - -Taxonomy over the sealed ConversationItem set (transcript_reader.dart): UserMessage, AssistantTextMessage, AssistantThinkingMessage, AssistantToolUse, ToolResultMessage. -- Always sticky: UserMessage, AssistantTextMessage. Permission/AskUserQuestion prompts already live in the interaction zone (D-78) and are inherently sticky / cluster-breaking — no change, but note they break the run too. -- Foldable: AssistantToolUse + its paired ToolResultMessage (pairing already exists via toolUseById, T-168). - -Refinement decisions (2026-06-03, user): -1. WHAT FOLDS is a 3-level setting (conservative → aggressive); DEFAULT = level 1. Build the foldable predicate so all three are switchable: - - L1 (default): fold tool calls + results only; diffs (Edit/Write results) AND AssistantThinkingMessage stay first-class/sticky. - - L2: also fold thinking; diffs stay sticky. - - L3: fold everything except UserMessage + AssistantTextMessage (incl. diffs + thinking). -2. ERRORS: a failed/error ToolResultMessage SURFACES — treated as sticky, stays visible and breaks the cluster (see wireframe state B). Start here; the user noted some non-fatal errors are themselves clutter, so expect to tune which errors surface vs. fold. -3. DEFAULT/RUN STATE: always collapsed, showing the last line + step count, with the ticker updating live. NO setting. (Rejected auto-expanding the in-flight cluster: it scrolls the last useful sticky message out of view during long runs and only collapses when done — worst timing. Collapsed-with-live-ticker keeps the last sticky message anchored while preserving the motion/dynamism.) - -Implementation home: a ''collapse adjacent foldable items'' grouping pass over ConversationController.items, consumed by ConversationView; the card is a new conversation_card variant. Keep it accessible (keyboard expand/collapse + semantics: announce step count and collapsed/expanded state). - -Acceptance: -1. Consecutive foldable items collapse into one card; the collapsed card shows the latest line + a live-updating step count. -2. A sticky message (user / Claude prose / surfaced error / interaction-zone prompt) seals the card and starts a new cluster after it. -3. Expanding shows every folded step (tool call + result pairs) in order; collapsing returns to the one-line view. -4. The foldable level is a setting (default L1: diffs + thinking stay first-class); switching levels re-groups the transcript. -5. Failed tool results surface as their own (sticky) row and break the cluster. -6. Keyboard + screen-reader accessible (expand/collapse, step count announced). - -Implemented (2026-06-03): pure grouping in activity_cluster.dart (groupConversation + FoldLevel none/tools/thinking/everything + RenderGroup StickyItem/FoldedCluster), fully unit-tested. Activity card + grouping wired into conversation_view.dart (collapsed live ticker of the latest step + step count; click/Enter expands to the folded steps in order; Semantics announces count + expanded/collapsed). Default level = L1 (tools): tool calls+results fold; user/Claude prose, FAILED results (sticky, surfaced), diffs (Edit/Write), and thinking stay first-class. Added FoldLevel.none (no folding) used by the existing item-level renderer tests. DEFERRED (acceptance 4''s persistence): the fold level is a switchable ConversationView parameter (proven by tests; none/L1/L2/L3 re-group) but is NOT yet wired to a persisted user setting + a UI control — the live pane uses the L1 default. Follow-up: read it from settings (ctx.settings) + a control to change it. Did NOT match the wireframe pixel-for-pixel; functional shape per the refinement decisions.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 11:15:39', '2026-06-03 15:17:33', NULL, '2f9702465f4d0079bca7f4f4e7b60095', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'story', 'T-132', 'Render pasted-image @path tokens as thumbnails in the message log', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. - -The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). - -Scope: -- When rendering UserMessage text, detect @ tokens; for image paths (reuse the extension check behind _looksLikeImage: .png/.jpg/.jpeg/.gif/.webp/.bmp), render a bounded Image.file thumbnail in place of the bare token. Surrounding prose still renders normally; a message can carry multiple tokens. -- Click/activate the thumbnail to open the full-size image (a preview dialog). Keyboard-operable + a11y label (filename). -- Graceful fallback: a missing/unreadable file (pasted temp files can be cleaned up) degrades to a small placeholder or the path text via Image.file errorBuilder — never a crash/exception. -- Non-image @path tokens MAY render as a file chip (filename + icon) like the composer, but image thumbnails are the focus. -- Note: Image.file reads the path directly via dart:io, so this is NOT gated by the files.read allow-list (D-80) — the cache dir is outside the workspace and that''s fine for display. -- Display-only: the text actually sent to Claude is unchanged; this only affects rendering. - -Acceptance: -1. A user message with an @ image token shows an inline thumbnail in the log (not the raw path). -2. Clicking/activating the thumbnail opens the full image. -3. A missing/unreadable referenced file degrades to a placeholder (or the path text), no exception. -4. Prose and any non-image @path tokens around it still render readably; multiple tokens in one message all resolve. -5. The message content delivered to Claude is unchanged (render-only). Relates to T-142 (paste attachments).', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-03 15:31:01', '2026-06-03 15:31:01', NULL, '6cf569924ec0dbc31a4b2711aa420be2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-237', 'task', NULL, 'Theme picker polish: collapse -hc into a checkbox, sort, widen, palette icon', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-03 15:43:45', '2026-06-03 15:43:45', NULL, '411a82da35f5bad2611cf4f4ff38aa13', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-237', 'task', NULL, 'Theme picker polish: collapse -hc into a checkbox, sort, widen, palette icon', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists.', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-03 15:43:45', '2026-06-03 15:43:55', NULL, 'fc2bc3d466dbae36975e79cabcf289ee', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-237', 'task', NULL, 'Theme picker polish: collapse -hc into a checkbox, sort, widen, palette icon', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 15:43:45', '2026-06-03 21:11:32', NULL, '7fa03d7cd677b0c785ac598d1c7735c6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-238', 'task', NULL, 'Apply the -hc-as-checkbox + sort to the ctrl+k theme modal (T-237 follow-up)', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish.', 'backlog', 'low', NULL, NULL, 'D-6', '2026-06-03 21:11:40', '2026-06-03 21:11:40', NULL, 'e4bd30acf9fdacf246d04fa71bee1f5c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-237', 'task', NULL, 'Theme picker polish: collapse -hc into a checkbox, sort, widen, palette icon', 'User feedback on the T-234 status-bar theme popover (screenshot 2026-06-03): (1) the menu order looks random — the -hc variants are interleaved. (2) Hide all the high-contrast theme entries and replace them with a single ''[ ] High contrast'' checkbox at the top; toggling it applies the -hc sibling of the chosen base theme. (3) Widen the popover so long names (''Catppuccin Mocha'') don''t wrap; rows ellipsize. (4) The status-bar items (''application ok'' + theme switcher) float in the middle (boxed into the workspace column between the per-column bottom rails) instead of hugging the window''s right edge — align them right. (5) Replace the swatch dot on the trigger with a Phosphor icon (palette, 0xe6c8, already in the registry — paint-roller has no registered codepoint). Apply the -hc-as-checkbox + sort to the modal picker_view too for consistency. Helpers: base = name without -hc/-cb; show base themes only; resolve(base, hc) -> base+''-hc'' if it exists. - -Done (2026-06-03): popover collapses -hc into a ''High contrast'' toggle, lists base themes sorted by display name, widened to 280 with ellipsis, palette icon on the trigger, lowercased bar label; status bar split so the global right group (tool status + theme switcher) hugs the window''s right edge past the context rail (643417e), keeping the 3-rail structure. Pure theme_families helpers unit-tested + popover widget tests. DEFERRED: applying the same -hc-toggle + sort to the modal picker_view (ctrl+k) for consistency — minor, follow-up.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 15:43:45', '2026-06-03 21:29:47', NULL, '3b28bbd2fced2df777273f8a9c4ec6ab', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'bug', NULL, 'Status-bar right group still not hugging the center block''s right edge', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column).', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-03 21:41:58', '2026-06-03 21:41:58', NULL, 'da8e48d61a97503f2f8f611644d49b4e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-191', 'story', 'T-186', 'Edit pencil in the reader action bar — open the current doc in the editor', 'Since clicking a .md now opens the READER (T-187), give the user a deliberate one-click way to open the SAME doc in the editor in edit mode. Add an edit pencil to the action bar of BOTH sidebar readers (markdown reader, decision reader). The pencil fires the editor path the readers moved away from for plain clicks: kernel.ipc.request(''editor.open'', {path}) on the reader''s current doc. Markdown reader: its current file path. Decision reader: the D-record''s backing file (decisions expose file_path = governance/decisions/.md via pql decisions read; resolve and open that — note it opens the whole domain file, since a decision is a section within it, unless we later add anchor support). Lives in the same reader chrome as back/forward (T-189) and pin (T-190). Custom chrome only (no Material). Acceptance: a pencil button in each reader opens the currently-shown doc in the editor; widget tests. Sequence with the other reader-chrome work (Wave B).', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 11:58:19', '2026-06-04 12:00:09', NULL, '265d796a9319bdef32331ba38fd3c570', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'task', 'T-164', 'Claude sidebar sub-tabs: Activity / Team / Config', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view (T-183). Default to Activity. SHARED GEOMETRY: Activity values and the Config settings table use the SAME two-column label->value table (same column x + row pitch + section-header style) so toggling between tabs does not visually jump. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; Activity + Config settings render on the shared table so toggling is seamless; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:26:31', '2026-06-04 12:00:09', NULL, '7cd00407096511f1d56a8936081e5990', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-190', 'story', 'T-186', 'Pin a doc in the markdown & decision readers (single-slot quick-return)', 'Add a one-click PIN to both sidebar readers. Semantics per the reporter: a SINGLE pin slot per reader — clicking pin remembers the CURRENT doc; setting a new pin REPLACES the previous one (no list to manage); the pinned doc is always one click away to return to, so it is easy to use inline as you work. It is a quick-return bookmark, NOT a freeze-incoming toggle. Add a pinned-doc ref to _MarkdownViewerState and _DecisionDetailViewState; a pin button (set/replace) plus a jump-to-pin affordance in the reader chrome; persist for at least the session. Custom chrome only (no Material). Acceptance: pinning a doc lets you one-click return to it later; pinning another replaces it; works on both readers; widget tests. Sequence after the reader-open bug fixes (T-187/T-188); pairs naturally with back/forward (T-189).', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 11:38:42', '2026-06-04 12:00:09', NULL, 'a91f76fe64e90bbbd3e7e0e8dc842427', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', '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. +Done (2026-06-03): popover collapses -hc into a ''High contrast'' toggle, lists base themes sorted by display name, widened to 280 with ellipsis, palette icon on the trigger, lowercased bar label; status bar split so the global right group (tool status + theme switcher) hugs the window''s right edge past the context rail (643417e), keeping the 3-rail structure. Pure theme_families helpers unit-tested + popover widget tests. DEFERRED: applying the same -hc-toggle + sort to the modal picker_view (ctrl+k) for consistency — minor, follow-up.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 15:43:45', '2026-06-03 21:29:47', NULL, '881382733953e72c2de56ba5a3fdc9fe', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5V7GHH42RHV04DPTR', 'story', NULL, 'Vim keybinding preset', '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. **Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place; modal Vim presets are more involved than the VS Code preset (T-64) because modes need to be expressed as scope flags (`vim.normal`, `vim.insert`, `vim.visual`) that the when-clause grammar can branch on. Implementation work: @@ -488,56 +153,12 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, 2. `KeymapService.setPreset("vim")` + the mode-tracking service together produce correct mode transitions. 3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert). -Refinement (2026-06-01): scoped Vim-first for a Vim-power-user demo this weekend. ''Author a YAML file'' was wrong — T-117 shipped single-chord resolution + scope flags + when-clauses only. Decomposed into children: T-204 (fix dead default preset — live bug), T-205 (key-sequence + count resolution), T-206 (modal editor motion/edit intents), T-207 (Vim mode service + status indicator). T-65 itself becomes assets/keymaps/vim.yaml + regression tests once children land. T-64/T-66 deferred (single-chord, easy; T-205 hands JetBrains shift+shift later). Foundation is shared, not Vim-only.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-04 12:00:09', NULL, 'd1bee803c5e20ed51fbdc3c7bfa5fbca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-189', 'story', 'T-186', 'Back/forward navigation on the markdown & decision readers', 'Add Back/Forward buttons to both sidebar readers so you can return to what you were reading. Neither reader keeps history today: _MarkdownViewerState (markdown_viewer.dart) holds only _path/_content; _DecisionDetailViewState (decision_detail_view.dart) holds only _decision. Add a history stack + index to each, pushing on each new open; Back/Forward walk the stack and load the entry WITHOUT re-publishing a ''selection'' (in-widget navigation). For the decision reader, back/forward must not trigger the extension tab re-contribution — coordinate with the decision-open bug fix (T-188), which should stop re-contributing per selection. Custom chrome only (no Material). Acceptance: Back/Forward controls on both readers walk reading history and disable at the ends; widget tests. Sequence after the reader-open bug fixes (T-187/T-188) since it shares those widgets.', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 11:38:36', '2026-06-04 12:00:09', NULL, 'e0f65a8e72b7990301d65e71cdd7f591', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'task', NULL, 'Number-key shortcuts for interactive prompt buttons (CLI-style 1/2/3)', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-05 09:38:46', '2026-06-05 09:38:46', NULL, '16df8e1dbc8cfe6387f1e65669608830', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'task', NULL, 'Number-key shortcuts for interactive prompt buttons (CLI-style 1/2/3)', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. - -Refinement (2026-06-05, user): match the Claude Code CLI''s actual behavior for the text-field interaction. The number key triggers the button/option shortcut when it would be the FIRST character typed -- i.e. focus is NOT in a text field, OR focus IS in the note/Other field but that field is currently EMPTY. Once the field has any content, digits type normally (no shortcut). This supersedes the earlier ''never capture digits while a text field has focus'' gate: it''s better because focus often defaults into the (empty) note field, so the shortcut still fires there (muscle-memory case) while a digit mid-note still types. Implementation: intercept the digit at the prompt focus scope; consume+activate only when the focused editable (if any) is empty, else let it through to type. The CLI exhibits this exact ''footgun'' (digit-as-first-char in an empty field acts as the choice) and we intentionally mirror it.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-05 09:38:46', '2026-06-05 09:40:57', NULL, '105ec4229b71ff18a39d829dbbcbe5be', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'bug', NULL, 'Status-bar right group still not hugging the center block''s right edge', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column).', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-03 21:41:58', '2026-06-05 09:45:51', NULL, 'f6475185bc5cb77cd4be61c60d2458da', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'bug', NULL, 'Status-bar right group still not hugging the center block''s right edge', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column). - -FIXED (2026-06-05): root cause confirmed by probe at 3440px width — the bottom bar used Row[ ...left (incl. a Flexible flex:1 loose item, the Claude status marquee), Spacer(), ...right ]. The Spacer (Expanded, flex:1) and the left flex:1 item SPLIT the free space 50/50, so the Spacer only pushed the right group by HALF the free space. That drift is proportional to width: tiny at 1200px (why probes/normal screens looked fine), ~1500px at 3440px (the right group floated to mid-bar) — hence ''only on ultrawide''. Probe: OLD R.right=1934 at a 3440 edge; NEW R.right=3440. FIX (app.dart StatusbarHost): explicit two-column layout — left group wrapped in Expanded(Row[...]) so it absorbs ALL free space (flex item flexes within it), right group trails at intrinsic width → hugs the workspace block''s right edge by construction, width-independent. Regression test test/app_statusbar_test.dart asserts the right group at width-8 for BOTH 600px and 3440px. Full fast suite green.', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-03 21:41:58', '2026-06-05 10:02:12', NULL, 'effd6fcdcd6f2c2c3fcfcba5be656691', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'bug', NULL, 'Status-bar right group still not hugging the center block''s right edge', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column). - -FIXED (2026-06-05): root cause confirmed by probe at 3440px width — the bottom bar used Row[ ...left (incl. a Flexible flex:1 loose item, the Claude status marquee), Spacer(), ...right ]. The Spacer (Expanded, flex:1) and the left flex:1 item SPLIT the free space 50/50, so the Spacer only pushed the right group by HALF the free space. That drift is proportional to width: tiny at 1200px (why probes/normal screens looked fine), ~1500px at 3440px (the right group floated to mid-bar) — hence ''only on ultrawide''. Probe: OLD R.right=1934 at a 3440 edge; NEW R.right=3440. FIX (app.dart StatusbarHost): explicit two-column layout — left group wrapped in Expanded(Row[...]) so it absorbs ALL free space (flex item flexes within it), right group trails at intrinsic width → hugs the workspace block''s right edge by construction, width-independent. Regression test test/app_statusbar_test.dart asserts the right group at width-8 for BOTH 600px and 3440px. Full fast suite green.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 21:41:58', '2026-06-05 10:02:12', NULL, 'ed1b3328a85f75c11f90df13cd667995', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-241', 'task', NULL, 'Audit widget tests for ultrawide coverage (IDE runs wide)', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end.', 'backlog', 'medium', NULL, NULL, 'D-23', '2026-06-05 10:02:31', '2026-06-05 10:02:31', NULL, '642d817c1cc2019ac7d7550137b31509', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-242', 'task', NULL, 'Quiet test reporter: use failures-only in ci scripts (only failures + final count)', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-05 10:17:47', '2026-06-05 10:17:47', NULL, 'fc2704e0a4388b220d44578b7e55e2e8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'task', 'T-5', 'Decide diff view placement', '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.', 'ready', 'low', NULL, NULL, 'D-48', '2026-04-22 21:03:40', '2026-06-05 11:04:12', NULL, 'c8f5d40e725215f57ee812d620166de2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'story', NULL, 'toast notification system', '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).', 'ready', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:48', '2026-06-05 11:04:25', NULL, 'cc078173e0e617deefe39dfef8176bdf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'task', 'T-115', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle. - -Adding a tap-driven (or even render-only) test that pumps WelcomeView with a non-empty recents list hangs the Flutter test runner until timeout. - -Investigation 2026-05-22 (narrowed the cause): -- The hang is SYNCHRONOUS: `flutter test --timeout 45s` never fires; only an external `timeout` kills it. So the Dart isolate event loop is blocked, not awaiting. -- Reduced to a minimal repro: pumping two NESTED ClideTappable widgets under the shared test harness (test/helpers/widget_harness.dart) hangs. A SINGLE ClideTappable (as in _ActionRow) renders fine — that is why all existing WelcomeView tests pass (empty recents = no nested tappable). The recents path nests one: _RecentRow is a ClideTappable whose subtree contains _StickyToggle (a Semantics-wrapped ClideTappable). -- The shared harness uses `Overlay(canSizeOverlay: true)` over a zero-size `MediaQuery`, which triggers an intrinsic-sizing pass. Strongly suspect the nested ClideTappable (Focus + MouseRegion + GestureDetector + DecoratedBox stack) diverges under intrinsic dimension computation. -- Workarounds that did NOT help: setting tester.view.physicalSize; wrapping in SizedBox; wrapping in Center+SizedBox; a custom bounded MediaQuery harness (that custom harness hung even on a single tappable — likely its own confound, do not reuse it). -- The prior ClideTooltip/MouseRegion-timer hypothesis is WRONG: the recents widgets pass no tooltip to ClideTappable, so no ClideTooltip is built, and _StickyToggle uses Semantics(tooltip:) (metadata only, no widget/timer). - -Next steps: -- Decide whether this is a real ClideTappable bug (nested tappables would also hang the live app welcome screen with recents) or strictly a canSizeOverlay-intrinsic-sizing test artifact. Check the running app: open welcome with >=1 recent project and confirm it does NOT hang. If the app is fine, the fix is harness-side (give WelcomeView tight constraints so no intrinsic pass), and the shared harness or a welcome-specific harness needs adjusting. If the app DOES hang, ClideTappable has a real nested-layout bug — fix it (likely in lib/widgets/src/clide_tappable.dart intrinsic/layout handling) and this becomes higher priority. -- Once unblocked, this also unblocks lib/builtin/welcome/src/welcome_view.dart coverage (74 uncovered lines, the dominant lib/builtin/ gap toward the T-89 95% target). - -ProjectManager sticky-startup logic is already covered by test/kernel/src/project_test.dart (14 cases).', 'ready', 'medium', NULL, NULL, NULL, '2026-05-18 09:06:59', '2026-06-05 12:15:59', NULL, '91d9b46dcf8787e88f1dd14ebab7ccfa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'task', 'T-115', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle. - -Adding a tap-driven (or even render-only) test that pumps WelcomeView with a non-empty recents list hangs the Flutter test runner until timeout. - -Investigation 2026-05-22 (narrowed the cause): -- The hang is SYNCHRONOUS: `flutter test --timeout 45s` never fires; only an external `timeout` kills it. So the Dart isolate event loop is blocked, not awaiting. -- Reduced to a minimal repro: pumping two NESTED ClideTappable widgets under the shared test harness (test/helpers/widget_harness.dart) hangs. A SINGLE ClideTappable (as in _ActionRow) renders fine — that is why all existing WelcomeView tests pass (empty recents = no nested tappable). The recents path nests one: _RecentRow is a ClideTappable whose subtree contains _StickyToggle (a Semantics-wrapped ClideTappable). -- The shared harness uses `Overlay(canSizeOverlay: true)` over a zero-size `MediaQuery`, which triggers an intrinsic-sizing pass. Strongly suspect the nested ClideTappable (Focus + MouseRegion + GestureDetector + DecoratedBox stack) diverges under intrinsic dimension computation. -- Workarounds that did NOT help: setting tester.view.physicalSize; wrapping in SizedBox; wrapping in Center+SizedBox; a custom bounded MediaQuery harness (that custom harness hung even on a single tappable — likely its own confound, do not reuse it). -- The prior ClideTooltip/MouseRegion-timer hypothesis is WRONG: the recents widgets pass no tooltip to ClideTappable, so no ClideTooltip is built, and _StickyToggle uses Semantics(tooltip:) (metadata only, no widget/timer). - -Next steps: -- Decide whether this is a real ClideTappable bug (nested tappables would also hang the live app welcome screen with recents) or strictly a canSizeOverlay-intrinsic-sizing test artifact. Check the running app: open welcome with >=1 recent project and confirm it does NOT hang. If the app is fine, the fix is harness-side (give WelcomeView tight constraints so no intrinsic pass), and the shared harness or a welcome-specific harness needs adjusting. If the app DOES hang, ClideTappable has a real nested-layout bug — fix it (likely in lib/widgets/src/clide_tappable.dart intrinsic/layout handling) and this becomes higher priority. -- Once unblocked, this also unblocks lib/builtin/welcome/src/welcome_view.dart coverage (74 uncovered lines, the dominant lib/builtin/ gap toward the T-89 95% target). - -ProjectManager sticky-startup logic is already covered by test/kernel/src/project_test.dart (14 cases).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-18 09:06:59', '2026-06-05 13:07:20', NULL, 'f8a0e5f4e3c2d52b7c28f4359d4663bf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'task', 'T-115', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle. +Refinement (2026-06-01): scoped Vim-first for a Vim-power-user demo this weekend. ''Author a YAML file'' was wrong — T-117 shipped single-chord resolution + scope flags + when-clauses only. Decomposed into children: T-204 (fix dead default preset — live bug), T-205 (key-sequence + count resolution), T-206 (modal editor motion/edit intents), T-207 (Vim mode service + status indicator). T-65 itself becomes assets/keymaps/vim.yaml + regression tests once children land. T-64/T-66 deferred (single-chord, easy; T-205 hands JetBrains shift+shift later). Foundation is shared, not Vim-only.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-04 12:00:09', NULL, '217a76ef73823789521206612b9835c8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5BWXW61EVYGMZD47G', 'story', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Edit pencil in the reader action bar — open the current doc in the editor', 'Since clicking a .md now opens the READER (T-187), give the user a deliberate one-click way to open the SAME doc in the editor in edit mode. Add an edit pencil to the action bar of BOTH sidebar readers (markdown reader, decision reader). The pencil fires the editor path the readers moved away from for plain clicks: kernel.ipc.request(''editor.open'', {path}) on the reader''s current doc. Markdown reader: its current file path. Decision reader: the D-record''s backing file (decisions expose file_path = governance/decisions/.md via pql decisions read; resolve and open that — note it opens the whole domain file, since a decision is a section within it, unless we later add anchor support). Lives in the same reader chrome as back/forward (T-189) and pin (T-190). Custom chrome only (no Material). Acceptance: a pencil button in each reader opens the currently-shown doc in the editor; widget tests. Sequence with the other reader-chrome work (Wave B).', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 11:58:19', '2026-06-04 12:00:09', NULL, '37b451f2b65b9bb9c0bca27383b84c69', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5ABRPK2SPW8VY7RP4', 'story', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Pin a doc in the markdown & decision readers (single-slot quick-return)', 'Add a one-click PIN to both sidebar readers. Semantics per the reporter: a SINGLE pin slot per reader — clicking pin remembers the CURRENT doc; setting a new pin REPLACES the previous one (no list to manage); the pinned doc is always one click away to return to, so it is easy to use inline as you work. It is a quick-return bookmark, NOT a freeze-incoming toggle. Add a pinned-doc ref to _MarkdownViewerState and _DecisionDetailViewState; a pin button (set/replace) plus a jump-to-pin affordance in the reader chrome; persist for at least the session. Custom chrome only (no Material). Acceptance: pinning a doc lets you one-click return to it later; pinning another replaces it; works on both readers; widget tests. Sequence after the reader-open bug fixes (T-187/T-188); pairs naturally with back/forward (T-189).', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 11:38:42', '2026-06-04 12:00:09', NULL, '58323eaf0b443895404e88ff7f9977d1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM56FKPV4WSPWA0KMB4', 'story', '06FB0TNQM6S7FDAXQX32JNCSFG', 'Back/forward navigation on the markdown & decision readers', 'Add Back/Forward buttons to both sidebar readers so you can return to what you were reading. Neither reader keeps history today: _MarkdownViewerState (markdown_viewer.dart) holds only _path/_content; _DecisionDetailViewState (decision_detail_view.dart) holds only _decision. Add a history stack + index to each, pushing on each new open; Back/Forward walk the stack and load the entry WITHOUT re-publishing a ''selection'' (in-widget navigation). For the decision reader, back/forward must not trigger the extension tab re-contribution — coordinate with the decision-open bug fix (T-188), which should stop re-contributing per selection. Custom chrome only (no Material). Acceptance: Back/Forward controls on both readers walk reading history and disable at the ends; widget tests. Sequence after the reader-open bug fixes (T-187/T-188) since it shares those widgets.', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 11:38:36', '2026-06-04 12:00:09', NULL, '8e500bb51805cf46d8abad4748f6e9a2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4W624VD4JW1GHDHAR', 'task', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Claude sidebar sub-tabs: Activity / Team / Config', 'The Claude meta-sidebar is overloaded (roster + tasks + messages + stats + config do not fit one scroll). Split it into a sub-tab strip (reuse the search-panel mode-strip pattern) with three tabs: (1) Activity — stats + live runtime visibility (T-141/T-157); (2) Team — roster + tasks + message-feed widget (T-171 + T-180), mostly hidden when solo and auto-fronted when a team is live; (3) Config — the Claude-environment view (T-183). Default to Activity. SHARED GEOMETRY: Activity values and the Config settings table use the SAME two-column label->value table (same column x + row pitch + section-header style) so toggling between tabs does not visually jump. Acceptance: a sub-tab strip switches the sidebar body between the three surfaces; Activity + Config settings render on the shared table so toggling is seamless; Team auto-fronts on team spawn; widget tests for tab switching. Wireframes: docs/design/wireframes/claude-prompts/07-sidebar-activity-tab.png, 08-sidebar-team-tab.png, 09-sidebar-config-tab.png. Relates to T-141, T-157, T-171, T-180.', 'done', 'medium', NULL, NULL, 'D-77', '2026-05-25 09:26:31', '2026-06-04 12:00:09', NULL, 'bee23004633073deaa93cb2d0154aa1e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM41S6RRTMTY9TYZA50', 'task', '06FB0TNQM614M7ES54V3SETAQM', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle. Adding a tap-driven (or even render-only) test that pumps WelcomeView with a non-empty recents list hangs the Flutter test runner until timeout. @@ -554,67 +175,12 @@ Next steps: ProjectManager sticky-startup logic is already covered by test/kernel/src/project_test.dart (14 cases). -ROOT CAUSE FOUND + FIXED (2026-06-05). The strand is NOT nested ClideTappable / intrinsic sizing (that prior hypothesis is disproven: seeding an EMPTY recents list — no rows, no nested tappables — also hangs, while rendering real recent rows seeded correctly passes). Real cause: SettingsStore.set does real file I/O (writeAsString); awaiting settings.set + project.loadRecents INSIDE the testWidgets body runs that I/O in fake-async, which traps the completion callback so the await never returns (a +0 strand only SIGKILL clears). FIX: seed recents via tester.runAsync(() async {...}) (real event loop), then pump in a tight bounded tree (not the shared harness, whose unbounded width is a separate WelcomeView layout hazard). test/builtin/welcome/widget_test.dart now has working recents render + sticky-toggle + open-recent tests; the skip is removed. Harness lesson recorded.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-18 09:06:59', '2026-06-05 13:08:02', NULL, '87332a80948a452e162826512ee84358', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-122', 'task', 'T-115', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle', 'T-115 follow-up: tap-driven widget test for sticky-startup toggle. +ROOT CAUSE FOUND + FIXED (2026-06-05). The strand is NOT nested ClideTappable / intrinsic sizing (that prior hypothesis is disproven: seeding an EMPTY recents list — no rows, no nested tappables — also hangs, while rendering real recent rows seeded correctly passes). Real cause: SettingsStore.set does real file I/O (writeAsString); awaiting settings.set + project.loadRecents INSIDE the testWidgets body runs that I/O in fake-async, which traps the completion callback so the await never returns (a +0 strand only SIGKILL clears). FIX: seed recents via tester.runAsync(() async {...}) (real event loop), then pump in a tight bounded tree (not the shared harness, whose unbounded width is a separate WelcomeView layout hazard). test/builtin/welcome/widget_test.dart now has working recents render + sticky-toggle + open-recent tests; the skip is removed. Harness lesson recorded.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 09:06:59', '2026-06-05 13:08:05', NULL, 'c94dba8a56ab16c17e26439dece58f47', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66TK8TQ1X5T9GK6V0', 'task', '06FB0TNQM4XZA5399D2EHYCVFC', 'Decide diff view placement', '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. -Adding a tap-driven (or even render-only) test that pumps WelcomeView with a non-empty recents list hangs the Flutter test runner until timeout. - -Investigation 2026-05-22 (narrowed the cause): -- The hang is SYNCHRONOUS: `flutter test --timeout 45s` never fires; only an external `timeout` kills it. So the Dart isolate event loop is blocked, not awaiting. -- Reduced to a minimal repro: pumping two NESTED ClideTappable widgets under the shared test harness (test/helpers/widget_harness.dart) hangs. A SINGLE ClideTappable (as in _ActionRow) renders fine — that is why all existing WelcomeView tests pass (empty recents = no nested tappable). The recents path nests one: _RecentRow is a ClideTappable whose subtree contains _StickyToggle (a Semantics-wrapped ClideTappable). -- The shared harness uses `Overlay(canSizeOverlay: true)` over a zero-size `MediaQuery`, which triggers an intrinsic-sizing pass. Strongly suspect the nested ClideTappable (Focus + MouseRegion + GestureDetector + DecoratedBox stack) diverges under intrinsic dimension computation. -- Workarounds that did NOT help: setting tester.view.physicalSize; wrapping in SizedBox; wrapping in Center+SizedBox; a custom bounded MediaQuery harness (that custom harness hung even on a single tappable — likely its own confound, do not reuse it). -- The prior ClideTooltip/MouseRegion-timer hypothesis is WRONG: the recents widgets pass no tooltip to ClideTappable, so no ClideTooltip is built, and _StickyToggle uses Semantics(tooltip:) (metadata only, no widget/timer). - -Next steps: -- Decide whether this is a real ClideTappable bug (nested tappables would also hang the live app welcome screen with recents) or strictly a canSizeOverlay-intrinsic-sizing test artifact. Check the running app: open welcome with >=1 recent project and confirm it does NOT hang. If the app is fine, the fix is harness-side (give WelcomeView tight constraints so no intrinsic pass), and the shared harness or a welcome-specific harness needs adjusting. If the app DOES hang, ClideTappable has a real nested-layout bug — fix it (likely in lib/widgets/src/clide_tappable.dart intrinsic/layout handling) and this becomes higher priority. -- Once unblocked, this also unblocks lib/builtin/welcome/src/welcome_view.dart coverage (74 uncovered lines, the dominant lib/builtin/ gap toward the T-89 95% target). - -ProjectManager sticky-startup logic is already covered by test/kernel/src/project_test.dart (14 cases). - -ROOT CAUSE FOUND + FIXED (2026-06-05). The strand is NOT nested ClideTappable / intrinsic sizing (that prior hypothesis is disproven: seeding an EMPTY recents list — no rows, no nested tappables — also hangs, while rendering real recent rows seeded correctly passes). Real cause: SettingsStore.set does real file I/O (writeAsString); awaiting settings.set + project.loadRecents INSIDE the testWidgets body runs that I/O in fake-async, which traps the completion callback so the await never returns (a +0 strand only SIGKILL clears). FIX: seed recents via tester.runAsync(() async {...}) (real event loop), then pump in a tight bounded tree (not the shared harness, whose unbounded width is a separate WelcomeView layout hazard). test/builtin/welcome/widget_test.dart now has working recents render + sticky-toggle + open-recent tests; the skip is removed. Harness lesson recorded.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 09:06:59', '2026-06-05 13:08:05', NULL, 'accb384a88b9bd863b2d0a440a1a2c3d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'task', 'T-5', 'Decide diff view placement', '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. - -DECIDED 2026-06-05 (D-84): diff view = editor-mode surface, inline above Claude (NOT a workspace tab, NOT a context-panel viewer), spawned from the git sidebar. Rationale: a diff is a review+intervention surface needing room for hunk stage/discard and conflict-resolution widgets — too cramped for the ~420px context panel; the middle column above Claude gives space while keeping Claude''s prompt bar fixed (D-47/D-49). This decision ticket is satisfied; the actual re-placement (generalize the editor-mode slot to host a diff, wire git-sidebar spawn, build resolution widgets) is a follow-up implementation story.', 'ready', 'low', NULL, NULL, 'D-48', '2026-04-22 21:03:40', '2026-06-05 15:14:18', NULL, '8eee2f6fa4689e0d7c965ae65b73b3a3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-243', 'story', 'T-5', 'Move diff view to the editor-mode surface (D-84): spawn from git sidebar, above Claude', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-05 15:14:25', '2026-06-05 15:14:25', NULL, 'd854264edbb54e71b3a3d3d9f93ba059', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-243', 'story', 'T-5', 'Move diff view to the editor-mode surface (D-84): spawn from git sidebar, above Claude', 'Implements D-84. Today the diff view is a workspace TAB (lib/builtin/diff/src/extension.dart, slot: Slots.workspace) — remove that. Instead: (1) generalize the editor-mode surface (currently hosts the single inline editor in _WorkspaceSlot, app.dart, gated by arrangement.editorOpen/editorRatio) so it can host EITHER the editor or a diff (mutually exclusive in that slot, like the viewer<->editor swap in D-49); (2) selecting a changed file in the git sidebar opens its diff in that above-Claude surface; (3) the diff keeps Claude''s prompt bar fixed (D-47) and gets a draggable divider like the editor. Acceptance: diff no longer appears as a workspace tab; clicking a file in the git panel opens its diff above Claude; closing it (Ctrl/Cmd+W) returns to Claude full-height; coexists with the editor by swap, not both at once. Hunk stage/discard + conflict-resolution widgets can be a further follow-up. Tests: arrangement state for the diff surface; git-sidebar -> diff open wiring; widget render.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-05 15:14:25', '2026-06-05 15:17:09', NULL, 'f5b98dc529e06114817586e23b2d5562', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-42', 'task', 'T-5', 'Decide diff view placement', '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. - -DECIDED 2026-06-05 (D-84): diff view = editor-mode surface, inline above Claude (NOT a workspace tab, NOT a context-panel viewer), spawned from the git sidebar. Rationale: a diff is a review+intervention surface needing room for hunk stage/discard and conflict-resolution widgets — too cramped for the ~420px context panel; the middle column above Claude gives space while keeping Claude''s prompt bar fixed (D-47/D-49). This decision ticket is satisfied; the actual re-placement (generalize the editor-mode slot to host a diff, wire git-sidebar spawn, build resolution widgets) is a follow-up implementation story.', 'done', 'low', NULL, NULL, 'D-48', '2026-04-22 21:03:40', '2026-06-05 15:17:09', NULL, '3349f66187c4db86850154af52d8f546', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'story', NULL, 'toast notification system', '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). - -REFINED 2026-06-05. Build (bottom-right, system + proof emitter): -- kernel ToastService (ChangeNotifier): show(message, {severity, duration}) -> id, dismiss(id), queue of active toasts, per-toast auto-dismiss Timer (default ~4s; errors longer/sticky), cancel on manual dismiss. ToastSeverity {success, warning, error, info}. -- ClideToast widget (custom, NO Material per D-7): severity accent from status tokens (statusSuccess/Warning/Error/info), message text, dismiss affordance (x / Esc-less, click); slide+fade in from bottom-right, auto slide-out. -- ToastOverlay: mounted in the app-root Stack (app.dart) alongside ClidePalette/QuickOpenOverlay, anchored bottom-right; stacks multiple toasts vertically; animates enter/exit. -- a11y: Semantics liveRegion for the message + labelled dismiss; keyboard-dismissable. -- Proof emitter: git push success/failure raises a toast (wire in the git command path). Broad wiring (extension activated, update available, generic errors) = follow-up tickets. -- Tests: ToastService queue + auto-dismiss (fakeAsync timers) + severity mapping; ClideToast render per severity; a11y contract; overlay mount.', 'ready', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:48', '2026-06-05 15:17:36', NULL, '5f5b8ffa2e1f767a2dd594764258e370', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'story', NULL, 'toast notification system', '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). - -REFINED 2026-06-05. Build (bottom-right, system + proof emitter): -- kernel ToastService (ChangeNotifier): show(message, {severity, duration}) -> id, dismiss(id), queue of active toasts, per-toast auto-dismiss Timer (default ~4s; errors longer/sticky), cancel on manual dismiss. ToastSeverity {success, warning, error, info}. -- ClideToast widget (custom, NO Material per D-7): severity accent from status tokens (statusSuccess/Warning/Error/info), message text, dismiss affordance (x / Esc-less, click); slide+fade in from bottom-right, auto slide-out. -- ToastOverlay: mounted in the app-root Stack (app.dart) alongside ClidePalette/QuickOpenOverlay, anchored bottom-right; stacks multiple toasts vertically; animates enter/exit. -- a11y: Semantics liveRegion for the message + labelled dismiss; keyboard-dismissable. -- Proof emitter: git push success/failure raises a toast (wire in the git command path). Broad wiring (extension activated, update available, generic errors) = follow-up tickets. -- Tests: ToastService queue + auto-dismiss (fakeAsync timers) + severity mapping; ClideToast render per severity; a11y contract; overlay mount.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:48', '2026-06-05 15:17:36', NULL, '33d606d624889ed2d075c5cc771dbf23', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'task', 'T-4', 'wire command palette keybinding', '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. - -**Progress (2026-05-17, T-117):** The keymap layer now binds `ctrl+shift+p` / `meta+shift+p` to `PaletteOpenIntent` in `assets/keymaps/default.yaml`. The intent resolves end-to-end through `KeymapService.resolveEvent` → `Actions.maybeInvoke`. **Still pending**: an `Actions` provider somewhere in the tree that handles `PaletteOpenIntent` by calling `kernel.palette.open()`, plus the arrow-key / Escape / Enter handlers on `ClidePalette` itself. Those land as part of T-100 (palette keyboard nav). - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Bindings live in the keymap (T-117) — not in `lib/kernel/src/commands/keybindings.dart` (that file is legacy). -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).', 'ready', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-06-05 15:21:04', NULL, 'dab0f542ba364bd8a2e8adfe8c2b4879', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-244', 'bug', NULL, 'Claude pane shows no UI or status-bar progress while compacting', NULL, 'backlog', 'medium', NULL, NULL, 'D-77', '2026-06-05 15:21:10', '2026-06-05 15:21:10', NULL, 'df1dd015f250add3e0830f2738be8647', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-244', 'bug', NULL, 'Claude pane shows no UI or status-bar progress while compacting', 'When a Claude Code session hits context compaction, the Claude pane gives no signal that anything is happening: the conversation surface shows no in-pane indicator and the status bar shows no progress. From the user''s side the pane just appears to stall until compaction finishes and the next response streams in. +DECIDED 2026-06-05 (D-84): diff view = editor-mode surface, inline above Claude (NOT a workspace tab, NOT a context-panel viewer), spawned from the git sidebar. Rationale: a diff is a review+intervention surface needing room for hunk stage/discard and conflict-resolution widgets — too cramped for the ~420px context panel; the middle column above Claude gives space while keeping Claude''s prompt bar fixed (D-47/D-49). This decision ticket is satisfied; the actual re-placement (generalize the editor-mode slot to host a diff, wire git-sidebar spawn, build resolution widgets) is a follow-up implementation story.', 'done', 'low', NULL, NULL, 'D-48', '2026-04-22 21:03:40', '2026-06-05 15:17:09', NULL, 'eb79594591de24156f21a5a3deca97f9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5WT3BHB8JZY77G5AW', 'story', '06FB0TNQM4XZA5399D2EHYCVFC', 'Move diff view to the editor-mode surface (D-84): spawn from git sidebar, above Claude', 'Implements D-84. Today the diff view is a workspace TAB (lib/builtin/diff/src/extension.dart, slot: Slots.workspace) — remove that. Instead: (1) generalize the editor-mode surface (currently hosts the single inline editor in _WorkspaceSlot, app.dart, gated by arrangement.editorOpen/editorRatio) so it can host EITHER the editor or a diff (mutually exclusive in that slot, like the viewer<->editor swap in D-49); (2) selecting a changed file in the git sidebar opens its diff in that above-Claude surface; (3) the diff keeps Claude''s prompt bar fixed (D-47) and gets a draggable divider like the editor. Acceptance: diff no longer appears as a workspace tab; clicking a file in the git panel opens its diff above Claude; closing it (Ctrl/Cmd+W) returns to Claude full-height; coexists with the editor by swap, not both at once. Hunk stage/discard + conflict-resolution widgets can be a further follow-up. Tests: arrangement state for the diff surface; git-sidebar -> diff open wiring; widget render.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-05 15:14:25', '2026-06-05 15:17:09', NULL, 'f97772f7c06252c76de0d158a7d6c3d8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KPN4VK4EZP013NNG', 'bug', NULL, 'Claude pane shows no UI or status-bar progress while compacting', 'When a Claude Code session hits context compaction, the Claude pane gives no signal that anything is happening: the conversation surface shows no in-pane indicator and the status bar shows no progress. From the user''s side the pane just appears to stall until compaction finishes and the next response streams in. Expected: compaction is a visible, first-class state. - In-pane: a clear "Compacting context…" affordance on the conversation surface (e.g. a transient status card / banner in the live-status line from T-168) so it''s obvious the session is busy, not wedged. @@ -624,8 +190,8 @@ Implementation notes: - Source the state from the stream-json event stream (T-164/T-165), not transcript tailing — identify the compaction signal (system/compact_boundary or equivalent event) and thread it through the conversation controller into both the in-pane live-status line and the status-bar slot. - Clear the indicator when compaction completes and normal streaming resumes. -Acceptance: triggering /compact (or an auto-compaction) shows an in-pane "compacting" indicator and a status-bar progress affordance for the duration, both of which clear when it finishes; covered by a unit test against a canned event fixture containing the compaction event.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-06-05 15:21:10', '2026-06-05 15:24:35', NULL, '04db2dabeefd0f0889978c352419b7ef', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-50', 'story', NULL, 'toast notification system', '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). +Acceptance: triggering /compact (or an auto-compaction) shows an in-pane "compacting" indicator and a status-bar progress affordance for the duration, both of which clear when it finishes; covered by a unit test against a canned event fixture containing the compaction event.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-06-05 15:21:10', '2026-06-05 15:24:35', NULL, '8f0b6c74540f9e8d68c2ce076d8f13fb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYY4FVPX9KRVHCMC', 'story', NULL, 'toast notification system', '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). REFINED 2026-06-05. Build (bottom-right, system + proof emitter): - kernel ToastService (ChangeNotifier): show(message, {severity, duration}) -> id, dismiss(id), queue of active toasts, per-toast auto-dismiss Timer (default ~4s; errors longer/sticky), cancel on manual dismiss. ToastSeverity {success, warning, error, info}. @@ -633,20 +199,16 @@ REFINED 2026-06-05. Build (bottom-right, system + proof emitter): - ToastOverlay: mounted in the app-root Stack (app.dart) alongside ClidePalette/QuickOpenOverlay, anchored bottom-right; stacks multiple toasts vertically; animates enter/exit. - a11y: Semantics liveRegion for the message + labelled dismiss; keyboard-dismissable. - Proof emitter: git push success/failure raises a toast (wire in the git command path). Broad wiring (extension activated, update available, generic errors) = follow-up tickets. -- Tests: ToastService queue + auto-dismiss (fakeAsync timers) + severity mapping; ClideToast render per severity; a11y contract; overlay mount.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:48', '2026-06-05 21:12:23', NULL, '847e008c4dcad791c53a9f34729414de', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-245', 'task', 'T-50', 'clide ui toast — raise a toast from the CLI (D-6 drive-half for T-50)', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-05 21:20:00', '2026-06-05 21:20:00', NULL, 'c7177b5ba4e9b6163107116d84a73681', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-245', 'task', 'T-50', 'clide ui toast — raise a toast from the CLI (D-6 drive-half for T-50)', 'Implements the agent/CLI drive-half of T-50 (parity, like ui.open/T-231): clide ui toast "message" [--severity success|warning|error|info] [--duration MS]. Registered as ui.toast in lib/src/daemon/ui_command.dart; publishes {message,severity,durationMs?} on the kernel MessageBus ''toast'' channel (literal kept Flutter-free), which the ToastService consumes. Lets a hosted Claude session (or any script) surface ''done/failed'' on the user''s screen. Validates message-required, severity, integer duration; toolError when no live GUI. Tested in test/daemon/ui_command_test.dart. NOTE: a running GUI must be rebuilt to pick this up.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-05 21:20:00', '2026-06-05 21:24:52', NULL, 'a541f15f4ef238572f5945d96c9229a7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-245', 'task', 'T-50', 'clide ui toast — raise a toast from the CLI (D-6 drive-half for T-50)', 'Implements the agent/CLI drive-half of T-50 (parity, like ui.open/T-231): clide ui toast "message" [--severity success|warning|error|info] [--duration MS]. Registered as ui.toast in lib/src/daemon/ui_command.dart; publishes {message,severity,durationMs?} on the kernel MessageBus ''toast'' channel (literal kept Flutter-free), which the ToastService consumes. Lets a hosted Claude session (or any script) surface ''done/failed'' on the user''s screen. Validates message-required, severity, integer duration; toolError when no live GUI. Tested in test/daemon/ui_command_test.dart. NOTE: a running GUI must be rebuilt to pick this up.', 'done', 'medium', NULL, NULL, NULL, '2026-06-05 21:20:00', '2026-06-05 21:24:52', NULL, 'b85c0483a2d7df0766e187face0e5105', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-246', 'bug', NULL, 'pane list omits the loaded subject — CLI can''t tell which ticket/decision/file a detail pane shows', NULL, 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-05 21:28:44', '2026-06-05 21:28:44', NULL, '48e7c56912885e639cf87a9266566c7c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-247', 'bug', NULL, 'clide CLI ignores CLIDE_SOCK and auto-discovers; stale sockets orphaned in runtime dir', NULL, 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-05 21:28:48', '2026-06-05 21:28:48', NULL, '620d197fc03620d733b0fa250fdd3ef6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-246', 'bug', NULL, 'pane list omits the loaded subject — CLI can''t tell which ticket/decision/file a detail pane shows', '`clide pane list` reports every GUI tab (T-219, D-83) but only its generic chrome: `id`, `title`, `active`, `visible`. For the context-slot detail panes the title is a static word — `tickets.detail` → "Ticket", `decisions.detail` → "Decision", `editor.active` → "Editor" — so the CLI cannot tell *which* ticket / decision / file the pane currently shows. The loaded subject lives in the view''s reader-nav state and never reaches the wire. +- Tests: ToastService queue + auto-dismiss (fakeAsync timers) + severity mapping; ClideToast render per severity; a11y contract; overlay mount.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:31:48', '2026-06-05 21:12:23', NULL, 'e0477e4edfd5043e7f47082375985ed6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM66DNEK5FS9C0T4J5R', 'task', '06FB0TNQM5YYY4FVPX9KRVHCMC', 'clide ui toast — raise a toast from the CLI (D-6 drive-half for T-50)', 'Implements the agent/CLI drive-half of T-50 (parity, like ui.open/T-231): clide ui toast "message" [--severity success|warning|error|info] [--duration MS]. Registered as ui.toast in lib/src/daemon/ui_command.dart; publishes {message,severity,durationMs?} on the kernel MessageBus ''toast'' channel (literal kept Flutter-free), which the ToastService consumes. Lets a hosted Claude session (or any script) surface ''done/failed'' on the user''s screen. Validates message-required, severity, integer duration; toolError when no live GUI. Tested in test/daemon/ui_command_test.dart. NOTE: a running GUI must be rebuilt to pick this up.', 'done', 'medium', NULL, NULL, NULL, '2026-06-05 21:20:00', '2026-06-05 21:24:52', NULL, 'b0058695aa24a1418720d2d33ef1c51e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6DH7C6GQN9WNKQNXR', 'bug', NULL, 'pane list omits the loaded subject — CLI can''t tell which ticket/decision/file a detail pane shows', '`clide pane list` reports every GUI tab (T-219, D-83) but only its generic chrome: `id`, `title`, `active`, `visible`. For the context-slot detail panes the title is a static word — `tickets.detail` → "Ticket", `decisions.detail` → "Decision", `editor.active` → "Editor" — so the CLI cannot tell *which* ticket / decision / file the pane currently shows. The loaded subject lives in the view''s reader-nav state and never reaches the wire. Concrete failure: with a ticket open in the right-hand context pane, there is no `clide` verb that answers "what ticket am I looking at". `pane list` shows `{"id":"tickets.detail","title":"Ticket","active":true}` and stops there. This breaks D-6 parity — the UI surfaces the open ticket; the CLI can''t observe it. Fix: give `ViewPane` (lib/src/panes/view_pane.dart) an optional `subject` (and/or `subtitle`) field and have `snapshotViewPanes` (lib/kernel/src/panels/view_pane_snapshot.dart) populate it from the active reader-nav selection for detail panes — `tickets.detail` → `T-NNN`, `decisions.detail` → `D-NNN`, `editor.active` → the file path. Serialise it in `ViewPane.toJson` so `pane list` carries it. Keep ViewPane Flutter-free (it must stay usable under `dart test`); read the selection in the kernel-side snapshot, not in the value object. -Acceptance: with a ticket open in the context pane, `clide pane list` returns that pane with its loaded id (e.g. `"subject":"T-244"`); same for an open decision and an open editor file; panes with no subject omit the field; covered by a unit test over a snapshot with a populated reader-nav selection.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-05 21:28:44', '2026-06-05 21:29:50', NULL, 'a2214de9649c24973e22e32ca2673dda', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-247', 'bug', NULL, 'clide CLI ignores CLIDE_SOCK and auto-discovers; stale sockets orphaned in runtime dir', 'The `clide` CLI does not honor the `CLIDE_SOCK` env var — it auto-discovers a running instance instead. Proof: with `CLIDE_SOCK=/run/user/1000/clide/DOES_NOT_EXIST.sock`, `clide version` still returns `{"version":"2.1.0"}` from the live app. Separately, the runtime socket dir accumulates orphaned socket files: `/run/user/1000/clide/` held two `.sock` entries while only one GUI process (the `build/linux/x64/debug/bundle/clide` bundle) was running, so a previous run''s socket was never cleaned up. +Acceptance: with a ticket open in the context pane, `clide pane list` returns that pane with its loaded id (e.g. `"subject":"T-244"`); same for an open decision and an open editor file; panes with no subject omit the field; covered by a unit test over a snapshot with a populated reader-nav selection.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-05 21:28:44', '2026-06-05 21:29:50', NULL, '31e0b8f73eae2c4c23d8bbe8265c4c53', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6QCYT7JBF352YVJ48', 'bug', NULL, 'clide CLI ignores CLIDE_SOCK and auto-discovers; stale sockets orphaned in runtime dir', 'The `clide` CLI does not honor the `CLIDE_SOCK` env var — it auto-discovers a running instance instead. Proof: with `CLIDE_SOCK=/run/user/1000/clide/DOES_NOT_EXIST.sock`, `clide version` still returns `{"version":"2.1.0"}` from the live app. Separately, the runtime socket dir accumulates orphaned socket files: `/run/user/1000/clide/` held two `.sock` entries while only one GUI process (the `build/linux/x64/debug/bundle/clide` bundle) was running, so a previous run''s socket was never cleaned up. Why it matters: today (single instance) it''s harmless, but it''s a latent split-brain + observability hole. If two clide instances are ever live on the same machine, the CLI attaches to whichever discovery resolves first, with (a) no way to target a specific instance and (b) no way to find out which one you''re talking to. Combined with the stale-socket litter, `clide` could silently drive the wrong window. This is a D-6 surface gap — the CLI must be able to address the same instance the user is in. @@ -655,25 +217,35 @@ Fix (scope to confirm): - Clean up orphaned/stale socket files on app startup (and on clean shutdown) — detect a dead listener and unlink before binding a new hash. - Add a way to enumerate/identify live instances (e.g. a `clide instances` verb, or include the instance id/socket path + pid in `clide version`) so a human or agent can pick the right one. -Acceptance: a bogus `CLIDE_SOCK` fails loudly instead of returning data from a different instance; a valid `CLIDE_SOCK` pins the CLI to that instance; startup leaves exactly one live socket for one running app (no orphan accumulation); there is a CLI affordance to list/identify running instances.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-05 21:28:48', '2026-06-05 21:30:04', NULL, '7d27dbda86b2499ce84a8fda9535d9ab', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-239', 'bug', NULL, 'Status-bar right group still not hugging the center block''s right edge', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column). +Acceptance: a bogus `CLIDE_SOCK` fails loudly instead of returning data from a different instance; a valid `CLIDE_SOCK` pins the CLI to that instance; startup leaves exactly one live socket for one running app (no orphan accumulation); there is a CLI affordance to list/identify running instances.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-05 21:28:48', '2026-06-05 21:30:04', NULL, '7fd8f211a761d1974b5b51fd7026e594', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F4D2Q363QF7V40K8', 'bug', NULL, 'Status-bar right group still not hugging the center block''s right edge', 'Unresolved after T-237 (commits 643417e then reverted by dc84f4c). GOAL: in the single bottom status bar, left items (git branch ''main'', skills count) sit flush-LEFT and the global right group (ipc ''application ok'' + theme switcher) sits flush-RIGHT of the CENTER (workspace) block — NOT the window edge, and NOT two separate left-aligned columns. Keep the 3-rail bottom bar (sidebar rail | workspace status | context tab-rail). CURRENT STATE (dc84f4c): StatusbarHost is a single Container(Row[ ...left, Spacer(), ...right ]) with NO alignment, sitting in Expanded(StatusbarHost()) inside the bottom-bar Row (app.dart ~261). In theory the Spacer should right-align the right group within the workspace Expanded; in practice (verified live by the user, even after a hot RESTART) the right group does NOT hug the center block''s right edge. ATTEMPTS THAT FAILED: (a) removing ''alignment: Alignment.center'' from the StatusbarHost Container; (b) splitting into two hosts and trailing the right group past the context rail (overshot to the window edge — wrong). LEAD / NEXT STEP: instrument the actual constraints rather than guess. Prime suspect: the OUTER Column at app.dart ~214 has NO crossAxisAlignment (defaults to center), so the bottom-bar Container may receive LOOSE width and size to its content, collapsing the Spacer (right group ends up adjacent to the left group, both effectively left-aligned). Try CrossAxisAlignment.stretch on that Column (or give the status Container an explicit full width), and confirm Expanded(StatusbarHost) actually receives a bounded full-workspace width. A widget/integration test asserting the right group''s x-offset == workspace-region right edge would lock it. Files: lib/app.dart (StatusbarHost + the bottom-bar Row + the outer Column). -FIXED (2026-06-05): root cause confirmed by probe at 3440px width — the bottom bar used Row[ ...left (incl. a Flexible flex:1 loose item, the Claude status marquee), Spacer(), ...right ]. The Spacer (Expanded, flex:1) and the left flex:1 item SPLIT the free space 50/50, so the Spacer only pushed the right group by HALF the free space. That drift is proportional to width: tiny at 1200px (why probes/normal screens looked fine), ~1500px at 3440px (the right group floated to mid-bar) — hence ''only on ultrawide''. Probe: OLD R.right=1934 at a 3440 edge; NEW R.right=3440. FIX (app.dart StatusbarHost): explicit two-column layout — left group wrapped in Expanded(Row[...]) so it absorbs ALL free space (flex item flexes within it), right group trails at intrinsic width → hugs the workspace block''s right edge by construction, width-independent. Regression test test/app_statusbar_test.dart asserts the right group at width-8 for BOTH 600px and 3440px. Full fast suite green.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 21:41:58', '2026-06-06 06:44:40', NULL, '12e99f52a8fbd81e86cff491fcce2b29', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-238', 'task', NULL, 'Apply the -hc-as-checkbox + sort to the ctrl+k theme modal (T-237 follow-up)', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish.', 'in_progress', 'low', NULL, NULL, 'D-6', '2026-06-03 21:11:40', '2026-06-06 06:51:47', NULL, '13ce67e56195cefb843cd67352e8de03', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'story', NULL, '/clide skill — advertise the clide tool surface to Claude', NULL, 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 07:21:17', NULL, '93050411c08275cb579a410bf915506d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-249', 'story', NULL, 'Image viewer conversation card + plumbing', NULL, 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 07:21:17', NULL, 'b2f7caef031f2af6a341cd2bdcca6553', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'story', NULL, '/clide skill — advertise the clide tool surface to Claude', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. +FIXED (2026-06-05): root cause confirmed by probe at 3440px width — the bottom bar used Row[ ...left (incl. a Flexible flex:1 loose item, the Claude status marquee), Spacer(), ...right ]. The Spacer (Expanded, flex:1) and the left flex:1 item SPLIT the free space 50/50, so the Spacer only pushed the right group by HALF the free space. That drift is proportional to width: tiny at 1200px (why probes/normal screens looked fine), ~1500px at 3440px (the right group floated to mid-bar) — hence ''only on ultrawide''. Probe: OLD R.right=1934 at a 3440 edge; NEW R.right=3440. FIX (app.dart StatusbarHost): explicit two-column layout — left group wrapped in Expanded(Row[...]) so it absorbs ALL free space (flex item flexes within it), right group trails at intrinsic width → hugs the workspace block''s right edge by construction, width-independent. Regression test test/app_statusbar_test.dart asserts the right group at width-8 for BOTH 600px and 3440px. Full fast suite green.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 21:41:58', '2026-06-06 06:44:40', NULL, '8534c325d61e138f74d6d03ca85496f4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5F2GC0ABFFVAH34EC', 'task', NULL, 'Apply the -hc-as-checkbox + sort to the ctrl+k theme modal (T-237 follow-up)', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish. -Problem: my knowledge of the clide surface comes from a hand-curated session blurb. There is no runtime discovery — `clide help` is a stub, `clide ` with no verb just prints `usage:`, and the dispatcher''s registered command table (e.g. pane_commands.dart) is never exposed. D-6 guarantees a verb EXISTS for every UI action, but parity != discoverability: a correctly-registered verb is still unreachable if nothing tells me it''s there. +DONE (2026-06-06). Re-scoped per user: instead of just applying the -hc-checkbox+sort polish to the theme modal, the modal was PROMOTED to a general Settings modal (picker_view.dart -> settings_view.dart, ThemePickerView -> SettingsView). ctrl+k / theme.pick now opens Settings, whose only section today is Appearance: base themes (sorted) + a High contrast toggle, reusing the theme_families helpers (shared with the status-bar popover, T-237). Command id kept as theme.pick (welcome link + tests reference it); retitled ''Settings...''. Extensible: more sections later (or split to a builtin/settings extension when it grows). Tests in test/builtin/theme_picker/widget_test.dart (base-only list, hc toggle applies sibling). NOTE: status-bar popover (T-237) was already done — that is what the user saw ''done in the ui''.', 'done', 'low', NULL, NULL, 'D-6', '2026-06-03 21:11:40', '2026-06-06 07:24:00', NULL, 'e6a5dd2bf5172a8b88ccac69014815be', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZQNDVPAPFSD9KYS4', 'task', '06FB0TNQM5GSR5WPC2T7CJM8XW', 'Command palette: pinned commands + cross-session recency persistence (T-23 follow-up)', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-06 07:40:43', '2026-06-06 07:40:43', NULL, '966e6962f8f2c381c3d65a18edfa01f6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BVE5EYM92CWGSXA4', 'bug', NULL, 'Permission-mode badge + Ctrl-M cycle don''t update the status bar', 'Symptom: the permission-mode badge in the Claude status line does nothing visible when clicked, and Ctrl/Cmd+M (cycle mode, T-226) likewise appears to do nothing. Either the mode isn''t actually changing, or — more likely — it changes in the Claude process but the status-bar tracker is never updated. -Scope: -- Self-description first (prereq for a non-rotting skill): add a discovery verb that reflects the live dispatcher registry — e.g. `clide capabilities` (machine-readable JSON: subsystems -> verbs -> arg schema) and/or flesh out `clide help` to enumerate subsystems/verbs. Sourced from the registry so it never drifts. -- `/clide` SKILL.md: trigger description always visible to Claude; body points at the discovery verb rather than hard-coding a verb list, plus conventions (slots: sidebar/workspace/context, pane kinds, focus/spawn/close/write/resize). Thin and always-correct. +Likely root cause (confirmed by reading the code): the cycle is fire-and-forget with no status feedback. +- Composer Ctrl/Cmd+M -> widget.onCycleMode -> _ClaudePaneState._cycleMode (lib/builtin/claude/src/claude_pane.dart:302) -> s.setPermissionMode(nextSafePermissionMode(...)). +- The badge click path (_ModeBadge.onCycle) routes to the same _cycleMode, so both symptoms share one cause. +- StreamJsonSession.setPermissionMode (lib/builtin/claude/src/stream_json_session.dart:671) only writes a `set_permission_mode` control_request to the process. It does NOT optimistically update `_status` nor emit on `_statusCtl`. +- `_status.permissionMode` is only ever set from a `system/init` event via `_statusFromEvent` (stream_json_session.dart:567-571). A set_permission_mode control_request returns a control_response, which is not folded into status — so `_status` never re-emits, the badge label (claude_pane.dart:105-122, driven by `_status` over statusStream) is stale, and the UI looks dead. -This is what makes T-249 (image viewer) and future panels reachable by Claude the moment they register — no skill edit per panel. +So the status bar / badge value is not updated on change — matching the reported hypothesis. -Refs: D-6 (CLI/event-surface parity). Related: T-249.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 07:21:30', NULL, '6657ddeb69fd1b4c1851dba75b9ae40f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-249', 'story', NULL, 'Image viewer conversation card + plumbing', 'Add an image-viewer card to the Claude conversation log, with the CLI verb that drives it (D-6 parity). +Fix direction: +- Optimistically merge the new mode into `_status` and emit on `_statusCtl` immediately after writing the control_request (mirror how other state transitions surface), and/or handle the `control_response` for set_permission_mode and reconcile from it. +- Separately verify the control_request is actually honored by the Claude process (the ''they don''t work'' branch) — if the response reports failure, surface it rather than silently assuming success. + +Acceptance: +- Clicking the badge and pressing Ctrl/Cmd+M both visibly cycle default -> accept-edits -> plan -> default in the status line. +- The displayed mode reflects the session''s actual mode (reconciled from the response / next init), not just an optimistic guess. +- Regression test at the session level: setPermissionMode emits an updated SessionStatus on statusStream. + +Refs: T-226 (interactive mode badge + Ctrl/Cmd+M), T-181 (bypassPermissions behind confirmed path — keep excluded from the safe cycle), D-78 (interaction-zone / display-only conventions).', 'done', 'high', NULL, NULL, NULL, '2026-06-06 07:26:50', '2026-06-06 07:49:08', NULL, 'cb4f4331f0aedf5a61dd350ab1dfdde7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7JXD3FKW91934D0W0', 'story', NULL, 'Image viewer conversation card + plumbing', 'Add an image-viewer card to the Claude conversation log, with the CLI verb that drives it (D-6 parity). Scope: - Conversation card: render an image inline in the Claude conversation widget — clide-owned rendering (no opinionated package), display-only per D-78 (no inline interactive controls; any controls belong in the interaction zone). Respect theme tokens / ui-design. @@ -682,8 +254,8 @@ Scope: Dependency: pairs with T-248 — without the /clide skill + discovery verb, Claude won''t know this card/verb exists even once shipped. Build the plumbing here; T-248 makes it discoverable. -Refs: D-6 (parity), D-78 (interaction zone, display-only conversation widgets). Related: T-248.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 07:21:36', NULL, 'd680715048a59598aa95144197765f1f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'story', NULL, '/clide skill — advertise the clide tool surface to Claude', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. +Refs: D-6 (parity), D-78 (interaction zone, display-only conversation widgets). Related: T-248.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 08:21:40', NULL, '2c320b610b77976f25199581c3d04f04', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM70TW38E78FFXDHYAC', 'story', NULL, '/clide skill — advertise the clide tool surface to Claude', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. Problem: my knowledge of the clide surface comes from a hand-curated session blurb. There is no runtime discovery — `clide help` is a stub, `clide ` with no verb just prints `usage:`, and the dispatcher''s registered command table (e.g. pane_commands.dart) is never exposed. D-6 guarantees a verb EXISTS for every UI action, but parity != discoverability: a correctly-registered verb is still unreachable if nothing tells me it''s there. @@ -693,342 +265,28 @@ Scope: This is what makes T-249 (image viewer) and future panels reachable by Claude the moment they register — no skill edit per panel. -Refs: D-6 (CLI/event-surface parity). Related: T-249.', 'ready', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 07:22:48', NULL, '0dcde6af3f6f4064aa90b4b1599f3bcb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-249', 'story', NULL, 'Image viewer conversation card + plumbing', 'Add an image-viewer card to the Claude conversation log, with the CLI verb that drives it (D-6 parity). +Refs: D-6 (CLI/event-surface parity). Related: T-249. -Scope: -- Conversation card: render an image inline in the Claude conversation widget — clide-owned rendering (no opinionated package), display-only per D-78 (no inline interactive controls; any controls belong in the interaction zone). Respect theme tokens / ui-design. -- Plumbing: a `clide` verb to show an image in the log (e.g. `clide image show ` or via the pane subsystem), accepting a workspace path; define accepted formats (PNG/JPEG/...), path resolution (workspace-relative), and sizing/scaling behavior. -- Parity (D-6): the verb is the CLI counterpart of the card; ensure it registers in the dispatcher so it shows up in the discovery verb from T-248. +RE-SCOPE (2026-06-06, from T-208 completeness review): partly overtaken by reality — do NOT build from scratch. +- The `clide capabilities` verb already EXISTS in code: registered in lib/src/daemon/dispatcher.dart:12 (built-in) and as an umbrella command in lib/src/cli/argv_to_request.dart:28. Its handler (_capabilities, dispatcher.dart:86) enumerates the live registry with arg schemas — so the non-rotting design is already implemented server-side. +- A `/clide` skill is ALSO already present this session (other ongoing work), and it instructs agents to "Start with `clide capabilities` to enumerate the live tool surface." +- BUT: against the live instance, `clide capabilities` returns a usage stub at exit 0 (`usage: clide capabilities [args...]`) instead of the command table — i.e. the discoverability entry point the skill advertises is currently DEAD. Likely a stale running build or a C-client umbrella mismatch (native/clide-cli/clide.c forwards argv but the live server treats `capabilities` as a subsystem needing a verb). -Dependency: pairs with T-248 — without the /clide skill + discovery verb, Claude won''t know this card/verb exists even once shipped. Build the plumbing here; T-248 makes it discoverable. +New scope for this ticket: +1. Verify/fix the live `clide capabilities` path end-to-end (rebuild/restart vs real wiring gap in the C client / server umbrella handling). Acceptance: `clide capabilities` returns the JSON command table against a running instance. +2. Reconcile with the already-shipped `/clide` skill rather than authoring anew — confirm it points at the working verb and covers the conventions. +Net: this is now a verify-and-reconcile task, not a build-from-scratch one. Related: T-249, T-208.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 08:31:25', NULL, '2d81d44df3ca33ac1c5ada98d842519a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5NEXC0P46NJHACNM0', 'task', '06FB0TNQM7XMQ44AVCAARQZX14', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open '' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 09:20:02', NULL, 'f0131be14b518c6169966a8719b5700b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM78Q3VYWSE830CTVB4', 'task', '06FB0TNQM4QYYKHPQ03H3QD61W', 'In-app ''Install clide command in PATH'' affordance', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell. -Refs: D-6 (parity), D-78 (interaction zone, display-only conversation widgets). Related: T-248.', 'ready', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 07:22:48', NULL, '2961d27113d9f367b8e98da950c519ec', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-238', 'task', NULL, 'Apply the -hc-as-checkbox + sort to the ctrl+k theme modal (T-237 follow-up)', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish. - -DONE (2026-06-06). Re-scoped per user: instead of just applying the -hc-checkbox+sort polish to the theme modal, the modal was PROMOTED to a general Settings modal (picker_view.dart -> settings_view.dart, ThemePickerView -> SettingsView). ctrl+k / theme.pick now opens Settings, whose only section today is Appearance: base themes (sorted) + a High contrast toggle, reusing the theme_families helpers (shared with the status-bar popover, T-237). Command id kept as theme.pick (welcome link + tests reference it); retitled ''Settings...''. Extensible: more sections later (or split to a builtin/settings extension when it grows). Tests in test/builtin/theme_picker/widget_test.dart (base-only list, hc toggle applies sibling). NOTE: status-bar popover (T-237) was already done — that is what the user saw ''done in the ui''.', 'in_progress', 'low', NULL, NULL, 'D-6', '2026-06-03 21:11:40', '2026-06-06 07:24:00', NULL, 'c17e9e7ec5a97c6f7adbc537f609f339', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-238', 'task', NULL, 'Apply the -hc-as-checkbox + sort to the ctrl+k theme modal (T-237 follow-up)', 'T-237 redesigned the status-bar theme POPOVER (theme_status_item.dart): base themes only, sorted by display name, a ''High contrast'' toggle that applies the -hc sibling, palette icon. The MODAL picker (picker_view.dart, opened by theme.pick / ctrl+k) still lists every theme including the -hc rows in available order. For consistency, apply the same treatment to picker_view: use the theme_families helpers (baseThemes / isHcName / resolveThemeName, already built + unit-tested in lib/builtin/theme_picker/src/theme_families.dart) to show base themes sorted, with a High contrast checkbox at the top. Low priority — the modal works; this is consistency polish. - -DONE (2026-06-06). Re-scoped per user: instead of just applying the -hc-checkbox+sort polish to the theme modal, the modal was PROMOTED to a general Settings modal (picker_view.dart -> settings_view.dart, ThemePickerView -> SettingsView). ctrl+k / theme.pick now opens Settings, whose only section today is Appearance: base themes (sorted) + a High contrast toggle, reusing the theme_families helpers (shared with the status-bar popover, T-237). Command id kept as theme.pick (welcome link + tests reference it); retitled ''Settings...''. Extensible: more sections later (or split to a builtin/settings extension when it grows). Tests in test/builtin/theme_picker/widget_test.dart (base-only list, hc toggle applies sibling). NOTE: status-bar popover (T-237) was already done — that is what the user saw ''done in the ui''.', 'done', 'low', NULL, NULL, 'D-6', '2026-06-03 21:11:40', '2026-06-06 07:24:00', NULL, 'e25b56f7c3ddbddcf94993177830c98e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-250', 'bug', NULL, 'Permission-mode badge + Ctrl-M cycle don''t update the status bar', NULL, 'backlog', 'high', NULL, NULL, NULL, '2026-06-06 07:26:50', '2026-06-06 07:26:50', NULL, '28e51870658c6c7ca40f57b68d4cd618', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-250', 'bug', NULL, 'Permission-mode badge + Ctrl-M cycle don''t update the status bar', 'Symptom: the permission-mode badge in the Claude status line does nothing visible when clicked, and Ctrl/Cmd+M (cycle mode, T-226) likewise appears to do nothing. Either the mode isn''t actually changing, or — more likely — it changes in the Claude process but the status-bar tracker is never updated. - -Likely root cause (confirmed by reading the code): the cycle is fire-and-forget with no status feedback. -- Composer Ctrl/Cmd+M -> widget.onCycleMode -> _ClaudePaneState._cycleMode (lib/builtin/claude/src/claude_pane.dart:302) -> s.setPermissionMode(nextSafePermissionMode(...)). -- The badge click path (_ModeBadge.onCycle) routes to the same _cycleMode, so both symptoms share one cause. -- StreamJsonSession.setPermissionMode (lib/builtin/claude/src/stream_json_session.dart:671) only writes a `set_permission_mode` control_request to the process. It does NOT optimistically update `_status` nor emit on `_statusCtl`. -- `_status.permissionMode` is only ever set from a `system/init` event via `_statusFromEvent` (stream_json_session.dart:567-571). A set_permission_mode control_request returns a control_response, which is not folded into status — so `_status` never re-emits, the badge label (claude_pane.dart:105-122, driven by `_status` over statusStream) is stale, and the UI looks dead. - -So the status bar / badge value is not updated on change — matching the reported hypothesis. - -Fix direction: -- Optimistically merge the new mode into `_status` and emit on `_statusCtl` immediately after writing the control_request (mirror how other state transitions surface), and/or handle the `control_response` for set_permission_mode and reconcile from it. -- Separately verify the control_request is actually honored by the Claude process (the ''they don''t work'' branch) — if the response reports failure, surface it rather than silently assuming success. - -Acceptance: -- Clicking the badge and pressing Ctrl/Cmd+M both visibly cycle default -> accept-edits -> plan -> default in the status line. -- The displayed mode reflects the session''s actual mode (reconciled from the response / next init), not just an optimistic guess. -- Regression test at the session level: setPermissionMode emits an updated SessionStatus on statusStream. - -Refs: T-226 (interactive mode badge + Ctrl/Cmd+M), T-181 (bypassPermissions behind confirmed path — keep excluded from the safe cycle), D-78 (interaction-zone / display-only conventions).', 'backlog', 'high', NULL, NULL, NULL, '2026-06-06 07:26:50', '2026-06-06 07:31:06', NULL, 'e555fe45be44f3a22e30005370aa6ec0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-250', 'bug', NULL, 'Permission-mode badge + Ctrl-M cycle don''t update the status bar', 'Symptom: the permission-mode badge in the Claude status line does nothing visible when clicked, and Ctrl/Cmd+M (cycle mode, T-226) likewise appears to do nothing. Either the mode isn''t actually changing, or — more likely — it changes in the Claude process but the status-bar tracker is never updated. - -Likely root cause (confirmed by reading the code): the cycle is fire-and-forget with no status feedback. -- Composer Ctrl/Cmd+M -> widget.onCycleMode -> _ClaudePaneState._cycleMode (lib/builtin/claude/src/claude_pane.dart:302) -> s.setPermissionMode(nextSafePermissionMode(...)). -- The badge click path (_ModeBadge.onCycle) routes to the same _cycleMode, so both symptoms share one cause. -- StreamJsonSession.setPermissionMode (lib/builtin/claude/src/stream_json_session.dart:671) only writes a `set_permission_mode` control_request to the process. It does NOT optimistically update `_status` nor emit on `_statusCtl`. -- `_status.permissionMode` is only ever set from a `system/init` event via `_statusFromEvent` (stream_json_session.dart:567-571). A set_permission_mode control_request returns a control_response, which is not folded into status — so `_status` never re-emits, the badge label (claude_pane.dart:105-122, driven by `_status` over statusStream) is stale, and the UI looks dead. - -So the status bar / badge value is not updated on change — matching the reported hypothesis. - -Fix direction: -- Optimistically merge the new mode into `_status` and emit on `_statusCtl` immediately after writing the control_request (mirror how other state transitions surface), and/or handle the `control_response` for set_permission_mode and reconcile from it. -- Separately verify the control_request is actually honored by the Claude process (the ''they don''t work'' branch) — if the response reports failure, surface it rather than silently assuming success. - -Acceptance: -- Clicking the badge and pressing Ctrl/Cmd+M both visibly cycle default -> accept-edits -> plan -> default in the status line. -- The displayed mode reflects the session''s actual mode (reconciled from the response / next init), not just an optimistic guess. -- Regression test at the session level: setPermissionMode emits an updated SessionStatus on statusStream. - -Refs: T-226 (interactive mode badge + Ctrl/Cmd+M), T-181 (bypassPermissions behind confirmed path — keep excluded from the safe cycle), D-78 (interaction-zone / display-only conventions).', 'ready', 'high', NULL, NULL, NULL, '2026-06-06 07:26:50', '2026-06-06 07:32:57', NULL, 'b40e943095a1e40ec8e868b392fd41c1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'task', 'T-4', 'wire command palette keybinding', '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. - -**Progress (2026-05-17, T-117):** The keymap layer now binds `ctrl+shift+p` / `meta+shift+p` to `PaletteOpenIntent` in `assets/keymaps/default.yaml`. The intent resolves end-to-end through `KeymapService.resolveEvent` → `Actions.maybeInvoke`. **Still pending**: an `Actions` provider somewhere in the tree that handles `PaletteOpenIntent` by calling `kernel.palette.open()`, plus the arrow-key / Escape / Enter handlers on `ClidePalette` itself. Those land as part of T-100 (palette keyboard nav). - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Bindings live in the keymap (T-117) — not in `lib/kernel/src/commands/keybindings.dart` (that file is legacy). -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open). - -DONE (2026-06-06). Keybinding + nav were already wired (T-117 binds ctrl/meta+shift+p -> PaletteOpenIntent; _RootShell handles it -> palette.open(); T-100 added arrow/Enter/Esc nav + selected-index). Remaining acceptance implemented now: (1) FUZZY match — PaletteController.filtered() uses a shared subsequence matcher (lib/kernel/src/fuzzy.dart, extracted from quick_open so both share one source of truth), ranked best-score-first; (2) RECENCY — invoked commands float to the top on empty filter and break fuzzy-score ties (in-session MRU). DEFERRED: ''pinned'' commands + cross-session recency persistence need a pin affordance + settings storage — filed as a follow-up. Tests: test/kernel/src/commands/palette_test.dart + test/kernel/src/fuzzy_test.dart.', 'ready', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-06-06 07:40:43', NULL, 'b0194b050cfb6e5dec567933287c186f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'task', 'T-4', 'wire command palette keybinding', '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. - -**Progress (2026-05-17, T-117):** The keymap layer now binds `ctrl+shift+p` / `meta+shift+p` to `PaletteOpenIntent` in `assets/keymaps/default.yaml`. The intent resolves end-to-end through `KeymapService.resolveEvent` → `Actions.maybeInvoke`. **Still pending**: an `Actions` provider somewhere in the tree that handles `PaletteOpenIntent` by calling `kernel.palette.open()`, plus the arrow-key / Escape / Enter handlers on `ClidePalette` itself. Those land as part of T-100 (palette keyboard nav). - -**Acceptance:** -- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace. -- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight. -- Commands listed are everything registered via `CommandContribution` across all activated extensions. -- Fuzzy match against command title; recent / pinned commands float to the top. - -**Implementation hints:** -- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart). -- Bindings live in the keymap (T-117) — not in `lib/kernel/src/commands/keybindings.dart` (that file is legacy). -- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open). - -DONE (2026-06-06). Keybinding + nav were already wired (T-117 binds ctrl/meta+shift+p -> PaletteOpenIntent; _RootShell handles it -> palette.open(); T-100 added arrow/Enter/Esc nav + selected-index). Remaining acceptance implemented now: (1) FUZZY match — PaletteController.filtered() uses a shared subsequence matcher (lib/kernel/src/fuzzy.dart, extracted from quick_open so both share one source of truth), ranked best-score-first; (2) RECENCY — invoked commands float to the top on empty filter and break fuzzy-score ties (in-session MRU). DEFERRED: ''pinned'' commands + cross-session recency persistence need a pin affordance + settings storage — filed as a follow-up. Tests: test/kernel/src/commands/palette_test.dart + test/kernel/src/fuzzy_test.dart.', 'done', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-06-06 07:40:43', NULL, '0a837620f95c93871c8dcbde152adffc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-251', 'task', 'T-4', 'Command palette: pinned commands + cross-session recency persistence (T-23 follow-up)', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-06 07:40:43', '2026-06-06 07:40:43', NULL, '3d69fb0be99289aa0d9f9bdd48f02aae', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-250', 'bug', NULL, 'Permission-mode badge + Ctrl-M cycle don''t update the status bar', 'Symptom: the permission-mode badge in the Claude status line does nothing visible when clicked, and Ctrl/Cmd+M (cycle mode, T-226) likewise appears to do nothing. Either the mode isn''t actually changing, or — more likely — it changes in the Claude process but the status-bar tracker is never updated. - -Likely root cause (confirmed by reading the code): the cycle is fire-and-forget with no status feedback. -- Composer Ctrl/Cmd+M -> widget.onCycleMode -> _ClaudePaneState._cycleMode (lib/builtin/claude/src/claude_pane.dart:302) -> s.setPermissionMode(nextSafePermissionMode(...)). -- The badge click path (_ModeBadge.onCycle) routes to the same _cycleMode, so both symptoms share one cause. -- StreamJsonSession.setPermissionMode (lib/builtin/claude/src/stream_json_session.dart:671) only writes a `set_permission_mode` control_request to the process. It does NOT optimistically update `_status` nor emit on `_statusCtl`. -- `_status.permissionMode` is only ever set from a `system/init` event via `_statusFromEvent` (stream_json_session.dart:567-571). A set_permission_mode control_request returns a control_response, which is not folded into status — so `_status` never re-emits, the badge label (claude_pane.dart:105-122, driven by `_status` over statusStream) is stale, and the UI looks dead. - -So the status bar / badge value is not updated on change — matching the reported hypothesis. - -Fix direction: -- Optimistically merge the new mode into `_status` and emit on `_statusCtl` immediately after writing the control_request (mirror how other state transitions surface), and/or handle the `control_response` for set_permission_mode and reconcile from it. -- Separately verify the control_request is actually honored by the Claude process (the ''they don''t work'' branch) — if the response reports failure, surface it rather than silently assuming success. - -Acceptance: -- Clicking the badge and pressing Ctrl/Cmd+M both visibly cycle default -> accept-edits -> plan -> default in the status line. -- The displayed mode reflects the session''s actual mode (reconciled from the response / next init), not just an optimistic guess. -- Regression test at the session level: setPermissionMode emits an updated SessionStatus on statusStream. - -Refs: T-226 (interactive mode badge + Ctrl/Cmd+M), T-181 (bypassPermissions behind confirmed path — keep excluded from the safe cycle), D-78 (interaction-zone / display-only conventions).', 'done', 'high', NULL, NULL, NULL, '2026-06-06 07:26:50', '2026-06-06 07:49:08', NULL, 'e381f4325ef95d18da12a82646d24d9a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'story', NULL, '/clide skill — advertise the clide tool surface to Claude', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. - -Problem: my knowledge of the clide surface comes from a hand-curated session blurb. There is no runtime discovery — `clide help` is a stub, `clide ` with no verb just prints `usage:`, and the dispatcher''s registered command table (e.g. pane_commands.dart) is never exposed. D-6 guarantees a verb EXISTS for every UI action, but parity != discoverability: a correctly-registered verb is still unreachable if nothing tells me it''s there. - -Scope: -- Self-description first (prereq for a non-rotting skill): add a discovery verb that reflects the live dispatcher registry — e.g. `clide capabilities` (machine-readable JSON: subsystems -> verbs -> arg schema) and/or flesh out `clide help` to enumerate subsystems/verbs. Sourced from the registry so it never drifts. -- `/clide` SKILL.md: trigger description always visible to Claude; body points at the discovery verb rather than hard-coding a verb list, plus conventions (slots: sidebar/workspace/context, pane kinds, focus/spawn/close/write/resize). Thin and always-correct. - -This is what makes T-249 (image viewer) and future panels reachable by Claude the moment they register — no skill edit per panel. - -Refs: D-6 (CLI/event-surface parity). Related: T-249.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 07:57:16', NULL, 'f6a976eef4c722612111256e339c8f11', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-208', 'initiative', NULL, 'Give Claude hands', 'Make a Claude agent able to actually WORK inside clide: reach the CLI, know clide is hosting it, and observe the same workspace the user sees. Source: self-analysis.md (2026-06-02), a live dogfood probe by a Claude (Opus 4.8) running against a live instance (socket + MCP SSE up, version 2.1.0). - -Finding: the IPC/CLI foundation (D-1, D-6, D-56; built under T-99 incl. the C client T-126 and the `clide tail --events` stream T-129) works end-to-end at the socket level. But a fresh agent cannot REACH or OBSERVE it. Three blockers: -1. No `clide` binary on PATH, and no install path that delivers one (Epic A — blocker). -2. No bootstrap: nothing tells a fresh agent clide is running or where its socket is (Epic B). -3. The live UI panes / active file are not reflected in the CLI registries, so the D-6 parity premise (agent sees what the user sees) breaks (Epic C). - -Plus: a governance decision on WHICH agent model is the dogfood target (hosted stream-json session vs external CLI driver vs both) and an MCP follow-up. The pleasant surprise per the probe: the hard part (a live, correct, single-process IPC contract with proper 0/1/3 exit codes) is done; what''s missing is the last mile that puts the tool in the agent''s hands. NOTE: the report says ''daemon'' but D-56 dissolved it — this is the in-process IPC server; the socket contract is unchanged.', 'ready', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-06 08:06:55', NULL, '021035195ac2b960d7ac7f3aa35efcf9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-209', 'epic', 'T-208', 'Ship the clide CLI on PATH', 'Gap 1 (BLOCKER) from self-analysis.md. The C client native/clide-cli/clide.c (T-126, done) is the real CLI, but there is no supported way for `clide` to land on an agent''s PATH: +Refinement (2026-06-03, from live dogfooding): scope should include PROACTIVE detection on launch, not just a palette command. When the clide IDE starts in a repo, check whether ''clide'' resolves on PATH AND points to the C client (not a stale symlink to the GUI bundle runner) -- we hit exactly this: ~/.local/bin/clide was a May-6 symlink to ~/.local/lib/clide/clide (the Flutter GUI), so a bare ''clide pane list'' launched a second app instead of querying. If missing or stale, prompt/offer to install (copy the bundled C client to a PATH dir, VS Code ''Install code command'' style) and report success. This is what lets a fresh agent actually reach the CLI (D-83 names the hosted session primary, but an external agent benefits too). Detecting ''stale GUI symlink'' specifically: the target should be an ELF/Mach-O executable, not a symlink into the bundle. Alternative path the user raised: instead of/alongside this, make the /ide MCP surface reachable (T-225) -- but CLI is primary per D-68.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-06 10:02:03', NULL, '58895405f0e740d4b8db4a37e8ca3f87', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QYYKHPQ03H3QD61W', 'epic', '06FB0TNQM7XMQ44AVCAARQZX14', 'Ship the clide CLI on PATH', 'Gap 1 (BLOCKER) from self-analysis.md. The C client native/clide-cli/clide.c (T-126, done) is the real CLI, but there is no supported way for `clide` to land on an agent''s PATH: - `make clide-cli` (Makefile:284) is a non-default target; `build` (Makefile:169) does not depend on it, so the binary is never produced by a normal build (native/macos-arm64/clide had never been built). - `make install` (Makefile:195) copies the GUI bundle and `ln -sf $(INSTALL_PREFIX)/clide/clide $(INSTALL_DIR)/clide` (Makefile:200) symlinks the FLUTTER GUI runner — not the C client — on BOTH macOS and Linux (on macOS that path doesn''t even resolve inside clide.app). -Net: `which clide` -> not found, `clide ping` -> exit 127. The CLI-first contract (D-1) has no delivery mechanism. Related to the broader installer epic T-46, but that is the GUI/runtime installer; this epic is specifically C-client-on-PATH delivery.', 'in_progress', 'high', NULL, NULL, 'D-1', '2026-06-02 18:13:52', '2026-06-06 08:12:45', NULL, 'b6572a0ff80e81002cb8ce71550e8e08', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-208', 'initiative', NULL, 'Give Claude hands', 'Make a Claude agent able to actually WORK inside clide: reach the CLI, know clide is hosting it, and observe the same workspace the user sees. Source: self-analysis.md (2026-06-02), a live dogfood probe by a Claude (Opus 4.8) running against a live instance (socket + MCP SSE up, version 2.1.0). - -Finding: the IPC/CLI foundation (D-1, D-6, D-56; built under T-99 incl. the C client T-126 and the `clide tail --events` stream T-129) works end-to-end at the socket level. But a fresh agent cannot REACH or OBSERVE it. Three blockers: -1. No `clide` binary on PATH, and no install path that delivers one (Epic A — blocker). -2. No bootstrap: nothing tells a fresh agent clide is running or where its socket is (Epic B). -3. The live UI panes / active file are not reflected in the CLI registries, so the D-6 parity premise (agent sees what the user sees) breaks (Epic C). - -Plus: a governance decision on WHICH agent model is the dogfood target (hosted stream-json session vs external CLI driver vs both) and an MCP follow-up. The pleasant surprise per the probe: the hard part (a live, correct, single-process IPC contract with proper 0/1/3 exit codes) is done; what''s missing is the last mile that puts the tool in the agent''s hands. NOTE: the report says ''daemon'' but D-56 dissolved it — this is the in-process IPC server; the socket contract is unchanged.', 'in_progress', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-06 08:12:45', NULL, 'ebd93ec3f2bcf8381bd4131117527357', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'story', NULL, '/clide skill — advertise the clide tool surface to Claude', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. - -Problem: my knowledge of the clide surface comes from a hand-curated session blurb. There is no runtime discovery — `clide help` is a stub, `clide ` with no verb just prints `usage:`, and the dispatcher''s registered command table (e.g. pane_commands.dart) is never exposed. D-6 guarantees a verb EXISTS for every UI action, but parity != discoverability: a correctly-registered verb is still unreachable if nothing tells me it''s there. - -Scope: -- Self-description first (prereq for a non-rotting skill): add a discovery verb that reflects the live dispatcher registry — e.g. `clide capabilities` (machine-readable JSON: subsystems -> verbs -> arg schema) and/or flesh out `clide help` to enumerate subsystems/verbs. Sourced from the registry so it never drifts. -- `/clide` SKILL.md: trigger description always visible to Claude; body points at the discovery verb rather than hard-coding a verb list, plus conventions (slots: sidebar/workspace/context, pane kinds, focus/spawn/close/write/resize). Thin and always-correct. - -This is what makes T-249 (image viewer) and future panels reachable by Claude the moment they register — no skill edit per panel. - -Refs: D-6 (CLI/event-surface parity). Related: T-249. - -RE-SCOPE (2026-06-06, from T-208 completeness review): partly overtaken by reality — do NOT build from scratch. -- The `clide capabilities` verb already EXISTS in code: registered in lib/src/daemon/dispatcher.dart:12 (built-in) and as an umbrella command in lib/src/cli/argv_to_request.dart:28. Its handler (_capabilities, dispatcher.dart:86) enumerates the live registry with arg schemas — so the non-rotting design is already implemented server-side. -- A `/clide` skill is ALSO already present this session (other ongoing work), and it instructs agents to "Start with `clide capabilities` to enumerate the live tool surface." -- BUT: against the live instance, `clide capabilities` returns a usage stub at exit 0 (`usage: clide capabilities [args...]`) instead of the command table — i.e. the discoverability entry point the skill advertises is currently DEAD. Likely a stale running build or a C-client umbrella mismatch (native/clide-cli/clide.c forwards argv but the live server treats `capabilities` as a subsystem needing a verb). - -New scope for this ticket: -1. Verify/fix the live `clide capabilities` path end-to-end (rebuild/restart vs real wiring gap in the C client / server umbrella handling). Acceptance: `clide capabilities` returns the JSON command table against a running instance. -2. Reconcile with the already-shipped `/clide` skill rather than authoring anew — confirm it points at the working verb and covers the conventions. -Net: this is now a verify-and-reconcile task, not a build-from-scratch one. Related: T-249, T-208.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 08:12:53', NULL, 'b644ca71025fba6601ccb7ea5fa5cc18', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'story', NULL, '/clide skill — advertise the clide tool surface to Claude', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. - -Problem: my knowledge of the clide surface comes from a hand-curated session blurb. There is no runtime discovery — `clide help` is a stub, `clide ` with no verb just prints `usage:`, and the dispatcher''s registered command table (e.g. pane_commands.dart) is never exposed. D-6 guarantees a verb EXISTS for every UI action, but parity != discoverability: a correctly-registered verb is still unreachable if nothing tells me it''s there. - -Scope: -- Self-description first (prereq for a non-rotting skill): add a discovery verb that reflects the live dispatcher registry — e.g. `clide capabilities` (machine-readable JSON: subsystems -> verbs -> arg schema) and/or flesh out `clide help` to enumerate subsystems/verbs. Sourced from the registry so it never drifts. -- `/clide` SKILL.md: trigger description always visible to Claude; body points at the discovery verb rather than hard-coding a verb list, plus conventions (slots: sidebar/workspace/context, pane kinds, focus/spawn/close/write/resize). Thin and always-correct. - -This is what makes T-249 (image viewer) and future panels reachable by Claude the moment they register — no skill edit per panel. - -Refs: D-6 (CLI/event-surface parity). Related: T-249. - -RE-SCOPE (2026-06-06, from T-208 completeness review): partly overtaken by reality — do NOT build from scratch. -- The `clide capabilities` verb already EXISTS in code: registered in lib/src/daemon/dispatcher.dart:12 (built-in) and as an umbrella command in lib/src/cli/argv_to_request.dart:28. Its handler (_capabilities, dispatcher.dart:86) enumerates the live registry with arg schemas — so the non-rotting design is already implemented server-side. -- A `/clide` skill is ALSO already present this session (other ongoing work), and it instructs agents to "Start with `clide capabilities` to enumerate the live tool surface." -- BUT: against the live instance, `clide capabilities` returns a usage stub at exit 0 (`usage: clide capabilities [args...]`) instead of the command table — i.e. the discoverability entry point the skill advertises is currently DEAD. Likely a stale running build or a C-client umbrella mismatch (native/clide-cli/clide.c forwards argv but the live server treats `capabilities` as a subsystem needing a verb). - -New scope for this ticket: -1. Verify/fix the live `clide capabilities` path end-to-end (rebuild/restart vs real wiring gap in the C client / server umbrella handling). Acceptance: `clide capabilities` returns the JSON command table against a running instance. -2. Reconcile with the already-shipped `/clide` skill rather than authoring anew — confirm it points at the working verb and covers the conventions. -Net: this is now a verify-and-reconcile task, not a build-from-scratch one. Related: T-249, T-208.', 'review', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 08:14:01', NULL, 'bca8ce0c246ffea147f7702c4cd0518d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-249', 'story', NULL, 'Image viewer conversation card + plumbing', 'Add an image-viewer card to the Claude conversation log, with the CLI verb that drives it (D-6 parity). - -Scope: -- Conversation card: render an image inline in the Claude conversation widget — clide-owned rendering (no opinionated package), display-only per D-78 (no inline interactive controls; any controls belong in the interaction zone). Respect theme tokens / ui-design. -- Plumbing: a `clide` verb to show an image in the log (e.g. `clide image show ` or via the pane subsystem), accepting a workspace path; define accepted formats (PNG/JPEG/...), path resolution (workspace-relative), and sizing/scaling behavior. -- Parity (D-6): the verb is the CLI counterpart of the card; ensure it registers in the dispatcher so it shows up in the discovery verb from T-248. - -Dependency: pairs with T-248 — without the /clide skill + discovery verb, Claude won''t know this card/verb exists even once shipped. Build the plumbing here; T-248 makes it discoverable. - -Refs: D-6 (parity), D-78 (interaction zone, display-only conversation widgets). Related: T-248.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 08:21:40', NULL, '7280f99bd28193b894588954367eb367', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-252', 'story', NULL, 'Full-screen lightbox overlay (zoom + pan) for image cards', 'Click an inline image card (T-249) to open it in a full-screen overlay with zoom + pan — the standard lightbox UX from other apps (dimmed backdrop, image fit-to-screen, scroll/pinch to zoom, drag to pan when zoomed past fit, Esc or backdrop-click to dismiss). - -Build the overlay as a SHARED PRIMITIVE WIDGET, not image-specific. A reusable zoomable-viewer / lightbox primitive under lib/widgets/ (e.g. ClideLightbox + ClideZoomPanViewer) that takes any child/content; the image card is its first consumer, and the canvas, graph, and diff/image previews can adopt it later. Own-the-rendering-stack + prefer-zero-deps still apply: Flutter''s SDK InteractiveViewer is the SDK-first starting point (allowed), but keep the gesture/zoom logic in the primitive so we control the UX, not an opinionated package. - -Scope: -- Shared primitive (lib/widgets/): a full-screen overlay with a dimmed backdrop hosted via the existing DialogRouter (single-at-a-time modal, D-78 interaction surface). Content scales to fit on open; zoom via scroll wheel / pinch / +- ; pan by drag when zoomed in; double-click toggles fit<->100%/2x; Esc and backdrop-click dismiss. Min/max zoom clamp. Keyboard + AT reachable (ClideTappable / Semantics), theme tokens / ui-design for any chrome (close affordance, zoom hint). -- Image card wiring (T-249): the inline card stays display-only per D-78 — the click is a navigation gesture that opens the lightbox, not an inline control. Render the same resolved file at full resolution in the overlay; missing-file degrades to the existing placeholder. -- Parity (D-6): a CLI counterpart to open an image straight into the lightbox (e.g. clide image show --fullscreen, or clide image open ), registered so it surfaces in clide capabilities (T-248). - -Refs: D-78 (interaction zone / display-only conversation widgets), D-6 (CLI/UI parity), DialogRouter (lib/kernel/src/dialog.dart). Builds on T-249 (image card + clide image show). Related: T-248.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-06 08:28:43', '2026-06-06 08:28:43', NULL, '2a4f0e70eb9f313249480c14aa56f2c4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-248', 'story', NULL, '/clide skill — advertise the clide tool surface to Claude', 'Create a `/clide` skill (none exists today — only legacy templates under legacy/clide/templates/skills/) that advertises the clide CLI surface to Claude so new affordances become discoverable. - -Problem: my knowledge of the clide surface comes from a hand-curated session blurb. There is no runtime discovery — `clide help` is a stub, `clide ` with no verb just prints `usage:`, and the dispatcher''s registered command table (e.g. pane_commands.dart) is never exposed. D-6 guarantees a verb EXISTS for every UI action, but parity != discoverability: a correctly-registered verb is still unreachable if nothing tells me it''s there. - -Scope: -- Self-description first (prereq for a non-rotting skill): add a discovery verb that reflects the live dispatcher registry — e.g. `clide capabilities` (machine-readable JSON: subsystems -> verbs -> arg schema) and/or flesh out `clide help` to enumerate subsystems/verbs. Sourced from the registry so it never drifts. -- `/clide` SKILL.md: trigger description always visible to Claude; body points at the discovery verb rather than hard-coding a verb list, plus conventions (slots: sidebar/workspace/context, pane kinds, focus/spawn/close/write/resize). Thin and always-correct. - -This is what makes T-249 (image viewer) and future panels reachable by Claude the moment they register — no skill edit per panel. - -Refs: D-6 (CLI/event-surface parity). Related: T-249. - -RE-SCOPE (2026-06-06, from T-208 completeness review): partly overtaken by reality — do NOT build from scratch. -- The `clide capabilities` verb already EXISTS in code: registered in lib/src/daemon/dispatcher.dart:12 (built-in) and as an umbrella command in lib/src/cli/argv_to_request.dart:28. Its handler (_capabilities, dispatcher.dart:86) enumerates the live registry with arg schemas — so the non-rotting design is already implemented server-side. -- A `/clide` skill is ALSO already present this session (other ongoing work), and it instructs agents to "Start with `clide capabilities` to enumerate the live tool surface." -- BUT: against the live instance, `clide capabilities` returns a usage stub at exit 0 (`usage: clide capabilities [args...]`) instead of the command table — i.e. the discoverability entry point the skill advertises is currently DEAD. Likely a stale running build or a C-client umbrella mismatch (native/clide-cli/clide.c forwards argv but the live server treats `capabilities` as a subsystem needing a verb). - -New scope for this ticket: -1. Verify/fix the live `clide capabilities` path end-to-end (rebuild/restart vs real wiring gap in the C client / server umbrella handling). Acceptance: `clide capabilities` returns the JSON command table against a running instance. -2. Reconcile with the already-shipped `/clide` skill rather than authoring anew — confirm it points at the working verb and covers the conventions. -Net: this is now a verify-and-reconcile task, not a build-from-scratch one. Related: T-249, T-208.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-06 07:21:17', '2026-06-06 08:31:25', NULL, 'd2d8d30c578bce8229924619542d5aa7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open '' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 08:33:42', NULL, '41f3cdaedfb436b6102ce6aebbbc0161', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open '' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 08:50:12', NULL, 'f2e24a5a804b0d3b95bef0be2f8327b7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open '' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 09:04:03', NULL, '6458d14b4f6178c4da81afdb70a6eb6b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-233', 'task', 'T-208', 'Open a file in the diff panel for review via ui.open', 'Follow-up from T-231 (2026-06-03). T-231 added ''clide ui open '' for the ReaderNav-backed readers (tickets/decisions/markdown), which open by subscribing to a MessageBus ''selection''. The DIFF panel is not covered: DiffController (lib/builtin/diff/src/diff_controller.dart) loads the git working-tree diff via the git.diff IPC verb and refreshes on git.changed events; it has no ''selection''/''focus'' bus channel, so ui.open has no entry point to target a file. Note: because the diff panel shows the working tree, an agent''s uncommitted edits already APPEAR there on the next git.changed refresh -- what''s missing is (a) bringing the diff tab to the front and (b) scrolling/filtering to a specific file. Proposed: give the diff view a bus subscription (publisher ''builtin.diff'', channel ''selection''/''focus'', {path}) that reveals its tab and focuses/filters that file''s diff (DiffController.load already accepts a paths filter), then add ''diff'' as a 4th ui.open target. Related gap: pane.focus is advisory-only (pane_commands.dart) so ''bring tab to front'' isn''t truly wired -- the reveal likely needs the same mechanism the readers use to reveal their tab on selection. Depends in spirit on T-232 (CLI args) only if a non-bus path is chosen; the bus path works regardless.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-03 13:29:59', '2026-06-06 09:20:02', NULL, '747c6d2aa0dad416c150621aa01b9d3d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'story', NULL, 'Make links in the Claude conversation panel clickable to open in OS URL handler', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:53', '2026-06-06 09:32:53', NULL, '4691c2da955f42c4e32b463b182279ab', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-06 09:32:54', NULL, '135fa5dfac92c4d33ce95d6522fb7a9a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'story', NULL, 'Make links in the Claude conversation panel clickable to open in OS URL handler', 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. - -## Behaviour -- Click (or ctrl/cmd-click) on a colorized link opens it via the OS default handler. -- Hover affordance (cursor change / underline) so it reads as clickable. -- Keep the existing colorization. - -## Notes -- Honour user/Claude parity (D-6) where relevant. -- Use the platform URL launcher; avoid pulling in an opinionated package if a thin native/url_launcher shim already exists in the tree. -- Guard against non-http schemes / malformed URLs.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:53', '2026-06-06 09:33:01', NULL, '3fca4883e4c0fdf9028894c837c29c29', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-06 09:33:04', NULL, 'c4da32a37d6d30e651b6a3d9406d5378', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-212', 'task', 'T-209', 'In-app ''Install clide command in PATH'' affordance', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell. - -Refinement (2026-06-03, from live dogfooding): scope should include PROACTIVE detection on launch, not just a palette command. When the clide IDE starts in a repo, check whether ''clide'' resolves on PATH AND points to the C client (not a stale symlink to the GUI bundle runner) -- we hit exactly this: ~/.local/bin/clide was a May-6 symlink to ~/.local/lib/clide/clide (the Flutter GUI), so a bare ''clide pane list'' launched a second app instead of querying. If missing or stale, prompt/offer to install (copy the bundled C client to a PATH dir, VS Code ''Install code command'' style) and report success. This is what lets a fresh agent actually reach the CLI (D-83 names the hosted session primary, but an external agent benefits too). Detecting ''stale GUI symlink'' specifically: the target should be an ELF/Mach-O executable, not a symlink into the bundle. Alternative path the user raised: instead of/alongside this, make the /ide MCP surface reachable (T-225) -- but CLI is primary per D-68.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-06 09:43:49', NULL, '18d859e44950f02ace74dfdaba5bc8a1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'story', NULL, 'Animate the ''running…'' turn indicator with rotating status verbs', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-06 09:56:23', '2026-06-06 09:56:23', NULL, 'ff1d9a2a2a9c6aa948b45fb242d70a55', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'story', NULL, 'Animate the ''running…'' turn indicator with rotating status verbs', '## Problem - -While a Claude turn is in flight, the composer swaps in a static, muted -`running…` label next to the Stop button (`lib/builtin/claude/src/claude_composer.dart:486`, -shown when `widget.busy && widget.onInterrupt != null`). It''s gray, motionless, -and a little lifeless — it gives no sense that anything is actually happening. - -## Goal - -Make the in-flight indicator feel alive: - -1. **Animation.** Add subtle motion — e.g. an animated ellipsis (`running` → `running.` - → `running..` → `running...`), a shimmer/pulse on the text, or a small spinner glyph. - Custom-painted / token-driven per the "own the rendering stack" guardrail; no - opinionated animation packages. -2. **Rotating status verbs.** Cycle through playful gerunds the way the Claude Code CLI - does (e.g. "Clauding…", "Flibbertigibbeting…", "Pondering…", "Conjuring…"). Pick a - word from a curated list and rotate it every few seconds while the turn runs. - -## Open question — can we reuse the CLI''s words? - -The Claude Code CLI ships its own list of these status verbs. Before hand-rolling our -own, check whether that list is something we''re allowed to reuse / surface (licensing, -where it lives, whether stream-json exposes the current one). If we can''t pull the CLI''s -list, ship our own curated, on-brand list instead. Document the decision. - -## Notes / constraints - -- Respect reduced-motion / accessibility settings — animation must be disable-able and - the a11y semantics should still read sensibly (the Stop button already carries the - interrupt hint). -- Use theme tokens for color; keep it muted/tasteful, not distracting. -- Touch point today is the `widget.busy` branch in `claude_composer.dart`; consider - whether the rotating-word state belongs there or in the session/orchestrator layer. -- Add a widget/golden test for the animated states (bounded pumps — no real timers).', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-06 09:56:23', '2026-06-06 09:57:12', NULL, '17a3feaae4f5481f212578f0174fd09a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-212', 'task', 'T-209', 'In-app ''Install clide command in PATH'' affordance', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell. - -Refinement (2026-06-03, from live dogfooding): scope should include PROACTIVE detection on launch, not just a palette command. When the clide IDE starts in a repo, check whether ''clide'' resolves on PATH AND points to the C client (not a stale symlink to the GUI bundle runner) -- we hit exactly this: ~/.local/bin/clide was a May-6 symlink to ~/.local/lib/clide/clide (the Flutter GUI), so a bare ''clide pane list'' launched a second app instead of querying. If missing or stale, prompt/offer to install (copy the bundled C client to a PATH dir, VS Code ''Install code command'' style) and report success. This is what lets a fresh agent actually reach the CLI (D-83 names the hosted session primary, but an external agent benefits too). Detecting ''stale GUI symlink'' specifically: the target should be an ELF/Mach-O executable, not a symlink into the bundle. Alternative path the user raised: instead of/alongside this, make the /ide MCP surface reachable (T-225) -- but CLI is primary per D-68.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-06 10:02:03', NULL, 'cc452389e195d3efa2f0244e74444768', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-209', 'epic', 'T-208', 'Ship the clide CLI on PATH', 'Gap 1 (BLOCKER) from self-analysis.md. The C client native/clide-cli/clide.c (T-126, done) is the real CLI, but there is no supported way for `clide` to land on an agent''s PATH: -- `make clide-cli` (Makefile:284) is a non-default target; `build` (Makefile:169) does not depend on it, so the binary is never produced by a normal build (native/macos-arm64/clide had never been built). -- `make install` (Makefile:195) copies the GUI bundle and `ln -sf $(INSTALL_PREFIX)/clide/clide $(INSTALL_DIR)/clide` (Makefile:200) symlinks the FLUTTER GUI runner — not the C client — on BOTH macOS and Linux (on macOS that path doesn''t even resolve inside clide.app). - -Net: `which clide` -> not found, `clide ping` -> exit 127. The CLI-first contract (D-1) has no delivery mechanism. Related to the broader installer epic T-46, but that is the GUI/runtime installer; this epic is specifically C-client-on-PATH delivery.', 'done', 'high', NULL, NULL, 'D-1', '2026-06-02 18:13:52', '2026-06-06 10:02:06', NULL, '7a173eae965eedcfaf742707873651aa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-256', 'task', 'T-208', 'Install affordance: distinguish a dev-tree clide from a production-installed build', 'Flagged during T-212 (2026-06-06). The Install-clide-command affordance copies whatever CliInstaller resolves as the bundled C client into ~/.local/bin, and ''make run'' points CLIDE_CLI_BIN at the dev-tree client (native//clide). On a normal user machine that is correct: the bundled client beside the GUI runner IS the production build. But on a clide DEV system the clide on PATH should point at the properly-installed production build, not a dev-tree binary or stale build artefact. CliInstaller.inspect only classifies missing / staleGui (points into the Flutter bundle) / installed, and treats any non-GUI binary as installed without distinguishing dev-tree vs a real install. Good enough for now per the user; the affordance works for end users and fixes the stale-GUI-symlink footgun. Decide desired dev-machine behaviour (e.g. detect dev tree via CLIDE_PROJECT / repo-relative path and warn instead of silently installing, or prefer an already-correct production build) and refine inspect() accordingly.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-06 10:07:35', '2026-06-06 10:07:35', NULL, '979892310810e63cd436dcbccf9bd3a1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-223', 'task', 'T-222', 'Add cursor-based pull events: clide events --since ', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both. - -Refinement (2026-06-06): blockers resolved. Q-2 + Q-3 closed by D-85 (event bus delivery). Design is now fixed: serve --since from a bounded in-memory ring keyed by a monotonic cursor; return events-after-cursor + next-cursor, and a gap marker (per-subscriber dropped-count) when the requested cursor has aged out so callers detect loss rather than silently miss events. Back-pressure is drop-oldest (producer never blocks, subscribers never killed). No on-disk persistence in v1. Ready to implement.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-06 10:24:57', NULL, 'b3ab2d01b025d2577a12f04def93797f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-225', 'task', 'T-208', 'MCP transport reachable by an external agent', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface). - -Refinement (2026-06-06): tool-surface question resolved. Q-32 closed by D-86 — expose the full mcp__clide__* namespace, but GENERATE tools/list from the co-registered command registry (D-74) that already feeds the CLI + palette, so there is no hand-maintained second surface; add a per-command MCP opt-out for poor-fit verbs (long-lived streams, UI-side-effecting). Transport stays SSE-only per D-73 (Q-33 re-confirmed, not reopened). Remaining stubs to make real: getDiagnostics + executeCode. Scope is now: registry->MCP tool-definition adapter (arg-schema -> JSON-Schema), served over the existing SSE transport.', 'backlog', 'low', NULL, NULL, 'D-68', '2026-06-02 18:13:52', '2026-06-06 10:25:02', NULL, '041f27c551ea57bd86a3b3d5caf678c1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-257', 'bug', NULL, 'Esc closes the file/pane instead of returning to Vim normal mode', NULL, 'backlog', 'high', NULL, NULL, NULL, '2026-06-06 14:01:03', '2026-06-06 14:01:03', NULL, '5e33f421fd0226e5169b2246b5ae8596', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-257', 'bug', NULL, 'Esc closes the file/pane instead of returning to Vim normal mode', '**Symptom:** In the editor with the Vim preset active, pressing `Esc` to leave insert/visual mode and return to normal mode closes the current file/pane instead of switching modes. - -**Repro:** -1. Select the Vim keybinding preset (T-65). -2. Open a file in the editor pane; enter insert mode (`i`). -3. Press `Esc`. - -**Expected:** Editor returns to Vim normal mode (`-- NORMAL --`), file/pane stays open. This is T-207''s contract: `Esc -> normal from any mode`. - -**Actual:** The file/pane closes. - -**Root-cause hypothesis:** `assets/keymaps/default.yaml:26-27` binds `escape -> intent: dismiss` with NO `when:` clause, so it matches globally. The Vim mode-reset binding (`assets/keymaps/vim.yaml:69-71`, `escape -> command:vim.mode.normal`, gated `when: vim.insert || vim.visual`) is being out-resolved or shadowed by the unconditional `dismiss`, which falls through to closing the active pane/file when no overlay is open. Either the Vim preset still inherits an unconditional dismiss-on-escape, or the when-clause resolver lets the unguarded binding win over the more-specific scoped one for the same chord. - -**Likely fix direction:** -- Ensure the keymap resolver prefers the more-specific `when`-scoped binding over an unconditional one for the same chord, and/or gate the global `dismiss`-on-escape so it does not fire when `editor.focused && (vim.insert || vim.visual)`. -- Sanity-check that `dismiss` should close a pane/file at all here — closing the active file on a bare Esc is surprising even outside Vim. - -**Related:** epic T-65 (Vim preset), T-207 (Vim mode service owns Esc -> normal), T-205 / T-117 (key-sequence resolution + when-clauses). -**Files:** `assets/keymaps/default.yaml`, `assets/keymaps/vim.yaml`, `lib/builtin/editor/src/editor_view.dart`, `lib/kernel/src/keymap/`.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-06 14:01:03', '2026-06-06 14:01:03', NULL, '5caecbc30404184d0b16182d6d3c2a15', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-257', 'bug', NULL, 'Esc closes the file/pane instead of returning to Vim normal mode', NULL, 'in_progress', 'high', NULL, NULL, NULL, '2026-06-06 14:01:03', '2026-06-06 18:44:58', NULL, '2b855c3601a5c853abbafef6d12391d1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-257', 'bug', NULL, 'Esc closes the file/pane instead of returning to Vim normal mode', NULL, 'done', 'high', NULL, NULL, NULL, '2026-06-06 14:01:03', '2026-06-06 18:58:25', NULL, 'b2685c6aad474c32dea6f42c6d4b3bd2', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-223', 'task', 'T-222', 'Add cursor-based pull events: clide events --since ', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both. - -Refinement (2026-06-06): blockers resolved. Q-2 + Q-3 closed by D-85 (event bus delivery). Design is now fixed: serve --since from a bounded in-memory ring keyed by a monotonic cursor; return events-after-cursor + next-cursor, and a gap marker (per-subscriber dropped-count) when the requested cursor has aged out so callers detect loss rather than silently miss events. Back-pressure is drop-oldest (producer never blocks, subscribers never killed). No on-disk persistence in v1. Ready to implement.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-06 19:06:30', NULL, '0fa1be0c98fbf57863d810b07657b585', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-225', 'task', 'T-208', 'MCP transport reachable by an external agent', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface). - -Refinement (2026-06-06): tool-surface question resolved. Q-32 closed by D-86 — expose the full mcp__clide__* namespace, but GENERATE tools/list from the co-registered command registry (D-74) that already feeds the CLI + palette, so there is no hand-maintained second surface; add a per-command MCP opt-out for poor-fit verbs (long-lived streams, UI-side-effecting). Transport stays SSE-only per D-73 (Q-33 re-confirmed, not reopened). Remaining stubs to make real: getDiagnostics + executeCode. Scope is now: registry->MCP tool-definition adapter (arg-schema -> JSON-Schema), served over the existing SSE transport.', 'in_progress', 'low', NULL, NULL, 'D-68', '2026-06-02 18:13:52', '2026-06-06 19:06:30', NULL, '31bef654a8426007a7af14bef90b66f7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-23', 'task', 'T-4', 'wire command palette keybinding', '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. +Net: `which clide` -> not found, `clide ping` -> exit 127. The CLI-first contract (D-1) has no delivery mechanism. Related to the broader installer epic T-46, but that is the GUI/runtime installer; this epic is specifically C-client-on-PATH delivery.', 'done', 'high', NULL, NULL, 'D-1', '2026-06-02 18:13:52', '2026-06-06 10:02:06', NULL, '5a5a8c818ff51c55ff8fd57914cce71b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4K2AAPK52Y8BEJ3YR', 'bug', NULL, 'Esc closes the file/pane instead of returning to Vim normal mode', NULL, 'done', 'high', NULL, NULL, NULL, '2026-06-06 14:01:03', '2026-06-06 18:58:25', NULL, '7c4ca77bf4e0ee1116bfd5b4964ef21a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5KYAV8TMH3Y3FPRSR', 'task', '06FB0TNQM5GSR5WPC2T7CJM8XW', 'wire command palette keybinding', '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. **Progress (2026-05-17, T-117):** The keymap layer now binds `ctrl+shift+p` / `meta+shift+p` to `PaletteOpenIntent` in `assets/keymaps/default.yaml`. The intent resolves end-to-end through `KeymapService.resolveEvent` → `Actions.maybeInvoke`. **Still pending**: an `Actions` provider somewhere in the tree that handles `PaletteOpenIntent` by calling `kernel.palette.open()`, plus the arrow-key / Escape / Enter handlers on `ClidePalette` itself. Those land as part of T-100 (palette keyboard nav). @@ -1043,99 +301,35 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - Bindings live in the keymap (T-117) — not in `lib/kernel/src/commands/keybindings.dart` (that file is legacy). - The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open). -DONE (2026-06-06). Keybinding + nav were already wired (T-117 binds ctrl/meta+shift+p -> PaletteOpenIntent; _RootShell handles it -> palette.open(); T-100 added arrow/Enter/Esc nav + selected-index). Remaining acceptance implemented now: (1) FUZZY match — PaletteController.filtered() uses a shared subsequence matcher (lib/kernel/src/fuzzy.dart, extracted from quick_open so both share one source of truth), ranked best-score-first; (2) RECENCY — invoked commands float to the top on empty filter and break fuzzy-score ties (in-session MRU). DEFERRED: ''pinned'' commands + cross-session recency persistence need a pin affordance + settings storage — filed as a follow-up. Tests: test/kernel/src/commands/palette_test.dart + test/kernel/src/fuzzy_test.dart.', 'done', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-06-06 19:06:30', NULL, 'd19d861483f6769473072c195c0575b9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-223', 'task', 'T-222', 'Add cursor-based pull events: clide events --since ', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both. +DONE (2026-06-06). Keybinding + nav were already wired (T-117 binds ctrl/meta+shift+p -> PaletteOpenIntent; _RootShell handles it -> palette.open(); T-100 added arrow/Enter/Esc nav + selected-index). Remaining acceptance implemented now: (1) FUZZY match — PaletteController.filtered() uses a shared subsequence matcher (lib/kernel/src/fuzzy.dart, extracted from quick_open so both share one source of truth), ranked best-score-first; (2) RECENCY — invoked commands float to the top on empty filter and break fuzzy-score ties (in-session MRU). DEFERRED: ''pinned'' commands + cross-session recency persistence need a pin affordance + settings storage — filed as a follow-up. Tests: test/kernel/src/commands/palette_test.dart + test/kernel/src/fuzzy_test.dart.', 'done', 'medium', NULL, NULL, 'D-6', '2026-04-22 14:08:40', '2026-06-06 19:06:30', NULL, 'a31b3cddfbdc254bd7b908b4ebfce28c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72ACQSDBJTKRBC58W', 'task', '06FB0TNQM620F5ENRYNT9S0JDW', 'Add cursor-based pull events: clide events --since ', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since ` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both. -Refinement (2026-06-06): blockers resolved. Q-2 + Q-3 closed by D-85 (event bus delivery). Design is now fixed: serve --since from a bounded in-memory ring keyed by a monotonic cursor; return events-after-cursor + next-cursor, and a gap marker (per-subscriber dropped-count) when the requested cursor has aged out so callers detect loss rather than silently miss events. Back-pressure is drop-oldest (producer never blocks, subscribers never killed). No on-disk persistence in v1. Ready to implement.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-06 19:24:44', NULL, '23589dbcd4aaf654ab3895f5b8e0c3dc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-222', 'epic', 'T-208', 'Agent-friendly event observation (pull-based)', 'Gap 4 from self-analysis.md. `clide tail --events` (T-129) is a never-returning stream — awkward for a request/response agent loop; the agent must background it and poll a file or it misses events between polls. Provide a pull-based form that fits an agent loop natively.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-06 19:24:44', NULL, '3fbc9f786440a2d57dca0201137f5eb0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-225', 'task', 'T-208', 'MCP transport reachable by an external agent', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface). +Refinement (2026-06-06): blockers resolved. Q-2 + Q-3 closed by D-85 (event bus delivery). Design is now fixed: serve --since from a bounded in-memory ring keyed by a monotonic cursor; return events-after-cursor + next-cursor, and a gap marker (per-subscriber dropped-count) when the requested cursor has aged out so callers detect loss rather than silently miss events. Back-pressure is drop-oldest (producer never blocks, subscribers never killed). No on-disk persistence in v1. Ready to implement.', 'done', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-06 19:24:44', NULL, '8479cf97831fa506407dcffb548c1b13', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM620F5ENRYNT9S0JDW', 'epic', '06FB0TNQM7XMQ44AVCAARQZX14', 'Agent-friendly event observation (pull-based)', 'Gap 4 from self-analysis.md. `clide tail --events` (T-129) is a never-returning stream — awkward for a request/response agent loop; the agent must background it and poll a file or it misses events between polls. Provide a pull-based form that fits an agent loop natively.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-06 19:24:44', NULL, 'b5bc5abd92e28abe4f7c05369038f054', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5DYC7CRP8TMHADW8W', 'task', '06FB0TNQM7XMQ44AVCAARQZX14', 'MCP transport reachable by an external agent', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface). -Refinement (2026-06-06): tool-surface question resolved. Q-32 closed by D-86 — expose the full mcp__clide__* namespace, but GENERATE tools/list from the co-registered command registry (D-74) that already feeds the CLI + palette, so there is no hand-maintained second surface; add a per-command MCP opt-out for poor-fit verbs (long-lived streams, UI-side-effecting). Transport stays SSE-only per D-73 (Q-33 re-confirmed, not reopened). Remaining stubs to make real: getDiagnostics + executeCode. Scope is now: registry->MCP tool-definition adapter (arg-schema -> JSON-Schema), served over the existing SSE transport.', 'done', 'low', NULL, NULL, 'D-68', '2026-06-02 18:13:52', '2026-06-06 19:37:47', NULL, '45f318499292f9987fe8c8d99d9948e8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-256', 'task', 'T-208', 'Install affordance: distinguish a dev-tree clide from a production-installed build', 'Flagged during T-212 (2026-06-06). The Install-clide-command affordance copies whatever CliInstaller resolves as the bundled C client into ~/.local/bin, and ''make run'' points CLIDE_CLI_BIN at the dev-tree client (native//clide). On a normal user machine that is correct: the bundled client beside the GUI runner IS the production build. But on a clide DEV system the clide on PATH should point at the properly-installed production build, not a dev-tree binary or stale build artefact. CliInstaller.inspect only classifies missing / staleGui (points into the Flutter bundle) / installed, and treats any non-GUI binary as installed without distinguishing dev-tree vs a real install. Good enough for now per the user; the affordance works for end users and fixes the stale-GUI-symlink footgun. Decide desired dev-machine behaviour (e.g. detect dev tree via CLIDE_PROJECT / repo-relative path and warn instead of silently installing, or prefer an already-correct production build) and refine inspect() accordingly.', 'in_progress', 'low', NULL, NULL, NULL, '2026-06-06 10:07:35', '2026-06-06 19:45:03', NULL, 'edd0f58a489218a6de945f8c0e1f92b3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-208', 'initiative', NULL, 'Give Claude hands', 'Make a Claude agent able to actually WORK inside clide: reach the CLI, know clide is hosting it, and observe the same workspace the user sees. Source: self-analysis.md (2026-06-02), a live dogfood probe by a Claude (Opus 4.8) running against a live instance (socket + MCP SSE up, version 2.1.0). +Refinement (2026-06-06): tool-surface question resolved. Q-32 closed by D-86 — expose the full mcp__clide__* namespace, but GENERATE tools/list from the co-registered command registry (D-74) that already feeds the CLI + palette, so there is no hand-maintained second surface; add a per-command MCP opt-out for poor-fit verbs (long-lived streams, UI-side-effecting). Transport stays SSE-only per D-73 (Q-33 re-confirmed, not reopened). Remaining stubs to make real: getDiagnostics + executeCode. Scope is now: registry->MCP tool-definition adapter (arg-schema -> JSON-Schema), served over the existing SSE transport.', 'done', 'low', NULL, NULL, 'D-68', '2026-06-02 18:13:52', '2026-06-06 19:37:47', NULL, '7ac30a4d6cdb64bdf8d8b609c8388751', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XMQ44AVCAARQZX14', 'initiative', NULL, 'Give Claude hands', 'Make a Claude agent able to actually WORK inside clide: reach the CLI, know clide is hosting it, and observe the same workspace the user sees. Source: self-analysis.md (2026-06-02), a live dogfood probe by a Claude (Opus 4.8) running against a live instance (socket + MCP SSE up, version 2.1.0). Finding: the IPC/CLI foundation (D-1, D-6, D-56; built under T-99 incl. the C client T-126 and the `clide tail --events` stream T-129) works end-to-end at the socket level. But a fresh agent cannot REACH or OBSERVE it. Three blockers: 1. No `clide` binary on PATH, and no install path that delivers one (Epic A — blocker). 2. No bootstrap: nothing tells a fresh agent clide is running or where its socket is (Epic B). 3. The live UI panes / active file are not reflected in the CLI registries, so the D-6 parity premise (agent sees what the user sees) breaks (Epic C). -Plus: a governance decision on WHICH agent model is the dogfood target (hosted stream-json session vs external CLI driver vs both) and an MCP follow-up. The pleasant surprise per the probe: the hard part (a live, correct, single-process IPC contract with proper 0/1/3 exit codes) is done; what''s missing is the last mile that puts the tool in the agent''s hands. NOTE: the report says ''daemon'' but D-56 dissolved it — this is the in-process IPC server; the socket contract is unchanged.', 'done', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-06 19:53:07', NULL, '3575a67bc5f4df7fddc67269641dfe2b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-256', 'task', 'T-208', 'Install affordance: distinguish a dev-tree clide from a production-installed build', 'Flagged during T-212 (2026-06-06). The Install-clide-command affordance copies whatever CliInstaller resolves as the bundled C client into ~/.local/bin, and ''make run'' points CLIDE_CLI_BIN at the dev-tree client (native//clide). On a normal user machine that is correct: the bundled client beside the GUI runner IS the production build. But on a clide DEV system the clide on PATH should point at the properly-installed production build, not a dev-tree binary or stale build artefact. CliInstaller.inspect only classifies missing / staleGui (points into the Flutter bundle) / installed, and treats any non-GUI binary as installed without distinguishing dev-tree vs a real install. Good enough for now per the user; the affordance works for end users and fixes the stale-GUI-symlink footgun. Decide desired dev-machine behaviour (e.g. detect dev tree via CLIDE_PROJECT / repo-relative path and warn instead of silently installing, or prefer an already-correct production build) and refine inspect() accordingly.', 'done', 'low', NULL, NULL, NULL, '2026-06-06 10:07:35', '2026-06-06 19:53:07', NULL, 'a736e06701cbdb90f6256e2dab2485f8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'story', NULL, 'output and log panel', '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.', 'ready', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-06 20:00:45', NULL, '0865a967d2c535bfdcac7f5f7bd3749a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-258', 'story', NULL, 'Terminal as a first-class editor-pane surface (swap vs split)', 'Spun off from the T-54 dock design (D-87), which scoped the bottom dock to read-only output (logs + problems) and explicitly kept the terminal OUT of it. Terminals are core functionality, not an afterthought: the terminal should live in the editor pane (above Claude, D-49 editor-mode) as a first-class peer of editor and diff — not in a read-only log strip. +Plus: a governance decision on WHICH agent model is the dogfood target (hosted stream-json session vs external CLI driver vs both) and an MCP follow-up. The pleasant surprise per the probe: the hard part (a live, correct, single-process IPC contract with proper 0/1/3 exit codes) is done; what''s missing is the last mile that puts the tool in the agent''s hands. NOTE: the report says ''daemon'' but D-56 dissolved it — this is the in-process IPC server; the socket contract is unchanged.', 'done', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-06 19:53:07', NULL, '4f9ba8adbe68c68f76d76acda314dd3c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6S7KHYPE9W7S1JHZ8', 'task', '06FB0TNQM7XMQ44AVCAARQZX14', 'Install affordance: distinguish a dev-tree clide from a production-installed build', 'Flagged during T-212 (2026-06-06). The Install-clide-command affordance copies whatever CliInstaller resolves as the bundled C client into ~/.local/bin, and ''make run'' points CLIDE_CLI_BIN at the dev-tree client (native//clide). On a normal user machine that is correct: the bundled client beside the GUI runner IS the production build. But on a clide DEV system the clide on PATH should point at the properly-installed production build, not a dev-tree binary or stale build artefact. CliInstaller.inspect only classifies missing / staleGui (points into the Flutter bundle) / installed, and treats any non-GUI binary as installed without distinguishing dev-tree vs a real install. Good enough for now per the user; the affordance works for end users and fixes the stale-GUI-symlink footgun. Decide desired dev-machine behaviour (e.g. detect dev tree via CLIDE_PROJECT / repo-relative path and warn instead of silently installing, or prefer an already-correct production build) and refine inspect() accordingly.', 'done', 'low', NULL, NULL, NULL, '2026-06-06 10:07:35', '2026-06-06 19:53:07', NULL, 'd2fec2ab90638b74a87ec39146730cdd', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6WZ7DKVDFAGQ4QDMG', 'story', NULL, 'Terminal as a first-class editor-pane surface (swap vs split)', 'Spun off from the T-54 dock design (D-87), which scoped the bottom dock to read-only output (logs + problems) and explicitly kept the terminal OUT of it. Terminals are core functionality, not an afterthought: the terminal should live in the editor pane (above Claude, D-49 editor-mode) as a first-class peer of editor and diff — not in a read-only log strip. First-class means: reachable by a dedicated keybinding (e.g. Ctrl+`), multiple terminals, and tmux-backed persistence (D-41/D-75) so swapping away never stops a running terminal — you swap back and the build is still streaming. Today the terminal is a generic workspace tab; this moves it into the editor-mode family. -OPEN QUESTION (decide here): clide''s editor-mode is swap (one surface above Claude at a time), so you cannot currently watch a terminal AND edit a file side-by-side. Options: (a) swap-only, leaning on tmux persistence + a fast toggle; or (b) allow the editor pane to split so a terminal can sit beside editor/diff. This is a facet of Q-27 (two-editor split) — resolve them together. Relates to D-47 (Claude-is-home), D-48 (chrome budget), D-49 (editor-mode), D-84 (diff placement, the existing editor-mode peer).', 'backlog', 'medium', NULL, NULL, 'D-49', '2026-06-06 20:55:49', '2026-06-06 20:55:49', NULL, '52adad3337fdc5ca6f8dbb237f31e5a7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'story', NULL, 'output and log panel', '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. - -UX design (2026-06-06, D-87): the panel is a bottom OUTPUT DOCK, read-only, two tabs — Output (the Logger stream, filter by source/level/text, auto-scroll) + Problems (moved out of the sidebar; no duplication). Toggled by a single status-bar widget that REPLACES the app-status indicator (merged health+log: green check when clean, warn/error counts when not, chevron for open state) — opens with click or Cmd/Ctrl+J. Needs a bounded in-memory ring sink on the Logger (no history today). Layout amends D-47 (dock pushes Claude up, capped so Claude stays >=50%). Terminal is NOT in the dock — kept first-class in the editor pane, tracked by T-258. Resolves Q-28. Wireframe: docs/design/wireframes/output-dock/.', 'ready', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-06 20:56:04', NULL, 'd7252d09c6f9f8a8533ef74dfc1a094b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'story', 'T-7', 'Context auto-behavior: right panel reacts to Claude references', '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.', 'review', 'low', NULL, NULL, 'D-50', '2026-04-22 20:34:09', '2026-06-06 20:56:20', NULL, '014c81c57e20079eb3c67ff0e338ca1f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-252', 'story', NULL, 'Full-screen lightbox overlay (zoom + pan) for image cards', 'Click an inline image card (T-249) to open it in a full-screen overlay with zoom + pan — the standard lightbox UX from other apps (dimmed backdrop, image fit-to-screen, scroll/pinch to zoom, drag to pan when zoomed past fit, Esc or backdrop-click to dismiss). - -Build the overlay as a SHARED PRIMITIVE WIDGET, not image-specific. A reusable zoomable-viewer / lightbox primitive under lib/widgets/ (e.g. ClideLightbox + ClideZoomPanViewer) that takes any child/content; the image card is its first consumer, and the canvas, graph, and diff/image previews can adopt it later. Own-the-rendering-stack + prefer-zero-deps still apply: Flutter''s SDK InteractiveViewer is the SDK-first starting point (allowed), but keep the gesture/zoom logic in the primitive so we control the UX, not an opinionated package. - -Scope: -- Shared primitive (lib/widgets/): a full-screen overlay with a dimmed backdrop hosted via the existing DialogRouter (single-at-a-time modal, D-78 interaction surface). Content scales to fit on open; zoom via scroll wheel / pinch / +- ; pan by drag when zoomed in; double-click toggles fit<->100%/2x; Esc and backdrop-click dismiss. Min/max zoom clamp. Keyboard + AT reachable (ClideTappable / Semantics), theme tokens / ui-design for any chrome (close affordance, zoom hint). -- Image card wiring (T-249): the inline card stays display-only per D-78 — the click is a navigation gesture that opens the lightbox, not an inline control. Render the same resolved file at full resolution in the overlay; missing-file degrades to the existing placeholder. -- Parity (D-6): a CLI counterpart to open an image straight into the lightbox (e.g. clide image show --fullscreen, or clide image open ), registered so it surfaces in clide capabilities (T-248). - -Refs: D-78 (interaction zone / display-only conversation widgets), D-6 (CLI/UI parity), DialogRouter (lib/kernel/src/dialog.dart). Builds on T-249 (image card + clide image show). Related: T-248.', 'ready', 'medium', NULL, NULL, 'D-78', '2026-06-06 08:28:43', '2026-06-06 21:00:34', NULL, '3abd5de82d6d82812e51bb9138785adf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'story', NULL, 'output and log panel', '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. - -UX design (2026-06-06, D-87): the panel is a bottom OUTPUT DOCK, read-only, two tabs — Output (the Logger stream, filter by source/level/text, auto-scroll) + Problems (moved out of the sidebar; no duplication). Toggled by a single status-bar widget that REPLACES the app-status indicator (merged health+log: green check when clean, warn/error counts when not, chevron for open state) — opens with click or Cmd/Ctrl+J. Needs a bounded in-memory ring sink on the Logger (no history today). Layout amends D-47 (dock pushes Claude up, capped so Claude stays >=50%). Terminal is NOT in the dock — kept first-class in the editor pane, tracked by T-258. Resolves Q-28. Wireframe: docs/design/wireframes/output-dock/.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-06 21:07:50', NULL, '4c049b9428db5ff733899d698fcd8a44', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-259', 'epic', NULL, 'Interaction model — context panel, readers, live-sync, focus mode', 'Home for the D-47–D-57 interaction-model surface that isn''t a numbered build tier: the reactive context panel (D-50), editor-mode viewer↔editor swap (D-49), panel collapse/spine (D-51), focus mode (D-52), layout state persistence (D-53), and the keyboard map (D-54). Created 2026-06-06 to re-home T-36, which was mis-parented under T-7 (Tier 5 canvas/graph). These behaviors span tiers and belong to the interaction model rather than a single feature tier.', 'backlog', 'medium', NULL, NULL, 'D-47', '2026-06-06 21:12:44', '2026-06-06 21:12:44', NULL, 'ee0cf7a62c376d44ec43bd2907875f9c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'story', 'T-7', 'Live-sync read-mirror: context panel tracks the open editor buffer', 'When an editor is open in edit mode on a renderable doc (.md), the right-hand context panel auto-opens a read-only view that mirrors the buffer and updates live as the user types (D-50 behavior 4). If the editor is on a non-renderable file, no auto-viewer (D-50 behavior 5, already holds). +OPEN QUESTION (decide here): clide''s editor-mode is swap (one surface above Claude at a time), so you cannot currently watch a terminal AND edit a file side-by-side. Options: (a) swap-only, leaning on tmux persistence + a fast toggle; or (b) allow the editor pane to split so a terminal can sit beside editor/diff. This is a facet of Q-27 (two-editor split) — resolve them together. Relates to D-47 (Claude-is-home), D-48 (chrome budget), D-49 (editor-mode), D-84 (diff placement, the existing editor-mode peer).', 'backlog', 'medium', NULL, NULL, 'D-49', '2026-06-06 20:55:49', '2026-06-06 20:55:49', NULL, '6108bdb6bc0ea059d93f12021fe617cf', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7RQMN1GKEZ5JQKJT0', 'epic', NULL, 'Interaction model — context panel, readers, live-sync, focus mode', 'Home for the D-47–D-57 interaction-model surface that isn''t a numbered build tier: the reactive context panel (D-50), editor-mode viewer↔editor swap (D-49), panel collapse/spine (D-51), focus mode (D-52), layout state persistence (D-53), and the keyboard map (D-54). Created 2026-06-06 to re-home T-36, which was mis-parented under T-7 (Tier 5 canvas/graph). These behaviors span tiers and belong to the interaction model rather than a single feature tier.', 'backlog', 'medium', NULL, NULL, 'D-47', '2026-06-06 21:12:44', '2026-06-06 21:12:44', NULL, '22b26d155a9fdffa44909ed4811c42f4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5JK0RHMRAH47K8ND4', 'story', '06FB0TNQM7RQMN1GKEZ5JQKJT0', 'Live-sync read-mirror: context panel tracks the open editor buffer', 'When an editor is open in edit mode on a renderable doc (.md), the right-hand context panel auto-opens a read-only view that mirrors the buffer and updates live as the user types (D-50 behavior 4). If the editor is on a non-renderable file, no auto-viewer (D-50 behavior 5, already holds). Wiring sketch: EditorRegistry already emits editor.opened / editor.active-changed / editor.edited (lib/src/editor/registry.dart:85). The markdown reader (lib/builtin/markdown/src/markdown_viewer.dart) currently pulls content once via files.read with no subscription. The live-sync work is: (1) on editor.opened for a renderable file, auto-activate the markdown reader in the context panel; (2) subscribe the reader to editor.edited and re-render from the in-memory buffer rather than re-reading disk; (3) read-only — no edit affordances in the mirror. -HISTORY: T-36 originally bundled four D-50 clauses (parse Claude''s output for file references, swap the open viewer, badge the spine when collapsed, live-sync). The give-clide-hands push (T-208) superseded the first three: instead of clide scraping the terminal for references, the agent explicitly drives the reader via `clide ui open markdown ` (T-231) and ui.open -> diff (T-233). The spine badge was dropped (the open is now an intentional agent act, not a passive notification). Re-scoped 2026-06-06 to the one UI-owned piece that give-clide-hands did not deliver: the live-sync read-mirror. Re-homed from T-7 (Tier 5 canvas/graph, a mis-parent) to T-259 (interaction model). See the D-50 amendment.', 'review', 'low', NULL, NULL, 'D-50', '2026-04-22 20:34:09', '2026-06-06 21:14:16', NULL, '9e26103c9eb60cf1fb5513c0334faae8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'story', 'T-259', 'Live-sync read-mirror: context panel tracks the open editor buffer', 'When an editor is open in edit mode on a renderable doc (.md), the right-hand context panel auto-opens a read-only view that mirrors the buffer and updates live as the user types (D-50 behavior 4). If the editor is on a non-renderable file, no auto-viewer (D-50 behavior 5, already holds). - -Wiring sketch: EditorRegistry already emits editor.opened / editor.active-changed / editor.edited (lib/src/editor/registry.dart:85). The markdown reader (lib/builtin/markdown/src/markdown_viewer.dart) currently pulls content once via files.read with no subscription. The live-sync work is: (1) on editor.opened for a renderable file, auto-activate the markdown reader in the context panel; (2) subscribe the reader to editor.edited and re-render from the in-memory buffer rather than re-reading disk; (3) read-only — no edit affordances in the mirror. - -HISTORY: T-36 originally bundled four D-50 clauses (parse Claude''s output for file references, swap the open viewer, badge the spine when collapsed, live-sync). The give-clide-hands push (T-208) superseded the first three: instead of clide scraping the terminal for references, the agent explicitly drives the reader via `clide ui open markdown ` (T-231) and ui.open -> diff (T-233). The spine badge was dropped (the open is now an intentional agent act, not a passive notification). Re-scoped 2026-06-06 to the one UI-owned piece that give-clide-hands did not deliver: the live-sync read-mirror. Re-homed from T-7 (Tier 5 canvas/graph, a mis-parent) to T-259 (interaction model). See the D-50 amendment.', 'review', 'low', NULL, NULL, 'D-50', '2026-04-22 20:34:09', '2026-06-06 21:14:19', NULL, 'baa9a19008d439005574c42c36ce1639', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-36', 'story', 'T-259', 'Live-sync read-mirror: context panel tracks the open editor buffer', 'When an editor is open in edit mode on a renderable doc (.md), the right-hand context panel auto-opens a read-only view that mirrors the buffer and updates live as the user types (D-50 behavior 4). If the editor is on a non-renderable file, no auto-viewer (D-50 behavior 5, already holds). - -Wiring sketch: EditorRegistry already emits editor.opened / editor.active-changed / editor.edited (lib/src/editor/registry.dart:85). The markdown reader (lib/builtin/markdown/src/markdown_viewer.dart) currently pulls content once via files.read with no subscription. The live-sync work is: (1) on editor.opened for a renderable file, auto-activate the markdown reader in the context panel; (2) subscribe the reader to editor.edited and re-render from the in-memory buffer rather than re-reading disk; (3) read-only — no edit affordances in the mirror. - -HISTORY: T-36 originally bundled four D-50 clauses (parse Claude''s output for file references, swap the open viewer, badge the spine when collapsed, live-sync). The give-clide-hands push (T-208) superseded the first three: instead of clide scraping the terminal for references, the agent explicitly drives the reader via `clide ui open markdown ` (T-231) and ui.open -> diff (T-233). The spine badge was dropped (the open is now an intentional agent act, not a passive notification). Re-scoped 2026-06-06 to the one UI-owned piece that give-clide-hands did not deliver: the live-sync read-mirror. Re-homed from T-7 (Tier 5 canvas/graph, a mis-parent) to T-259 (interaction model). See the D-50 amendment.', 'backlog', 'low', NULL, NULL, 'D-50', '2026-04-22 20:34:09', '2026-06-06 21:14:22', NULL, '318e7923a1671f26c131d8a973f95ac3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'story', NULL, 'Animate the ''running…'' turn indicator with rotating status verbs', '## Problem - -While a Claude turn is in flight, the composer swaps in a static, muted -`running…` label next to the Stop button (`lib/builtin/claude/src/claude_composer.dart:486`, -shown when `widget.busy && widget.onInterrupt != null`). It''s gray, motionless, -and a little lifeless — it gives no sense that anything is actually happening. - -## Goal - -Make the in-flight indicator feel alive: - -1. **Animation.** Add subtle motion — e.g. an animated ellipsis (`running` → `running.` - → `running..` → `running...`), a shimmer/pulse on the text, or a small spinner glyph. - Custom-painted / token-driven per the "own the rendering stack" guardrail; no - opinionated animation packages. -2. **Rotating status verbs.** Cycle through playful gerunds the way the Claude Code CLI - does (e.g. "Clauding…", "Flibbertigibbeting…", "Pondering…", "Conjuring…"). Pick a - word from a curated list and rotate it every few seconds while the turn runs. - -## Open question — can we reuse the CLI''s words? - -The Claude Code CLI ships its own list of these status verbs. Before hand-rolling our -own, check whether that list is something we''re allowed to reuse / surface (licensing, -where it lives, whether stream-json exposes the current one). If we can''t pull the CLI''s -list, ship our own curated, on-brand list instead. Document the decision. - -## Notes / constraints - -- Respect reduced-motion / accessibility settings — animation must be disable-able and - the a11y semantics should still read sensibly (the Stop button already carries the - interrupt hint). -- Use theme tokens for color; keep it muted/tasteful, not distracting. -- Touch point today is the `widget.busy` branch in `claude_composer.dart`; consider - whether the rotating-word state belongs there or in the session/orchestrator layer. -- Add a widget/golden test for the animated states (bounded pumps — no real timers).', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:56:23', '2026-06-06 21:43:40', NULL, '30e4ba6709c48848953df571c4d25650', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-260', 'story', NULL, 'Annotation tools on the image overlay — boxes, arrows, labels for visual dialog with Claude', 'Add an annotation layer to the full-screen image overlay (T-252) so the user can draw boxes, arrows, and text labels directly on a shown image and have a VISUAL DIALOG with Claude about it — Claude shows an image, the user marks it up ("this button is misaligned", arrow pointing at the bug, box around the region), and the markup is communicated back to Claude. +HISTORY: T-36 originally bundled four D-50 clauses (parse Claude''s output for file references, swap the open viewer, badge the spine when collapsed, live-sync). The give-clide-hands push (T-208) superseded the first three: instead of clide scraping the terminal for references, the agent explicitly drives the reader via `clide ui open markdown ` (T-231) and ui.open -> diff (T-233). The spine badge was dropped (the open is now an intentional agent act, not a passive notification). Re-scoped 2026-06-06 to the one UI-owned piece that give-clide-hands did not deliver: the live-sync read-mirror. Re-homed from T-7 (Tier 5 canvas/graph, a mis-parent) to T-259 (interaction model). See the D-50 amendment.', 'backlog', 'low', NULL, NULL, 'D-50', '2026-04-22 20:34:09', '2026-06-06 21:14:22', NULL, '6609c1a39c7dd974a0055ab2ed80e3cd', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6DSPH569T4458B8JC', 'story', NULL, 'Annotation tools on the image overlay — boxes, arrows, labels for visual dialog with Claude', 'Add an annotation layer to the full-screen image overlay (T-252) so the user can draw boxes, arrows, and text labels directly on a shown image and have a VISUAL DIALOG with Claude about it — Claude shows an image, the user marks it up ("this button is misaligned", arrow pointing at the bug, box around the region), and the markup is communicated back to Claude. The crux is the round-trip: annotations are not just decoration, they are a message to Claude. The overlay must turn what the user draws into something Claude can consume. @@ -1150,12 +344,12 @@ Open questions to resolve in design: - Structured-annotation schema (shared with canvas/graph later?). - Whether annotations persist with the image or are ephemeral per-overlay-session. -Refs: D-78 (interaction zone / display-only conversation widgets), D-6 (CLI/UI parity). Builds on T-252 (lightbox overlay) and T-249 (image card + clide image show). Related: T-248 (discovery), T-259 (interaction-model epic).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-06 21:46:42', '2026-06-06 21:46:42', NULL, '27207574416b1c1fc2f9cd0d62d503d1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-261', 'task', 'T-54', 'Output dock: drag-resize the dock height', 'Follow-up from T-54 (D-87). The bottom output dock ships with a persisted default height but no drag-to-resize handle. DragResizeHandle supports Axis.vertical but its drag-sign logic (lib/kernel/src/panels/drag_resize.dart _onMove + bumpedSlotSize) only negates for contextPanel; a bottom dock with a top handle needs the same negation (drag up = grow). Add Slots.dock to those sign cases, render a vertical DragResizeHandle above the dock in RootLayout (lib/app.dart, between the workspace Expanded and the dock SizedBox), and keep the existing >=50% clamp. Persistence of the size already works (project.layout.dock.size).', 'backlog', 'low', NULL, NULL, 'D-87', '2026-06-07 08:03:10', '2026-06-07 08:03:10', NULL, '2fd73c277fd2512f1f7d4c08274e3917', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-54', 'story', NULL, 'output and log panel', '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. +Refs: D-78 (interaction zone / display-only conversation widgets), D-6 (CLI/UI parity). Builds on T-252 (lightbox overlay) and T-249 (image card + clide image show). Related: T-248 (discovery), T-259 (interaction-model epic).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-06 21:46:42', '2026-06-06 21:46:42', NULL, '5322506b7dc2d05515fd978118b8d5de', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7GJW4V3CXMMXXV988', 'task', '06FB0TNQM7GCVGCH9RMGYHENCC', 'Output dock: drag-resize the dock height', 'Follow-up from T-54 (D-87). The bottom output dock ships with a persisted default height but no drag-to-resize handle. DragResizeHandle supports Axis.vertical but its drag-sign logic (lib/kernel/src/panels/drag_resize.dart _onMove + bumpedSlotSize) only negates for contextPanel; a bottom dock with a top handle needs the same negation (drag up = grow). Add Slots.dock to those sign cases, render a vertical DragResizeHandle above the dock in RootLayout (lib/app.dart, between the workspace Expanded and the dock SizedBox), and keep the existing >=50% clamp. Persistence of the size already works (project.layout.dock.size).', 'backlog', 'low', NULL, NULL, 'D-87', '2026-06-07 08:03:10', '2026-06-07 08:03:10', NULL, '63450e3c14cc719fb0da8a4a19d82c83', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7GCVGCH9RMGYHENCC', 'story', NULL, 'output and log panel', '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. -UX design (2026-06-06, D-87): the panel is a bottom OUTPUT DOCK, read-only, two tabs — Output (the Logger stream, filter by source/level/text, auto-scroll) + Problems (moved out of the sidebar; no duplication). Toggled by a single status-bar widget that REPLACES the app-status indicator (merged health+log: green check when clean, warn/error counts when not, chevron for open state) — opens with click or Cmd/Ctrl+J. Needs a bounded in-memory ring sink on the Logger (no history today). Layout amends D-47 (dock pushes Claude up, capped so Claude stays >=50%). Terminal is NOT in the dock — kept first-class in the editor pane, tracked by T-258. Resolves Q-28. Wireframe: docs/design/wireframes/output-dock/.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-07 08:03:17', NULL, 'ced24a85b437a7c7f91515dd0ddf86cc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-252', 'story', NULL, 'Full-screen lightbox overlay (zoom + pan) for image cards', 'Click an inline image card (T-249) to open it in a full-screen overlay with zoom + pan — the standard lightbox UX from other apps (dimmed backdrop, image fit-to-screen, scroll/pinch to zoom, drag to pan when zoomed past fit, Esc or backdrop-click to dismiss). +UX design (2026-06-06, D-87): the panel is a bottom OUTPUT DOCK, read-only, two tabs — Output (the Logger stream, filter by source/level/text, auto-scroll) + Problems (moved out of the sidebar; no duplication). Toggled by a single status-bar widget that REPLACES the app-status indicator (merged health+log: green check when clean, warn/error counts when not, chevron for open state) — opens with click or Cmd/Ctrl+J. Needs a bounded in-memory ring sink on the Logger (no history today). Layout amends D-47 (dock pushes Claude up, capped so Claude stays >=50%). Terminal is NOT in the dock — kept first-class in the editor pane, tracked by T-258. Resolves Q-28. Wireframe: docs/design/wireframes/output-dock/.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-07 08:03:17', NULL, '411f3d80c7dba30b330678d58a27a74f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4QW8VH454J8V3E174', 'story', NULL, 'Full-screen lightbox overlay (zoom + pan) for image cards', 'Click an inline image card (T-249) to open it in a full-screen overlay with zoom + pan — the standard lightbox UX from other apps (dimmed backdrop, image fit-to-screen, scroll/pinch to zoom, drag to pan when zoomed past fit, Esc or backdrop-click to dismiss). Build the overlay as a SHARED PRIMITIVE WIDGET, not image-specific. A reusable zoomable-viewer / lightbox primitive under lib/widgets/ (e.g. ClideLightbox + ClideZoomPanViewer) that takes any child/content; the image card is its first consumer, and the canvas, graph, and diff/image previews can adopt it later. Own-the-rendering-stack + prefer-zero-deps still apply: Flutter''s SDK InteractiveViewer is the SDK-first starting point (allowed), but keep the gesture/zoom logic in the primitive so we control the UX, not an opinionated package. @@ -1164,284 +358,8 @@ Scope: - Image card wiring (T-249): the inline card stays display-only per D-78 — the click is a navigation gesture that opens the lightbox, not an inline control. Render the same resolved file at full resolution in the overlay; missing-file degrades to the existing placeholder. - Parity (D-6): a CLI counterpart to open an image straight into the lightbox (e.g. clide image show --fullscreen, or clide image open ), registered so it surfaces in clide capabilities (T-248). -Refs: D-78 (interaction zone / display-only conversation widgets), D-6 (CLI/UI parity), DialogRouter (lib/kernel/src/dialog.dart). Builds on T-249 (image card + clide image show). Related: T-248.', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-06 08:28:43', '2026-06-07 08:07:42', NULL, '4abfa2ec36914b4ffcb8115a154f12c4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'story', NULL, 'Merge tool-call + success result into one card with header status check', 'Today a Claude tool call renders as TWO stacked cards in the conversation log: the tool-use card (e.g. "Write " with a collapsible call body) and a separate success/result card ("Write · result / File created successfully..."). Collapse the successful pair into ONE card; failure keeps the current two-card interaction. - -Current code: lib/builtin/claude/src/conversation_view.dart — _toolUse (~L294) renders AssistantToolUse, _toolResult (~L329) renders ToolResultMessage; the two ConversationItems are linked by toolUseId (ToolResultMessage.isError marks failure). ConversationCard header is built in lib/builtin/claude/src/conversation_card.dart _header (~L200). Builds on T-168 (per-tool body rendering). - -Desired behavior (success): -- One card per successful tool call — the tool-use card. The standalone success result card is suppressed (folded in), not rendered separately. -- A success-green check mark sits at the RIGHT END of the card header row (reuse tokens.statusSuccess + a check glyph: PhosphorIcons.check or CheckIcon). No new theme tokens. -- Collapsed by default: header shows the existing summary + the check. Expanding the card reveals the CALL segment (the input — path / diff / command, as today) and, BELOW it, the RESULT segment (the swallowed output). This applies to ALL tools, not just Write/Edit: for Bash/Read/Grep/LS the real output relocates into the expandable body so nothing is lost; for Write/Edit the trivial confirmation folds in the same way. -- Render the result segment as an inline ClideCodeBlock (lib/widgets/src/clide_code_block.dart) WITH editor syntax colorization. ClideCodeBlock already colorizes via TreeSitterService when handed a language — so the work is inferring/passing the right language per tool: Read → from the file extension/path (language_map.grammarForPath), Bash → bash, Grep/LS/others → text fallback. Where a path is available, prefer it so highlighting matches the editor. - -Desired behavior (failure) — UNCHANGED: -- Leave the current interaction as-is: the tool-use card plus the prominent red "· error" result card (borderColor statusError, expanded-by-default). Optionally show a red status mark on the tool-use header for symmetry, but do NOT fold the error into one card. - -What to build: -1. ConversationCard: a trailing header status slot (e.g. status: success|error|none) rendered in _header between the Spacer/summary and the hover action buttons. -2. ConversationCard: support a second body segment so an expanded card can show CALL then RESULT with a clear visual separator / sub-label (e.g. a divider or a muted "result" label) so the user can tell the call from its output. -3. conversation_view: a result-by-toolUseId lookup (reverse of the existing toolUseById) so a tool-use card knows its outcome; on a successful pair, stamp the check + fold the result body and SUPPRESS the standalone success ToolResultMessage from the rendered list. Errors render both cards as today. -4. Per-tool result language inference for the folded code block. - -Edge cases to handle: -- In-flight tool-use with no result yet: render the call card as today (no check, no folded result); status/result appear once the result arrives. -- Orphan result with no paired tool-use: keep rendering it standalone. -- Permission-resolved tool-use cards (conversation_view.dart ~L297, already green/red bordered + collapsed): reconcile so the merged-card + check treatment is consistent and not duplicated with the existing border-outcome styling. -- Result items may not be strictly adjacent to their tool-use in the list; suppression must be keyed by toolUseId, not list position. - -Tests: unit/widget coverage for the merged success card (collapsed shows check; expanded shows call + colorized result), language inference per tool, the unchanged error path, and the in-flight/orphan cases. Add a golden for the merged success card. - -Refs: D-78 (interaction zone / display-only conversation widgets). Builds on T-168 (per-tool tool-use/result body rendering).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:10:42', '2026-06-07 08:10:42', NULL, '3eec2008cc279ef886612e74651cb081', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-252', 'story', NULL, 'Full-screen lightbox overlay (zoom + pan) for image cards', 'Click an inline image card (T-249) to open it in a full-screen overlay with zoom + pan — the standard lightbox UX from other apps (dimmed backdrop, image fit-to-screen, scroll/pinch to zoom, drag to pan when zoomed past fit, Esc or backdrop-click to dismiss). - -Build the overlay as a SHARED PRIMITIVE WIDGET, not image-specific. A reusable zoomable-viewer / lightbox primitive under lib/widgets/ (e.g. ClideLightbox + ClideZoomPanViewer) that takes any child/content; the image card is its first consumer, and the canvas, graph, and diff/image previews can adopt it later. Own-the-rendering-stack + prefer-zero-deps still apply: Flutter''s SDK InteractiveViewer is the SDK-first starting point (allowed), but keep the gesture/zoom logic in the primitive so we control the UX, not an opinionated package. - -Scope: -- Shared primitive (lib/widgets/): a full-screen overlay with a dimmed backdrop hosted via the existing DialogRouter (single-at-a-time modal, D-78 interaction surface). Content scales to fit on open; zoom via scroll wheel / pinch / +- ; pan by drag when zoomed in; double-click toggles fit<->100%/2x; Esc and backdrop-click dismiss. Min/max zoom clamp. Keyboard + AT reachable (ClideTappable / Semantics), theme tokens / ui-design for any chrome (close affordance, zoom hint). -- Image card wiring (T-249): the inline card stays display-only per D-78 — the click is a navigation gesture that opens the lightbox, not an inline control. Render the same resolved file at full resolution in the overlay; missing-file degrades to the existing placeholder. -- Parity (D-6): a CLI counterpart to open an image straight into the lightbox (e.g. clide image show --fullscreen, or clide image open ), registered so it surfaces in clide capabilities (T-248). - -Refs: D-78 (interaction zone / display-only conversation widgets), D-6 (CLI/UI parity), DialogRouter (lib/kernel/src/dialog.dart). Builds on T-249 (image card + clide image show). Related: T-248.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-06 08:28:43', '2026-06-07 08:32:36', NULL, 'ed2e2cec91fa720e170d287db445fdaa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'story', NULL, 'Fold sub-agent prompt into the Agent card; relabel from you to agent prompt', 'When Claude launches a sub-agent (the Agent/Task tool), the sub-agent conversation runs as a SIDECHAIN. The prompt Claude wrote for that sub-agent comes through the transcript as a UserMessage with isSidechain=true but injected=false. The conversation view does not look at isSidechain, so it renders that prompt with the blue "you" label exactly like real user input — FALSELY implying the user sent it. It sits as a standalone block below the Agent tool-use card. This is misleading: the user did not write that prompt. - -Confirmed facts from the code: every transcript envelope carries BOTH isSidechain AND parentUuid (transcript_reader.dart ~L547 parses isSidechain but never USES it; parentUuid is dropped entirely). The Agent/Task tool-use is rendered by _toolUse in lib/builtin/claude/src/conversation_view.dart (only AskUserQuestion is special-cased today). The "you" vs "context" label is chosen in conversation_view.dart (~L200): UserMessage with injected=true → "context" (muted); else → "you" (blue, tokens.globalFocus). isSidechain is currently never read by any rendering/grouping logic. - -Design (confirmed with user): -1. Never label a sidechain prompt "you". A UserMessage with isSidechain=true is an AGENT PROMPT, not user input — label it "agent prompt" with muted / de-emphasized styling (same precedent as injected "context" messages, D-78). Never the blue "you" treatment. -2. Fold the prompt INTO the Agent tool-use card above it, COLLAPSED BY DEFAULT. The Agent card becomes the container that owns its prompt: expand the card to reveal the prompt; collapsed (default) it is hidden. The standalone prompt block is SUPPRESSED from the list — same fold-a-standalone-item-into-its-card pattern as T-262. -3. Prompt folds into the CALL, not the results. Only the prompt folds into the collapsed Agent call card. The sub-agent work (its thinking / tool calls / results) stays VISIBLE as the results chain after the call — not buried. Shape: [collapsed Agent call, owning the prompt] then [the run results], with no misleading "you" block anywhere. Agent prompts are valuable enough to keep with the call rather than fold into the subsequent results chain. -4. Link the prompt to the RIGHT Agent card via parentUuid. Parse parentUuid (currently dropped) so each sidechain prompt attaches to its spawning Agent tool-use. This matters when several agents run in parallel in one turn — a positional "nearest preceding Agent" heuristic would misattach; the parent link is the robust route. Heuristic only as a fallback when the link cannot be resolved. - -Touch points: -- transcript_reader.dart: capture parentUuid from the envelope and expose it on ConversationItem (it is present in the JSONL, just not parsed). -- conversation_view.dart: relabel sidechain UserMessage to "agent prompt" (stop the "you" path); fold it into the Agent card body; special-case the Agent/Task tool name (today only AskUserQuestion is special-cased); suppress the standalone block. -- activity_cluster.dart: ensure the suppressed prompt is not double-counted in grouping; leave the sidechain results in the chain. - -Edge cases: -- Parallel agents in one turn → each prompt attaches to its own card (the reason for parentUuid). -- Orphan prompt (no resolvable parent) → still render as "agent prompt", muted/collapsed, never "you". -- Nested / background sub-agents. - -Tests: unit/widget coverage for sidechain-prompt relabel (never "you"), folding into the Agent card (collapsed hides, expand reveals), suppression of the standalone block, parentUuid attachment with parallel agents, and the orphan fallback. Add a golden for the Agent card with a folded prompt. - -Refs: D-78 (interaction zone / display-only conversation widgets; injected/context muting precedent). Related: T-262 (fold-standalone-item-into-its-card pattern).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:34:02', '2026-06-07 08:34:02', NULL, 'b5ea4c6bb5486fd0ef28ffdc884387c9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'story', NULL, 'Nest the whole sub-agent run under its Agent card (attribute sidechain via parentUuid)', 'Completion of T-263. T-263 folds the sub-agent PROMPT into the Agent card, but the sub-agent run itself — its prose, thinking, and tool calls — still renders inline in the MAIN conversation chain. A reader cannot tell where the sub-agent run ends and the main thread resumes; sidechain items are visually indistinguishable from main-thread items. - -Root cause: the conversation is a flat list of ConversationItems rendered by _ConversationTurn (lib/builtin/claude/src/conversation_view.dart build ~L130, _ConversationTurn ~L182). isSidechain exists on every item (transcript_reader.dart ~L46) but no rendering/grouping logic reads it. activity_cluster.dart groups consecutive meta runs but is sidechain-agnostic. - -Design: use the parentUuid link (parsed in T-263) to group an ENTIRE sidechain run as a nested region OWNED by its spawning Agent card. The run renders as a distinct, attributed, collapsible region (indent / attribution band / nested holder) under the Agent call rather than as loose cards in the main chain. Per the user rule from T-263, the run results stay VISIBLE (not folded away) — this is about attribution and containment, not hiding. - -Scope: -- Build a sidechain-aware grouping pass (extend or complement activity_cluster.groupConversation) that collects all items sharing a sidechain ancestry (via parentUuid chain back to the Agent tool_use) into one nested group. -- Render that group as a nested region under the Agent card (visual indent / attribution), keeping the sub-agent results first-class within it. -- Handle parallel agents (each run nests under its own card), nested sub-agents, and background agents. -- Orphan sidechain items (no resolvable parent) degrade to the current inline rendering but still attributed (see T-264/relabel work). - -Depends on T-263 (parentUuid parsing + Agent card as container). Pairs with the sidechain-prose relabel ticket. Note the holder-card restyle + auto-scroll race is a separate design discussion (see the holder-card ticket). - -Refs: D-78. Builds on T-263. Related: T-262, T-230 (activity card grouping).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:25', '2026-06-07 08:40:25', NULL, '557a16556497643a5ce66007ceeea44b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'story', NULL, 'Relabel sidechain assistant prose/thinking — attribute to agent, not claude', 'Mirror of T-263 on the OUTPUT side. A sub-agent runs as a sidechain; its assistant prose renders with the label "claude" and its thinking with "thinking", identically to the MAIN assistant — because the label logic does not check isSidechain. So a sub-agents output is presented as if the main Claude said it. Same misattribution T-263 fixes for the prompt (the "you" problem), mirrored on the assistant side. - -Current code (lib/builtin/claude/src/conversation_view.dart _ConversationTurn build ~L221): AssistantTextMessage → ConversationCard(accent: claudeAccent, label: "claude"); AssistantThinkingMessage (~L227) → label "thinking". Neither inspects ConversationItem.isSidechain (transcript_reader.dart ~L46), which is present but unused. - -Design: when isSidechain is true, attribute the assistant items to the AGENT rather than the main Claude — e.g. label "agent" (or the agent/sub-agent name where resolvable) with de-emphasised styling, NOT the coral "claude" brand accent (claudeAccent is the main-thread brand, reserved for the real Claude). Thinking inside a sidechain attributed/muted likewise. - -Scope: -- Branch the AssistantTextMessage / AssistantThinkingMessage rendering on isSidechain. -- Pick the sidechain label + styling (distinct from "claude"; reuse muted tokens, not claudeAccent). -- Keep behavior identical for main-thread items. - -Tests: relabel of sidechain assistant prose/thinking (never "claude"/coral); main-thread unchanged; golden for an attributed agent prose card. - -Pairs with the nest-the-agent-run ticket (attribution + containment) and T-263 (prompt side). Refs: D-78.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:33', '2026-06-07 08:40:33', NULL, '1da5cd4c8f41cacb7508d6298b223820', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'story', NULL, 'Merge tool-call + success result into one card with header status check', 'Today a Claude tool call renders as TWO stacked cards in the conversation log: the tool-use card (e.g. "Write " with a collapsible call body) and a separate success/result card ("Write · result / File created successfully..."). Collapse the successful pair into ONE card; failure keeps the current two-card interaction. - -Current code: lib/builtin/claude/src/conversation_view.dart — _toolUse (~L294) renders AssistantToolUse, _toolResult (~L329) renders ToolResultMessage; the two ConversationItems are linked by toolUseId (ToolResultMessage.isError marks failure). ConversationCard header is built in lib/builtin/claude/src/conversation_card.dart _header (~L200). Builds on T-168 (per-tool body rendering). - -Desired behavior (success): -- One card per successful tool call — the tool-use card. The standalone success result card is suppressed (folded in), not rendered separately. -- A success-green check mark sits at the RIGHT END of the card header row (reuse tokens.statusSuccess + a check glyph: PhosphorIcons.check or CheckIcon). No new theme tokens. -- Collapsed by default: header shows the existing summary + the check. Expanding the card reveals the CALL segment (the input — path / diff / command, as today) and, BELOW it, the RESULT segment (the swallowed output). This applies to ALL tools, not just Write/Edit: for Bash/Read/Grep/LS the real output relocates into the expandable body so nothing is lost; for Write/Edit the trivial confirmation folds in the same way. -- Render the result segment as an inline ClideCodeBlock (lib/widgets/src/clide_code_block.dart) WITH editor syntax colorization. ClideCodeBlock already colorizes via TreeSitterService when handed a language — so the work is inferring/passing the right language per tool: Read → from the file extension/path (language_map.grammarForPath), Bash → bash, Grep/LS/others → text fallback. Where a path is available, prefer it so highlighting matches the editor. - -Desired behavior (failure) — UNCHANGED: -- Leave the current interaction as-is: the tool-use card plus the prominent red "· error" result card (borderColor statusError, expanded-by-default). Optionally show a red status mark on the tool-use header for symmetry, but do NOT fold the error into one card. - -What to build: -1. ConversationCard: a trailing header status slot (e.g. status: success|error|none) rendered in _header between the Spacer/summary and the hover action buttons. -2. ConversationCard: support a second body segment so an expanded card can show CALL then RESULT with a clear visual separator / sub-label (e.g. a divider or a muted "result" label) so the user can tell the call from its output. -3. conversation_view: a result-by-toolUseId lookup (reverse of the existing toolUseById) so a tool-use card knows its outcome; on a successful pair, stamp the check + fold the result body and SUPPRESS the standalone success ToolResultMessage from the rendered list. Errors render both cards as today. -4. Per-tool result language inference for the folded code block. - -Edge cases to handle: -- In-flight tool-use with no result yet: render the call card as today (no check, no folded result); status/result appear once the result arrives. -- Orphan result with no paired tool-use: keep rendering it standalone. -- Permission-resolved tool-use cards (conversation_view.dart ~L297, already green/red bordered + collapsed): reconcile so the merged-card + check treatment is consistent and not duplicated with the existing border-outcome styling. -- Result items may not be strictly adjacent to their tool-use in the list; suppression must be keyed by toolUseId, not list position. - -Tests: unit/widget coverage for the merged success card (collapsed shows check; expanded shows call + colorized result), language inference per tool, the unchanged error path, and the in-flight/orphan cases. Add a golden for the merged success card. - -Refs: D-78 (interaction zone / display-only conversation widgets). Builds on T-168 (per-tool tool-use/result body rendering). - -IMPLEMENTATION NOTES (from streamlining analysis): - -C — Error-card symmetry decision: this ticket merges only SUCCESS into the tool card; the error path intentionally stays a separate prominent red "· error" card (conversation_view.dart ~L337) for failure visibility. Decide explicitly: keep that asymmetry (success folds, failure stays two-card) OR also stamp a red status mark on the tool-use header for visual symmetry while still keeping the separate red card. Default leaning: keep the separate red card; optionally add the red header mark. - -D — Activity-cluster coupling: activity_cluster.groupConversation currently folds a tool CALL and its RESULT as TWO separate foldable items into a cluster (activity_cluster.dart ~L100-109, classifying each by toolUseId). Once this ticket makes call+result a single self-contained card, the grouping pass must treat the tool call as ONE unit (its result is part of the card, no longer a separate foldable item) or the result will double-render (once folded into the card, once as a cluster item). Update _isFoldable / the pairing logic accordingly and add a test that a merged tool card is not double-counted.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:10:42', '2026-06-07 08:40:45', NULL, 'a1299dfbafefeb2f4691b47869ed41bb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'story', NULL, 'Fold sub-agent prompt into the Agent card; relabel from you to agent prompt', 'When Claude launches a sub-agent (the Agent/Task tool), the sub-agent conversation runs as a SIDECHAIN. The prompt Claude wrote for that sub-agent comes through the transcript as a UserMessage with isSidechain=true but injected=false. The conversation view does not look at isSidechain, so it renders that prompt with the blue "you" label exactly like real user input — FALSELY implying the user sent it. It sits as a standalone block below the Agent tool-use card. This is misleading: the user did not write that prompt. - -Confirmed facts from the code: every transcript envelope carries BOTH isSidechain AND parentUuid (transcript_reader.dart ~L547 parses isSidechain but never USES it; parentUuid is dropped entirely). The Agent/Task tool-use is rendered by _toolUse in lib/builtin/claude/src/conversation_view.dart (only AskUserQuestion is special-cased today). The "you" vs "context" label is chosen in conversation_view.dart (~L200): UserMessage with injected=true → "context" (muted); else → "you" (blue, tokens.globalFocus). isSidechain is currently never read by any rendering/grouping logic. - -Design (confirmed with user): -1. Never label a sidechain prompt "you". A UserMessage with isSidechain=true is an AGENT PROMPT, not user input — label it "agent prompt" with muted / de-emphasized styling (same precedent as injected "context" messages, D-78). Never the blue "you" treatment. -2. Fold the prompt INTO the Agent tool-use card above it, COLLAPSED BY DEFAULT. The Agent card becomes the container that owns its prompt: expand the card to reveal the prompt; collapsed (default) it is hidden. The standalone prompt block is SUPPRESSED from the list — same fold-a-standalone-item-into-its-card pattern as T-262. -3. Prompt folds into the CALL, not the results. Only the prompt folds into the collapsed Agent call card. The sub-agent work (its thinking / tool calls / results) stays VISIBLE as the results chain after the call — not buried. Shape: [collapsed Agent call, owning the prompt] then [the run results], with no misleading "you" block anywhere. Agent prompts are valuable enough to keep with the call rather than fold into the subsequent results chain. -4. Link the prompt to the RIGHT Agent card via parentUuid. Parse parentUuid (currently dropped) so each sidechain prompt attaches to its spawning Agent tool-use. This matters when several agents run in parallel in one turn — a positional "nearest preceding Agent" heuristic would misattach; the parent link is the robust route. Heuristic only as a fallback when the link cannot be resolved. - -Touch points: -- transcript_reader.dart: capture parentUuid from the envelope and expose it on ConversationItem (it is present in the JSONL, just not parsed). -- conversation_view.dart: relabel sidechain UserMessage to "agent prompt" (stop the "you" path); fold it into the Agent card body; special-case the Agent/Task tool name (today only AskUserQuestion is special-cased); suppress the standalone block. -- activity_cluster.dart: ensure the suppressed prompt is not double-counted in grouping; leave the sidechain results in the chain. - -Edge cases: -- Parallel agents in one turn → each prompt attaches to its own card (the reason for parentUuid). -- Orphan prompt (no resolvable parent) → still render as "agent prompt", muted/collapsed, never "you". -- Nested / background sub-agents. - -Tests: unit/widget coverage for sidechain-prompt relabel (never "you"), folding into the Agent card (collapsed hides, expand reveals), suppression of the standalone block, parentUuid attachment with parallel agents, and the orphan fallback. Add a golden for the Agent card with a folded prompt. - -Refs: D-78 (interaction zone / display-only conversation widgets; injected/context muting precedent). Related: T-262 (fold-standalone-item-into-its-card pattern). - -IMPLEMENTATION NOTE (E — Agent-card layered ordering, from streamlining analysis): - -With this ticket (fold prompt) and T-262 (merge success result) both landing, the Agent/Task card ends up owning multiple segments: the tool INPUT, the folded PROMPT, and — via T-262 — the sub-agents final returned RESULT (the Task ToolResultMessage). Define a deliberate layered order when expanded (e.g. call/input → prompt → returned result) with clear sub-labels/dividers so the Agent card stays readable and does not become a kitchen sink. Coordinate with T-262 (result merge) and T-264 (nesting the whole run): the nested run region vs the returned-result segment must not duplicate the sub-agent output.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:34:02', '2026-06-07 08:40:49', NULL, '25445bc5e6238eb2a2c592d304a105c9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'story', NULL, 'Restyle activity/holder card as a container of sub-cards (fix collapse-control scroll race)', 'DESIGN DISCUSSION PENDING — do not build until T-262/T-263/T-264/T-265 settle. Captured from user feedback; the interaction model needs a design pass first. - -Problem: the current activity card (lib/builtin/claude/src/conversation_view.dart _ActivityCard ~L427, grouping in activity_cluster.dart) is a COLLAPSER HEADER that, when expanded, reveals the subsequent cards rendered below it. Two issues: -1. Model: it reads as a header-plus-loose-cards rather than a single container. Proposal: make it a HOLDER CARD that visually CONTAINS its sub-cards, where clicking anywhere in the card space (the holder) toggles collapse/expand — the whole card is the affordance, not just a small caret/header at the top. -2. Scroll race: when a holder card is actively being written to (a live agent turn), the view keeps auto-scrolling to the bottom (conversation_view._onChanged ~L119 jumps to maxScrollExtent on every change). The collapse control lives at the TOP of the card, so the user gets into a scrolling race trying to reach it while new content keeps yanking the viewport down. The collapse affordance must remain reachable during live writes — e.g. card-space toggle (so any visible part of the holder toggles it), a sticky/pinned control, and/or pausing autoscroll on user interaction. - -Likely scope (to be refined after the design discussion): -- Re-model _ActivityCard as a container that wraps its children rather than a header followed by siblings. -- Make the whole holder card the collapse/expand hit target (ClideTappable over the card body), keeping keyboard + AT semantics. -- Resolve the autoscroll-vs-reach-the-control conflict (sticky control, whole-card toggle, and/or suspend tail-follow on user scroll/interaction). - -This interacts with T-264 (nesting a whole sub-agent run is itself a holder/container) — settle the container model once so the agent-run region and the activity card share it. - -Refs: D-78. Related: T-230 (activity card), T-264 (nested agent run), T-262, T-263.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:41:13', '2026-06-07 08:41:13', NULL, 'cee9f56b059740c7c7e9dfdfa0f8d28f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'epic', NULL, 'Conversation rendering streamlining — fold, attribute, contain', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). - -Common substrate across the children: the flat ConversationItem list and its renderers in lib/builtin/claude/src/conversation_view.dart, the pure grouping pass in activity_cluster.dart, the ConversationCard template in conversation_card.dart, and the so-far-unused ConversationItem.isSidechain / parentUuid transcript fields (transcript_reader.dart). - -Children: T-262 (merge tool-call + success result into one card with header status check), T-263 (fold sub-agent prompt into the Agent card; relabel you → agent prompt), T-264 (nest the whole sub-agent run under its Agent card via parentUuid), T-265 (relabel sidechain assistant prose/thinking — agent, not claude), T-266 (restyle activity/holder card as a container of sub-cards + fix the collapse-control scroll race). Sequencing: T-262/T-263 first, T-264/T-265 build the sidechain story, T-266 settles the shared container model last. - -Refs: D-78 (interaction zone / display-only conversation widgets). Built on T-168 (per-tool body rendering) and T-230 (activity card).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:42:53', '2026-06-07 08:42:53', NULL, '625bcecf71d8b057d18886c31cc0496d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'story', 'T-267', 'Nest the whole sub-agent run under its Agent card (attribute sidechain via parentUuid)', 'Completion of T-263. T-263 folds the sub-agent PROMPT into the Agent card, but the sub-agent run itself — its prose, thinking, and tool calls — still renders inline in the MAIN conversation chain. A reader cannot tell where the sub-agent run ends and the main thread resumes; sidechain items are visually indistinguishable from main-thread items. - -Root cause: the conversation is a flat list of ConversationItems rendered by _ConversationTurn (lib/builtin/claude/src/conversation_view.dart build ~L130, _ConversationTurn ~L182). isSidechain exists on every item (transcript_reader.dart ~L46) but no rendering/grouping logic reads it. activity_cluster.dart groups consecutive meta runs but is sidechain-agnostic. - -Design: use the parentUuid link (parsed in T-263) to group an ENTIRE sidechain run as a nested region OWNED by its spawning Agent card. The run renders as a distinct, attributed, collapsible region (indent / attribution band / nested holder) under the Agent call rather than as loose cards in the main chain. Per the user rule from T-263, the run results stay VISIBLE (not folded away) — this is about attribution and containment, not hiding. - -Scope: -- Build a sidechain-aware grouping pass (extend or complement activity_cluster.groupConversation) that collects all items sharing a sidechain ancestry (via parentUuid chain back to the Agent tool_use) into one nested group. -- Render that group as a nested region under the Agent card (visual indent / attribution), keeping the sub-agent results first-class within it. -- Handle parallel agents (each run nests under its own card), nested sub-agents, and background agents. -- Orphan sidechain items (no resolvable parent) degrade to the current inline rendering but still attributed (see T-264/relabel work). - -Depends on T-263 (parentUuid parsing + Agent card as container). Pairs with the sidechain-prose relabel ticket. Note the holder-card restyle + auto-scroll race is a separate design discussion (see the holder-card ticket). - -Refs: D-78. Builds on T-263. Related: T-262, T-230 (activity card grouping).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:25', '2026-06-07 08:43:00', NULL, '3e0d83fdef9cd8fae0d4162f5fc6a447', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'story', 'T-267', 'Restyle activity/holder card as a container of sub-cards (fix collapse-control scroll race)', 'DESIGN DISCUSSION PENDING — do not build until T-262/T-263/T-264/T-265 settle. Captured from user feedback; the interaction model needs a design pass first. - -Problem: the current activity card (lib/builtin/claude/src/conversation_view.dart _ActivityCard ~L427, grouping in activity_cluster.dart) is a COLLAPSER HEADER that, when expanded, reveals the subsequent cards rendered below it. Two issues: -1. Model: it reads as a header-plus-loose-cards rather than a single container. Proposal: make it a HOLDER CARD that visually CONTAINS its sub-cards, where clicking anywhere in the card space (the holder) toggles collapse/expand — the whole card is the affordance, not just a small caret/header at the top. -2. Scroll race: when a holder card is actively being written to (a live agent turn), the view keeps auto-scrolling to the bottom (conversation_view._onChanged ~L119 jumps to maxScrollExtent on every change). The collapse control lives at the TOP of the card, so the user gets into a scrolling race trying to reach it while new content keeps yanking the viewport down. The collapse affordance must remain reachable during live writes — e.g. card-space toggle (so any visible part of the holder toggles it), a sticky/pinned control, and/or pausing autoscroll on user interaction. - -Likely scope (to be refined after the design discussion): -- Re-model _ActivityCard as a container that wraps its children rather than a header followed by siblings. -- Make the whole holder card the collapse/expand hit target (ClideTappable over the card body), keeping keyboard + AT semantics. -- Resolve the autoscroll-vs-reach-the-control conflict (sticky control, whole-card toggle, and/or suspend tail-follow on user scroll/interaction). - -This interacts with T-264 (nesting a whole sub-agent run is itself a holder/container) — settle the container model once so the agent-run region and the activity card share it. - -Refs: D-78. Related: T-230 (activity card), T-264 (nested agent run), T-262, T-263.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:41:13', '2026-06-07 08:43:00', NULL, '868b65ae2df08d32976141d9e6aff3b3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'story', 'T-267', 'Merge tool-call + success result into one card with header status check', 'Today a Claude tool call renders as TWO stacked cards in the conversation log: the tool-use card (e.g. "Write " with a collapsible call body) and a separate success/result card ("Write · result / File created successfully..."). Collapse the successful pair into ONE card; failure keeps the current two-card interaction. - -Current code: lib/builtin/claude/src/conversation_view.dart — _toolUse (~L294) renders AssistantToolUse, _toolResult (~L329) renders ToolResultMessage; the two ConversationItems are linked by toolUseId (ToolResultMessage.isError marks failure). ConversationCard header is built in lib/builtin/claude/src/conversation_card.dart _header (~L200). Builds on T-168 (per-tool body rendering). - -Desired behavior (success): -- One card per successful tool call — the tool-use card. The standalone success result card is suppressed (folded in), not rendered separately. -- A success-green check mark sits at the RIGHT END of the card header row (reuse tokens.statusSuccess + a check glyph: PhosphorIcons.check or CheckIcon). No new theme tokens. -- Collapsed by default: header shows the existing summary + the check. Expanding the card reveals the CALL segment (the input — path / diff / command, as today) and, BELOW it, the RESULT segment (the swallowed output). This applies to ALL tools, not just Write/Edit: for Bash/Read/Grep/LS the real output relocates into the expandable body so nothing is lost; for Write/Edit the trivial confirmation folds in the same way. -- Render the result segment as an inline ClideCodeBlock (lib/widgets/src/clide_code_block.dart) WITH editor syntax colorization. ClideCodeBlock already colorizes via TreeSitterService when handed a language — so the work is inferring/passing the right language per tool: Read → from the file extension/path (language_map.grammarForPath), Bash → bash, Grep/LS/others → text fallback. Where a path is available, prefer it so highlighting matches the editor. - -Desired behavior (failure) — UNCHANGED: -- Leave the current interaction as-is: the tool-use card plus the prominent red "· error" result card (borderColor statusError, expanded-by-default). Optionally show a red status mark on the tool-use header for symmetry, but do NOT fold the error into one card. - -What to build: -1. ConversationCard: a trailing header status slot (e.g. status: success|error|none) rendered in _header between the Spacer/summary and the hover action buttons. -2. ConversationCard: support a second body segment so an expanded card can show CALL then RESULT with a clear visual separator / sub-label (e.g. a divider or a muted "result" label) so the user can tell the call from its output. -3. conversation_view: a result-by-toolUseId lookup (reverse of the existing toolUseById) so a tool-use card knows its outcome; on a successful pair, stamp the check + fold the result body and SUPPRESS the standalone success ToolResultMessage from the rendered list. Errors render both cards as today. -4. Per-tool result language inference for the folded code block. - -Edge cases to handle: -- In-flight tool-use with no result yet: render the call card as today (no check, no folded result); status/result appear once the result arrives. -- Orphan result with no paired tool-use: keep rendering it standalone. -- Permission-resolved tool-use cards (conversation_view.dart ~L297, already green/red bordered + collapsed): reconcile so the merged-card + check treatment is consistent and not duplicated with the existing border-outcome styling. -- Result items may not be strictly adjacent to their tool-use in the list; suppression must be keyed by toolUseId, not list position. - -Tests: unit/widget coverage for the merged success card (collapsed shows check; expanded shows call + colorized result), language inference per tool, the unchanged error path, and the in-flight/orphan cases. Add a golden for the merged success card. - -Refs: D-78 (interaction zone / display-only conversation widgets). Builds on T-168 (per-tool tool-use/result body rendering). - -IMPLEMENTATION NOTES (from streamlining analysis): - -C — Error-card symmetry decision: this ticket merges only SUCCESS into the tool card; the error path intentionally stays a separate prominent red "· error" card (conversation_view.dart ~L337) for failure visibility. Decide explicitly: keep that asymmetry (success folds, failure stays two-card) OR also stamp a red status mark on the tool-use header for visual symmetry while still keeping the separate red card. Default leaning: keep the separate red card; optionally add the red header mark. - -D — Activity-cluster coupling: activity_cluster.groupConversation currently folds a tool CALL and its RESULT as TWO separate foldable items into a cluster (activity_cluster.dart ~L100-109, classifying each by toolUseId). Once this ticket makes call+result a single self-contained card, the grouping pass must treat the tool call as ONE unit (its result is part of the card, no longer a separate foldable item) or the result will double-render (once folded into the card, once as a cluster item). Update _isFoldable / the pairing logic accordingly and add a test that a merged tool card is not double-counted.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:10:42', '2026-06-07 08:43:00', NULL, 'deac94850480dff730b4ef208c6785b4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'story', 'T-267', 'Relabel sidechain assistant prose/thinking — attribute to agent, not claude', 'Mirror of T-263 on the OUTPUT side. A sub-agent runs as a sidechain; its assistant prose renders with the label "claude" and its thinking with "thinking", identically to the MAIN assistant — because the label logic does not check isSidechain. So a sub-agents output is presented as if the main Claude said it. Same misattribution T-263 fixes for the prompt (the "you" problem), mirrored on the assistant side. - -Current code (lib/builtin/claude/src/conversation_view.dart _ConversationTurn build ~L221): AssistantTextMessage → ConversationCard(accent: claudeAccent, label: "claude"); AssistantThinkingMessage (~L227) → label "thinking". Neither inspects ConversationItem.isSidechain (transcript_reader.dart ~L46), which is present but unused. - -Design: when isSidechain is true, attribute the assistant items to the AGENT rather than the main Claude — e.g. label "agent" (or the agent/sub-agent name where resolvable) with de-emphasised styling, NOT the coral "claude" brand accent (claudeAccent is the main-thread brand, reserved for the real Claude). Thinking inside a sidechain attributed/muted likewise. - -Scope: -- Branch the AssistantTextMessage / AssistantThinkingMessage rendering on isSidechain. -- Pick the sidechain label + styling (distinct from "claude"; reuse muted tokens, not claudeAccent). -- Keep behavior identical for main-thread items. - -Tests: relabel of sidechain assistant prose/thinking (never "claude"/coral); main-thread unchanged; golden for an attributed agent prose card. - -Pairs with the nest-the-agent-run ticket (attribution + containment) and T-263 (prompt side). Refs: D-78.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:33', '2026-06-07 08:43:00', NULL, 'ebf4472cdcc19ae1eceec74486b9406d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'story', 'T-267', 'Fold sub-agent prompt into the Agent card; relabel from you to agent prompt', 'When Claude launches a sub-agent (the Agent/Task tool), the sub-agent conversation runs as a SIDECHAIN. The prompt Claude wrote for that sub-agent comes through the transcript as a UserMessage with isSidechain=true but injected=false. The conversation view does not look at isSidechain, so it renders that prompt with the blue "you" label exactly like real user input — FALSELY implying the user sent it. It sits as a standalone block below the Agent tool-use card. This is misleading: the user did not write that prompt. - -Confirmed facts from the code: every transcript envelope carries BOTH isSidechain AND parentUuid (transcript_reader.dart ~L547 parses isSidechain but never USES it; parentUuid is dropped entirely). The Agent/Task tool-use is rendered by _toolUse in lib/builtin/claude/src/conversation_view.dart (only AskUserQuestion is special-cased today). The "you" vs "context" label is chosen in conversation_view.dart (~L200): UserMessage with injected=true → "context" (muted); else → "you" (blue, tokens.globalFocus). isSidechain is currently never read by any rendering/grouping logic. - -Design (confirmed with user): -1. Never label a sidechain prompt "you". A UserMessage with isSidechain=true is an AGENT PROMPT, not user input — label it "agent prompt" with muted / de-emphasized styling (same precedent as injected "context" messages, D-78). Never the blue "you" treatment. -2. Fold the prompt INTO the Agent tool-use card above it, COLLAPSED BY DEFAULT. The Agent card becomes the container that owns its prompt: expand the card to reveal the prompt; collapsed (default) it is hidden. The standalone prompt block is SUPPRESSED from the list — same fold-a-standalone-item-into-its-card pattern as T-262. -3. Prompt folds into the CALL, not the results. Only the prompt folds into the collapsed Agent call card. The sub-agent work (its thinking / tool calls / results) stays VISIBLE as the results chain after the call — not buried. Shape: [collapsed Agent call, owning the prompt] then [the run results], with no misleading "you" block anywhere. Agent prompts are valuable enough to keep with the call rather than fold into the subsequent results chain. -4. Link the prompt to the RIGHT Agent card via parentUuid. Parse parentUuid (currently dropped) so each sidechain prompt attaches to its spawning Agent tool-use. This matters when several agents run in parallel in one turn — a positional "nearest preceding Agent" heuristic would misattach; the parent link is the robust route. Heuristic only as a fallback when the link cannot be resolved. - -Touch points: -- transcript_reader.dart: capture parentUuid from the envelope and expose it on ConversationItem (it is present in the JSONL, just not parsed). -- conversation_view.dart: relabel sidechain UserMessage to "agent prompt" (stop the "you" path); fold it into the Agent card body; special-case the Agent/Task tool name (today only AskUserQuestion is special-cased); suppress the standalone block. -- activity_cluster.dart: ensure the suppressed prompt is not double-counted in grouping; leave the sidechain results in the chain. - -Edge cases: -- Parallel agents in one turn → each prompt attaches to its own card (the reason for parentUuid). -- Orphan prompt (no resolvable parent) → still render as "agent prompt", muted/collapsed, never "you". -- Nested / background sub-agents. - -Tests: unit/widget coverage for sidechain-prompt relabel (never "you"), folding into the Agent card (collapsed hides, expand reveals), suppression of the standalone block, parentUuid attachment with parallel agents, and the orphan fallback. Add a golden for the Agent card with a folded prompt. - -Refs: D-78 (interaction zone / display-only conversation widgets; injected/context muting precedent). Related: T-262 (fold-standalone-item-into-its-card pattern). - -IMPLEMENTATION NOTE (E — Agent-card layered ordering, from streamlining analysis): - -With this ticket (fold prompt) and T-262 (merge success result) both landing, the Agent/Task card ends up owning multiple segments: the tool INPUT, the folded PROMPT, and — via T-262 — the sub-agents final returned RESULT (the Task ToolResultMessage). Define a deliberate layered order when expanded (e.g. call/input → prompt → returned result) with clear sub-labels/dividers so the Agent card stays readable and does not become a kitchen sink. Coordinate with T-262 (result merge) and T-264 (nesting the whole run): the nested run region vs the returned-result segment must not duplicate the sub-agent output.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:34:02', '2026-06-07 08:43:00', NULL, 'f28b34941b8247b986b65b3aa16e9d71', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'story', NULL, 'Animate the ''running…'' turn indicator with rotating status verbs', '## Problem +Refs: D-78 (interaction zone / display-only conversation widgets), D-6 (CLI/UI parity), DialogRouter (lib/kernel/src/dialog.dart). Builds on T-249 (image card + clide image show). Related: T-248.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-06 08:28:43', '2026-06-07 08:32:36', NULL, '050945fb60f781edbc0a56552a3e5324', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63WP564EZRERJHVCM', 'story', NULL, 'Animate the ''running…'' turn indicator with rotating status verbs', '## Problem While a Claude turn is in flight, the composer swaps in a static, muted `running…` label next to the Stop button (`lib/builtin/claude/src/claude_composer.dart:486`, @@ -1477,127 +395,11 @@ list, ship our own curated, on-brand list instead. Document the decision. whether the rotating-word state belongs there or in the session/orchestrator layer. - Add a widget/golden test for the animated states (bounded pumps — no real timers). -Refinement (2026-06-07): open question resolved — ship OUR OWN curated verb list, do not reuse the CLI''s. Rationale: the spinner words are a TUI cosmetic not surfaced by the stream-json control protocol (so there''s nothing to read live), and extracting Anthropic''s bundled list is a licensing gray area. A clide-owned list aligns with ''own the rendering stack'' and D-75 (isolate/version-pin CC coupling). State lives in the WIDGET layer (a RunningIndicator in lib/builtin/claude/), not the orchestrator — it''s ephemeral UI. Animation via AnimationController (no Timers, so tests use bounded pumps); reduced-motion (MediaQuery.disableAnimations) shows a static verb. Ready to implement.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:56:23', '2026-06-07 08:44:32', NULL, 'f6cc0a6e48810d2ad97fbf99df58dc82', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'story', NULL, 'Animate the ''running…'' turn indicator with rotating status verbs', '## Problem +Refinement (2026-06-07): open question resolved — ship OUR OWN curated verb list, do not reuse the CLI''s. Rationale: the spinner words are a TUI cosmetic not surfaced by the stream-json control protocol (so there''s nothing to read live), and extracting Anthropic''s bundled list is a licensing gray area. A clide-owned list aligns with ''own the rendering stack'' and D-75 (isolate/version-pin CC coupling). State lives in the WIDGET layer (a RunningIndicator in lib/builtin/claude/), not the orchestrator — it''s ephemeral UI. Animation via AnimationController (no Timers, so tests use bounded pumps); reduced-motion (MediaQuery.disableAnimations) shows a static verb. Ready to implement.', 'done', 'medium', NULL, NULL, NULL, '2026-06-06 09:56:23', '2026-06-07 08:51:08', NULL, '83072782536ca8de71773dab1e5c4a41', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM74JAV0MJMB4VQNTRG', 'bug', NULL, 'primary /clear must clear the deterministic session in place, not orphan to a fresh id', 'The primary Claude pane is anchored to a deterministic session id derived from the repo path (primarySessionId), so it resumes the same transcript across restarts (D-77/T-146). But clide-owned /clear (T-156) respawns on a fresh RANDOM id (freshSessionId). Next clide launch recomputes the deterministic id, finds its old transcript on disk, and resumes the PRE-clear conversation — the cleared session is orphaned. Continuity appears broken. -While a Claude turn is in flight, the composer swaps in a static, muted -`running…` label next to the Stop button (`lib/builtin/claude/src/claude_composer.dart:486`, -shown when `widget.busy && widget.onInterrupt != null`). It''s gray, motionless, -and a little lifeless — it gives no sense that anything is actually happening. - -## Goal - -Make the in-flight indicator feel alive: - -1. **Animation.** Add subtle motion — e.g. an animated ellipsis (`running` → `running.` - → `running..` → `running...`), a shimmer/pulse on the text, or a small spinner glyph. - Custom-painted / token-driven per the "own the rendering stack" guardrail; no - opinionated animation packages. -2. **Rotating status verbs.** Cycle through playful gerunds the way the Claude Code CLI - does (e.g. "Clauding…", "Flibbertigibbeting…", "Pondering…", "Conjuring…"). Pick a - word from a curated list and rotate it every few seconds while the turn runs. - -## Open question — can we reuse the CLI''s words? - -The Claude Code CLI ships its own list of these status verbs. Before hand-rolling our -own, check whether that list is something we''re allowed to reuse / surface (licensing, -where it lives, whether stream-json exposes the current one). If we can''t pull the CLI''s -list, ship our own curated, on-brand list instead. Document the decision. - -## Notes / constraints - -- Respect reduced-motion / accessibility settings — animation must be disable-able and - the a11y semantics should still read sensibly (the Stop button already carries the - interrupt hint). -- Use theme tokens for color; keep it muted/tasteful, not distracting. -- Touch point today is the `widget.busy` branch in `claude_composer.dart`; consider - whether the rotating-word state belongs there or in the session/orchestrator layer. -- Add a widget/golden test for the animated states (bounded pumps — no real timers). - -Refinement (2026-06-07): open question resolved — ship OUR OWN curated verb list, do not reuse the CLI''s. Rationale: the spinner words are a TUI cosmetic not surfaced by the stream-json control protocol (so there''s nothing to read live), and extracting Anthropic''s bundled list is a licensing gray area. A clide-owned list aligns with ''own the rendering stack'' and D-75 (isolate/version-pin CC coupling). State lives in the WIDGET layer (a RunningIndicator in lib/builtin/claude/), not the orchestrator — it''s ephemeral UI. Animation via AnimationController (no Timers, so tests use bounded pumps); reduced-motion (MediaQuery.disableAnimations) shows a static verb. Ready to implement.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-06 09:56:23', '2026-06-07 08:44:32', NULL, '7a7db6892b178c72a45106b7ba09b76c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'story', 'T-267', 'Restyle activity/holder card as a container of sub-cards (fix collapse-control scroll race)', 'Restyle the activity card (and, via a shared primitive, the nested sub-agent run from T-264) so a folded run reads as ONE container that holds its sub-cards, with a reachable collapse/expand affordance that survives live writes. - -DESIGN RESOLVED (with user) — supersedes the earlier design-pending note. - -1. HOLDER = a shared container primitive. Extract one container widget (e.g. a ClideHolderCard / conversation-level container) that renders a titled/attributed frame WRAPPING its child sub-cards. Both the activity card (_ActivityCard, lib/builtin/claude/src/conversation_view.dart ~L427) and the nested sub-agent run (T-264) consume it, so the container model is settled once. NOTE: this makes T-266 a DEPENDENCY of T-264 (the shared primitive must exist before the agent-run nesting can consume it) — update epic T-267 sequencing: T-266''s primitive lands before/with T-264, no longer strictly last. - -2. Toggle = the HOLDER BACKGROUND, not the children. Clicking the container''s own background/chrome (its padding, the gaps between sub-cards, its gutter — any region NOT covered by a child) toggles collapse/expand. Crucially: -- Taps on a child sub-card lying on top of the background MUST NOT toggle the holder. Child cards opaquely consume their own hit region across their FULL bounds (including their body), so a click on a sub-card interacts with that card, never the holder. -- The copy button (and any child control / ClideTappable) keeps its own interaction — never swallowed by the holder toggle. -- Implementation: a background gesture target behind the children that only fires for hits the children do not consume (HitTestBehavior; children opaque over their bounds). No whole-card overlay that would intercept child taps. - -3. Autoscroll stays ALWAYS-FOLLOW. Keep the current tail-follow (conversation_view._onChanged ~L119 jumps to maxScrollExtent on every change). Reachability of the collapse control no longer depends on getting to a top header: because the toggle is the ever-present holder BACKGROUND, a click on whatever background area is currently in view (near the latest content while following) collapses the holder — ending the race. Once collapsed the card shrinks to its ticker, so the scroll-yank stops. - -4. Keep the collapsed ticker. Collapsed (default) still shows the one-line live ticker (latest step) + step count, as today. Expanded shows the contained sub-cards. - -5. Accessibility (obligation): a background tap is not keyboard/AT reachable on its own, so keep an EXPLICIT focusable, Enter/Space-activatable collapse control (e.g. the ticker row remains a Semantics button / a focusable caret) in addition to the background-click affordance. Announce expanded/collapsed + step count (as _ActivityCard does today). - -Touch points: -- New shared container primitive (extracted from _ActivityCard). -- conversation_view.dart: _ActivityCard adopts the primitive; background-toggle wiring; ensure child ConversationCards opaquely consume their bounds so they do not bubble taps to the holder. -- T-264 consumes the same primitive for the nested agent run. - -Edge cases: -- A holder whose child is itself collapsible (a tool card): tapping the child''s caret/body toggles the CHILD, never the holder. -- Text selection inside a child (ClideSelectionArea wraps the list): selection drags on child bodies must not be hijacked by the holder toggle — another reason the holder responds only to its own background, and only to taps (not drags). -- Empty / single-item holder. - -Tests: holder background tap toggles; tap on a child sub-card does NOT toggle the holder; copy button still copies (not swallowed); keyboard/AT path toggles via the explicit control; collapsed ticker + step count preserved. Add a golden for the holder container (collapsed + expanded). - -Refs: D-78. Provides the shared container consumed by T-264. Related: T-230 (activity card), T-262 / T-263 / T-265. Parent: T-267.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:41:13', '2026-06-07 08:48:47', NULL, 'bcbaf22e20d1009459e8de7f981d2f6c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'epic', NULL, 'Conversation rendering streamlining — fold, attribute, contain', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). - -Common substrate across the children: the flat ConversationItem list and its renderers in lib/builtin/claude/src/conversation_view.dart, the pure grouping pass in activity_cluster.dart, the ConversationCard template in conversation_card.dart, and the so-far-unused ConversationItem.isSidechain / parentUuid transcript fields (transcript_reader.dart). - -Children: T-262 (merge tool-call + success result into one card with header status check), T-263 (fold sub-agent prompt into the Agent card; relabel you → agent prompt), T-264 (nest the whole sub-agent run under its Agent card via parentUuid), T-265 (relabel sidechain assistant prose/thinking — agent, not claude), T-266 (restyle activity/holder card as a container of sub-cards + fix the collapse-control scroll race). Sequencing: T-262/T-263 first, T-264/T-265 build the sidechain story, T-266 settles the shared container model last. - -Refs: D-78 (interaction zone / display-only conversation widgets). Built on T-168 (per-tool body rendering) and T-230 (activity card). - -SEQUENCING UPDATE (after T-266 refinement): the shared container/holder primitive now lives in T-266 and is CONSUMED by T-264 (nested agent run), so T-266 is no longer "last" — its primitive lands before/with T-264. T-264 is now blocked by T-266. Revised order: T-262 / T-263 (fold success result, fold agent prompt) → T-266 (shared holder/container primitive + activity-card restyle) → T-264 (nest the whole agent run on that primitive) → T-265 (relabel sidechain prose) can land anytime alongside.', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:42:53', '2026-06-07 08:49:16', NULL, '1371008c924f5c0e6998894fde235c6b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-255', 'story', NULL, 'Animate the ''running…'' turn indicator with rotating status verbs', '## Problem - -While a Claude turn is in flight, the composer swaps in a static, muted -`running…` label next to the Stop button (`lib/builtin/claude/src/claude_composer.dart:486`, -shown when `widget.busy && widget.onInterrupt != null`). It''s gray, motionless, -and a little lifeless — it gives no sense that anything is actually happening. - -## Goal - -Make the in-flight indicator feel alive: - -1. **Animation.** Add subtle motion — e.g. an animated ellipsis (`running` → `running.` - → `running..` → `running...`), a shimmer/pulse on the text, or a small spinner glyph. - Custom-painted / token-driven per the "own the rendering stack" guardrail; no - opinionated animation packages. -2. **Rotating status verbs.** Cycle through playful gerunds the way the Claude Code CLI - does (e.g. "Clauding…", "Flibbertigibbeting…", "Pondering…", "Conjuring…"). Pick a - word from a curated list and rotate it every few seconds while the turn runs. - -## Open question — can we reuse the CLI''s words? - -The Claude Code CLI ships its own list of these status verbs. Before hand-rolling our -own, check whether that list is something we''re allowed to reuse / surface (licensing, -where it lives, whether stream-json exposes the current one). If we can''t pull the CLI''s -list, ship our own curated, on-brand list instead. Document the decision. - -## Notes / constraints - -- Respect reduced-motion / accessibility settings — animation must be disable-able and - the a11y semantics should still read sensibly (the Stop button already carries the - interrupt hint). -- Use theme tokens for color; keep it muted/tasteful, not distracting. -- Touch point today is the `widget.busy` branch in `claude_composer.dart`; consider - whether the rotating-word state belongs there or in the session/orchestrator layer. -- Add a widget/golden test for the animated states (bounded pumps — no real timers). - -Refinement (2026-06-07): open question resolved — ship OUR OWN curated verb list, do not reuse the CLI''s. Rationale: the spinner words are a TUI cosmetic not surfaced by the stream-json control protocol (so there''s nothing to read live), and extracting Anthropic''s bundled list is a licensing gray area. A clide-owned list aligns with ''own the rendering stack'' and D-75 (isolate/version-pin CC coupling). State lives in the WIDGET layer (a RunningIndicator in lib/builtin/claude/), not the orchestrator — it''s ephemeral UI. Animation via AnimationController (no Timers, so tests use bounded pumps); reduced-motion (MediaQuery.disableAnimations) shows a static verb. Ready to implement.', 'done', 'medium', NULL, NULL, NULL, '2026-06-06 09:56:23', '2026-06-07 08:51:08', NULL, '2e77a2ef6235bc9f450e53c91d430b3e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-268', 'bug', NULL, 'primary /clear must clear the deterministic session in place, not orphan to a fresh id', 'The primary Claude pane is anchored to a deterministic session id derived from the repo path (primarySessionId), so it resumes the same transcript across restarts (D-77/T-146). But clide-owned /clear (T-156) respawns on a fresh RANDOM id (freshSessionId). Next clide launch recomputes the deterministic id, finds its old transcript on disk, and resumes the PRE-clear conversation — the cleared session is orphaned. Continuity appears broken. - -Fix: /clear in the PRIMARY pane should empty the deterministic session in place — delete its .jsonl (and sidecar / dir) so ''claude --session-id'' re-creates it empty, then respawn on the SAME deterministic id. Secondary panes are throwaway and keep the fresh-random behavior. Factor the transcript path/dir construction (duplicated in claude_pane._spawn and _resumeFlow) into session_naming helpers (claudeProjectDir/claudeTranscriptPath/clearSessionTranscript) so the clear logic is DRY and unit-testable.', 'backlog', 'high', NULL, NULL, 'D-77', '2026-06-07 09:35:03', '2026-06-07 09:35:03', NULL, 'a4508cc5c3e4d9aaaca886e7076b3f80', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-268', 'bug', NULL, 'primary /clear must clear the deterministic session in place, not orphan to a fresh id', 'The primary Claude pane is anchored to a deterministic session id derived from the repo path (primarySessionId), so it resumes the same transcript across restarts (D-77/T-146). But clide-owned /clear (T-156) respawns on a fresh RANDOM id (freshSessionId). Next clide launch recomputes the deterministic id, finds its old transcript on disk, and resumes the PRE-clear conversation — the cleared session is orphaned. Continuity appears broken. - -Fix: /clear in the PRIMARY pane should empty the deterministic session in place — delete its .jsonl (and sidecar / dir) so ''claude --session-id'' re-creates it empty, then respawn on the SAME deterministic id. Secondary panes are throwaway and keep the fresh-random behavior. Factor the transcript path/dir construction (duplicated in claude_pane._spawn and _resumeFlow) into session_naming helpers (claudeProjectDir/claudeTranscriptPath/clearSessionTranscript) so the clear logic is DRY and unit-testable.', 'done', 'high', NULL, NULL, 'D-77', '2026-06-07 09:35:03', '2026-06-07 09:36:59', NULL, '824de390dc6da465575d998291976377', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-269', 'bug', NULL, 'primary Claude pane keeps the previous repo''s session after an in-place workspace switch', 'Separate clide WINDOWS are isolated (separate process, per-root IPC socket, per-repo deterministic session id), so parallel repos in separate windows are fine. The bug is the in-place workspace switch (Open Project / Open Folder -> kernel.project.open, app.dart:456/474): it only emits ProjectOpened and updates settings (project.dart:147) — nothing tears down Claude sessions. +Fix: /clear in the PRIMARY pane should empty the deterministic session in place — delete its .jsonl (and sidecar / dir) so ''claude --session-id'' re-creates it empty, then respawn on the SAME deterministic id. Secondary panes are throwaway and keep the fresh-random behavior. Factor the transcript path/dir construction (duplicated in claude_pane._spawn and _resumeFlow) into session_naming helpers (claudeProjectDir/claudeTranscriptPath/clearSessionTranscript) so the clear logic is DRY and unit-testable.', 'done', 'high', NULL, NULL, 'D-77', '2026-06-07 09:35:03', '2026-06-07 09:36:59', NULL, '62386d2e15f2218ba886babfb01b37ae', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6P9YQ5V7WS6RFJNHW', 'bug', NULL, 'primary Claude pane keeps the previous repo''s session after an in-place workspace switch', 'Separate clide WINDOWS are isolated (separate process, per-root IPC socket, per-repo deterministic session id), so parallel repos in separate windows are fine. The bug is the in-place workspace switch (Open Project / Open Folder -> kernel.project.open, app.dart:456/474): it only emits ProjectOpened and updates settings (project.dart:147) — nothing tears down Claude sessions. Two compounding causes: 1. ClaudeSessionOrchestrator.spawn() is idempotent on the literal key ''primary'' WITHOUT checking cwd (session_orchestrator.dart:192-194) — it returns the old repo''s session for the new repo. @@ -1607,24 +409,11 @@ Fix (T-269): - Orchestrator safety net: spawn() must reuse an existing session only when its cwd matches spec.cwd; a cwd mismatch is a stale cross-workspace binding -> close + spawn fresh. - ClaudePane (primary) listens for ProjectOpened and rebinds: close its orch entry, null out _sessionId/_repoRoot, respawn for the new repo. Secondaries do not self-rebind. - ClaudeSessionHost listens for ProjectOpened and drops secondary/fork tabs (the old repo''s), keeping just the primary — a switched workspace starts like a fresh launch. -- Extension tears down all sessions whose cwd != new root on ProjectOpened (catches team/non-pane sessions).', 'backlog', 'high', NULL, NULL, 'D-77', '2026-06-07 09:44:17', '2026-06-07 09:44:17', NULL, 'e2da4b11d8f64cfeddeab64f6407004a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-269', 'bug', NULL, 'primary Claude pane keeps the previous repo''s session after an in-place workspace switch', 'Separate clide WINDOWS are isolated (separate process, per-root IPC socket, per-repo deterministic session id), so parallel repos in separate windows are fine. The bug is the in-place workspace switch (Open Project / Open Folder -> kernel.project.open, app.dart:456/474): it only emits ProjectOpened and updates settings (project.dart:147) — nothing tears down Claude sessions. - -Two compounding causes: -1. ClaudeSessionOrchestrator.spawn() is idempotent on the literal key ''primary'' WITHOUT checking cwd (session_orchestrator.dart:192-194) — it returns the old repo''s session for the new repo. -2. ClaudePane spawns once behind a _spawned guard, under a GlobalKey host (claude_pane.dart:140, extension.dart:33/52) — so after an in-place switch it never re-resolves; it keeps the previous repo''s session/conversation. - -Fix (T-269): -- Orchestrator safety net: spawn() must reuse an existing session only when its cwd matches spec.cwd; a cwd mismatch is a stale cross-workspace binding -> close + spawn fresh. -- ClaudePane (primary) listens for ProjectOpened and rebinds: close its orch entry, null out _sessionId/_repoRoot, respawn for the new repo. Secondaries do not self-rebind. -- ClaudeSessionHost listens for ProjectOpened and drops secondary/fork tabs (the old repo''s), keeping just the primary — a switched workspace starts like a fresh launch. -- Extension tears down all sessions whose cwd != new root on ProjectOpened (catches team/non-pane sessions).', 'done', 'high', NULL, NULL, 'D-77', '2026-06-07 09:44:17', '2026-06-07 09:51:32', NULL, '9b92ed68f5c352d5461c01d7e98c0b50', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'story', NULL, 'application menu bar (Zed-style)', '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.', 'ready', 'medium', NULL, NULL, NULL, '2026-04-23 20:30:09', '2026-06-07 10:03:36', NULL, 'e09873c62cfab523b2b4048d9681c9ae', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-270', 'story', NULL, 'make sidebar filter boxes addressable via MessageBus (clide ui filter)', NULL, 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-07 10:23:13', '2026-06-07 10:23:13', NULL, '0d79acd088da9d0b240ee5473c3a21c4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-271', 'task', 'T-48', 'Edit menu (hat-bar menu bar) — routed to focused surface', 'Deferred from T-48 v1. Adds the Edit top-level menu to the hat-bar application menu (T-48). Items (undo, redo, cut, copy, paste, select-all, find/replace) reference registered command ids and route to the focused surface; inapplicable items render disabled (greyed), per T-48''s show-but-disable context behavior. Depends on focused-surface command routing so the menu reflects the active editor/pane. Reuses T-48''s menu widgets, command->item mapping, and keyboard/a11y model.', 'backlog', 'medium', NULL, NULL, 'D-7', '2026-06-07 10:42:32', '2026-06-07 10:42:32', NULL, '4896030bd5e76b49f4bcb56239b2cb96', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-272', 'task', 'T-48', 'Selection menu (hat-bar menu bar) — routed to focused surface', 'Deferred from T-48 v1. Adds the Selection top-level menu to the hat-bar application menu (T-48). Selection operations reference registered command ids and route to the focused surface; inapplicable items render disabled per T-48''s show-but-disable context behavior. Depends on focused-surface command routing. Reuses T-48''s menu widgets, command->item mapping, and keyboard/a11y model.', 'backlog', 'medium', NULL, NULL, 'D-7', '2026-06-07 10:42:38', '2026-06-07 10:42:38', NULL, '9bc20e9a0cd664e6de878fec4fe199de', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-270', 'story', NULL, 'make sidebar filter boxes addressable via MessageBus (clide ui filter)', NULL, 'done', 'medium', NULL, NULL, 'D-6', '2026-06-07 10:23:13', '2026-06-07 10:43:18', NULL, '28f5c3ff053d5a7bf209acfa34ddfd8b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'story', NULL, 'application menu bar (Zed-style)', '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. +- Extension tears down all sessions whose cwd != new root on ProjectOpened (catches team/non-pane sessions).', 'done', 'high', NULL, NULL, 'D-77', '2026-06-07 09:44:17', '2026-06-07 09:51:32', NULL, '2cc07f04fac1c3a563e818eac8f8a8e2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4XBRZJ6DEHFWREWC0', 'task', '06FB0TNQM551VZ2NYV6PD0XQRC', 'Edit menu (hat-bar menu bar) — routed to focused surface', 'Deferred from T-48 v1. Adds the Edit top-level menu to the hat-bar application menu (T-48). Items (undo, redo, cut, copy, paste, select-all, find/replace) reference registered command ids and route to the focused surface; inapplicable items render disabled (greyed), per T-48''s show-but-disable context behavior. Depends on focused-surface command routing so the menu reflects the active editor/pane. Reuses T-48''s menu widgets, command->item mapping, and keyboard/a11y model.', 'backlog', 'medium', NULL, NULL, 'D-7', '2026-06-07 10:42:32', '2026-06-07 10:42:32', NULL, '6f53d58d5090926757e63498d3a7d96d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM67PDXAFTPPW7B1WDR', 'task', '06FB0TNQM551VZ2NYV6PD0XQRC', 'Selection menu (hat-bar menu bar) — routed to focused surface', 'Deferred from T-48 v1. Adds the Selection top-level menu to the hat-bar application menu (T-48). Selection operations reference registered command ids and route to the focused surface; inapplicable items render disabled per T-48''s show-but-disable context behavior. Depends on focused-surface command routing. Reuses T-48''s menu widgets, command->item mapping, and keyboard/a11y model.', 'backlog', 'medium', NULL, NULL, 'D-7', '2026-06-07 10:42:38', '2026-06-07 10:42:38', NULL, '5b1ac6bba233c1b0c30c1947dd023377', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM40JE9MZZWCXC91THG', 'story', NULL, 'make sidebar filter boxes addressable via MessageBus (clide ui filter)', NULL, 'done', 'medium', NULL, NULL, 'D-6', '2026-06-07 10:23:13', '2026-06-07 10:43:18', NULL, '0a09af66fc3d2a61d4cc7837fe86127f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM551VZ2NYV6PD0XQRC', 'story', NULL, 'application menu bar (Zed-style)', '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. ## Refinement (decisions, 2026-06-07) @@ -1636,39 +425,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, **Context behavior:** Items reflect the focused surface; inapplicable items render disabled (greyed), not hidden. Mainly exercised once Edit/Selection land, but View/File items honor it too where relevant. -**Keyboard / a11y:** Full keyboard support — Alt+mnemonic opens a menu, arrow-key navigation within, Enter activates, Esc / click-away closes. Must meet the a11y contract (keyboard nav + semantics).', 'ready', 'medium', NULL, NULL, NULL, '2026-04-23 20:30:09', '2026-06-07 10:43:22', NULL, '56aea322a8228babee96dec5befced5a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-48', 'story', NULL, 'application menu bar (Zed-style)', '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. - -## Refinement (decisions, 2026-06-07) - -**v1 scope:** File, View, Help/About only. Edit and Selection are deferred to follow-ups [[T-271]] (Edit) and [[T-272]] (Selection) — both blocked on this story, since they need focused-surface command routing. -- View submenu: panel toggles, zoom, focus mode. -- Help: About modal (version, license, links). - -**Command mapping (hybrid):** A hand-authored menu tree defines curated placement — ordering, grouping, separators, and which commands sit where. Any registered command not explicitly placed auto-fills from the command registry (by category) into the matching submenu / an overflow section, so newly registered commands surface without manual wiring. Each item''s title + keybinding are pulled from the registry. - -**Context behavior:** Items reflect the focused surface; inapplicable items render disabled (greyed), not hidden. Mainly exercised once Edit/Selection land, but View/File items honor it too where relevant. - -**Keyboard / a11y:** Full keyboard support — Alt+mnemonic opens a menu, arrow-key navigation within, Enter activates, Esc / click-away closes. Must meet the a11y contract (keyboard nav + semantics).', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:30:09', '2026-06-07 15:40:06', NULL, 'be3c4df9b99ea7da3b0a1b856f944ba5', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'epic', NULL, 'Conversation rendering streamlining — fold, attribute, contain', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). - -Common substrate across the children: the flat ConversationItem list and its renderers in lib/builtin/claude/src/conversation_view.dart, the pure grouping pass in activity_cluster.dart, the ConversationCard template in conversation_card.dart, and the so-far-unused ConversationItem.isSidechain / parentUuid transcript fields (transcript_reader.dart). - -Children: T-262 (merge tool-call + success result into one card with header status check), T-263 (fold sub-agent prompt into the Agent card; relabel you → agent prompt), T-264 (nest the whole sub-agent run under its Agent card via parentUuid), T-265 (relabel sidechain assistant prose/thinking — agent, not claude), T-266 (restyle activity/holder card as a container of sub-cards + fix the collapse-control scroll race). Sequencing: T-262/T-263 first, T-264/T-265 build the sidechain story, T-266 settles the shared container model last. - -Refs: D-78 (interaction zone / display-only conversation widgets). Built on T-168 (per-tool body rendering) and T-230 (activity card). - -SEQUENCING UPDATE (after T-266 refinement): the shared container/holder primitive now lives in T-266 and is CONSUMED by T-264 (nested agent run), so T-266 is no longer "last" — its primitive lands before/with T-264. T-264 is now blocked by T-266. Revised order: T-262 / T-263 (fold success result, fold agent prompt) → T-266 (shared holder/container primitive + activity-card restyle) → T-264 (nest the whole agent run on that primitive) → T-265 (relabel sidechain prose) can land anytime alongside.', 'ready', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:42:53', '2026-06-07 16:25:28', NULL, '5c603f0a4c444f398ec3c285d728ffcc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'epic', NULL, 'Conversation rendering streamlining — fold, attribute, contain', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). - -Common substrate across the children: the flat ConversationItem list and its renderers in lib/builtin/claude/src/conversation_view.dart, the pure grouping pass in activity_cluster.dart, the ConversationCard template in conversation_card.dart, and the so-far-unused ConversationItem.isSidechain / parentUuid transcript fields (transcript_reader.dart). - -Children: T-262 (merge tool-call + success result into one card with header status check), T-263 (fold sub-agent prompt into the Agent card; relabel you → agent prompt), T-264 (nest the whole sub-agent run under its Agent card via parentUuid), T-265 (relabel sidechain assistant prose/thinking — agent, not claude), T-266 (restyle activity/holder card as a container of sub-cards + fix the collapse-control scroll race). Sequencing: T-262/T-263 first, T-264/T-265 build the sidechain story, T-266 settles the shared container model last. - -Refs: D-78 (interaction zone / display-only conversation widgets). Built on T-168 (per-tool body rendering) and T-230 (activity card). - -SEQUENCING UPDATE (after T-266 refinement): the shared container/holder primitive now lives in T-266 and is CONSUMED by T-264 (nested agent run), so T-266 is no longer "last" — its primitive lands before/with T-264. T-264 is now blocked by T-266. Revised order: T-262 / T-263 (fold success result, fold agent prompt) → T-266 (shared holder/container primitive + activity-card restyle) → T-264 (nest the whole agent run on that primitive) → T-265 (relabel sidechain prose) can land anytime alongside.', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:42:53', '2026-06-07 17:22:06', NULL, '7c3e5c3bf44d56262332c07b5b77842e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'story', 'T-267', 'Merge tool-call + success result into one card with header status check', 'Today a Claude tool call renders as TWO stacked cards in the conversation log: the tool-use card (e.g. "Write " with a collapsible call body) and a separate success/result card ("Write · result / File created successfully..."). Collapse the successful pair into ONE card; failure keeps the current two-card interaction. +**Keyboard / a11y:** Full keyboard support — Alt+mnemonic opens a menu, arrow-key navigation within, Enter activates, Esc / click-away closes. Must meet the a11y contract (keyboard nav + semantics).', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:30:09', '2026-06-07 15:40:06', NULL, 'c61a7b26026c9727506b5ec163c1af94', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM71P14PAF7K4QE61G0', 'story', '06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'Merge tool-call + success result into one card with header status check', 'Today a Claude tool call renders as TWO stacked cards in the conversation log: the tool-use card (e.g. "Write " with a collapsible call body) and a separate success/result card ("Write · result / File created successfully..."). Collapse the successful pair into ONE card; failure keeps the current two-card interaction. Current code: lib/builtin/claude/src/conversation_view.dart — _toolUse (~L294) renders AssistantToolUse, _toolResult (~L329) renders ToolResultMessage; the two ConversationItems are linked by toolUseId (ToolResultMessage.isError marks failure). ConversationCard header is built in lib/builtin/claude/src/conversation_card.dart _header (~L200). Builds on T-168 (per-tool body rendering). @@ -1701,43 +459,33 @@ IMPLEMENTATION NOTES (from streamlining analysis): C — Error-card symmetry decision: this ticket merges only SUCCESS into the tool card; the error path intentionally stays a separate prominent red "· error" card (conversation_view.dart ~L337) for failure visibility. Decide explicitly: keep that asymmetry (success folds, failure stays two-card) OR also stamp a red status mark on the tool-use header for visual symmetry while still keeping the separate red card. Default leaning: keep the separate red card; optionally add the red header mark. -D — Activity-cluster coupling: activity_cluster.groupConversation currently folds a tool CALL and its RESULT as TWO separate foldable items into a cluster (activity_cluster.dart ~L100-109, classifying each by toolUseId). Once this ticket makes call+result a single self-contained card, the grouping pass must treat the tool call as ONE unit (its result is part of the card, no longer a separate foldable item) or the result will double-render (once folded into the card, once as a cluster item). Update _isFoldable / the pairing logic accordingly and add a test that a merged tool card is not double-counted.', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:10:42', '2026-06-07 17:23:39', NULL, '04fae1762c477c0e883f426080c90b53', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-273', 'task', NULL, 'turn indicator font color → Claude accent orange', 'Change the animated running/turn indicator''s text color to the Claude accent orange (the brand accent), instead of its current color. Lives in lib/builtin/claude/src/running_indicator.dart (the rotating-verb turn indicator, T-255). Use the appropriate accent token from SurfaceTokens (ui-design skill: confirm which token maps to Claude accent orange) — no hardcoded hex. Update/adjust any golden or widget test that asserts the indicator color.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-07 17:39:10', '2026-06-07 17:39:10', NULL, '2f09f004ad10014a7e881fe28d9e2f15', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-262', 'story', 'T-267', 'Merge tool-call + success result into one card with header status check', 'Today a Claude tool call renders as TWO stacked cards in the conversation log: the tool-use card (e.g. "Write " with a collapsible call body) and a separate success/result card ("Write · result / File created successfully..."). Collapse the successful pair into ONE card; failure keeps the current two-card interaction. +D — Activity-cluster coupling: activity_cluster.groupConversation currently folds a tool CALL and its RESULT as TWO separate foldable items into a cluster (activity_cluster.dart ~L100-109, classifying each by toolUseId). Once this ticket makes call+result a single self-contained card, the grouping pass must treat the tool call as ONE unit (its result is part of the card, no longer a separate foldable item) or the result will double-render (once folded into the card, once as a cluster item). Update _isFoldable / the pairing logic accordingly and add a test that a merged tool card is not double-counted.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:10:42', '2026-06-07 17:41:11', NULL, 'e62becebc27914b830a058e34cf7756e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5W6VN98RQM6S22X28', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Claude bottom status bar often stays empty / doesn''t update', 'The bottom status-bar context slot for the Claude pane (model · permission-mode · context, T-145/T-150) frequently renders empty and doesn''t update. The slot is fed by the active pane''s status widget via the focus service (PaneContextStatusItem -> ClidePane.statusWidget in claude_pane.dart), sourced from StreamJsonSession.statusStream (model/permissionMode from the ''system/init'' event; cost/contextWindow from ''result'' events in stream_json_session.dart). -Current code: lib/builtin/claude/src/conversation_view.dart — _toolUse (~L294) renders AssistantToolUse, _toolResult (~L329) renders ToolResultMessage; the two ConversationItems are linked by toolUseId (ToolResultMessage.isError marks failure). ConversationCard header is built in lib/builtin/claude/src/conversation_card.dart _header (~L200). Builds on T-168 (per-tool body rendering). +Repro: open clide; the status line is often blank and stays blank until/unless a turn completes (or never populates). -Desired behavior (success): -- One card per successful tool call — the tool-use card. The standalone success result card is suppressed (folded in), not rendered separately. -- A success-green check mark sits at the RIGHT END of the card header row (reuse tokens.statusSuccess + a check glyph: PhosphorIcons.check or CheckIcon). No new theme tokens. -- Collapsed by default: header shows the existing summary + the check. Expanding the card reveals the CALL segment (the input — path / diff / command, as today) and, BELOW it, the RESULT segment (the swallowed output). This applies to ALL tools, not just Write/Edit: for Bash/Read/Grep/LS the real output relocates into the expandable body so nothing is lost; for Write/Edit the trivial confirmation folds in the same way. -- Render the result segment as an inline ClideCodeBlock (lib/widgets/src/clide_code_block.dart) WITH editor syntax colorization. ClideCodeBlock already colorizes via TreeSitterService when handed a language — so the work is inferring/passing the right language per tool: Read → from the file extension/path (language_map.grammarForPath), Bash → bash, Grep/LS/others → text fallback. Where a path is available, prefer it so highlighting matches the editor. +Likely suspects to investigate: +- status only published while the pane is the focused contribution (active==true) — if focus isn''t on the Claude pane, the slot clears. +- statusStream may not emit until the first ''result''/''init'' event; a resumed session (--resume) may not re-emit init, so model/mode never arrive. +- _statusWidget returns null when _status.isEmpty AND no skills, so an unstarted/!init session shows nothing. +- focus-slot wiring (FocusTracker) may not re-publish on pane (re)build / session rebind. -Desired behavior (failure) — UNCHANGED: -- Leave the current interaction as-is: the tool-use card plus the prominent red "· error" result card (borderColor statusError, expanded-by-default). Optionally show a red status mark on the tool-use header for symmetry, but do NOT fold the error into one card. +Acceptance: the Claude status line shows model · mode · context promptly after a session starts/resumes and stays current across turns and focus changes; add a test covering the resumed-session (no fresh init) case.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 07:26:13', '2026-06-08 07:46:46', NULL, 'd5c2cfe56ffde42485efa9eb596c0610', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'epic', NULL, 'UI tweaks & fixes', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work. -What to build: -1. ConversationCard: a trailing header status slot (e.g. status: success|error|none) rendered in _header between the Spacer/summary and the hover action buttons. -2. ConversationCard: support a second body segment so an expanded card can show CALL then RESULT with a clear visual separator / sub-label (e.g. a divider or a muted "result" label) so the user can tell the call from its output. -3. conversation_view: a result-by-toolUseId lookup (reverse of the existing toolUseById) so a tool-use card knows its outcome; on a successful pair, stamp the check + fold the result body and SUPPRESS the standalone success ToolResultMessage from the rendered list. Errors render both cards as today. -4. Per-tool result language inference for the folded code block. +**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 07:46:23', '2026-06-08 07:53:32', NULL, '61e4e273c9a2f876ba02b8ba3299c90e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM60PAS5RJ55CZGPM1G', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Surface pql labels in the clide tickets UI (render + label navigator)', 'clide doesn''t surface pql ticket labels, so a rolling ''UI tweaks'' tracker has to be modeled as an epic (T-276) instead of a label — which is the cleaner primitive. Make labels first-class in the tickets UI so a label can BE the tracker. -Edge cases to handle: -- In-flight tool-use with no result yet: render the call card as today (no check, no folded result); status/result appear once the result arrives. -- Orphan result with no paired tool-use: keep rendering it standalone. -- Permission-resolved tool-use cards (conversation_view.dart ~L297, already green/red bordered + collapsed): reconcile so the merged-card + check treatment is consistent and not duplicated with the existing border-outcome styling. -- Result items may not be strictly adjacent to their tool-use in the list; suppression must be keyed by toolUseId, not list position. +Scope: +- Render a ticket''s labels on its card and detail view in the tickets panel (lib/builtin/tickets) — small chips/pills, ui-design tokens, no hardcoded hex. +- A label navigator: filter/group the board by label (a sidepane affordance or a filter control on the tickets panel) so ''show me everything labelled ui-tweak'' is one click. Reuse the existing filter-box / MessageBus addressability (T-270) where it fits. +- Read/write labels via the pql wrapper only (D-3 — wrap, don''t duplicate); pql already supports the ''ticket label'' verb and exposes labels on records. +- CLI/UI parity (D-6): label add/remove + filter should each have a clide verb counterpart. -Tests: unit/widget coverage for the merged success card (collapsed shows check; expanded shows call + colorized result), language inference per tool, the unchanged error path, and the in-flight/orphan cases. Add a golden for the merged success card. +Once this lands, loose UI/UX work + bugs can move from the T-276 tracker-epic to a label (e.g. ''ui-tweak''); file that migration as a follow-up — do NOT auto-close T-276 (it''s marked never-close). -Refs: D-78 (interaction zone / display-only conversation widgets). Builds on T-168 (per-tool tool-use/result body rendering). - -IMPLEMENTATION NOTES (from streamlining analysis): - -C — Error-card symmetry decision: this ticket merges only SUCCESS into the tool card; the error path intentionally stays a separate prominent red "· error" card (conversation_view.dart ~L337) for failure visibility. Decide explicitly: keep that asymmetry (success folds, failure stays two-card) OR also stamp a red status mark on the tool-use header for visual symmetry while still keeping the separate red card. Default leaning: keep the separate red card; optionally add the red header mark. - -D — Activity-cluster coupling: activity_cluster.groupConversation currently folds a tool CALL and its RESULT as TWO separate foldable items into a cluster (activity_cluster.dart ~L100-109, classifying each by toolUseId). Once this ticket makes call+result a single self-contained card, the grouping pass must treat the tool call as ONE unit (its result is part of the card, no longer a separate foldable item) or the result will double-render (once folded into the card, once as a cluster item). Update _isFoldable / the pairing logic accordingly and add a test that a merged tool card is not double-counted.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:10:42', '2026-06-07 17:41:11', NULL, '536c793d50d899e4d403b5a2a246f6a5', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'story', 'T-267', 'Fold sub-agent prompt into the Agent card; relabel from you to agent prompt', 'When Claude launches a sub-agent (the Agent/Task tool), the sub-agent conversation runs as a SIDECHAIN. The prompt Claude wrote for that sub-agent comes through the transcript as a UserMessage with isSidechain=true but injected=false. The conversation view does not look at isSidechain, so it renders that prompt with the blue "you" label exactly like real user input — FALSELY implying the user sent it. It sits as a standalone block below the Agent tool-use card. This is misleading: the user did not write that prompt. +Relevant: lib/builtin/tickets/, lib/src/pql/ (pql wrapper), and pql''s ''ticket label'' / ''ticket list'' label fields.', 'backlog', 'medium', NULL, NULL, 'D-3', '2026-06-08 08:32:30', '2026-06-08 08:34:47', NULL, 'f5b713e5222e00e1a8074a0abead336b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7XXSERDHKR0EEH3N4', 'story', '06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'Fold sub-agent prompt into the Agent card; relabel from you to agent prompt', 'When Claude launches a sub-agent (the Agent/Task tool), the sub-agent conversation runs as a SIDECHAIN. The prompt Claude wrote for that sub-agent comes through the transcript as a UserMessage with isSidechain=true but injected=false. The conversation view does not look at isSidechain, so it renders that prompt with the blue "you" label exactly like real user input — FALSELY implying the user sent it. It sits as a standalone block below the Agent tool-use card. This is misleading: the user did not write that prompt. Confirmed facts from the code: every transcript envelope carries BOTH isSidechain AND parentUuid (transcript_reader.dart ~L547 parses isSidechain but never USES it; parentUuid is dropped entirely). The Agent/Task tool-use is rendered by _toolUse in lib/builtin/claude/src/conversation_view.dart (only AskUserQuestion is special-cased today). The "you" vs "context" label is chosen in conversation_view.dart (~L200): UserMessage with injected=true → "context" (muted); else → "you" (blue, tokens.globalFocus). isSidechain is currently never read by any rendering/grouping logic. @@ -1763,117 +511,8 @@ Refs: D-78 (interaction zone / display-only conversation widgets; injected/conte IMPLEMENTATION NOTE (E — Agent-card layered ordering, from streamlining analysis): -With this ticket (fold prompt) and T-262 (merge success result) both landing, the Agent/Task card ends up owning multiple segments: the tool INPUT, the folded PROMPT, and — via T-262 — the sub-agents final returned RESULT (the Task ToolResultMessage). Define a deliberate layered order when expanded (e.g. call/input → prompt → returned result) with clear sub-labels/dividers so the Agent card stays readable and does not become a kitchen sink. Coordinate with T-262 (result merge) and T-264 (nesting the whole run): the nested run region vs the returned-result segment must not duplicate the sub-agent output.', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:34:02', '2026-06-08 06:53:38', NULL, '0e4c99f123f408855af890ceee68b486', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-274', 'bug', NULL, 'Claude bottom status bar often stays empty / doesn''t update', 'The bottom status-bar context slot for the Claude pane (model · permission-mode · context, T-145/T-150) frequently renders empty and doesn''t update. The slot is fed by the active pane''s status widget via the focus service (PaneContextStatusItem -> ClidePane.statusWidget in claude_pane.dart), sourced from StreamJsonSession.statusStream (model/permissionMode from the ''system/init'' event; cost/contextWindow from ''result'' events in stream_json_session.dart). - -Repro: open clide; the status line is often blank and stays blank until/unless a turn completes (or never populates). - -Likely suspects to investigate: -- status only published while the pane is the focused contribution (active==true) — if focus isn''t on the Claude pane, the slot clears. -- statusStream may not emit until the first ''result''/''init'' event; a resumed session (--resume) may not re-emit init, so model/mode never arrive. -- _statusWidget returns null when _status.isEmpty AND no skills, so an unstarted/!init session shows nothing. -- focus-slot wiring (FocusTracker) may not re-publish on pane (re)build / session rebind. - -Acceptance: the Claude status line shows model · mode · context promptly after a session starts/resumes and stays current across turns and focus changes; add a test covering the resumed-session (no fresh init) case.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 07:26:13', '2026-06-08 07:26:13', NULL, 'cc0f2ec60e2b8e4e97374b4c708d6125', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'task', NULL, 'Frame0 design: permission-mode dropdown beside the Claude composer', 'Produce a Frame0 wireframe for a persistent permission-mode control next to the Claude conversation composer, then use it to drive implementation. - -Goal: surface the current permission mode (default · accept-edits · plan; bypass behind the cockpit guard) AT ALL TIMES, not only in the bottom status bar. Move/duplicate the mode affordance to an icon button sitting to the RIGHT of the composer text box (ClaudeComposer), showing the current mode via icon (+ short label), and opening a dropdown/popover menu to switch mode on click. - -Design to wireframe (frame0-wireframe skill): -- Composer row with the mode icon-button anchored at its right edge (trailing), aligned with the send/interrupt control; show resting state for each mode (distinct icon/color per mode) and the open-dropdown state listing selectable modes with the active one marked. -- Account for the interaction zone (D-78): when a permission/AskUserQuestion prompt replaces the composer, define what happens to the mode button (hidden? disabled? persists above?). -- Keep it consistent with the existing status-bar mode badge (T-226: click or Ctrl/Cmd+M cycles the safe trio) — decide whether the status-bar badge stays, is removed, or mirrors the new control. -- Token/geometry per the ui-design skill (no hardcoded hex; trailing-control geometry like the composer''s existing buttons). - -Deliverable: a Frame0 wireframe (resting + open states, per-mode variants) exported, plus a short note on the chosen prompt-mode interaction, ready to implement. Relevant code: lib/builtin/claude/src/claude_composer.dart, claude_pane.dart (_ModeBadge / _statusWidget / _cycleMode), claude_status.dart (nextSafePermissionMode / permissionModeLabel).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-08 07:28:34', '2026-06-08 07:28:34', NULL, '4332a73ebc0aed9d6524cdebab747084', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-276', 'epic', NULL, 'UI tweaks & fixes', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 07:46:23', '2026-06-08 07:46:23', NULL, 'aab8b05e226ae959fe34b41f224e0016', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-273', 'task', 'T-276', 'turn indicator font color → Claude accent orange', 'Change the animated running/turn indicator''s text color to the Claude accent orange (the brand accent), instead of its current color. Lives in lib/builtin/claude/src/running_indicator.dart (the rotating-verb turn indicator, T-255). Use the appropriate accent token from SurfaceTokens (ui-design skill: confirm which token maps to Claude accent orange) — no hardcoded hex. Update/adjust any golden or widget test that asserts the indicator color.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-07 17:39:10', '2026-06-08 07:46:46', NULL, '1f569ec970a960c4e221bd9ef684f76a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-274', 'bug', 'T-276', 'Claude bottom status bar often stays empty / doesn''t update', 'The bottom status-bar context slot for the Claude pane (model · permission-mode · context, T-145/T-150) frequently renders empty and doesn''t update. The slot is fed by the active pane''s status widget via the focus service (PaneContextStatusItem -> ClidePane.statusWidget in claude_pane.dart), sourced from StreamJsonSession.statusStream (model/permissionMode from the ''system/init'' event; cost/contextWindow from ''result'' events in stream_json_session.dart). - -Repro: open clide; the status line is often blank and stays blank until/unless a turn completes (or never populates). - -Likely suspects to investigate: -- status only published while the pane is the focused contribution (active==true) — if focus isn''t on the Claude pane, the slot clears. -- statusStream may not emit until the first ''result''/''init'' event; a resumed session (--resume) may not re-emit init, so model/mode never arrive. -- _statusWidget returns null when _status.isEmpty AND no skills, so an unstarted/!init session shows nothing. -- focus-slot wiring (FocusTracker) may not re-publish on pane (re)build / session rebind. - -Acceptance: the Claude status line shows model · mode · context promptly after a session starts/resumes and stays current across turns and focus changes; add a test covering the resumed-session (no fresh init) case.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 07:26:13', '2026-06-08 07:46:46', NULL, '27129a72d60fb4094deab965a8b6f2e6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'task', 'T-276', 'Frame0 design: permission-mode dropdown beside the Claude composer', 'Produce a Frame0 wireframe for a persistent permission-mode control next to the Claude conversation composer, then use it to drive implementation. - -Goal: surface the current permission mode (default · accept-edits · plan; bypass behind the cockpit guard) AT ALL TIMES, not only in the bottom status bar. Move/duplicate the mode affordance to an icon button sitting to the RIGHT of the composer text box (ClaudeComposer), showing the current mode via icon (+ short label), and opening a dropdown/popover menu to switch mode on click. - -Design to wireframe (frame0-wireframe skill): -- Composer row with the mode icon-button anchored at its right edge (trailing), aligned with the send/interrupt control; show resting state for each mode (distinct icon/color per mode) and the open-dropdown state listing selectable modes with the active one marked. -- Account for the interaction zone (D-78): when a permission/AskUserQuestion prompt replaces the composer, define what happens to the mode button (hidden? disabled? persists above?). -- Keep it consistent with the existing status-bar mode badge (T-226: click or Ctrl/Cmd+M cycles the safe trio) — decide whether the status-bar badge stays, is removed, or mirrors the new control. -- Token/geometry per the ui-design skill (no hardcoded hex; trailing-control geometry like the composer''s existing buttons). - -Deliverable: a Frame0 wireframe (resting + open states, per-mode variants) exported, plus a short note on the chosen prompt-mode interaction, ready to implement. Relevant code: lib/builtin/claude/src/claude_composer.dart, claude_pane.dart (_ModeBadge / _statusWidget / _cycleMode), claude_status.dart (nextSafePermissionMode / permissionModeLabel).', 'backlog', 'medium', NULL, NULL, 'D-78', '2026-06-08 07:28:34', '2026-06-08 07:46:46', NULL, '7b90ced5ac59f9f287f71eb0ca7d5517', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-276', 'epic', NULL, 'UI tweaks & fixes', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work. - -**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 07:46:23', '2026-06-08 07:53:32', NULL, '5f7d9057da93b43b31bce70bfe45e7d8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-277', 'story', NULL, 'Surface pql labels in the clide tickets UI (render + label navigator)', 'clide doesn''t surface pql ticket labels, so a rolling ''UI tweaks'' tracker has to be modeled as an epic (T-276) instead of a label — which is the cleaner primitive. Make labels first-class in the tickets UI so a label can BE the tracker. - -Scope: -- Render a ticket''s labels on its card and detail view in the tickets panel (builtin/tickets) — small chips/pills, ui-design tokens, no hardcoded hex. -- A label navigator: filter/group the board by label (a sidepane affordance or a filter control on the tickets panel) so ''show me everything labelled ui-tweak'' is one click. Reuse the existing filter-box / MessageBus addressability (T-270) where it fits. -- Read labels via the pql wrapper only (D-3 — wrap, don''t duplicate); pql already supports + labels on records. -- CLI/UI parity (D-6): label add/remove + filter should have a clide verb counterpart. - -Once this lands, loose UI/UX work + bugs can move from the T-276 tracker-epic to a label (e.g. ''ui-tweak''); note that migration as a follow-up, don''t auto-close T-276 (it''s marked never-close). - -Relevant: lib/builtin/tickets/, lib/src/pql/ (pql wrapper), pql / label fields.', 'backlog', 'medium', NULL, NULL, 'D-3', '2026-06-08 08:32:30', '2026-06-08 08:32:30', NULL, '20a73a7524dc01ead31f7866af329bbf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-277', 'story', NULL, 'Surface pql labels in the clide tickets UI (render + label navigator)', 'clide doesn''t surface pql ticket labels, so a rolling ''UI tweaks'' tracker has to be modeled as an epic (T-276) instead of a label — which is the cleaner primitive. Make labels first-class in the tickets UI so a label can BE the tracker. - -Scope: -- Render a ticket''s labels on its card and detail view in the tickets panel (lib/builtin/tickets) — small chips/pills, ui-design tokens, no hardcoded hex. -- A label navigator: filter/group the board by label (a sidepane affordance or a filter control on the tickets panel) so ''show me everything labelled ui-tweak'' is one click. Reuse the existing filter-box / MessageBus addressability (T-270) where it fits. -- Read/write labels via the pql wrapper only (D-3 — wrap, don''t duplicate); pql already supports the ''ticket label'' verb and exposes labels on records. -- CLI/UI parity (D-6): label add/remove + filter should each have a clide verb counterpart. - -Once this lands, loose UI/UX work + bugs can move from the T-276 tracker-epic to a label (e.g. ''ui-tweak''); file that migration as a follow-up — do NOT auto-close T-276 (it''s marked never-close). - -Relevant: lib/builtin/tickets/, lib/src/pql/ (pql wrapper), and pql''s ''ticket label'' / ''ticket list'' label fields.', 'backlog', 'medium', NULL, NULL, 'D-3', '2026-06-08 08:32:30', '2026-06-08 08:34:24', NULL, '6ab1cb9b6ef92d1b712f07858b1239e6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-277', 'story', 'T-276', 'Surface pql labels in the clide tickets UI (render + label navigator)', 'clide doesn''t surface pql ticket labels, so a rolling ''UI tweaks'' tracker has to be modeled as an epic (T-276) instead of a label — which is the cleaner primitive. Make labels first-class in the tickets UI so a label can BE the tracker. - -Scope: -- Render a ticket''s labels on its card and detail view in the tickets panel (lib/builtin/tickets) — small chips/pills, ui-design tokens, no hardcoded hex. -- A label navigator: filter/group the board by label (a sidepane affordance or a filter control on the tickets panel) so ''show me everything labelled ui-tweak'' is one click. Reuse the existing filter-box / MessageBus addressability (T-270) where it fits. -- Read/write labels via the pql wrapper only (D-3 — wrap, don''t duplicate); pql already supports the ''ticket label'' verb and exposes labels on records. -- CLI/UI parity (D-6): label add/remove + filter should each have a clide verb counterpart. - -Once this lands, loose UI/UX work + bugs can move from the T-276 tracker-epic to a label (e.g. ''ui-tweak''); file that migration as a follow-up — do NOT auto-close T-276 (it''s marked never-close). - -Relevant: lib/builtin/tickets/, lib/src/pql/ (pql wrapper), and pql''s ''ticket label'' / ''ticket list'' label fields.', 'backlog', 'medium', NULL, NULL, 'D-3', '2026-06-08 08:32:30', '2026-06-08 08:34:47', NULL, 'da5dd0bb879b9d088e009c735700c935', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-263', 'story', 'T-267', 'Fold sub-agent prompt into the Agent card; relabel from you to agent prompt', 'When Claude launches a sub-agent (the Agent/Task tool), the sub-agent conversation runs as a SIDECHAIN. The prompt Claude wrote for that sub-agent comes through the transcript as a UserMessage with isSidechain=true but injected=false. The conversation view does not look at isSidechain, so it renders that prompt with the blue "you" label exactly like real user input — FALSELY implying the user sent it. It sits as a standalone block below the Agent tool-use card. This is misleading: the user did not write that prompt. - -Confirmed facts from the code: every transcript envelope carries BOTH isSidechain AND parentUuid (transcript_reader.dart ~L547 parses isSidechain but never USES it; parentUuid is dropped entirely). The Agent/Task tool-use is rendered by _toolUse in lib/builtin/claude/src/conversation_view.dart (only AskUserQuestion is special-cased today). The "you" vs "context" label is chosen in conversation_view.dart (~L200): UserMessage with injected=true → "context" (muted); else → "you" (blue, tokens.globalFocus). isSidechain is currently never read by any rendering/grouping logic. - -Design (confirmed with user): -1. Never label a sidechain prompt "you". A UserMessage with isSidechain=true is an AGENT PROMPT, not user input — label it "agent prompt" with muted / de-emphasized styling (same precedent as injected "context" messages, D-78). Never the blue "you" treatment. -2. Fold the prompt INTO the Agent tool-use card above it, COLLAPSED BY DEFAULT. The Agent card becomes the container that owns its prompt: expand the card to reveal the prompt; collapsed (default) it is hidden. The standalone prompt block is SUPPRESSED from the list — same fold-a-standalone-item-into-its-card pattern as T-262. -3. Prompt folds into the CALL, not the results. Only the prompt folds into the collapsed Agent call card. The sub-agent work (its thinking / tool calls / results) stays VISIBLE as the results chain after the call — not buried. Shape: [collapsed Agent call, owning the prompt] then [the run results], with no misleading "you" block anywhere. Agent prompts are valuable enough to keep with the call rather than fold into the subsequent results chain. -4. Link the prompt to the RIGHT Agent card via parentUuid. Parse parentUuid (currently dropped) so each sidechain prompt attaches to its spawning Agent tool-use. This matters when several agents run in parallel in one turn — a positional "nearest preceding Agent" heuristic would misattach; the parent link is the robust route. Heuristic only as a fallback when the link cannot be resolved. - -Touch points: -- transcript_reader.dart: capture parentUuid from the envelope and expose it on ConversationItem (it is present in the JSONL, just not parsed). -- conversation_view.dart: relabel sidechain UserMessage to "agent prompt" (stop the "you" path); fold it into the Agent card body; special-case the Agent/Task tool name (today only AskUserQuestion is special-cased); suppress the standalone block. -- activity_cluster.dart: ensure the suppressed prompt is not double-counted in grouping; leave the sidechain results in the chain. - -Edge cases: -- Parallel agents in one turn → each prompt attaches to its own card (the reason for parentUuid). -- Orphan prompt (no resolvable parent) → still render as "agent prompt", muted/collapsed, never "you". -- Nested / background sub-agents. - -Tests: unit/widget coverage for sidechain-prompt relabel (never "you"), folding into the Agent card (collapsed hides, expand reveals), suppression of the standalone block, parentUuid attachment with parallel agents, and the orphan fallback. Add a golden for the Agent card with a folded prompt. - -Refs: D-78 (interaction zone / display-only conversation widgets; injected/context muting precedent). Related: T-262 (fold-standalone-item-into-its-card pattern). - -IMPLEMENTATION NOTE (E — Agent-card layered ordering, from streamlining analysis): - -With this ticket (fold prompt) and T-262 (merge success result) both landing, the Agent/Task card ends up owning multiple segments: the tool INPUT, the folded PROMPT, and — via T-262 — the sub-agents final returned RESULT (the Task ToolResultMessage). Define a deliberate layered order when expanded (e.g. call/input → prompt → returned result) with clear sub-labels/dividers so the Agent card stays readable and does not become a kitchen sink. Coordinate with T-262 (result merge) and T-264 (nesting the whole run): the nested run region vs the returned-result segment must not duplicate the sub-agent output.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:34:02', '2026-06-08 08:45:23', NULL, '561ac6a3308d08c0da27bcb23eb21cc0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'story', 'T-267', 'Restyle activity/holder card as a container of sub-cards (fix collapse-control scroll race)', 'Restyle the activity card (and, via a shared primitive, the nested sub-agent run from T-264) so a folded run reads as ONE container that holds its sub-cards, with a reachable collapse/expand affordance that survives live writes. +With this ticket (fold prompt) and T-262 (merge success result) both landing, the Agent/Task card ends up owning multiple segments: the tool INPUT, the folded PROMPT, and — via T-262 — the sub-agents final returned RESULT (the Task ToolResultMessage). Define a deliberate layered order when expanded (e.g. call/input → prompt → returned result) with clear sub-labels/dividers so the Agent card stays readable and does not become a kitchen sink. Coordinate with T-262 (result merge) and T-264 (nesting the whole run): the nested run region vs the returned-result segment must not duplicate the sub-agent output.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:34:02', '2026-06-08 08:45:23', NULL, 'ee9a4aa047db942114d25f30afdfc7e3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45HHRBCRMV2166Y6M', 'story', '06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'Restyle activity/holder card as a container of sub-cards (fix collapse-control scroll race)', 'Restyle the activity card (and, via a shared primitive, the nested sub-agent run from T-264) so a folded run reads as ONE container that holds its sub-cards, with a reachable collapse/expand affordance that survives live writes. DESIGN RESOLVED (with user) — supersedes the earlier design-pending note. @@ -1902,38 +541,8 @@ Edge cases: Tests: holder background tap toggles; tap on a child sub-card does NOT toggle the holder; copy button still copies (not swallowed); keyboard/AT path toggles via the explicit control; collapsed ticker + step count preserved. Add a golden for the holder container (collapsed + expanded). -Refs: D-78. Provides the shared container consumed by T-264. Related: T-230 (activity card), T-262 / T-263 / T-265. Parent: T-267.', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:41:13', '2026-06-08 08:47:43', NULL, 'c983e4459c327d71c9783c06286f4b49', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'story', 'T-267', 'Restyle activity/holder card as a container of sub-cards (fix collapse-control scroll race)', 'Restyle the activity card (and, via a shared primitive, the nested sub-agent run from T-264) so a folded run reads as ONE container that holds its sub-cards, with a reachable collapse/expand affordance that survives live writes. - -DESIGN RESOLVED (with user) — supersedes the earlier design-pending note. - -1. HOLDER = a shared container primitive. Extract one container widget (e.g. a ClideHolderCard / conversation-level container) that renders a titled/attributed frame WRAPPING its child sub-cards. Both the activity card (_ActivityCard, lib/builtin/claude/src/conversation_view.dart ~L427) and the nested sub-agent run (T-264) consume it, so the container model is settled once. NOTE: this makes T-266 a DEPENDENCY of T-264 (the shared primitive must exist before the agent-run nesting can consume it) — update epic T-267 sequencing: T-266''s primitive lands before/with T-264, no longer strictly last. - -2. Toggle = the HOLDER BACKGROUND, not the children. Clicking the container''s own background/chrome (its padding, the gaps between sub-cards, its gutter — any region NOT covered by a child) toggles collapse/expand. Crucially: -- Taps on a child sub-card lying on top of the background MUST NOT toggle the holder. Child cards opaquely consume their own hit region across their FULL bounds (including their body), so a click on a sub-card interacts with that card, never the holder. -- The copy button (and any child control / ClideTappable) keeps its own interaction — never swallowed by the holder toggle. -- Implementation: a background gesture target behind the children that only fires for hits the children do not consume (HitTestBehavior; children opaque over their bounds). No whole-card overlay that would intercept child taps. - -3. Autoscroll stays ALWAYS-FOLLOW. Keep the current tail-follow (conversation_view._onChanged ~L119 jumps to maxScrollExtent on every change). Reachability of the collapse control no longer depends on getting to a top header: because the toggle is the ever-present holder BACKGROUND, a click on whatever background area is currently in view (near the latest content while following) collapses the holder — ending the race. Once collapsed the card shrinks to its ticker, so the scroll-yank stops. - -4. Keep the collapsed ticker. Collapsed (default) still shows the one-line live ticker (latest step) + step count, as today. Expanded shows the contained sub-cards. - -5. Accessibility (obligation): a background tap is not keyboard/AT reachable on its own, so keep an EXPLICIT focusable, Enter/Space-activatable collapse control (e.g. the ticker row remains a Semantics button / a focusable caret) in addition to the background-click affordance. Announce expanded/collapsed + step count (as _ActivityCard does today). - -Touch points: -- New shared container primitive (extracted from _ActivityCard). -- conversation_view.dart: _ActivityCard adopts the primitive; background-toggle wiring; ensure child ConversationCards opaquely consume their bounds so they do not bubble taps to the holder. -- T-264 consumes the same primitive for the nested agent run. - -Edge cases: -- A holder whose child is itself collapsible (a tool card): tapping the child''s caret/body toggles the CHILD, never the holder. -- Text selection inside a child (ClideSelectionArea wraps the list): selection drags on child bodies must not be hijacked by the holder toggle — another reason the holder responds only to its own background, and only to taps (not drags). -- Empty / single-item holder. - -Tests: holder background tap toggles; tap on a child sub-card does NOT toggle the holder; copy button still copies (not swallowed); keyboard/AT path toggles via the explicit control; collapsed ticker + step count preserved. Add a golden for the holder container (collapsed + expanded). - -Refs: D-78. Provides the shared container consumed by T-264. Related: T-230 (activity card), T-262 / T-263 / T-265. Parent: T-267.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:41:13', '2026-06-08 09:04:12', NULL, '9da8b26d36d66bf1d26125519ec45872', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'story', 'T-267', 'Nest the whole sub-agent run under its Agent card (attribute sidechain via parentUuid)', 'Completion of T-263. T-263 folds the sub-agent PROMPT into the Agent card, but the sub-agent run itself — its prose, thinking, and tool calls — still renders inline in the MAIN conversation chain. A reader cannot tell where the sub-agent run ends and the main thread resumes; sidechain items are visually indistinguishable from main-thread items. +Refs: D-78. Provides the shared container consumed by T-264. Related: T-230 (activity card), T-262 / T-263 / T-265. Parent: T-267.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:41:13', '2026-06-08 09:04:12', NULL, '3cb18c97d0640b5f61c494d258aa3ef2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YYMMKFD5Z8WG9F60', 'story', '06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'Nest the whole sub-agent run under its Agent card (attribute sidechain via parentUuid)', 'Completion of T-263. T-263 folds the sub-agent PROMPT into the Agent card, but the sub-agent run itself — its prose, thinking, and tool calls — still renders inline in the MAIN conversation chain. A reader cannot tell where the sub-agent run ends and the main thread resumes; sidechain items are visually indistinguishable from main-thread items. Root cause: the conversation is a flat list of ConversationItems rendered by _ConversationTurn (lib/builtin/claude/src/conversation_view.dart build ~L130, _ConversationTurn ~L182). isSidechain exists on every item (transcript_reader.dart ~L46) but no rendering/grouping logic reads it. activity_cluster.dart groups consecutive meta runs but is sidechain-agnostic. @@ -1947,32 +556,8 @@ Scope: Depends on T-263 (parentUuid parsing + Agent card as container). Pairs with the sidechain-prose relabel ticket. Note the holder-card restyle + auto-scroll race is a separate design discussion (see the holder-card ticket). -Refs: D-78. Builds on T-263. Related: T-262, T-230 (activity card grouping).', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:25', '2026-06-08 09:07:02', NULL, 'fc5f31e40ace342f2b183eefed329067', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'bug', 'T-276', 'Slash-command typeahead breaks on ''-''; Tab-to-complete is flaky', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart): - -1. Typing a ''-'' character breaks the typeahead. Many command names contain hyphens (e.g. clear-context-style names, custom commands), but typing ''-'' appears to drop/empty the suggestion list or mis-parse the active query. Suspect activeSlashQuery''s token run or filterSlashCommands prefix matching not handling ''-'' (or the composer treating ''-'' as a boundary). - -2. Tab to accept the highlighted suggestion responds flakily — sometimes it completes, sometimes nothing happens. Suspect a focus/key-handling race between the composer''s key handler and the typeahead overlay, or Tab being consumed by focus traversal before the accept intent fires. - -Repro: open the composer, type ''/'' then a command fragment; (a) include a ''-'' and watch the list; (b) arrow-select an item and press Tab repeatedly. - -Acceptance: ''-'' is treated as a normal command-name character (suggestions keep filtering through hyphens); Tab reliably completes the highlighted suggestion every time (insert via completeSlash). Add/extend unit tests in slash_commands_test.dart for hyphenated queries and a composer widget test for Tab-accept.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 09:20:00', '2026-06-08 09:20:00', NULL, 'a15f416969aef224d838bf28d016b82a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'story', 'T-267', 'Nest the whole sub-agent run under its Agent card (attribute sidechain via parentUuid)', 'Completion of T-263. T-263 folds the sub-agent PROMPT into the Agent card, but the sub-agent run itself — its prose, thinking, and tool calls — still renders inline in the MAIN conversation chain. A reader cannot tell where the sub-agent run ends and the main thread resumes; sidechain items are visually indistinguishable from main-thread items. - -Root cause: the conversation is a flat list of ConversationItems rendered by _ConversationTurn (lib/builtin/claude/src/conversation_view.dart build ~L130, _ConversationTurn ~L182). isSidechain exists on every item (transcript_reader.dart ~L46) but no rendering/grouping logic reads it. activity_cluster.dart groups consecutive meta runs but is sidechain-agnostic. - -Design: use the parentUuid link (parsed in T-263) to group an ENTIRE sidechain run as a nested region OWNED by its spawning Agent card. The run renders as a distinct, attributed, collapsible region (indent / attribution band / nested holder) under the Agent call rather than as loose cards in the main chain. Per the user rule from T-263, the run results stay VISIBLE (not folded away) — this is about attribution and containment, not hiding. - -Scope: -- Build a sidechain-aware grouping pass (extend or complement activity_cluster.groupConversation) that collects all items sharing a sidechain ancestry (via parentUuid chain back to the Agent tool_use) into one nested group. -- Render that group as a nested region under the Agent card (visual indent / attribution), keeping the sub-agent results first-class within it. -- Handle parallel agents (each run nests under its own card), nested sub-agents, and background agents. -- Orphan sidechain items (no resolvable parent) degrade to the current inline rendering but still attributed (see T-264/relabel work). - -Depends on T-263 (parentUuid parsing + Agent card as container). Pairs with the sidechain-prose relabel ticket. Note the holder-card restyle + auto-scroll race is a separate design discussion (see the holder-card ticket). - -Refs: D-78. Builds on T-263. Related: T-262, T-230 (activity card grouping).', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:25', '2026-06-08 09:25:50', NULL, 'e3afb1ab7a0530c0b8dbd5775cc3803d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'story', 'T-267', 'Relabel sidechain assistant prose/thinking — attribute to agent, not claude', 'Mirror of T-263 on the OUTPUT side. A sub-agent runs as a sidechain; its assistant prose renders with the label "claude" and its thinking with "thinking", identically to the MAIN assistant — because the label logic does not check isSidechain. So a sub-agents output is presented as if the main Claude said it. Same misattribution T-263 fixes for the prompt (the "you" problem), mirrored on the assistant side. +Refs: D-78. Builds on T-263. Related: T-262, T-230 (activity card grouping).', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:25', '2026-06-08 09:25:50', NULL, '44e706a4dbbffb4d53ceef9236d38955', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4Q1Q97CHA0XNK2G4W', 'story', '06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'Relabel sidechain assistant prose/thinking — attribute to agent, not claude', 'Mirror of T-263 on the OUTPUT side. A sub-agent runs as a sidechain; its assistant prose renders with the label "claude" and its thinking with "thinking", identically to the MAIN assistant — because the label logic does not check isSidechain. So a sub-agents output is presented as if the main Claude said it. Same misattribution T-263 fixes for the prompt (the "you" problem), mirrored on the assistant side. Current code (lib/builtin/claude/src/conversation_view.dart _ConversationTurn build ~L221): AssistantTextMessage → ConversationCard(accent: claudeAccent, label: "claude"); AssistantThinkingMessage (~L227) → label "thinking". Neither inspects ConversationItem.isSidechain (transcript_reader.dart ~L46), which is present but unused. @@ -1985,22 +570,8 @@ Scope: Tests: relabel of sidechain assistant prose/thinking (never "claude"/coral); main-thread unchanged; golden for an attributed agent prose card. -Pairs with the nest-the-agent-run ticket (attribution + containment) and T-263 (prompt side). Refs: D-78.', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:33', '2026-06-08 09:27:49', NULL, '450360f9eec6f076e6db381633ce253c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'story', 'T-267', 'Relabel sidechain assistant prose/thinking — attribute to agent, not claude', 'Mirror of T-263 on the OUTPUT side. A sub-agent runs as a sidechain; its assistant prose renders with the label "claude" and its thinking with "thinking", identically to the MAIN assistant — because the label logic does not check isSidechain. So a sub-agents output is presented as if the main Claude said it. Same misattribution T-263 fixes for the prompt (the "you" problem), mirrored on the assistant side. - -Current code (lib/builtin/claude/src/conversation_view.dart _ConversationTurn build ~L221): AssistantTextMessage → ConversationCard(accent: claudeAccent, label: "claude"); AssistantThinkingMessage (~L227) → label "thinking". Neither inspects ConversationItem.isSidechain (transcript_reader.dart ~L46), which is present but unused. - -Design: when isSidechain is true, attribute the assistant items to the AGENT rather than the main Claude — e.g. label "agent" (or the agent/sub-agent name where resolvable) with de-emphasised styling, NOT the coral "claude" brand accent (claudeAccent is the main-thread brand, reserved for the real Claude). Thinking inside a sidechain attributed/muted likewise. - -Scope: -- Branch the AssistantTextMessage / AssistantThinkingMessage rendering on isSidechain. -- Pick the sidechain label + styling (distinct from "claude"; reuse muted tokens, not claudeAccent). -- Keep behavior identical for main-thread items. - -Tests: relabel of sidechain assistant prose/thinking (never "claude"/coral); main-thread unchanged; golden for an attributed agent prose card. - -Pairs with the nest-the-agent-run ticket (attribution + containment) and T-263 (prompt side). Refs: D-78.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:33', '2026-06-08 10:09:14', NULL, 'cfed081ba171d068e3ab6a2016c9c6d4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'epic', NULL, 'Conversation rendering streamlining — fold, attribute, contain', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). +Pairs with the nest-the-agent-run ticket (attribution + containment) and T-263 (prompt side). Refs: D-78.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:33', '2026-06-08 10:09:14', NULL, '161a4dd3976932afe0b038539d846c86', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7ZGZ9GQ5W1MKV00Y4', 'epic', NULL, 'Conversation rendering streamlining — fold, attribute, contain', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). Common substrate across the children: the flat ConversationItem list and its renderers in lib/builtin/claude/src/conversation_view.dart, the pure grouping pass in activity_cluster.dart, the ConversationCard template in conversation_card.dart, and the so-far-unused ConversationItem.isSidechain / parentUuid transcript fields (transcript_reader.dart). @@ -2008,37 +579,9 @@ Children: T-262 (merge tool-call + success result into one card with header stat Refs: D-78 (interaction zone / display-only conversation widgets). Built on T-168 (per-tool body rendering) and T-230 (activity card). -SEQUENCING UPDATE (after T-266 refinement): the shared container/holder primitive now lives in T-266 and is CONSUMED by T-264 (nested agent run), so T-266 is no longer "last" — its primitive lands before/with T-264. T-264 is now blocked by T-266. Revised order: T-262 / T-263 (fold success result, fold agent prompt) → T-266 (shared holder/container primitive + activity-card restyle) → T-264 (nest the whole agent run on that primitive) → T-265 (relabel sidechain prose) can land anytime alongside.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:42:53', '2026-06-08 10:09:21', NULL, 'bb82ba1e08ca8009613521e59e548e77', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-279', 'story', 'T-276', 'Clickable T/D/Q/R cross-refs in the Claude conversation view → open in context-pane readers', 'In the Claude conversation view, linkify governance/ticket references so they''re clickable and open the matching record in the correct context-pane reader. - -Patterns to detect in rendered message text: T-NNN (pql tickets), D-NNN (decisions), Q-NNN (open questions), R-NNN (rejected alternatives). - -Behaviour: -- Render each match as an inline link (ui-design tokens, link affordance consistent with other conversation links; do not linkify inside code spans/blocks). -- On click, open the record in the proper context-pane reader: - - T-NNN -> tickets reader/panel (the ticket detail). - - D-NNN / Q-NNN / R-NNN -> decisions reader, scrolled to the D/Q/R record under governance/decisions, /questions, /rejected. -- Reuse the existing reader-open plumbing rather than inventing new routing: ReaderNav / the ui.open verb / MessageBus reader addressing (T-186, T-231, T-233) and the decisions + tickets panels. -- D-6 parity: opening a record by id should already (or also) be reachable via a clide verb. - -Scope notes: -- Lives in the conversation rendering path (lib/builtin/claude/src/conversation_view.dart + conversation_card.dart + the markdown/text renderer it uses). Relates to the conversation-rendering epic T-267 — could move there if preferred. -- Resolve id -> source: tickets via the pql wrapper (lib/src/pql), governance records via the decisions index/reader. -- Handle unknown/missing ids gracefully (render as plain text or a dead-link tooltip, no crash). - -Tests: linkifier unit tests (matches T/D/Q/R, ignores code spans, ignores bare words like ''T-shirt''); a widget/integration test that clicking a ref dispatches the reader-open.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-08 10:21:20', '2026-06-08 10:21:20', NULL, 'f417cc6496819316b8926438a466bb2f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-273', 'task', 'T-276', 'turn indicator font color → Claude accent orange', 'Change the animated running/turn indicator''s text color to the Claude accent orange (the brand accent), instead of its current color. Lives in lib/builtin/claude/src/running_indicator.dart (the rotating-verb turn indicator, T-255). Use the appropriate accent token from SurfaceTokens (ui-design skill: confirm which token maps to Claude accent orange) — no hardcoded hex. Update/adjust any golden or widget test that asserts the indicator color.', 'ready', 'low', NULL, NULL, NULL, '2026-06-07 17:39:10', '2026-06-08 10:26:11', NULL, '44cc9635fc73dbd7c6d977759a89cb5e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-280', 'bug', NULL, 'app_test ''Open Folder non-repo'' hangs 10min — runAsync/ReceivePort teardown wedge', NULL, 'backlog', 'high', NULL, NULL, NULL, '2026-06-08 11:29:22', '2026-06-08 11:29:22', NULL, '9a6a887695fcbc55ea078a636bfc5adf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-280', 'bug', NULL, 'app_test ''Open Folder non-repo'' hangs 10min — runAsync/ReceivePort teardown wedge', 'Pre-existing (reproduces at base commit 3a78dfa, predates the T-267 conversation-rendering epic). The widget test `test/app_test.dart` › "Open Folder on a non-repo path surfaces the ''no git repo'' dialog" times out after 10 minutes; teardown is wedged on `_RawReceivePort._handleMessage`. - -Bisected (each a 45s-timeout repro, all on this box): -- A bare `Process.run` inside `tester.runAsync` (no app, no extensions) hangs → `Process.run`-in-`runAsync` leaks its exit ReceivePort here. -- `pumpApp` + empty `runAsync`, and `pumpApp` + a 1.5s real delay → both PASS (boot + runAsync alone is fine). -- The full openFolder tap flow hangs even when project validation is stubbed to a synchronous, pure-Dart `.git` walk (no subprocess) AND `runAsync` is removed — so the wedge is not solely the git subprocess; something in the booted-app + extensions + open-folder command path holds a native port that teardown waits on forever. - -Quarantined with `skip:` so the suite/gate stays green. Real fix: find the leaked native async resource (likely a Process/Isolate/FakeDaemonClient port reachable from the open-folder command or app boot under the test harness) and ensure it''s drained/cancelled before teardown — or drive the "no git repo" assertion without booting the resource. Then remove the skip.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-08 11:29:22', '2026-06-08 11:29:33', NULL, 'e06d0c0f0c06621f763068de5343cd10', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-273', 'task', 'T-276', 'turn indicator font color → Claude accent orange', 'Change the animated running/turn indicator''s text color to the Claude accent orange (the brand accent), instead of its current color. Lives in lib/builtin/claude/src/running_indicator.dart (the rotating-verb turn indicator, T-255). Use the appropriate accent token from SurfaceTokens (ui-design skill: confirm which token maps to Claude accent orange) — no hardcoded hex. Update/adjust any golden or widget test that asserts the indicator color.', 'done', 'low', NULL, NULL, NULL, '2026-06-07 17:39:10', '2026-06-08 11:44:27', NULL, 'a9eafaf6e7956b2ab0d1dc384bacd8cd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-281', 'task', 'T-276', 'Clearer parent/child nesting in the tickets panel (tree layout, bold focused ticket)', 'The tickets panel shows a ticket''s parent inline as ''T-273 ← T-276'', which reads ambiguously (which one am I looking at? what''s the direction of the arrow?). +SEQUENCING UPDATE (after T-266 refinement): the shared container/holder primitive now lives in T-266 and is CONSUMED by T-264 (nested agent run), so T-266 is no longer "last" — its primitive lands before/with T-264. T-264 is now blocked by T-266. Revised order: T-262 / T-263 (fold success result, fold agent prompt) → T-266 (shared holder/container primitive + activity-card restyle) → T-264 (nest the whole agent run on that primitive) → T-265 (relabel sidechain prose) can land anytime alongside.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:42:53', '2026-06-08 10:09:21', NULL, 'bde40f65dd3bb674325ecfaca7e9befa', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM55CV3VDYS88VBJZ60', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'turn indicator font color → Claude accent orange', 'Change the animated running/turn indicator''s text color to the Claude accent orange (the brand accent), instead of its current color. Lives in lib/builtin/claude/src/running_indicator.dart (the rotating-verb turn indicator, T-255). Use the appropriate accent token from SurfaceTokens (ui-design skill: confirm which token maps to Claude accent orange) — no hardcoded hex. Update/adjust any golden or widget test that asserts the indicator color.', 'done', 'low', NULL, NULL, NULL, '2026-06-07 17:39:10', '2026-06-08 11:44:27', NULL, 'cf11d45286b6475ce98a771026d8b53f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM59DG2WADJ9A0JFD1W', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'Clearer parent/child nesting in the tickets panel (tree layout, bold focused ticket)', 'The tickets panel shows a ticket''s parent inline as ''T-273 ← T-276'', which reads ambiguously (which one am I looking at? what''s the direction of the arrow?). Replace it with a small tree/indented layout that makes the hierarchy obvious, e.g.: @@ -2052,56 +595,8 @@ Requirements: - Make each id in the chain clickable to navigate to that ticket (ties into the reader/board navigation; relates to T-279 cross-ref linking). - ui-design tokens for the guide lines + bold/muted weights; no hardcoded hex. -Scope: tickets panel rendering — lib/builtin/tickets/. See attached screenshot for the current ''T-273 ← T-276'' card style being replaced.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 11:46:11', '2026-06-08 11:46:11', NULL, 'bbb16c80956550062c0f2e153ee46a94', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-281', 'task', 'T-276', 'Clearer parent/child nesting in the tickets panel (tree layout, bold focused ticket)', 'The tickets panel shows a ticket''s parent inline as ''T-273 ← T-276'', which reads ambiguously (which one am I looking at? what''s the direction of the arrow?). - -Replace it with a small tree/indented layout that makes the hierarchy obvious, e.g.: - - T-276 - └ T-273 - -Requirements: -- Parent on top, the current/child ticket indented below with a tree connector (└ / ├ style box-drawing or an equivalent indent + guide). -- The ticket the user is actually looking at (the focused/opened one) is bold; ancestors are normal weight (and ideally muted/secondary). -- Support more than one level if a chain exists (initiative → epic → story → task), indenting each level; at minimum handle the common 2-level parent→child case. -- Make each id in the chain clickable to navigate to that ticket (ties into the reader/board navigation; relates to T-279 cross-ref linking). -- ui-design tokens for the guide lines + bold/muted weights; no hardcoded hex. - -Scope: tickets panel rendering — lib/builtin/tickets/. See attached screenshot for the current ''T-273 ← T-276'' card style being replaced.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-08 11:46:11', '2026-06-08 11:47:56', NULL, '8a9b20a4419a7b15ad14a62e0af8a844', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-281', 'task', 'T-276', 'Clearer parent/child nesting in the tickets panel (tree layout, bold focused ticket)', 'The tickets panel shows a ticket''s parent inline as ''T-273 ← T-276'', which reads ambiguously (which one am I looking at? what''s the direction of the arrow?). - -Replace it with a small tree/indented layout that makes the hierarchy obvious, e.g.: - - T-276 - └ T-273 - -Requirements: -- Parent on top, the current/child ticket indented below with a tree connector (└ / ├ style box-drawing or an equivalent indent + guide). -- The ticket the user is actually looking at (the focused/opened one) is bold; ancestors are normal weight (and ideally muted/secondary). -- Support more than one level if a chain exists (initiative → epic → story → task), indenting each level; at minimum handle the common 2-level parent→child case. -- Make each id in the chain clickable to navigate to that ticket (ties into the reader/board navigation; relates to T-279 cross-ref linking). -- ui-design tokens for the guide lines + bold/muted weights; no hardcoded hex. - -Scope: tickets panel rendering — lib/builtin/tickets/. See attached screenshot for the current ''T-273 ← T-276'' card style being replaced.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 11:46:11', '2026-06-08 11:49:36', NULL, '457afd72feff0947506795a2da286447', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-282', 'bug', 'T-276', 'Collapsed conversation card has inconsistent bottom padding', 'The collapsed Agent (tool-call) card in the Claude conversation view has different padding beneath it than the spacing around all other cards — see the gap flagged below the `> Agent` disclosure in the conversation. Make the collapsed card''s bottom padding match the standard inter-card spacing used everywhere else. - -Card layout lives in lib/builtin/claude/src/conversation_card.dart: the outer wrapper applies EdgeInsets.only(bottom: 14) (line ~188) and the stripe variant adds inner EdgeInsets.fromLTRB(12, 8, 12, 8) (line ~210). When collapsed, only the header renders, so the combined bottom spacing (inner 8 + outer 14) reads differently from expanded cards. Reconcile so the visible gap below a collapsed card equals the gap below other cards.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-08 13:39:15', '2026-06-08 13:39:15', NULL, 'b966143b77ad13279803875d49da7b00', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-283', 'bug', 'T-164', 'Resume hang has no timeout/fallback; resume decided by file-exists not content', 'Surfaced 2026-06-08 while chasing a --resume hang in the Claude pane (T-274 diagnostic line). The specific corrupted-transcript repro may turn out to be a one-off, but the code trace found two real latent gaps that make a resume hang unrecoverable regardless of root cause. - -(a) No timeout / no fallback on the init-event wait. -On spawn the orchestrator listens for the session_id from claude''s init event (StreamJsonSession sessionIdResolved -> session_orchestrator.dart ~257) with NO timeout. If ''claude --resume '' never emits an init event (hangs), the listener never fires: the pane shows ''resumed · '' + running indicator forever, the process is never killed, and there is no fallback to a fresh --session-id spawn. Unrecoverable without killing the process / restarting clide. - -(b) Resume is decided by file existence, not resumable content. -claude_pane.dart ~279: ''final resume = await File(transcriptFile).exists();'' — resume=true purely because the .jsonl exists. A metadata-only transcript (only system / permission-mode / attachment records; parseTranscriptChunk().items returns []) yields resume=true with zero seeded items. So clide passes --resume against a functionally empty session, the diagnostic logs ''fresh session (no history)'' (it keys off seeded count, not the resume flag — itself misleading), and if that resume hangs there is no fallback per (a). - -Acceptance: -1. If a --resume spawn yields no init event within a short timeout (e.g. 10-30s), fall back to a fresh --session-id spawn (or surface a recoverable error with a retry affordance) — the pane must never spin forever with no recovery. -2. Don''t pass --resume for a transcript that has no resumable conversation items: validate parsed item count (not just file existence) before choosing --resume vs --session-id, and/or detect-and-repair a metadata-only transcript. -3. The T-274 diagnostic log reflects the actual spawn mode (resume vs fresh), not just seeded-item count. -4. Tests: (i) fake process that never emits init -> pane falls back / surfaces error within the timeout; (ii) metadata-only transcript -> spawn chooses fresh, not --resume. - -Cross-refs: T-274 (resumed-session status bar empty), T-167/T-185 (resume/fork session id capture), D-77, claude_pane.dart:279/300-308, session_orchestrator.dart:240/257.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-06-08 13:39:51', '2026-06-08 13:39:51', NULL, '308f7f124f9d0dba613af6fa95605e7e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-283', 'bug', 'T-164', 'Resume hang has no timeout/fallback; resume decided by file-exists not content', 'Surfaced 2026-06-08 while chasing a --resume hang in the Claude pane (T-274 diagnostic line). The specific corrupted-transcript repro may turn out to be a one-off, but the code trace found two real latent gaps that make a resume hang unrecoverable regardless of root cause. +Scope: tickets panel rendering — lib/builtin/tickets/. See attached screenshot for the current ''T-273 ← T-276'' card style being replaced.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 11:46:11', '2026-06-08 11:49:36', NULL, 'bf04e28befe771ec1a55fc44108cabb6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6K1QRC911JMQ9C960', 'bug', '06FB0TNQM5HM3N07VB3RPFJCFM', 'Resume hang has no timeout/fallback; resume decided by file-exists not content', 'Surfaced 2026-06-08 while chasing a --resume hang in the Claude pane (T-274 diagnostic line). The specific corrupted-transcript repro may turn out to be a one-off, but the code trace found two real latent gaps that make a resume hang unrecoverable regardless of root cause. (a) No timeout / no fallback on the init-event wait. On spawn the orchestrator listens for the session_id from claude''s init event (StreamJsonSession sessionIdResolved -> session_orchestrator.dart ~257) with NO timeout. If ''claude --resume '' never emits an init event (hangs), the listener never fires: the pane shows ''resumed · '' + running indicator forever, the process is never killed, and there is no fallback to a fresh --session-id spawn. Unrecoverable without killing the process / restarting clide. @@ -2117,11 +612,8 @@ Acceptance: Cross-refs: T-274 (resumed-session status bar empty), T-167/T-185 (resume/fork session id capture), D-77, claude_pane.dart:279/300-308, session_orchestrator.dart:240/257. -UPDATE 2026-06-08: the active hang did NOT reproduce — clide is running fine inside the 31b214bd primary session (this very session resumes cleanly). So the original break was a one-off (likely the single corrupted transcript), not a live resume bug. This ticket stands as defensive hardening only: the two gaps (no init-event timeout/fallback; resume keyed off file-exists not content) are real but latent — they''d only bite again if a resume genuinely stalls or a metadata-only transcript appears. Lowering to low priority.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-06-08 13:39:51', '2026-06-08 13:44:57', NULL, 'cf9aeb9f079ffbc01de1f46b779226d8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-282', 'bug', 'T-276', 'Collapsed conversation card has inconsistent bottom padding', 'The collapsed Agent (tool-call) card in the Claude conversation view has different padding beneath it than the spacing around all other cards — see the gap flagged below the `> Agent` disclosure in the conversation. Make the collapsed card''s bottom padding match the standard inter-card spacing used everywhere else. - -Card layout lives in lib/builtin/claude/src/conversation_card.dart: the outer wrapper applies EdgeInsets.only(bottom: 14) (line ~188) and the stripe variant adds inner EdgeInsets.fromLTRB(12, 8, 12, 8) (line ~210). When collapsed, only the header renders, so the combined bottom spacing (inner 8 + outer 14) reads differently from expanded cards. Reconcile so the visible gap below a collapsed card equals the gap below other cards.', 'ready', 'low', NULL, NULL, NULL, '2026-06-08 13:39:15', '2026-06-08 13:48:31', NULL, '90037351c352c6d19681c904f4fcbaea', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-279', 'story', 'T-276', 'Clickable T/D/Q/R cross-refs in the Claude conversation view → open in context-pane readers', 'In the Claude conversation view, linkify governance/ticket references so they''re clickable and open the matching record in the correct context-pane reader. +UPDATE 2026-06-08: the active hang did NOT reproduce — clide is running fine inside the 31b214bd primary session (this very session resumes cleanly). So the original break was a one-off (likely the single corrupted transcript), not a live resume bug. This ticket stands as defensive hardening only: the two gaps (no init-event timeout/fallback; resume keyed off file-exists not content) are real but latent — they''d only bite again if a resume genuinely stalls or a metadata-only transcript appears. Lowering to low priority.', 'backlog', 'medium', NULL, NULL, 'D-77', '2026-06-08 13:39:51', '2026-06-08 13:44:57', NULL, '1b3f27ac668b052d30b95ec8d98e38b4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6A6A56VFSVRRBKMP0', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Clickable T/D/Q/R cross-refs in the Claude conversation view → open in context-pane readers', 'In the Claude conversation view, linkify governance/ticket references so they''re clickable and open the matching record in the correct context-pane reader. Patterns to detect in rendered message text: T-NNN (pql tickets), D-NNN (decisions), Q-NNN (open questions), R-NNN (rejected alternatives). @@ -2138,58 +630,8 @@ Scope notes: - Resolve id -> source: tickets via the pql wrapper (lib/src/pql), governance records via the decisions index/reader. - Handle unknown/missing ids gracefully (render as plain text or a dead-link tooltip, no crash). -Tests: linkifier unit tests (matches T/D/Q/R, ignores code spans, ignores bare words like ''T-shirt''); a widget/integration test that clicking a ref dispatches the reader-open.', 'ready', 'medium', NULL, NULL, 'D-6', '2026-06-08 10:21:20', '2026-06-08 13:48:42', NULL, '7728659c72d9cf304006b21a649bac00', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-279', 'story', 'T-276', 'Clickable T/D/Q/R cross-refs in the Claude conversation view → open in context-pane readers', 'In the Claude conversation view, linkify governance/ticket references so they''re clickable and open the matching record in the correct context-pane reader. - -Patterns to detect in rendered message text: T-NNN (pql tickets), D-NNN (decisions), Q-NNN (open questions), R-NNN (rejected alternatives). - -Behaviour: -- Render each match as an inline link (ui-design tokens, link affordance consistent with other conversation links; do not linkify inside code spans/blocks). -- On click, open the record in the proper context-pane reader: - - T-NNN -> tickets reader/panel (the ticket detail). - - D-NNN / Q-NNN / R-NNN -> decisions reader, scrolled to the D/Q/R record under governance/decisions, /questions, /rejected. -- Reuse the existing reader-open plumbing rather than inventing new routing: ReaderNav / the ui.open verb / MessageBus reader addressing (T-186, T-231, T-233) and the decisions + tickets panels. -- D-6 parity: opening a record by id should already (or also) be reachable via a clide verb. - -Scope notes: -- Lives in the conversation rendering path (lib/builtin/claude/src/conversation_view.dart + conversation_card.dart + the markdown/text renderer it uses). Relates to the conversation-rendering epic T-267 — could move there if preferred. -- Resolve id -> source: tickets via the pql wrapper (lib/src/pql), governance records via the decisions index/reader. -- Handle unknown/missing ids gracefully (render as plain text or a dead-link tooltip, no crash). - -Tests: linkifier unit tests (matches T/D/Q/R, ignores code spans, ignores bare words like ''T-shirt''); a widget/integration test that clicking a ref dispatches the reader-open.', 'in_progress', 'medium', NULL, NULL, 'D-6', '2026-06-08 10:21:20', '2026-06-08 13:49:32', NULL, '3f8139821de7ab3bbfd63e6aa89dd2bd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-282', 'bug', 'T-276', 'Collapsed conversation card has inconsistent bottom padding', 'The collapsed Agent (tool-call) card in the Claude conversation view has different padding beneath it than the spacing around all other cards — see the gap flagged below the `> Agent` disclosure in the conversation. Make the collapsed card''s bottom padding match the standard inter-card spacing used everywhere else. - -Card layout lives in lib/builtin/claude/src/conversation_card.dart: the outer wrapper applies EdgeInsets.only(bottom: 14) (line ~188) and the stripe variant adds inner EdgeInsets.fromLTRB(12, 8, 12, 8) (line ~210). When collapsed, only the header renders, so the combined bottom spacing (inner 8 + outer 14) reads differently from expanded cards. Reconcile so the visible gap below a collapsed card equals the gap below other cards.', 'in_progress', 'low', NULL, NULL, NULL, '2026-06-08 13:39:15', '2026-06-08 13:49:32', NULL, 'b68bcfb7ec333fba8aa9e3f995e5dad5', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'task', 'T-276', 'Frame0 design: permission-mode dropdown beside the Claude composer', 'Produce a Frame0 wireframe for a persistent permission-mode control next to the Claude conversation composer, then use it to drive implementation. - -Goal: surface the current permission mode (default · accept-edits · plan; bypass behind the cockpit guard) AT ALL TIMES, not only in the bottom status bar. Move/duplicate the mode affordance to an icon button sitting to the RIGHT of the composer text box (ClaudeComposer), showing the current mode via icon (+ short label), and opening a dropdown/popover menu to switch mode on click. - -Design to wireframe (frame0-wireframe skill): -- Composer row with the mode icon-button anchored at its right edge (trailing), aligned with the send/interrupt control; show resting state for each mode (distinct icon/color per mode) and the open-dropdown state listing selectable modes with the active one marked. -- Account for the interaction zone (D-78): when a permission/AskUserQuestion prompt replaces the composer, define what happens to the mode button (hidden? disabled? persists above?). -- Keep it consistent with the existing status-bar mode badge (T-226: click or Ctrl/Cmd+M cycles the safe trio) — decide whether the status-bar badge stays, is removed, or mirrors the new control. -- Token/geometry per the ui-design skill (no hardcoded hex; trailing-control geometry like the composer''s existing buttons). - -Deliverable: a Frame0 wireframe (resting + open states, per-mode variants) exported, plus a short note on the chosen prompt-mode interaction, ready to implement. Relevant code: lib/builtin/claude/src/claude_composer.dart, claude_pane.dart (_ModeBadge / _statusWidget / _cycleMode), claude_status.dart (nextSafePermissionMode / permissionModeLabel).', 'ready', 'medium', NULL, NULL, 'D-78', '2026-06-08 07:28:34', '2026-06-08 14:12:26', NULL, 'e486c40ba7aa4f357c463fee89632233', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-279', 'story', 'T-276', 'Clickable T/D/Q/R cross-refs in the Claude conversation view → open in context-pane readers', 'In the Claude conversation view, linkify governance/ticket references so they''re clickable and open the matching record in the correct context-pane reader. - -Patterns to detect in rendered message text: T-NNN (pql tickets), D-NNN (decisions), Q-NNN (open questions), R-NNN (rejected alternatives). - -Behaviour: -- Render each match as an inline link (ui-design tokens, link affordance consistent with other conversation links; do not linkify inside code spans/blocks). -- On click, open the record in the proper context-pane reader: - - T-NNN -> tickets reader/panel (the ticket detail). - - D-NNN / Q-NNN / R-NNN -> decisions reader, scrolled to the D/Q/R record under governance/decisions, /questions, /rejected. -- Reuse the existing reader-open plumbing rather than inventing new routing: ReaderNav / the ui.open verb / MessageBus reader addressing (T-186, T-231, T-233) and the decisions + tickets panels. -- D-6 parity: opening a record by id should already (or also) be reachable via a clide verb. - -Scope notes: -- Lives in the conversation rendering path (lib/builtin/claude/src/conversation_view.dart + conversation_card.dart + the markdown/text renderer it uses). Relates to the conversation-rendering epic T-267 — could move there if preferred. -- Resolve id -> source: tickets via the pql wrapper (lib/src/pql), governance records via the decisions index/reader. -- Handle unknown/missing ids gracefully (render as plain text or a dead-link tooltip, no crash). - -Tests: linkifier unit tests (matches T/D/Q/R, ignores code spans, ignores bare words like ''T-shirt''); a widget/integration test that clicking a ref dispatches the reader-open.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-08 10:21:20', '2026-06-08 14:12:46', NULL, 'e9aba50ab6a83dd280b3e4a375782dab', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-284', 'bug', 'T-276', 'ClideMarquee ignores MediaQuery.disableAnimations (reduced-motion + pumpAndSettle hang)', 'Two animation mechanisms exist and are inconsistent. The turn/running indicator (running_indicator.dart, T-273) gates its AnimationController on MediaQuery.disableAnimations and stops when reduced-motion is set. ClideMarquee (clide_marquee.dart, the status-bar footer scroll, T-150/T-160) drives a raw Ticker whenever its content overflows, with NO disableAnimations check and no flag. +Tests: linkifier unit tests (matches T/D/Q/R, ignores code spans, ignores bare words like ''T-shirt''); a widget/integration test that clicking a ref dispatches the reader-open.', 'done', 'medium', NULL, NULL, 'D-6', '2026-06-08 10:21:20', '2026-06-08 14:12:46', NULL, 'bde704ef6fbb232fbe17ce5d26c3998c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KFHAS1WZFGBJ6JN4', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'ClideMarquee ignores MediaQuery.disableAnimations (reduced-motion + pumpAndSettle hang)', 'Two animation mechanisms exist and are inconsistent. The turn/running indicator (running_indicator.dart, T-273) gates its AnimationController on MediaQuery.disableAnimations and stops when reduced-motion is set. ClideMarquee (clide_marquee.dart, the status-bar footer scroll, T-150/T-160) drives a raw Ticker whenever its content overflows, with NO disableAnimations check and no flag. Two costs: 1. Accessibility — a reduced-motion user still gets the scrolling footer; the marquee ignores the OS reduce-motion setting the turn indicator honors. @@ -2201,34 +643,8 @@ Acceptance: 1. With MediaQuery(disableAnimations: true), an overflowing ClideMarquee does not scroll and pumpAndSettle completes (no hang). 2. With the flag off, overflow still scrolls as today. 3. Toggling the flag at runtime starts/stops the scroll. -4. Test covering the reduced-motion (no-scroll, settles) case.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 14:55:43', '2026-06-08 14:55:43', NULL, 'c347b735d788282c75fdb183eff05806', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-284', 'bug', 'T-276', 'ClideMarquee ignores MediaQuery.disableAnimations (reduced-motion + pumpAndSettle hang)', 'Two animation mechanisms exist and are inconsistent. The turn/running indicator (running_indicator.dart, T-273) gates its AnimationController on MediaQuery.disableAnimations and stops when reduced-motion is set. ClideMarquee (clide_marquee.dart, the status-bar footer scroll, T-150/T-160) drives a raw Ticker whenever its content overflows, with NO disableAnimations check and no flag. - -Two costs: -1. Accessibility — a reduced-motion user still gets the scrolling footer; the marquee ignores the OS reduce-motion setting the turn indicator honors. -2. Testing — the marquee''s perpetual Ticker is the historical pumpAndSettle-hang culprit; its own tests cope by only ever calling pump()/pump(Duration), never pumpAndSettle. - -Fix: unify on the one mechanism — make ClideMarquee honor MediaQuery.disableAnimations. When reduced motion is set, don''t start the ticker (stop + reset offset to 0 if running), and render the child statically (clipped) instead of looping. Read the flag in didChangeDependencies like running_indicator does. Then both animations obey one flag, the marquee becomes reduced-motion-correct, and it stops being a pumpAndSettle landmine. - -Acceptance: -1. With MediaQuery(disableAnimations: true), an overflowing ClideMarquee does not scroll and pumpAndSettle completes (no hang). -2. With the flag off, overflow still scrolls as today. -3. Toggling the flag at runtime starts/stops the scroll. -4. Test covering the reduced-motion (no-scroll, settles) case.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-08 14:55:43', '2026-06-08 14:55:51', NULL, '0a74870ce1d2e452c853fc42ba803919', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-284', 'bug', 'T-276', 'ClideMarquee ignores MediaQuery.disableAnimations (reduced-motion + pumpAndSettle hang)', 'Two animation mechanisms exist and are inconsistent. The turn/running indicator (running_indicator.dart, T-273) gates its AnimationController on MediaQuery.disableAnimations and stops when reduced-motion is set. ClideMarquee (clide_marquee.dart, the status-bar footer scroll, T-150/T-160) drives a raw Ticker whenever its content overflows, with NO disableAnimations check and no flag. - -Two costs: -1. Accessibility — a reduced-motion user still gets the scrolling footer; the marquee ignores the OS reduce-motion setting the turn indicator honors. -2. Testing — the marquee''s perpetual Ticker is the historical pumpAndSettle-hang culprit; its own tests cope by only ever calling pump()/pump(Duration), never pumpAndSettle. - -Fix: unify on the one mechanism — make ClideMarquee honor MediaQuery.disableAnimations. When reduced motion is set, don''t start the ticker (stop + reset offset to 0 if running), and render the child statically (clipped) instead of looping. Read the flag in didChangeDependencies like running_indicator does. Then both animations obey one flag, the marquee becomes reduced-motion-correct, and it stops being a pumpAndSettle landmine. - -Acceptance: -1. With MediaQuery(disableAnimations: true), an overflowing ClideMarquee does not scroll and pumpAndSettle completes (no hang). -2. With the flag off, overflow still scrolls as today. -3. Toggling the flag at runtime starts/stops the scroll. -4. Test covering the reduced-motion (no-scroll, settles) case.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 14:55:43', '2026-06-08 14:57:15', NULL, '31d8fd91f6d556a68d35c7d25410d7ec', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-285', 'bug', 'T-276', 'Conversation ListView items lack keys — card state mis-associates when results stream in', 'The conversation ListView.builder (conversation_view.dart:257) builds its items (_ConversationTurn, _ActivityCard) with NO keys. Each resolves to a stateful subtree (ConversationCard: _collapsed/_hover/focus nodes, with _collapsed set once and never re-synced in didUpdateWidget; ClideHolderCard owns cluster expand state). In a keyless list Flutter matches State to widgets by POSITION, so when the visible list reshapes, State attaches to the wrong logical card. +4. Test covering the reduced-motion (no-scroll, settles) case.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 14:55:43', '2026-06-08 14:57:15', NULL, '97144d20a2edf1911ee298421699fcc8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6GZ55VD4SAZZ6XXP4', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Conversation ListView items lack keys — card state mis-associates when results stream in', 'The conversation ListView.builder (conversation_view.dart:257) builds its items (_ConversationTurn, _ActivityCard) with NO keys. Each resolves to a stateful subtree (ConversationCard: _collapsed/_hover/focus nodes, with _collapsed set once and never re-synced in didUpdateWidget; ClideHolderCard owns cluster expand state). In a keyless list Flutter matches State to widgets by POSITION, so when the visible list reshapes, State attaches to the wrong logical card. The list reshapes exactly when a read/write result lands: T-262 folds a successful result into its call card and suppresses the standalone result via _visibleItems, errors append a sticky card, and activity clusters re-fold. So after a tool completes, collapse/hover state — and for a folded cluster, which tools/summary it shows — can jump to the wrong card. (The success/error mark itself is widget.status, recomputed per build, so it follows the widget; the stateful bits are what mis-associate.) @@ -2237,31 +653,11 @@ Fix: give each ListView item a stable ValueKey from its identity — sticky item Acceptance: 1. Streaming a tool-use then its success result keeps each card''s collapse state pinned to the right card (a card the user expanded stays expanded when a later tool completes). 2. A folded activity cluster keeps its identity/summary across reshapes. -3. Widget test: pump a conversation, toggle one card''s collapse, stream a new result that reshapes the list, assert the toggled card is still the one expanded.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-08 15:22:32', '2026-06-08 15:22:32', NULL, '0d7ef068ea04b9d4302f04b0c3134287', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-285', 'bug', 'T-276', 'Conversation ListView items lack keys — card state mis-associates when results stream in', 'The conversation ListView.builder (conversation_view.dart:257) builds its items (_ConversationTurn, _ActivityCard) with NO keys. Each resolves to a stateful subtree (ConversationCard: _collapsed/_hover/focus nodes, with _collapsed set once and never re-synced in didUpdateWidget; ClideHolderCard owns cluster expand state). In a keyless list Flutter matches State to widgets by POSITION, so when the visible list reshapes, State attaches to the wrong logical card. +3. Widget test: pump a conversation, toggle one card''s collapse, stream a new result that reshapes the list, assert the toggled card is still the one expanded.', 'done', 'high', NULL, NULL, NULL, '2026-06-08 15:22:32', '2026-06-08 15:37:29', NULL, 'fd360d075436a0459508bd994828b39a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W2MWZ45YNK7TJGC4', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Collapsed conversation card has inconsistent bottom padding', 'The collapsed Agent (tool-call) card in the Claude conversation view has different padding beneath it than the spacing around all other cards — see the gap flagged below the `> Agent` disclosure in the conversation. Make the collapsed card''s bottom padding match the standard inter-card spacing used everywhere else. -The list reshapes exactly when a read/write result lands: T-262 folds a successful result into its call card and suppresses the standalone result via _visibleItems, errors append a sticky card, and activity clusters re-fold. So after a tool completes, collapse/hover state — and for a folded cluster, which tools/summary it shows — can jump to the wrong card. (The success/error mark itself is widget.status, recomputed per build, so it follows the widget; the stateful bits are what mis-associate.) - -Fix: give each ListView item a stable ValueKey from its identity — sticky item by item.uuid, folded cluster by its first item''s uuid (namespaced so sticky vs cluster can''t collide) — plus add super.key to the _ConversationTurn/_ActivityCard constructors. Also key the nested run items in the Agent holder + activity card so sub-agent streams reshape safely. - -Acceptance: -1. Streaming a tool-use then its success result keeps each card''s collapse state pinned to the right card (a card the user expanded stays expanded when a later tool completes). -2. A folded activity cluster keeps its identity/summary across reshapes. -3. Widget test: pump a conversation, toggle one card''s collapse, stream a new result that reshapes the list, assert the toggled card is still the one expanded.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-08 15:22:32', '2026-06-08 15:22:35', NULL, 'f08107f9795fdf61f608e06e339a5002', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-285', 'bug', 'T-276', 'Conversation ListView items lack keys — card state mis-associates when results stream in', 'The conversation ListView.builder (conversation_view.dart:257) builds its items (_ConversationTurn, _ActivityCard) with NO keys. Each resolves to a stateful subtree (ConversationCard: _collapsed/_hover/focus nodes, with _collapsed set once and never re-synced in didUpdateWidget; ClideHolderCard owns cluster expand state). In a keyless list Flutter matches State to widgets by POSITION, so when the visible list reshapes, State attaches to the wrong logical card. - -The list reshapes exactly when a read/write result lands: T-262 folds a successful result into its call card and suppresses the standalone result via _visibleItems, errors append a sticky card, and activity clusters re-fold. So after a tool completes, collapse/hover state — and for a folded cluster, which tools/summary it shows — can jump to the wrong card. (The success/error mark itself is widget.status, recomputed per build, so it follows the widget; the stateful bits are what mis-associate.) - -Fix: give each ListView item a stable ValueKey from its identity — sticky item by item.uuid, folded cluster by its first item''s uuid (namespaced so sticky vs cluster can''t collide) — plus add super.key to the _ConversationTurn/_ActivityCard constructors. Also key the nested run items in the Agent holder + activity card so sub-agent streams reshape safely. - -Acceptance: -1. Streaming a tool-use then its success result keeps each card''s collapse state pinned to the right card (a card the user expanded stays expanded when a later tool completes). -2. A folded activity cluster keeps its identity/summary across reshapes. -3. Widget test: pump a conversation, toggle one card''s collapse, stream a new result that reshapes the list, assert the toggled card is still the one expanded.', 'done', 'high', NULL, NULL, NULL, '2026-06-08 15:22:32', '2026-06-08 15:37:29', NULL, '0d20b26d5b7a12af99c26cb2a849e123', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-282', 'bug', 'T-276', 'Collapsed conversation card has inconsistent bottom padding', 'The collapsed Agent (tool-call) card in the Claude conversation view has different padding beneath it than the spacing around all other cards — see the gap flagged below the `> Agent` disclosure in the conversation. Make the collapsed card''s bottom padding match the standard inter-card spacing used everywhere else. - -Card layout lives in lib/builtin/claude/src/conversation_card.dart: the outer wrapper applies EdgeInsets.only(bottom: 14) (line ~188) and the stripe variant adds inner EdgeInsets.fromLTRB(12, 8, 12, 8) (line ~210). When collapsed, only the header renders, so the combined bottom spacing (inner 8 + outer 14) reads differently from expanded cards. Reconcile so the visible gap below a collapsed card equals the gap below other cards.', 'done', 'low', NULL, NULL, NULL, '2026-06-08 13:39:15', '2026-06-08 15:42:01', NULL, '131affd221f44586d3a960dcaff57867', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'task', 'T-276', 'Frame0 design: permission-mode dropdown beside the Claude composer', 'Produce a Frame0 wireframe for a persistent permission-mode control next to the Claude conversation composer, then use it to drive implementation. +Card layout lives in lib/builtin/claude/src/conversation_card.dart: the outer wrapper applies EdgeInsets.only(bottom: 14) (line ~188) and the stripe variant adds inner EdgeInsets.fromLTRB(12, 8, 12, 8) (line ~210). When collapsed, only the header renders, so the combined bottom spacing (inner 8 + outer 14) reads differently from expanded cards. Reconcile so the visible gap below a collapsed card equals the gap below other cards.', 'done', 'low', NULL, NULL, NULL, '2026-06-08 13:39:15', '2026-06-08 15:42:01', NULL, '92ea10391ed6be62981f7f4117381d6f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5AAA9WW54JGP83HGW', 'task', '06FB0TNQM5R93N5J6RNVTBRB28', 'Frame0 design: permission-mode dropdown beside the Claude composer', 'Produce a Frame0 wireframe for a persistent permission-mode control next to the Claude conversation composer, then use it to drive implementation. Goal: surface the current permission mode (default · accept-edits · plan; bypass behind the cockpit guard) AT ALL TIMES, not only in the bottom status bar. Move/duplicate the mode affordance to an icon button sitting to the RIGHT of the composer text box (ClaudeComposer), showing the current mode via icon (+ short label), and opening a dropdown/popover menu to switch mode on click. @@ -2271,73 +667,8 @@ Design to wireframe (frame0-wireframe skill): - Keep it consistent with the existing status-bar mode badge (T-226: click or Ctrl/Cmd+M cycles the safe trio) — decide whether the status-bar badge stays, is removed, or mirrors the new control. - Token/geometry per the ui-design skill (no hardcoded hex; trailing-control geometry like the composer''s existing buttons). -Deliverable: a Frame0 wireframe (resting + open states, per-mode variants) exported, plus a short note on the chosen prompt-mode interaction, ready to implement. Relevant code: lib/builtin/claude/src/claude_composer.dart, claude_pane.dart (_ModeBadge / _statusWidget / _cycleMode), claude_status.dart (nextSafePermissionMode / permissionModeLabel).', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-08 07:28:34', '2026-06-08 15:45:13', NULL, '9411b3c37dec05c0efb130b120a574de', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'epic', 'T-276', 'Anchored popover + menu primitive; migrate anchored surfaces', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 16:30:27', '2026-06-08 16:30:27', NULL, '0da0822536199d472b3c166bf924da0d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'task', 'T-286', 'Frame0 design: permission-mode dropdown beside the Claude composer', 'Produce a Frame0 wireframe for a persistent permission-mode control next to the Claude conversation composer, then use it to drive implementation. - -Goal: surface the current permission mode (default · accept-edits · plan; bypass behind the cockpit guard) AT ALL TIMES, not only in the bottom status bar. Move/duplicate the mode affordance to an icon button sitting to the RIGHT of the composer text box (ClaudeComposer), showing the current mode via icon (+ short label), and opening a dropdown/popover menu to switch mode on click. - -Design to wireframe (frame0-wireframe skill): -- Composer row with the mode icon-button anchored at its right edge (trailing), aligned with the send/interrupt control; show resting state for each mode (distinct icon/color per mode) and the open-dropdown state listing selectable modes with the active one marked. -- Account for the interaction zone (D-78): when a permission/AskUserQuestion prompt replaces the composer, define what happens to the mode button (hidden? disabled? persists above?). -- Keep it consistent with the existing status-bar mode badge (T-226: click or Ctrl/Cmd+M cycles the safe trio) — decide whether the status-bar badge stays, is removed, or mirrors the new control. -- Token/geometry per the ui-design skill (no hardcoded hex; trailing-control geometry like the composer''s existing buttons). - -Deliverable: a Frame0 wireframe (resting + open states, per-mode variants) exported, plus a short note on the chosen prompt-mode interaction, ready to implement. Relevant code: lib/builtin/claude/src/claude_composer.dart, claude_pane.dart (_ModeBadge / _statusWidget / _cycleMode), claude_status.dart (nextSafePermissionMode / permissionModeLabel).', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-08 07:28:34', '2026-06-08 16:30:47', NULL, 'c5616a1e54caf9d9bf91c02f036617c4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-275', 'task', 'T-286', 'Frame0 design: permission-mode dropdown beside the Claude composer', 'Produce a Frame0 wireframe for a persistent permission-mode control next to the Claude conversation composer, then use it to drive implementation. - -Goal: surface the current permission mode (default · accept-edits · plan; bypass behind the cockpit guard) AT ALL TIMES, not only in the bottom status bar. Move/duplicate the mode affordance to an icon button sitting to the RIGHT of the composer text box (ClaudeComposer), showing the current mode via icon (+ short label), and opening a dropdown/popover menu to switch mode on click. - -Design to wireframe (frame0-wireframe skill): -- Composer row with the mode icon-button anchored at its right edge (trailing), aligned with the send/interrupt control; show resting state for each mode (distinct icon/color per mode) and the open-dropdown state listing selectable modes with the active one marked. -- Account for the interaction zone (D-78): when a permission/AskUserQuestion prompt replaces the composer, define what happens to the mode button (hidden? disabled? persists above?). -- Keep it consistent with the existing status-bar mode badge (T-226: click or Ctrl/Cmd+M cycles the safe trio) — decide whether the status-bar badge stays, is removed, or mirrors the new control. -- Token/geometry per the ui-design skill (no hardcoded hex; trailing-control geometry like the composer''s existing buttons). - -Deliverable: a Frame0 wireframe (resting + open states, per-mode variants) exported, plus a short note on the chosen prompt-mode interaction, ready to implement. Relevant code: lib/builtin/claude/src/claude_composer.dart, claude_pane.dart (_ModeBadge / _statusWidget / _cycleMode), claude_status.dart (nextSafePermissionMode / permissionModeLabel).', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-08 07:28:34', '2026-06-08 16:52:13', NULL, '1c72f135bee789826ba7aa17a8e4569f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'bug', NULL, 'GLib-GIO-CRITICAL: GFileInfo created without standard::size on startup (Linux)', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: - - GLib-GIO-CRITICAL: GFileInfo created without standard::size - file ../gio/gfileinfo.c: line 1865 (g_file_info_get_size): should not be reached - -Repro: launch the app (make run / flutter run) on Linux. The pair fires early in boot — right after the primary pane binds its session and before the IPC server starts listening (observed ~0.3s apart, e.g. 15:53:15.570 and 15:53:15.832), on every launch. - -Cause: some code path calls g_file_info_get_size() on a GFileInfo that was created/queried WITHOUT requesting the G_FILE_ATTRIBUTE_STANDARD_SIZE (''standard::size'') attribute. A grep of clide''s own code (lib/, linux/runner/, native/) finds no direct g_file_info / g_file_query_info usage, so it is most likely inside GTK/GLib itself or a Flutter Linux plugin''s file enumeration (icon/thumbnail/mime probe, path lookups), not clide Dart/C++. The GTK file-chooser in linux/runner/clide_app.cc is on-demand only, so it is not the trigger (the warnings fire at boot). - -Impact: low — console noise at CRITICAL level; no observed functional breakage. But a size query that ''should not be reached'' may be reading a bogus/zero size somewhere worth confirming. - -Investigation: run with G_DEBUG=fatal-warnings (or gdb break on g_log/g_logv) to capture the stack at the warning and identify the library/plugin frame; check whether a Flutter plugin (file_selector, path_provider, url_launcher) or GTK icon/mime loading is responsible. If upstream/GTK, document + suppress-from-our-side or pin; if a plugin, file upstream. - -Env: Fedora, GTK Linux embedder, flutter run.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-08 17:12:49', '2026-06-08 17:12:49', NULL, 'ff6e04e755a889ef090b498e560b83f1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'epic', 'T-276', 'Anchored popover + menu primitive; migrate anchored surfaces', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-08 16:30:27', '2026-06-08 17:34:29', NULL, 'ce3217282170e264b73c83eeb862d68d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'epic', 'T-276', 'Anchored popover + menu primitive; migrate anchored surfaces', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-08 16:30:27', '2026-06-08 17:34:31', NULL, '943af41d72d9e982968f0468c36a51d3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'story', 'T-4', 'editor respects .editorconfig settings', '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.', 'ready', 'medium', NULL, NULL, NULL, '2026-04-22 20:17:26', '2026-06-08 17:37:13', NULL, 'e41d7c517b91e5c6387b1abea44f42ae', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-288', 'task', 'T-286', 'Migrate remaining anchored surfaces onto ClideAnchoredOverlay (theme picker, typeaheads, quick-open)', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. - -Blockers found (theme picker attempt): -1. Focus-capture race. ClideAnchoredOverlay.captureFocus does a post-frame _scope.requestFocus(); ClideMenu also autofocuses its own node. Depending on mount timing (e.g. an extra ListenableBuilder wrapper) the scope wins and STEALS focus from ClideMenu''s node, so arrow/enter never reach the menu (Esc still works only via the ancestor fallback). Menu bar happens to win the race; the theme popover loses. Fix the primitive''s focus model so content''s own focus node reliably ends up focused (e.g. don''t force scope focus; or make the scope delegate to the autofocus child deterministically), then re-verify menu bar + anchored tests. -2. Follower content is not reliably mouse-tappable in the widget-test harness. The shared harness() uses Overlay(canSizeOverlay) + a zero-size MediaQuery, so an anchored follower positions the panel off-screen (observed tap offsets like (680,-45) and (680,654)) and tester.tap misses. Also autoFlip reads MediaQuery.size (zero in harness) so it can''t flip. Mitigations: have autoFlip use View.physicalSize (more correct anyway); position content without the inner Align (the panel shrink-wraps); and in tests drive selection via keyboard (as menu_bar does) or provide a real viewport. Consider a test helper for anchored-overlay content. - -Then migrate, one surface per commit, keeping each existing test green (theme_picker widget_test, team_chat_sidebar_test, claude_composer_test, quick_open_overlay_test). Quick-open is centered (no follower) and the weakest-fit — it only needs the lifecycle wrapper; migrate last or skip. - -Refs: D-88, T-286, T-275 (done), menu bar (done, commit e1d51eb).', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 17:52:51', '2026-06-08 17:52:51', NULL, '52f760eec6f108ce83041d7fd98f192e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-288', 'task', 'T-286', 'Migrate remaining anchored surfaces onto ClideAnchoredOverlay (theme picker, typeaheads, quick-open)', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. - -Blockers found (theme picker attempt): -1. Focus-capture race. ClideAnchoredOverlay.captureFocus does a post-frame _scope.requestFocus(); ClideMenu also autofocuses its own node. Depending on mount timing (e.g. an extra ListenableBuilder wrapper) the scope wins and STEALS focus from ClideMenu''s node, so arrow/enter never reach the menu (Esc still works only via the ancestor fallback). Menu bar happens to win the race; the theme popover loses. Fix the primitive''s focus model so content''s own focus node reliably ends up focused (e.g. don''t force scope focus; or make the scope delegate to the autofocus child deterministically), then re-verify menu bar + anchored tests. -2. Follower content is not reliably mouse-tappable in the widget-test harness. The shared harness() uses Overlay(canSizeOverlay) + a zero-size MediaQuery, so an anchored follower positions the panel off-screen (observed tap offsets like (680,-45) and (680,654)) and tester.tap misses. Also autoFlip reads MediaQuery.size (zero in harness) so it can''t flip. Mitigations: have autoFlip use View.physicalSize (more correct anyway); position content without the inner Align (the panel shrink-wraps); and in tests drive selection via keyboard (as menu_bar does) or provide a real viewport. Consider a test helper for anchored-overlay content. - -Then migrate, one surface per commit, keeping each existing test green (theme_picker widget_test, team_chat_sidebar_test, claude_composer_test, quick_open_overlay_test). Quick-open is centered (no follower) and the weakest-fit — it only needs the lifecycle wrapper; migrate last or skip. - -Refs: D-88, T-286, T-275 (done), menu bar (done, commit e1d51eb). - -RE-SCOPE (2026-06-08, per D-88 amendment): the goal is NOT ''migrate everything onto ClideMenu''. The shared base is ClideAnchoredOverlay; content matches the surface: -- ClideMenu stays scoped to selectable-list menus (menu bar + permission picker — done). -- Add a NEW shared content component ClideTypeahead for the slash + @ typeaheads (near-duplicate caret-anchored completion surfaces) — they share this, not ClideMenu. This dedups two hand-rolls. -- Quick-open keeps bespoke content (centred filter + fuzzy + two-column rows); uses ClideAnchoredOverlay base only. Weakest fit; last or skip. -- Theme picker: its own small toggle+list content on the base (or ClideMenu if it cleanly fits). -Order: (1) fix the base blockers — focus capture racing content autofocus, and follower content untappable in the canSizeOverlay/zero-MediaQuery test harness (use View size for autoFlip; add a test path for anchored content) — with tests; THEN (2) ClideTypeahead + migrate slash + @; (3) theme picker; (4) quick-open last.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 17:52:51', '2026-06-08 20:12:31', NULL, '254250a0c8666da7273e17f4a9c0d8fb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-289', 'story', 'T-276', 'Right-click context menus with context-aware actions', 'Add right-click (secondary-tap / context-menu key) handling across clide surfaces, opening a context menu anchored at the pointer. +Deliverable: a Frame0 wireframe (resting + open states, per-mode variants) exported, plus a short note on the chosen prompt-mode interaction, ready to implement. Relevant code: lib/builtin/claude/src/claude_composer.dart, claude_pane.dart (_ModeBadge / _statusWidget / _cycleMode), claude_status.dart (nextSafePermissionMode / permissionModeLabel).', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-08 07:28:34', '2026-06-08 16:52:13', NULL, 'dfbe1e3e3ea200d993d4e860da4d6769', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6NNRR61E8P6GHXABG', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Right-click context menus with context-aware actions', 'Add right-click (secondary-tap / context-menu key) handling across clide surfaces, opening a context menu anchored at the pointer. **Build on existing primitives.** Reuse `ClideAnchoredOverlay` + `ClideMenu` (D-88, landed via T-286/T-288) for the popup — do not introduce a new menu widget. The context menu is just another content component over the shared anchored-overlay base, opened on secondary tap instead of a click target. @@ -2350,8 +681,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, **Architecture.** Define a small contract so a surface can declare its context-menu items for a given target (the right-clicked element + its state), which get merged with the baseline edit actions. Honour user/Claude parity (D-6): each injected action should map to an existing `clide` verb / command where one exists, so the menu is a discoverable front-end to commands Claude can also invoke. -**Scope notes.** Start with file navigator + git pane as the two concrete consumers (they motivate the feature). Keyboard parity: context-menu key + Shift+F10 should open the same menu for the focused element. Accessibility: menu items reachable and labelled per the a11y contract.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 20:16:22', '2026-06-08 20:16:22', NULL, 'd09051ca5ca83fbc3a4e0fd63c5a56e8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-288', 'task', 'T-286', 'Migrate remaining anchored surfaces onto ClideAnchoredOverlay (theme picker, typeaheads, quick-open)', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. +**Scope notes.** Start with file navigator + git pane as the two concrete consumers (they motivate the feature). Keyboard parity: context-menu key + Shift+F10 should open the same menu for the focused element. Accessibility: menu items reachable and labelled per the a11y contract.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 20:16:22', '2026-06-08 20:16:22', NULL, 'cc6e2f2b7a787cc55b876ce031d7a5f4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4501EBPDFDH84RJ5G', 'task', '06FB0TNQM5R93N5J6RNVTBRB28', 'Migrate remaining anchored surfaces onto ClideAnchoredOverlay (theme picker, typeaheads, quick-open)', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. Blockers found (theme picker attempt): 1. Focus-capture race. ClideAnchoredOverlay.captureFocus does a post-frame _scope.requestFocus(); ClideMenu also autofocuses its own node. Depending on mount timing (e.g. an extra ListenableBuilder wrapper) the scope wins and STEALS focus from ClideMenu''s node, so arrow/enter never reach the menu (Esc still works only via the ancestor fallback). Menu bar happens to win the race; the theme popover loses. Fix the primitive''s focus model so content''s own focus node reliably ends up focused (e.g. don''t force scope focus; or make the scope delegate to the autofocus child deterministically), then re-verify menu bar + anchored tests. @@ -2368,64 +699,16 @@ RE-SCOPE (2026-06-08, per D-88 amendment): the goal is NOT ''migrate everything - Theme picker: its own small toggle+list content on the base (or ClideMenu if it cleanly fits). Order: (1) fix the base blockers — focus capture racing content autofocus, and follower content untappable in the canSizeOverlay/zero-MediaQuery test harness (use View size for autoFlip; add a test path for anchored content) — with tests; THEN (2) ClideTypeahead + migrate slash + @; (3) theme picker; (4) quick-open last. -DONE: Sweep complete. Base blockers fixed (View-size autoFlip; follower drops Align so it hit-tests; tests use anchoredHarness). Migrated: ClideTypeahead + slash + @-mention typeaheads (live ValueNotifier-bridged suggestions); theme picker onto ClideMenu (HC toggle = keepOpenOnSelect item). Quick-open deliberately NOT migrated — persistent centred widget sharing neither ClideMenu nor anchoring; rationale in D-88 closing amendment.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 17:52:51', '2026-06-09 06:18:55', NULL, 'fa7b612a8ccedb88a8c4753f669b016b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-288', 'task', 'T-286', 'Migrate remaining anchored surfaces onto ClideAnchoredOverlay (theme picker, typeaheads, quick-open)', 'Follow-up to the D-88 sweep. The primitive (ClideAnchoredOverlay + ClideMenu) shipped and the menu bar (T-286) + T-275 picker are migrated. The theme-picker migration was attempted and REVERTED after hitting two real blockers that affect all the remaining anchored surfaces; resolve these first, then migrate theme picker, @-mention typeahead, slash typeahead, and quick-open. +DONE: Sweep complete. Base blockers fixed (View-size autoFlip; follower drops Align so it hit-tests; tests use anchoredHarness). Migrated: ClideTypeahead + slash + @-mention typeaheads (live ValueNotifier-bridged suggestions); theme picker onto ClideMenu (HC toggle = keepOpenOnSelect item). Quick-open deliberately NOT migrated — persistent centred widget sharing neither ClideMenu nor anchoring; rationale in D-88 closing amendment.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 17:52:51', '2026-06-09 06:19:01', NULL, 'c67bf2dd6b082be4de1f53b2f89b4bb3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5R93N5J6RNVTBRB28', 'epic', '06FB0TNQM5TWC00GW0P3X02HZW', 'Anchored popover + menu primitive; migrate anchored surfaces', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration. -Blockers found (theme picker attempt): -1. Focus-capture race. ClideAnchoredOverlay.captureFocus does a post-frame _scope.requestFocus(); ClideMenu also autofocuses its own node. Depending on mount timing (e.g. an extra ListenableBuilder wrapper) the scope wins and STEALS focus from ClideMenu''s node, so arrow/enter never reach the menu (Esc still works only via the ancestor fallback). Menu bar happens to win the race; the theme popover loses. Fix the primitive''s focus model so content''s own focus node reliably ends up focused (e.g. don''t force scope focus; or make the scope delegate to the autofocus child deterministically), then re-verify menu bar + anchored tests. -2. Follower content is not reliably mouse-tappable in the widget-test harness. The shared harness() uses Overlay(canSizeOverlay) + a zero-size MediaQuery, so an anchored follower positions the panel off-screen (observed tap offsets like (680,-45) and (680,654)) and tester.tap misses. Also autoFlip reads MediaQuery.size (zero in harness) so it can''t flip. Mitigations: have autoFlip use View.physicalSize (more correct anyway); position content without the inner Align (the panel shrink-wraps); and in tests drive selection via keyboard (as menu_bar does) or provide a real viewport. Consider a test helper for anchored-overlay content. +DONE: Primitive (ClideAnchoredOverlay + ClideMenu + ClideMenuListController + ClideTypeahead) shipped and exported. Migrated: T-275 permission picker, menu bar, theme picker (ClideMenu), @-mention + slash typeaheads (ClideTypeahead). Quick-open intentionally left bespoke (centred persistent widget, no shared shape) — see D-88 closing amendment. Modal session/project/branch pickers stay on DialogRouter by design.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 16:30:27', '2026-06-09 06:19:36', NULL, '05f165342aa1d3708ca2d0f6e204c23e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM44CKN5QQGFKP1KWZC', 'story', '06FB0TNQM6T6580D8ABDVTMNZW', 'Edit editor settings (.editorconfig) from the settings panel', 'Write path for editor settings (T-29 follow-up). The editor now READS .editorconfig into a source-agnostic EditorSettings model (lib/src/editor/editor_settings.dart) and resolves it via editor_settings_resolver.dart. Add a settings-panel surface to EDIT these values and persist them — initially by writing the project .editorconfig (indent_style/size, end_of_line, max_line_length, trim_trailing_whitespace, insert_final_newline), later possibly a clide-owned settings file that merges as a higher-precedence source in resolveEditorSettings. On save the registry already re-resolves open buffers (editor.settings-changed), so the panel edits apply live. Respect EditorConfig semantics (sections/globs); start with the [*] section for the workspace defaults. Refs: T-29, D-88, editor_settings.dart, editor_settings_resolver.dart.', 'backlog', 'medium', NULL, NULL, 'D-88', '2026-06-09 07:15:14', '2026-06-09 07:15:14', NULL, '4ff37d3b847a5a0cbe89bab48aa27596', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM45BSF1B0R0JA1Z7FC', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'Re-resolve .editorconfig on external file changes (fs-watch)', 'T-29 follow-up. Today open buffers re-resolve their EditorSettings when a .editorconfig is saved IN-APP (a hook in EditorRegistry.save → editor.settings-changed). External edits — another editor, a git checkout/branch switch — are NOT picked up until reopen. Wire a filesystem watch so an external .editorconfig change re-resolves open buffers too. Prefer piggybacking the files subsystem''s existing watcher over a new dedicated watch; debounce; only re-resolve buffers under the changed config''s directory. Rare case, hence deferred from the initial T-29 work (the in-app save hook covers the common path). Refs: T-29, lib/src/editor/editor_settings_resolver.dart, EditorRegistry._reresolveSettings.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-09 07:15:22', '2026-06-09 07:15:22', NULL, 'ac3fcdaa95194c311e19fc22aef6fd59', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5E41Q8TX8X55X4MZ0', 'story', '06FB0TNQM5GSR5WPC2T7CJM8XW', 'editor respects .editorconfig settings', '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. -Then migrate, one surface per commit, keeping each existing test green (theme_picker widget_test, team_chat_sidebar_test, claude_composer_test, quick_open_overlay_test). Quick-open is centered (no follower) and the weakest-fit — it only needs the lifecycle wrapper; migrate last or skip. - -Refs: D-88, T-286, T-275 (done), menu bar (done, commit e1d51eb). - -RE-SCOPE (2026-06-08, per D-88 amendment): the goal is NOT ''migrate everything onto ClideMenu''. The shared base is ClideAnchoredOverlay; content matches the surface: -- ClideMenu stays scoped to selectable-list menus (menu bar + permission picker — done). -- Add a NEW shared content component ClideTypeahead for the slash + @ typeaheads (near-duplicate caret-anchored completion surfaces) — they share this, not ClideMenu. This dedups two hand-rolls. -- Quick-open keeps bespoke content (centred filter + fuzzy + two-column rows); uses ClideAnchoredOverlay base only. Weakest fit; last or skip. -- Theme picker: its own small toggle+list content on the base (or ClideMenu if it cleanly fits). -Order: (1) fix the base blockers — focus capture racing content autofocus, and follower content untappable in the canSizeOverlay/zero-MediaQuery test harness (use View size for autoFlip; add a test path for anchored content) — with tests; THEN (2) ClideTypeahead + migrate slash + @; (3) theme picker; (4) quick-open last. - -DONE: Sweep complete. Base blockers fixed (View-size autoFlip; follower drops Align so it hit-tests; tests use anchoredHarness). Migrated: ClideTypeahead + slash + @-mention typeaheads (live ValueNotifier-bridged suggestions); theme picker onto ClideMenu (HC toggle = keepOpenOnSelect item). Quick-open deliberately NOT migrated — persistent centred widget sharing neither ClideMenu nor anchoring; rationale in D-88 closing amendment.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 17:52:51', '2026-06-09 06:19:01', NULL, 'd70b0bb462c64d62ee7815855539a114', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'epic', 'T-276', 'Anchored popover + menu primitive; migrate anchored surfaces', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration. - -DONE: Primitive (ClideAnchoredOverlay + ClideMenu + ClideMenuListController + ClideTypeahead) shipped and exported. Migrated: T-275 permission picker, menu bar, theme picker (ClideMenu), @-mention + slash typeaheads (ClideTypeahead). Quick-open intentionally left bespoke (centred persistent widget, no shared shape) — see D-88 closing amendment. Modal session/project/branch pickers stay on DialogRouter by design.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-08 16:30:27', '2026-06-09 06:19:33', NULL, '952557ea525092b6c341f6bdc1bf4380', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-286', 'epic', 'T-276', 'Anchored popover + menu primitive; migrate anchored surfaces', 'Extract a clide-owned anchored-popover + menu widget primitive (lib/widgets/, no Material) and migrate every anchored surface onto it. Nine surfaces hand-roll the same anchored-overlay + row-list + barrier + keyboard-nav pattern. Two layers: ClideAnchoredOverlay (positioning/lifecycle: LayerLink/follower or centered, barrier, Esc, focus capture, autoFlip) + ClideMenu / ClideMenuListController (rows + reusable nav). Modal DialogRouter pickers (session/project/branch) stay modal. Built on T-275''s permission-mode picker first, then migrate menu bar, theme picker, @-mention, slash typeahead, quick-open. See decision (architecture domain) + plan. Children: primitive, T-275 picker, one per migration. - -DONE: Primitive (ClideAnchoredOverlay + ClideMenu + ClideMenuListController + ClideTypeahead) shipped and exported. Migrated: T-275 permission picker, menu bar, theme picker (ClideMenu), @-mention + slash typeaheads (ClideTypeahead). Quick-open intentionally left bespoke (centred persistent widget, no shared shape) — see D-88 closing amendment. Modal session/project/branch pickers stay on DialogRouter by design.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 16:30:27', '2026-06-09 06:19:36', NULL, 'e6c9a33aa6d9ca9802fc93bd2b526845', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'bug', 'T-276', 'Slash-command typeahead breaks on ''-''; Tab-to-complete is flaky', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart): - -1. Typing a ''-'' character breaks the typeahead. Many command names contain hyphens (e.g. clear-context-style names, custom commands), but typing ''-'' appears to drop/empty the suggestion list or mis-parse the active query. Suspect activeSlashQuery''s token run or filterSlashCommands prefix matching not handling ''-'' (or the composer treating ''-'' as a boundary). - -2. Tab to accept the highlighted suggestion responds flakily — sometimes it completes, sometimes nothing happens. Suspect a focus/key-handling race between the composer''s key handler and the typeahead overlay, or Tab being consumed by focus traversal before the accept intent fires. - -Repro: open the composer, type ''/'' then a command fragment; (a) include a ''-'' and watch the list; (b) arrow-select an item and press Tab repeatedly. - -Acceptance: ''-'' is treated as a normal command-name character (suggestions keep filtering through hyphens); Tab reliably completes the highlighted suggestion every time (insert via completeSlash). Add/extend unit tests in slash_commands_test.dart for hyphenated queries and a composer widget test for Tab-accept.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-08 09:20:00', '2026-06-09 06:33:31', NULL, 'f3d2e1c7b6efdaf6bc426282cf3275dc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'story', 'T-4', 'editor respects .editorconfig settings', '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.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-22 20:17:26', '2026-06-09 06:34:31', NULL, '9c6056aa6b5b5eabf0df7792c6c636f6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-290', 'story', 'T-8', 'Edit editor settings (.editorconfig) from the settings panel', 'Write path for editor settings (T-29 follow-up). The editor now READS .editorconfig into a source-agnostic EditorSettings model (lib/src/editor/editor_settings.dart) and resolves it via editor_settings_resolver.dart. Add a settings-panel surface to EDIT these values and persist them — initially by writing the project .editorconfig (indent_style/size, end_of_line, max_line_length, trim_trailing_whitespace, insert_final_newline), later possibly a clide-owned settings file that merges as a higher-precedence source in resolveEditorSettings. On save the registry already re-resolves open buffers (editor.settings-changed), so the panel edits apply live. Respect EditorConfig semantics (sections/globs); start with the [*] section for the workspace defaults. Refs: T-29, D-88, editor_settings.dart, editor_settings_resolver.dart.', 'backlog', 'medium', NULL, NULL, 'D-88', '2026-06-09 07:15:14', '2026-06-09 07:15:14', NULL, '54bd063a8718b263a316cfac4f23b40e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-291', 'task', 'T-8', 'Re-resolve .editorconfig on external file changes (fs-watch)', 'T-29 follow-up. Today open buffers re-resolve their EditorSettings when a .editorconfig is saved IN-APP (a hook in EditorRegistry.save → editor.settings-changed). External edits — another editor, a git checkout/branch switch — are NOT picked up until reopen. Wire a filesystem watch so an external .editorconfig change re-resolves open buffers too. Prefer piggybacking the files subsystem''s existing watcher over a new dedicated watch; debounce; only re-resolve buffers under the changed config''s directory. Rare case, hence deferred from the initial T-29 work (the in-app save hook covers the common path). Refs: T-29, lib/src/editor/editor_settings_resolver.dart, EditorRegistry._reresolveSettings.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-09 07:15:22', '2026-06-09 07:15:22', NULL, '032edf9cabf61178afb7d324e3273511', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'story', 'T-4', 'editor respects .editorconfig settings', '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. - -DONE: The editor honours .editorconfig end-to-end. New source-agnostic EditorSettings model (editor_settings.dart) + composition seam (editor_settings_resolver.dart); .editorconfig demoted to a source (editorconfig.dart, own INI parser + glob matcher, root/nearest-wins precedence, no deps). Registry resolves on open and re-resolves open buffers when a .editorconfig is saved in-app (editor.settings-changed; a save hook, not an fs-watch). Editor: Tab/Shift+Tab indent + max_line_length ruler; save applies end_of_line/trim_trailing_whitespace/insert_final_newline. Follow-ups: T-290 (edit from settings panel), T-291 (external fs-watch). 100% coverage on new model/resolver/parser.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-22 20:17:26', '2026-06-09 10:46:41', NULL, '97f3d9465bb7c4f75f534ce09b204937', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'story', 'T-4', 'editor respects .editorconfig settings', '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. - -DONE: The editor honours .editorconfig end-to-end. New source-agnostic EditorSettings model (editor_settings.dart) + composition seam (editor_settings_resolver.dart); .editorconfig demoted to a source (editorconfig.dart, own INI parser + glob matcher, root/nearest-wins precedence, no deps). Registry resolves on open and re-resolves open buffers when a .editorconfig is saved in-app (editor.settings-changed; a save hook, not an fs-watch). Editor: Tab/Shift+Tab indent + max_line_length ruler; save applies end_of_line/trim_trailing_whitespace/insert_final_newline. Follow-ups: T-290 (edit from settings panel), T-291 (external fs-watch). 100% coverage on new model/resolver/parser.', 'done', 'medium', NULL, NULL, NULL, '2026-04-22 20:17:26', '2026-06-09 10:46:41', NULL, '4904a5ad57b5508b37e13241d039d71a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'bug', NULL, 'Flaky: pty session_test ''write sends keystrokes to child'' starves under parallel/coverage load', 'test/pty/session_test.dart:47 ''write sends keystrokes to child'' (and its sibling pty integration tests) intermittently fail when run in a large parallel pool — observed twice during T-29: once in a bare ''flutter test --coverage'' (whole suite, max parallelism + coverage instrumentation) and once mid pre-push gate (''+570 -1''). Passes reliably in isolation and via the gate''s SERIAL test-core pass (''dart test test/pty'', +571 ok), so it is not a logic bug. - -Root cause: these tests spawn a real /bin/sh via NativePty and wait on the reader ISOLATE to deliver bytes. Under heavy CPU contention the reader isolate is starved and the 20s ioTimeout (test/helpers/timeouts.dart) elapses before the shell''s first byte / echo response is delivered. Already hardened once (T-108 swapped wall-clock sleeps for event-driven waits; first-byte Completer + 25ms-poll _waitForBuffer), but the isolate-scheduling assumption still breaks under load. - -These tests are tagged ''pty'' and are meant to run in the gate''s serial pass. Fix options: (a) ensure the parallel coverage pass EXCLUDES tag:pty (run pty only in the serial pass) so they never compete for isolate scheduling — verify ci/test.sh / dart_test.yaml tag routing; (b) harden against isolate starvation (e.g. give the reader isolate priority, or assert on delivery via a more robust signal); (c) document that ''flutter test --coverage'' over the whole tree is not a supported invocation (use make test + the gate). Prefer (a). Refs: T-108, T-96 (reader-isolate hangs), T-192 (gate parallel/serial passes), D-23 (test pyramid).', 'backlog', 'medium', NULL, NULL, 'D-23', '2026-06-09 14:57:32', '2026-06-09 14:57:32', NULL, 'aa9a060645d02824f2d6c5389b812c8b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'bug', 'T-276', 'Slash-command typeahead breaks on ''-''; Tab-to-complete is flaky', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart): - -1. Typing a ''-'' character breaks the typeahead. Many command names contain hyphens (e.g. clear-context-style names, custom commands), but typing ''-'' appears to drop/empty the suggestion list or mis-parse the active query. Suspect activeSlashQuery''s token run or filterSlashCommands prefix matching not handling ''-'' (or the composer treating ''-'' as a boundary). - -2. Tab to accept the highlighted suggestion responds flakily — sometimes it completes, sometimes nothing happens. Suspect a focus/key-handling race between the composer''s key handler and the typeahead overlay, or Tab being consumed by focus traversal before the accept intent fires. - -Repro: open the composer, type ''/'' then a command fragment; (a) include a ''-'' and watch the list; (b) arrow-select an item and press Tab repeatedly. - -Acceptance: ''-'' is treated as a normal command-name character (suggestions keep filtering through hyphens); Tab reliably completes the highlighted suggestion every time (insert via completeSlash). Add/extend unit tests in slash_commands_test.dart for hyphenated queries and a composer widget test for Tab-accept.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-08 09:20:00', '2026-06-09 14:59:45', NULL, 'f0cae2f4ba80ad98e2115d4d3cf1a11f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'bug', 'T-276', 'Slash-command typeahead breaks on ''-''; Tab-to-complete is flaky', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart): +DONE: The editor honours .editorconfig end-to-end. New source-agnostic EditorSettings model (editor_settings.dart) + composition seam (editor_settings_resolver.dart); .editorconfig demoted to a source (editorconfig.dart, own INI parser + glob matcher, root/nearest-wins precedence, no deps). Registry resolves on open and re-resolves open buffers when a .editorconfig is saved in-app (editor.settings-changed; a save hook, not an fs-watch). Editor: Tab/Shift+Tab indent + max_line_length ruler; save applies end_of_line/trim_trailing_whitespace/insert_final_newline. Follow-ups: T-290 (edit from settings panel), T-291 (external fs-watch). 100% coverage on new model/resolver/parser.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-22 20:17:26', '2026-06-09 10:46:41', NULL, '6d078026407dc764d9273b2fe9b94a70', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50BPNDT50PM24ZVA4', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Slash-command typeahead breaks on ''-''; Tab-to-complete is flaky', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart): 1. Typing a ''-'' character breaks the typeahead. Many command names contain hyphens (e.g. clear-context-style names, custom commands), but typing ''-'' appears to drop/empty the suggestion list or mis-parse the active query. Suspect activeSlashQuery''s token run or filterSlashCommands prefix matching not handling ''-'' (or the composer treating ''-'' as a boundary). @@ -2435,246 +718,15 @@ Repro: open the composer, type ''/'' then a command fragment; (a) include a ''-' Acceptance: ''-'' is treated as a normal command-name character (suggestions keep filtering through hyphens); Tab reliably completes the highlighted suggestion every time (insert via completeSlash). Add/extend unit tests in slash_commands_test.dart for hyphenated queries and a composer widget test for Tab-accept. -RESOLVED by the D-88 ClideTypeahead migration (T-286), verified 2026-06-09. Root cause of both symptoms was the OLD hand-rolled overlay, not the pure helpers (activeSlashQuery/filterSlashCommands always handled ''-''): (1) the old overlay didn''t narrow live as you typed, so a ''-'' looked like it emptied/broke the list; (2) the old focus model raced Tab. Post-migration the popover narrows live (ValueNotifier) and the field keeps focus (captureFocus:false), so ''-'' filters normally and Tab reliably completes. Confirmed bare ''-'' is NOT a keybinding (zoom is ctrl/meta+minus). Added regression tests: hyphen cases in slash_commands_test; composer widget test typing through a hyphen + Tab-accept. Commit d8b9a41.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-08 09:20:00', '2026-06-09 15:03:23', NULL, '703fe814a93d496788d2e3086847566f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'bug', 'T-276', 'Slash-command typeahead breaks on ''-''; Tab-to-complete is flaky', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart): - -1. Typing a ''-'' character breaks the typeahead. Many command names contain hyphens (e.g. clear-context-style names, custom commands), but typing ''-'' appears to drop/empty the suggestion list or mis-parse the active query. Suspect activeSlashQuery''s token run or filterSlashCommands prefix matching not handling ''-'' (or the composer treating ''-'' as a boundary). - -2. Tab to accept the highlighted suggestion responds flakily — sometimes it completes, sometimes nothing happens. Suspect a focus/key-handling race between the composer''s key handler and the typeahead overlay, or Tab being consumed by focus traversal before the accept intent fires. - -Repro: open the composer, type ''/'' then a command fragment; (a) include a ''-'' and watch the list; (b) arrow-select an item and press Tab repeatedly. - -Acceptance: ''-'' is treated as a normal command-name character (suggestions keep filtering through hyphens); Tab reliably completes the highlighted suggestion every time (insert via completeSlash). Add/extend unit tests in slash_commands_test.dart for hyphenated queries and a composer widget test for Tab-accept. - -RESOLVED by the D-88 ClideTypeahead migration (T-286), verified 2026-06-09. Root cause of both symptoms was the OLD hand-rolled overlay, not the pure helpers (activeSlashQuery/filterSlashCommands always handled ''-''): (1) the old overlay didn''t narrow live as you typed, so a ''-'' looked like it emptied/broke the list; (2) the old focus model raced Tab. Post-migration the popover narrows live (ValueNotifier) and the field keeps focus (captureFocus:false), so ''-'' filters normally and Tab reliably completes. Confirmed bare ''-'' is NOT a keybinding (zoom is ctrl/meta+minus). Added regression tests: hyphen cases in slash_commands_test; composer widget test typing through a hyphen + Tab-accept. Commit d8b9a41.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 09:20:00', '2026-06-09 15:03:23', NULL, 'e763ef44b7aee33629548cdb3529aa11', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'bug', NULL, 'Flaky: pty session_test ''write sends keystrokes to child'' starves under parallel/coverage load', 'test/pty/session_test.dart:47 ''write sends keystrokes to child'' (and its sibling pty integration tests) intermittently fail when run in a large parallel pool — observed twice during T-29: once in a bare ''flutter test --coverage'' (whole suite, max parallelism + coverage instrumentation) and once mid pre-push gate (''+570 -1''). Passes reliably in isolation and via the gate''s SERIAL test-core pass (''dart test test/pty'', +571 ok), so it is not a logic bug. - -Root cause: these tests spawn a real /bin/sh via NativePty and wait on the reader ISOLATE to deliver bytes. Under heavy CPU contention the reader isolate is starved and the 20s ioTimeout (test/helpers/timeouts.dart) elapses before the shell''s first byte / echo response is delivered. Already hardened once (T-108 swapped wall-clock sleeps for event-driven waits; first-byte Completer + 25ms-poll _waitForBuffer), but the isolate-scheduling assumption still breaks under load. - -These tests are tagged ''pty'' and are meant to run in the gate''s serial pass. Fix options: (a) ensure the parallel coverage pass EXCLUDES tag:pty (run pty only in the serial pass) so they never compete for isolate scheduling — verify ci/test.sh / dart_test.yaml tag routing; (b) harden against isolate starvation (e.g. give the reader isolate priority, or assert on delivery via a more robust signal); (c) document that ''flutter test --coverage'' over the whole tree is not a supported invocation (use make test + the gate). Prefer (a). Refs: T-108, T-96 (reader-isolate hangs), T-192 (gate parallel/serial passes), D-23 (test pyramid).', 'in_progress', 'medium', NULL, NULL, 'D-23', '2026-06-09 14:57:32', '2026-06-09 15:03:37', NULL, '69ddf72fcc00b5ee9a5e46ceca4591d9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'bug', NULL, 'Flaky: pty session_test ''write sends keystrokes to child'' starves under parallel/coverage load', 'test/pty/session_test.dart:47 ''write sends keystrokes to child'' (and its sibling pty integration tests) intermittently fail when run in a large parallel pool — observed twice during T-29: once in a bare ''flutter test --coverage'' (whole suite, max parallelism + coverage instrumentation) and once mid pre-push gate (''+570 -1''). Passes reliably in isolation and via the gate''s SERIAL test-core pass (''dart test test/pty'', +571 ok), so it is not a logic bug. +RESOLVED by the D-88 ClideTypeahead migration (T-286), verified 2026-06-09. Root cause of both symptoms was the OLD hand-rolled overlay, not the pure helpers (activeSlashQuery/filterSlashCommands always handled ''-''): (1) the old overlay didn''t narrow live as you typed, so a ''-'' looked like it emptied/broke the list; (2) the old focus model raced Tab. Post-migration the popover narrows live (ValueNotifier) and the field keeps focus (captureFocus:false), so ''-'' filters normally and Tab reliably completes. Confirmed bare ''-'' is NOT a keybinding (zoom is ctrl/meta+minus). Added regression tests: hyphen cases in slash_commands_test; composer widget test typing through a hyphen + Tab-accept. Commit d8b9a41.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 09:20:00', '2026-06-09 15:03:23', NULL, 'b398af8597d7249428c508c6365ec3dc', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BD8PXDRHBF001200', 'bug', NULL, 'Flaky: pty session_test ''write sends keystrokes to child'' starves under parallel/coverage load', 'test/pty/session_test.dart:47 ''write sends keystrokes to child'' (and its sibling pty integration tests) intermittently fail when run in a large parallel pool — observed twice during T-29: once in a bare ''flutter test --coverage'' (whole suite, max parallelism + coverage instrumentation) and once mid pre-push gate (''+570 -1''). Passes reliably in isolation and via the gate''s SERIAL test-core pass (''dart test test/pty'', +571 ok), so it is not a logic bug. Root cause: these tests spawn a real /bin/sh via NativePty and wait on the reader ISOLATE to deliver bytes. Under heavy CPU contention the reader isolate is starved and the 20s ioTimeout (test/helpers/timeouts.dart) elapses before the shell''s first byte / echo response is delivered. Already hardened once (T-108 swapped wall-clock sleeps for event-driven waits; first-byte Completer + 25ms-poll _waitForBuffer), but the isolate-scheduling assumption still breaks under load. These tests are tagged ''pty'' and are meant to run in the gate''s serial pass. Fix options: (a) ensure the parallel coverage pass EXCLUDES tag:pty (run pty only in the serial pass) so they never compete for isolate scheduling — verify ci/test.sh / dart_test.yaml tag routing; (b) harden against isolate starvation (e.g. give the reader isolate priority, or assert on delivery via a more robust signal); (c) document that ''flutter test --coverage'' over the whole tree is not a supported invocation (use make test + the gate). Prefer (a). Refs: T-108, T-96 (reader-isolate hangs), T-192 (gate parallel/serial passes), D-23 (test pyramid). -FIXED 2026-06-09 (commit 0231cb4). Root cause: ci/test_core.sh ran ''dart test test/ipc test/pty ... test/pql'' in the DEFAULT parallel pool, so test/pty''s real-PTY tests contended for fds+CPU with the other core suites and the reader isolate was starved. (ci/test.sh already isolated pty via --concurrency=1 --tags pty; test_core.sh was the gap.) My manual ''flutter test --coverage'' repro was a separate wrong-invocation artifact — the flutter runner can''t reliably deliver the PTY master fd, which is why the gate excludes pty from the flutter pool entirely.) Fix: test_core.sh now runs a serial ''--concurrency=1 --tags pty'' pass + a parallel ''--exclude-tags pty'' pass, mirroring ci/test.sh. Verified: pty pass +5 stable across repeated runs; full core +571 unchanged. Option (a) from the ticket.', 'in_progress', 'medium', NULL, NULL, 'D-23', '2026-06-09 14:57:32', '2026-06-09 15:09:05', NULL, '9b75a830e20847f5a3fdf78fd3f42951', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'bug', NULL, 'Flaky: pty session_test ''write sends keystrokes to child'' starves under parallel/coverage load', 'test/pty/session_test.dart:47 ''write sends keystrokes to child'' (and its sibling pty integration tests) intermittently fail when run in a large parallel pool — observed twice during T-29: once in a bare ''flutter test --coverage'' (whole suite, max parallelism + coverage instrumentation) and once mid pre-push gate (''+570 -1''). Passes reliably in isolation and via the gate''s SERIAL test-core pass (''dart test test/pty'', +571 ok), so it is not a logic bug. - -Root cause: these tests spawn a real /bin/sh via NativePty and wait on the reader ISOLATE to deliver bytes. Under heavy CPU contention the reader isolate is starved and the 20s ioTimeout (test/helpers/timeouts.dart) elapses before the shell''s first byte / echo response is delivered. Already hardened once (T-108 swapped wall-clock sleeps for event-driven waits; first-byte Completer + 25ms-poll _waitForBuffer), but the isolate-scheduling assumption still breaks under load. - -These tests are tagged ''pty'' and are meant to run in the gate''s serial pass. Fix options: (a) ensure the parallel coverage pass EXCLUDES tag:pty (run pty only in the serial pass) so they never compete for isolate scheduling — verify ci/test.sh / dart_test.yaml tag routing; (b) harden against isolate starvation (e.g. give the reader isolate priority, or assert on delivery via a more robust signal); (c) document that ''flutter test --coverage'' over the whole tree is not a supported invocation (use make test + the gate). Prefer (a). Refs: T-108, T-96 (reader-isolate hangs), T-192 (gate parallel/serial passes), D-23 (test pyramid). - -FIXED 2026-06-09 (commit 0231cb4). Root cause: ci/test_core.sh ran ''dart test test/ipc test/pty ... test/pql'' in the DEFAULT parallel pool, so test/pty''s real-PTY tests contended for fds+CPU with the other core suites and the reader isolate was starved. (ci/test.sh already isolated pty via --concurrency=1 --tags pty; test_core.sh was the gap.) My manual ''flutter test --coverage'' repro was a separate wrong-invocation artifact — the flutter runner can''t reliably deliver the PTY master fd, which is why the gate excludes pty from the flutter pool entirely.) Fix: test_core.sh now runs a serial ''--concurrency=1 --tags pty'' pass + a parallel ''--exclude-tags pty'' pass, mirroring ci/test.sh. Verified: pty pass +5 stable across repeated runs; full core +571 unchanged. Option (a) from the ticket.', 'done', 'medium', NULL, NULL, 'D-23', '2026-06-09 14:57:32', '2026-06-09 15:09:05', NULL, '6ee5f4f6527214ffff425726347cc0b1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'story', 'T-132', 'Render pasted-image @path tokens as thumbnails in the message log', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. - -The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). - -Scope: -- When rendering UserMessage text, detect @ tokens; for image paths (reuse the extension check behind _looksLikeImage: .png/.jpg/.jpeg/.gif/.webp/.bmp), render a bounded Image.file thumbnail in place of the bare token. Surrounding prose still renders normally; a message can carry multiple tokens. -- Click/activate the thumbnail to open the full-size image (a preview dialog). Keyboard-operable + a11y label (filename). -- Graceful fallback: a missing/unreadable file (pasted temp files can be cleaned up) degrades to a small placeholder or the path text via Image.file errorBuilder — never a crash/exception. -- Non-image @path tokens MAY render as a file chip (filename + icon) like the composer, but image thumbnails are the focus. -- Note: Image.file reads the path directly via dart:io, so this is NOT gated by the files.read allow-list (D-80) — the cache dir is outside the workspace and that''s fine for display. -- Display-only: the text actually sent to Claude is unchanged; this only affects rendering. - -Acceptance: -1. A user message with an @ image token shows an inline thumbnail in the log (not the raw path). -2. Clicking/activating the thumbnail opens the full image. -3. A missing/unreadable referenced file degrades to a placeholder (or the path text), no exception. -4. Prose and any non-image @path tokens around it still render readably; multiple tokens in one message all resolve. -5. The message content delivered to Claude is unchanged (render-only). Relates to T-142 (paste attachments).', 'ready', 'medium', NULL, NULL, NULL, '2026-06-03 15:31:01', '2026-06-09 15:10:57', NULL, '7e10192f86ab89f01d6d3bc268d1fadd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-293', 'bug', 'T-276', 'Chosen theme is not persisted per-repo across loads', 'Picking a theme (status-bar switcher T-234, or the Settings modal) applies live but is never saved, so every reload resets to the first bundled theme. The choice should persist PER REPO. - -Root cause: ThemeController.select() (lib/kernel/src/theme/controller.dart:43) only mutates _currentName + notifies — no persistence. It is constructed in facade.dart:160 as ThemeController(bundled: bundledThemes) with no initialName, so on boot it always falls back to bundled.first. - -Infra already exists — no new store needed: SettingsStore (lib/kernel/src/settings.dart) has a PROJECT scope that persists to /.clide/settings.yaml via ''project.*'' keys, and ProjectManager.openProject() already calls settings.setProjectDir() on open (project.dart:130), which loads that file. - -Proposed fix: -1. PERSIST on change — wire a listener on ThemeController (in facade.dart, where both theme + settings exist) that writes the current theme to settings whenever currentName changes; use project scope (''project.theme'') when a repo is open. currentName already encodes the high-contrast variant (resolveThemeName -> ''-hc''), so the HC toggle persists for free. -2. RESTORE on load — the project dir is set AFTER boot, so restoration must hook project-open (ProjectManager.openProject / settings.setProjectDir), not just construction: when a project opens, read ''project.theme'' and, if present + known, theme.select() it. Guard ArgumentError for a theme that is no longer bundled (fall back to default, don''t throw). - -Open question: app-scoped fallback. Persist project-scoped per the report; optionally also write ''app.theme'' as the global default so a brand-new repo inherits the last choice instead of bundled.first. Decide during implementation. - -Acceptance: pick a theme (incl. the High-contrast toggle) -> restart -> same theme restored for that repo; two repos keep independent choices; with no repo open, the app default applies; a saved theme that no longer exists degrades to the default without throwing. - -Tests: SettingsStore round-trips ''project.theme''; opening a project applies the saved theme to the controller; select() persists; unknown saved theme falls back. Refs: T-234 (theme switcher), T-288/D-88 (picker), SettingsStore, ProjectManager.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:12:15', '2026-06-09 15:12:15', NULL, '40f736095167bf5033a84316e0e13df8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-293', 'bug', 'T-276', 'Chosen theme is not persisted per-repo across loads', 'Picking a theme (status-bar switcher T-234, or the Settings modal) applies live but is never saved, so every reload resets to the first bundled theme. The choice should persist PER REPO. - -Root cause: ThemeController.select() (lib/kernel/src/theme/controller.dart:43) only mutates _currentName + notifies — no persistence. It is constructed in facade.dart:160 as ThemeController(bundled: bundledThemes) with no initialName, so on boot it always falls back to bundled.first. - -Infra already exists — no new store needed: SettingsStore (lib/kernel/src/settings.dart) has a PROJECT scope that persists to /.clide/settings.yaml via ''project.*'' keys, and ProjectManager.openProject() already calls settings.setProjectDir() on open (project.dart:130), which loads that file. - -Proposed fix: -1. PERSIST on change — wire a listener on ThemeController (in facade.dart, where both theme + settings exist) that writes the current theme to settings whenever currentName changes; use project scope (''project.theme'') when a repo is open. currentName already encodes the high-contrast variant (resolveThemeName -> ''-hc''), so the HC toggle persists for free. -2. RESTORE on load — the project dir is set AFTER boot, so restoration must hook project-open (ProjectManager.openProject / settings.setProjectDir), not just construction: when a project opens, read ''project.theme'' and, if present + known, theme.select() it. Guard ArgumentError for a theme that is no longer bundled (fall back to default, don''t throw). - -Open question: app-scoped fallback. Persist project-scoped per the report; optionally also write ''app.theme'' as the global default so a brand-new repo inherits the last choice instead of bundled.first. Decide during implementation. - -Acceptance: pick a theme (incl. the High-contrast toggle) -> restart -> same theme restored for that repo; two repos keep independent choices; with no repo open, the app default applies; a saved theme that no longer exists degrades to the default without throwing. - -Tests: SettingsStore round-trips ''project.theme''; opening a project applies the saved theme to the controller; select() persists; unknown saved theme falls back. Refs: T-234 (theme switcher), T-288/D-88 (picker), SettingsStore, ProjectManager.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:12:15', '2026-06-09 15:13:06', NULL, '138f608b7ff1aa7c449fe55f28d1c4d7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-09 15:13:25', NULL, '3dd9087f24ed2bb4981fadb491713243', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'story', 'T-132', 'Render pasted-image @path tokens as thumbnails in the message log', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. - -The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). - -Scope: -- When rendering UserMessage text, detect @ tokens; for image paths (reuse the extension check behind _looksLikeImage: .png/.jpg/.jpeg/.gif/.webp/.bmp), render a bounded Image.file thumbnail in place of the bare token. Surrounding prose still renders normally; a message can carry multiple tokens. -- Click/activate the thumbnail to open the full-size image (a preview dialog). Keyboard-operable + a11y label (filename). -- Graceful fallback: a missing/unreadable file (pasted temp files can be cleaned up) degrades to a small placeholder or the path text via Image.file errorBuilder — never a crash/exception. -- Non-image @path tokens MAY render as a file chip (filename + icon) like the composer, but image thumbnails are the focus. -- Note: Image.file reads the path directly via dart:io, so this is NOT gated by the files.read allow-list (D-80) — the cache dir is outside the workspace and that''s fine for display. -- Display-only: the text actually sent to Claude is unchanged; this only affects rendering. - -Acceptance: -1. A user message with an @ image token shows an inline thumbnail in the log (not the raw path). -2. Clicking/activating the thumbnail opens the full image. -3. A missing/unreadable referenced file degrades to a placeholder (or the path text), no exception. -4. Prose and any non-image @path tokens around it still render readably; multiple tokens in one message all resolve. -5. The message content delivered to Claude is unchanged (render-only). Relates to T-142 (paste attachments). - -MERGED T-254 (duplicate) into this ticket. Extra nuance carried over from T-254: keep the file path available (e.g. as a small caption/subtitle under the thumbnail, or via the lightbox) so it can still be copied/referenced. Reuse the existing inline image card + lightbox (T-252) rather than a new component.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-03 15:31:01', '2026-06-09 15:14:46', NULL, '9b8062e3e41f88ddf73d026d56d6ca01', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images.', 'cancelled', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-09 15:14:46', NULL, 'bd5b0c609c9e8eb26580ff62c37a9086', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. - -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236.', 'cancelled', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-09 15:14:46', NULL, '37cd035061a42262685fe11a862d0fa1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. - -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-09 15:15:18', NULL, '4f3dd564fd456983fae4712d5c1d1d30', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. - -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. - -RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-09 15:15:18', NULL, '27c33b0119ddb5943396648504b968e5', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'story', 'T-132', 'Render pasted-image @path tokens as thumbnails in the message log', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. - -The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). - -Scope: -- When rendering UserMessage text, detect @ tokens; for image paths (reuse the extension check behind _looksLikeImage: .png/.jpg/.jpeg/.gif/.webp/.bmp), render a bounded Image.file thumbnail in place of the bare token. Surrounding prose still renders normally; a message can carry multiple tokens. -- Click/activate the thumbnail to open the full-size image (a preview dialog). Keyboard-operable + a11y label (filename). -- Graceful fallback: a missing/unreadable file (pasted temp files can be cleaned up) degrades to a small placeholder or the path text via Image.file errorBuilder — never a crash/exception. -- Non-image @path tokens MAY render as a file chip (filename + icon) like the composer, but image thumbnails are the focus. -- Note: Image.file reads the path directly via dart:io, so this is NOT gated by the files.read allow-list (D-80) — the cache dir is outside the workspace and that''s fine for display. -- Display-only: the text actually sent to Claude is unchanged; this only affects rendering. - -Acceptance: -1. A user message with an @ image token shows an inline thumbnail in the log (not the raw path). -2. Clicking/activating the thumbnail opens the full image. -3. A missing/unreadable referenced file degrades to a placeholder (or the path text), no exception. -4. Prose and any non-image @path tokens around it still render readably; multiple tokens in one message all resolve. -5. The message content delivered to Claude is unchanged (render-only). Relates to T-142 (paste attachments). - -MERGED T-254 (duplicate) into this ticket. Extra nuance carried over from T-254: keep the file path available (e.g. as a small caption/subtitle under the thumbnail, or via the lightbox) so it can still be copied/referenced. Reuse the existing inline image card + lightbox (T-252) rather than a new component. - -RETRACTED the earlier ''merged T-254'' note — T-236 and T-254 are distinct tickets (per user); disregard that note. T-236 stays scoped to its own description.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-03 15:31:01', '2026-06-09 15:15:18', NULL, '8cfc730fea28133b2b032735dcadca7c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:18:22', NULL, 'e9563df2809b22d6e3b7177cfb28d7f4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:21:37', NULL, '20afe9ffa7639aca6c6e3f5e85bdbb5e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed).', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:24:00', NULL, '87737b23e9119a0d46bba4977801997f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-295', 'bug', 'T-276', 'clide image show is not tab-aware — opens in primary tab', '`clide image show ` (T-249 image card) renders the image into the PRIMARY tab regardless of which tab/pane is active. It should target the active tab (or otherwise be tab-aware), not hijack the primary one. +FIXED 2026-06-09 (commit 0231cb4). Root cause: ci/test_core.sh ran ''dart test test/ipc test/pty ... test/pql'' in the DEFAULT parallel pool, so test/pty''s real-PTY tests contended for fds+CPU with the other core suites and the reader isolate was starved. (ci/test.sh already isolated pty via --concurrency=1 --tags pty; test_core.sh was the gap.) My manual ''flutter test --coverage'' repro was a separate wrong-invocation artifact — the flutter runner can''t reliably deliver the PTY master fd, which is why the gate excludes pty from the flutter pool entirely.) Fix: test_core.sh now runs a serial ''--concurrency=1 --tags pty'' pass + a parallel ''--exclude-tags pty'' pass, mirroring ci/test.sh. Verified: pty pass +5 stable across repeated runs; full core +571 unchanged. Option (a) from the ticket.', 'in_progress', 'medium', NULL, NULL, 'D-23', '2026-06-09 14:57:32', '2026-06-09 15:09:05', NULL, '73fd633127cb5d51b0469e4559580db1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5DM43Q9HX352STN3M', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'clide image show is not tab-aware — opens in primary tab', '`clide image show ` (T-249 image card) renders the image into the PRIMARY tab regardless of which tab/pane is active. It should target the active tab (or otherwise be tab-aware), not hijack the primary one. Repro: - Have a non-primary tab focused. @@ -2684,285 +736,8 @@ Repro: Notes: - Surfaced while showing a Frame0 wireframe export during T-294. -- Likely the image-show dispatch resolves a fixed/primary slot instead of the active tab context. Audit the target-resolution path for the image-show command.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:26:00', '2026-06-09 15:26:00', NULL, 'aa861a651e4bcc707a70e74ff2844ca8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed). - -PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:28:16', NULL, '184b328df71f40525172c27f13f8779d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed). - -PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:29:08', NULL, 'a8a445b2cdfed1987597b39f66d8e581', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed). - -PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:29:11', NULL, '5936843822aa228b7fd70680774ce786', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed). - -PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation. - -PLACEMENT FINAL (supersedes "pinned-to-edge"): toggles are FIXED at the far-left and far-right ends of the bottom status bar in every state. They do not slide with the pane edge. Position is constant (muscle memory; always where the reference-screenshot arrows pointed); only the chevron flips per isCollapsed. Left end = sidebar toggle, right end = context toggle. Mirrored for parity. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:30:16', NULL, 'c1f7eb1b567c788bf7facf1852668753', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed). - -PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation. - -PLACEMENT FINAL (supersedes "pinned-to-edge"): toggles are FIXED at the far-left and far-right ends of the bottom status bar in every state. They do not slide with the pane edge. Position is constant (muscle memory; always where the reference-screenshot arrows pointed); only the chevron flips per isCollapsed. Left end = sidebar toggle, right end = context toggle. Mirrored for parity. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -SPACE NOTE: the status bar already hosts content at both ends (left: branch / skills count; right: Output / terminal). Fixed-end toggles therefore share that space. Resolution: reserve the OUTERMOST ~24px cell at each end for the toggle and shift the existing status items inward by that width — a constant reservation, not a dynamic fight (the toggle never moves or grows). The mock reflects this: status text begins after the left toggle and ends before the right toggle.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:30:41', NULL, 'b949608090fccd719433ef2f734dcae1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed). - -PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation. - -PLACEMENT FINAL (supersedes "pinned-to-edge"): toggles are FIXED at the far-left and far-right ends of the bottom status bar in every state. They do not slide with the pane edge. Position is constant (muscle memory; always where the reference-screenshot arrows pointed); only the chevron flips per isCollapsed. Left end = sidebar toggle, right end = context toggle. Mirrored for parity. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -SPACE NOTE: the status bar already hosts content at both ends (left: branch / skills count; right: Output / terminal). Fixed-end toggles therefore share that space. Resolution: reserve the OUTERMOST ~24px cell at each end for the toggle and shift the existing status items inward by that width — a constant reservation, not a dynamic fight (the toggle never moves or grows). The mock reflects this: status text begins after the left toggle and ends before the right toggle. - -GLYPH DECISION: use Phosphor caret-line icons (chevron + edge line), which read as "collapse to the edge": -- caret-line-left -> 0xe132 (CaretLineLeft) -- caret-line-right -> 0xe130 (CaretLineRight) -The glyphs already ship in assets/fonts/phosphor (codepoints.csv lines 150-151) — no new dependency. Add two consts to lib/widgets/src/icons/phosphor.dart (PhosphorIcons.caretLineLeft / caretLineRight) during implementation. Chevron-line direction flips per isCollapsed.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:33:14', NULL, 'bb36344f4c6c98190aaaa02f145537f6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed). - -PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation. - -PLACEMENT FINAL (supersedes "pinned-to-edge"): toggles are FIXED at the far-left and far-right ends of the bottom status bar in every state. They do not slide with the pane edge. Position is constant (muscle memory; always where the reference-screenshot arrows pointed); only the chevron flips per isCollapsed. Left end = sidebar toggle, right end = context toggle. Mirrored for parity. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -SPACE NOTE: the status bar already hosts content at both ends (left: branch / skills count; right: Output / terminal). Fixed-end toggles therefore share that space. Resolution: reserve the OUTERMOST ~24px cell at each end for the toggle and shift the existing status items inward by that width — a constant reservation, not a dynamic fight (the toggle never moves or grows). The mock reflects this: status text begins after the left toggle and ends before the right toggle. - -GLYPH DECISION: use Phosphor caret-line icons (chevron + edge line), which read as "collapse to the edge": -- caret-line-left -> 0xe132 (CaretLineLeft) -- caret-line-right -> 0xe130 (CaretLineRight) -The glyphs already ship in assets/fonts/phosphor (codepoints.csv lines 150-151) — no new dependency. Add two consts to lib/widgets/src/icons/phosphor.dart (PhosphorIcons.caretLineLeft / caretLineRight) during implementation. Chevron-line direction flips per isCollapsed. - -RESOLUTION (2026-06-09): considered moving the re-open control into the center (pane edge or center status segment) to dodge the end-of-bar space contention; user chose to KEEP TOGGLES FIXED AT THE STATUS-BAR ENDS (best muscle memory). Final design = v3 mock: caret-line glyphs at fixed far-left/far-right status-bar cells (~24px reserved, side items shifted in), chevron-line flips per isCollapsed, firing existing sidebar.collapse / context.collapse. Design fully settled; ready to implement.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 15:34:11', NULL, 'b289657e4a643b131da9e74904785e1e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-293', 'bug', 'T-276', 'Chosen theme is not persisted per-repo across loads', 'Picking a theme (status-bar switcher T-234, or the Settings modal) applies live but is never saved, so every reload resets to the first bundled theme. The choice should persist PER REPO. +- Likely the image-show dispatch resolves a fixed/primary slot instead of the active tab context. Audit the target-resolution path for the image-show command.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:26:00', '2026-06-09 15:26:00', NULL, 'fb011f536bf6c2734eb1fadc9b9ef5c6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42WW0SY3XEXKE8PK8', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Chosen theme is not persisted per-repo across loads', 'Picking a theme (status-bar switcher T-234, or the Settings modal) applies live but is never saved, so every reload resets to the first bundled theme. The choice should persist PER REPO. Root cause: ThemeController.select() (lib/kernel/src/theme/controller.dart:43) only mutates _currentName + notifies — no persistence. It is constructed in facade.dart:160 as ThemeController(bundled: bundledThemes) with no initialName, so on boot it always falls back to bundled.first. @@ -2978,80 +753,24 @@ Acceptance: pick a theme (incl. the High-contrast toggle) -> restart -> same the Tests: SettingsStore round-trips ''project.theme''; opening a project applies the saved theme to the controller; select() persists; unknown saved theme falls back. Refs: T-234 (theme switcher), T-288/D-88 (picker), SettingsStore, ProjectManager. -FIXED 2026-06-09 (this commit). Added lib/kernel/src/theme/theme_persistence.dart + wireThemePersistence() in facade. Decision on the open question: persist BOTH project.theme (per-repo, in .clide/settings.yaml) AND app.theme (global default) — so a themed repo keeps its choice, an unthemed/new repo inherits the last global choice, and the HC variant persists (name encodes -hc). Restore prefers project over app; unknown theme is ignored (no throw). Tests: test/kernel/src/theme_persistence_test.dart (6 cases — persist app/project, HC, restore-on-open, boot restore, unknown fallback). All green.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:12:15', '2026-06-09 15:36:15', NULL, '1406609c21a6470cb45db16b06682b3b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-293', 'bug', 'T-276', 'Chosen theme is not persisted per-repo across loads', 'Picking a theme (status-bar switcher T-234, or the Settings modal) applies live but is never saved, so every reload resets to the first bundled theme. The choice should persist PER REPO. +FIXED 2026-06-09 (this commit). Added lib/kernel/src/theme/theme_persistence.dart + wireThemePersistence() in facade. Decision on the open question: persist BOTH project.theme (per-repo, in .clide/settings.yaml) AND app.theme (global default) — so a themed repo keeps its choice, an unthemed/new repo inherits the last global choice, and the HC variant persists (name encodes -hc). Restore prefers project over app; unknown theme is ignored (no throw). Tests: test/kernel/src/theme_persistence_test.dart (6 cases — persist app/project, HC, restore-on-open, boot restore, unknown fallback). All green.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:12:15', '2026-06-09 15:36:15', NULL, 'ea78a24a1671017d25613be8a1295ae5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM61SRVBDRFFN2S80T4', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. -Root cause: ThemeController.select() (lib/kernel/src/theme/controller.dart:43) only mutates _currentName + notifies — no persistence. It is constructed in facade.dart:160 as ThemeController(bundled: bundledThemes) with no initialName, so on boot it always falls back to bundled.first. +## Behaviour +- Detect a pasted-image path in the conversation stream and render the image viewer card. +- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. +- Reuse the existing image viewer card component rather than building a new one. -Infra already exists — no new store needed: SettingsStore (lib/kernel/src/settings.dart) has a PROJECT scope that persists to /.clide/settings.yaml via ''project.*'' keys, and ProjectManager.openProject() already calls settings.setProjectDir() on open (project.dart:130), which loads that file. +## Notes +- Applies to the Claude conversation panel rendering path. +- Consider failure cases: missing/deleted file, non-image paste, very large images. -Proposed fix: -1. PERSIST on change — wire a listener on ThemeController (in facade.dart, where both theme + settings exist) that writes the current theme to settings whenever currentName changes; use project scope (''project.theme'') when a repo is open. currentName already encodes the high-contrast variant (resolveThemeName -> ''-hc''), so the HC toggle persists for free. -2. RESTORE on load — the project dir is set AFTER boot, so restoration must hook project-open (ProjectManager.openProject / settings.setProjectDir), not just construction: when a project opens, read ''project.theme'' and, if present + known, theme.select() it. Guard ArgumentError for a theme that is no longer bundled (fall back to default, don''t throw). +CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. -Open question: app-scoped fallback. Persist project-scoped per the report; optionally also write ''app.theme'' as the global default so a brand-new repo inherits the last choice instead of bundled.first. Decide during implementation. +RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket. -Acceptance: pick a theme (incl. the High-contrast toggle) -> restart -> same theme restored for that repo; two repos keep independent choices; with no repo open, the app default applies; a saved theme that no longer exists degrades to the default without throwing. - -Tests: SettingsStore round-trips ''project.theme''; opening a project applies the saved theme to the controller; select() persists; unknown saved theme falls back. Refs: T-234 (theme switcher), T-288/D-88 (picker), SettingsStore, ProjectManager. - -FIXED 2026-06-09 (this commit). Added lib/kernel/src/theme/theme_persistence.dart + wireThemePersistence() in facade. Decision on the open question: persist BOTH project.theme (per-repo, in .clide/settings.yaml) AND app.theme (global default) — so a themed repo keeps its choice, an unthemed/new repo inherits the last global choice, and the HC variant persists (name encodes -hc). Restore prefers project over app; unknown theme is ignored (no throw). Tests: test/kernel/src/theme_persistence_test.dart (6 cases — persist app/project, HC, restore-on-open, boot restore, unknown fallback). All green.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 15:12:15', '2026-06-09 15:36:15', NULL, '001b0228d70a8dc4839d6edbd29541b6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'task', 'T-276', 'Group consecutive edits to the same file into one collapsed card', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. - -Behaviour: -- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). -- Collapsed (default): one-line ticker of the latest edit + a count label. -- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. -- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. - -Notes: -- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. -- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. -- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:43:58', '2026-06-09 15:43:58', NULL, '94b7ea690c12e5e2353df9c15c1fcdfc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'task', 'T-276', 'Group consecutive edits to the same file into one collapsed card', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. - -Behaviour: -- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). -- Collapsed (default): one-line ticker of the latest edit + a count label. -- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. -- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. - -Notes: -- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. -- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. -- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run. - -WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stream renders 11 stacked cards — 3x "Edit clide_markdown.dart", then a folded "Read … 2 steps" holder, then 7x "Edit clide_markdown.dart". With this feature it collapses to THREE cards: -- [3 edits] (the first edit run) -- [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) -- [7 edits] (the second edit run) -Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:43:58', '2026-06-09 15:45:12', NULL, '12593c52032476c8c378394e8ccedc42', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'task', 'T-276', 'Group consecutive edits to the same file into one collapsed card', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. - -Behaviour: -- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). -- Collapsed (default): one-line ticker of the latest edit + a count label. -- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. -- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. - -Notes: -- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. -- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. -- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run. - -WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stream renders 11 stacked cards — 3x "Edit clide_markdown.dart", then a folded "Read … 2 steps" holder, then 7x "Edit clide_markdown.dart". With this feature it collapses to THREE cards: -- [3 edits] (the first edit run) -- [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) -- [7 edits] (the second edit run) -Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card. - -STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header status indicator that reuses the existing per-step success tick, driven by the latest edit''s state: -- edit in flight -> spinner -- edit completed -> success check (the current green tick) -- next edit starts -> back to spinner -- ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). -So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 15:43:58', '2026-06-09 15:46:02', NULL, '21544d43b43b8da6a16a2a4bf0bc901d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'story', 'T-132', 'Render pasted-image @path tokens as thumbnails in the message log', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. +DONE 2026-06-09 — satisfied by D-89 together with T-236. The lightbox-expansion + path-retention (filename a11y label / unchanged copyText) intent from T-254 is delivered by the shared ImageThumbnail → lightbox; the chosen presentation is inline (not a separate card) per the user. See T-236 / D-89 / commit.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-09 15:55:06', NULL, '407e83a02647656a01c31757fec93197', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4GN07FN8GAXZ20YAR', 'story', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Render pasted-image @path tokens as thumbnails in the message log', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). @@ -3074,64 +793,8 @@ MERGED T-254 (duplicate) into this ticket. Extra nuance carried over from T-254: RETRACTED the earlier ''merged T-254'' note — T-236 and T-254 are distinct tickets (per user); disregard that note. T-236 stays scoped to its own description. -DONE 2026-06-09 — implemented as the hybrid (D-89): inline thumbnail in the message prose that opens the lightbox on click/Enter. New ImageThumbnail + openImageLightbox (lib/builtin/claude/src/image_thumbnail.dart); ClideMarkdown gained an onImageToken WidgetSpan seam. Missing file → placeholder; multiple tokens; non-image @path stays text; render-only (copyText unchanged). Composer chips reuse the same 44px thumbnail. Tests: image_thumbnail_test + conversation_view_test cases. Closes alongside T-254.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-03 15:31:01', '2026-06-09 15:55:06', NULL, 'a7f6817b8f2f1ce8130ad5429415c373', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-236', 'story', 'T-132', 'Render pasted-image @path tokens as thumbnails in the message log', 'When a user message contains a pasted-screenshot reference — an @ token pointing at an image (the composer''s ComposerAttachment.pathToken format, e.g. @/home//.cache/clide/pasted/paste-.png, see screenshot) — the conversation log renders it as the raw path string. Show it as an inline THUMBNAIL instead. - -The composer already renders pre-send attachments as Image.file thumbnail chips (clipboard_paste.dart + claude_composer _chip); this carries that into the post-send message log (UserMessage rendering in conversation_view/conversation_card). - -Scope: -- When rendering UserMessage text, detect @ tokens; for image paths (reuse the extension check behind _looksLikeImage: .png/.jpg/.jpeg/.gif/.webp/.bmp), render a bounded Image.file thumbnail in place of the bare token. Surrounding prose still renders normally; a message can carry multiple tokens. -- Click/activate the thumbnail to open the full-size image (a preview dialog). Keyboard-operable + a11y label (filename). -- Graceful fallback: a missing/unreadable file (pasted temp files can be cleaned up) degrades to a small placeholder or the path text via Image.file errorBuilder — never a crash/exception. -- Non-image @path tokens MAY render as a file chip (filename + icon) like the composer, but image thumbnails are the focus. -- Note: Image.file reads the path directly via dart:io, so this is NOT gated by the files.read allow-list (D-80) — the cache dir is outside the workspace and that''s fine for display. -- Display-only: the text actually sent to Claude is unchanged; this only affects rendering. - -Acceptance: -1. A user message with an @ image token shows an inline thumbnail in the log (not the raw path). -2. Clicking/activating the thumbnail opens the full image. -3. A missing/unreadable referenced file degrades to a placeholder (or the path text), no exception. -4. Prose and any non-image @path tokens around it still render readably; multiple tokens in one message all resolve. -5. The message content delivered to Claude is unchanged (render-only). Relates to T-142 (paste attachments). - -MERGED T-254 (duplicate) into this ticket. Extra nuance carried over from T-254: keep the file path available (e.g. as a small caption/subtitle under the thumbnail, or via the lightbox) so it can still be copied/referenced. Reuse the existing inline image card + lightbox (T-252) rather than a new component. - -RETRACTED the earlier ''merged T-254'' note — T-236 and T-254 are distinct tickets (per user); disregard that note. T-236 stays scoped to its own description. - -DONE 2026-06-09 — implemented as the hybrid (D-89): inline thumbnail in the message prose that opens the lightbox on click/Enter. New ImageThumbnail + openImageLightbox (lib/builtin/claude/src/image_thumbnail.dart); ClideMarkdown gained an onImageToken WidgetSpan seam. Missing file → placeholder; multiple tokens; non-image @path stays text; render-only (copyText unchanged). Composer chips reuse the same 44px thumbnail. Tests: image_thumbnail_test + conversation_view_test cases. Closes alongside T-254.', 'done', 'medium', NULL, NULL, NULL, '2026-06-03 15:31:01', '2026-06-09 15:55:06', NULL, '081e1a85313e3da1455cc931c500a934', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. - -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. - -RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket. - -DONE 2026-06-09 — satisfied by D-89 together with T-236. The lightbox-expansion + path-retention (filename a11y label / unchanged copyText) intent from T-254 is delivered by the shared ImageThumbnail → lightbox; the chosen presentation is inline (not a separate card) per the user. See T-236 / D-89 / commit.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-09 15:55:06', NULL, 'cbc4c05333cd3cbec389030385541d17', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-254', 'story', NULL, 'Render pasted images via the image viewer card in the Claude conversation panel instead of echoing only the file path', 'When the user pastes an image, the conversation panel echoes back the file path as plain text (e.g. @/home/.../paste-.png). We now have an image viewer card — render the pasted image inline using that card instead of (or in addition to) the bare path. - -## Behaviour -- Detect a pasted-image path in the conversation stream and render the image viewer card. -- Still surface the file path (e.g. as a caption / subtitle on the card) so it can be copied/referenced. -- Reuse the existing image viewer card component rather than building a new one. - -## Notes -- Applies to the Claude conversation panel rendering path. -- Consider failure cases: missing/deleted file, non-image paste, very large images. - -CANCELLED as a duplicate of T-236 (2026-06-09). T-236 is the concrete spec for rendering pasted @path images inline in the Claude conversation; its scope now includes T-254''s path-as-caption nuance. Implement under T-236. - -RETRACTED the cancellation — NOT a duplicate of T-236 (per user, 2026-06-09). Both stay open. T-254 remains its own ticket. - -DONE 2026-06-09 — satisfied by D-89 together with T-236. The lightbox-expansion + path-retention (filename a11y label / unchanged copyText) intent from T-254 is delivered by the shared ImageThumbnail → lightbox; the chosen presentation is inline (not a separate card) per the user. See T-236 / D-89 / commit.', 'done', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:54', '2026-06-09 15:55:06', NULL, '0ed8b0f0f4012715eb1a62de2cae9ad6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-297', 'bug', 'T-276', 'Conversation doesn''t re-scroll when the bottom input resizes (content hides behind it)', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. +DONE 2026-06-09 — implemented as the hybrid (D-89): inline thumbnail in the message prose that opens the lightbox on click/Enter. New ImageThumbnail + openImageLightbox (lib/builtin/claude/src/image_thumbnail.dart); ClideMarkdown gained an onImageToken WidgetSpan seam. Missing file → placeholder; multiple tokens; non-image @path stays text; render-only (copyText unchanged). Composer chips reuse the same 44px thumbnail. Tests: image_thumbnail_test + conversation_view_test cases. Closes alongside T-254.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-03 15:31:01', '2026-06-09 15:55:06', NULL, 'bdd6cd8734a9a6a361cf6f128e3e632f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6RXN6HW6XTEHKYQDG', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Conversation doesn''t re-scroll when the bottom input resizes (content hides behind it)', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. Trigger: anything that resizes the bottom interaction zone (D-78) — e.g. opening a permission dialog or the AskUserQuestion UI, which replace/expand the composer. The taller box shrinks the conversation viewport from the bottom, but the scroll offset isn''t adjusted, so content that was at the bottom edge is now occluded. @@ -3145,59 +808,10 @@ Repro: Notes: - Likely the scroll controller doesn''t react to the composer/interaction-zone height change (no re-scroll on viewport-inset/size change). Audit the conversation view''s scroll handling around interaction-zone show/hide (D-78) and on keyboard/box resize. -- Affects the Claude conversation panel.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 16:01:09', '2026-06-09 16:01:09', NULL, '5287b26bc163fd424808be2151796347', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-297', 'bug', 'T-276', 'Conversation doesn''t re-scroll when the bottom input resizes (content hides behind it)', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. +- Affects the Claude conversation panel. -Trigger: anything that resizes the bottom interaction zone (D-78) — e.g. opening a permission dialog or the AskUserQuestion UI, which replace/expand the composer. The taller box shrinks the conversation viewport from the bottom, but the scroll offset isn''t adjusted, so content that was at the bottom edge is now occluded. - -Expected: when the interaction zone grows, the conversation re-anchors so the previously-visible bottom content stays visible above the box (preserve bottom-anchoring / keep the tail in view). Symmetric on shrink — no leftover gap when the box collapses back. - -Repro: -1. Scroll the Claude conversation to the bottom (tail in view). -2. Trigger a permission prompt or AskUserQuestion (interaction zone expands). -3. Observed: a strip of the last message/card is hidden behind the enlarged input box. -4. Expected: view scrolls so that content remains fully visible above the box. - -Notes: -- Likely the scroll controller doesn''t react to the composer/interaction-zone height change (no re-scroll on viewport-inset/size change). Audit the conversation view''s scroll handling around interaction-zone show/hide (D-78) and on keyboard/box resize. -- Affects the Claude conversation panel.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 16:01:09', '2026-06-09 16:01:58', NULL, '6508dfb893a7d12cad13e8d9e7b03718', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'task', 'T-276', 'Group consecutive edits to the same file into one collapsed card', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. - -Behaviour: -- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). -- Collapsed (default): one-line ticker of the latest edit + a count label. -- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. -- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. - -Notes: -- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. -- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. -- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run. - -WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stream renders 11 stacked cards — 3x "Edit clide_markdown.dart", then a folded "Read … 2 steps" holder, then 7x "Edit clide_markdown.dart". With this feature it collapses to THREE cards: -- [3 edits] (the first edit run) -- [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) -- [7 edits] (the second edit run) -Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card. - -STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header status indicator that reuses the existing per-step success tick, driven by the latest edit''s state: -- edit in flight -> spinner -- edit completed -> success check (the current green tick) -- next edit starts -> back to spinner -- ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). -So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:43:58', '2026-06-09 16:02:14', NULL, '79d8b599af75fc98290b7ab3751c077c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-298', 'bug', 'T-276', 'Bottom input box is misaligned when the status bar is hidden', 'The bottom input/prompt box (the composer, shown with the edit-pencil affordance on the right) is aligned weird when the status bar is not visible. - -Symptom: with the status bar hidden, the input box''s alignment/padding looks off (see screenshot — single-line box spanning full width, content not sitting where it should relative to the window bottom). - -Likely cause: the composer''s vertical alignment / bottom padding assumes the status bar is present below it and reserves/relies on that space. When the status bar is hidden, that assumption breaks and the box no longer sits flush/centered correctly. - -Expected: the input box sits correctly flush to the window bottom (or with consistent padding) regardless of whether the status bar is visible. Layout should not depend on the status bar being present. - -Notes: -- Audit the bottom-of-window layout stack (composer / interaction zone + status bar). The composer should bottom-anchor independently of the status bar''s visibility. -- Related to the bottom interaction zone work (D-78) and may interact with T-297 (scroll on input resize). Also relevant once the status-bar collapse toggles (T-294) can change what''s in the bar.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 16:06:41', '2026-06-09 16:06:41', NULL, 'b4bc1e798b51ba9cab140777ff40972a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). +DONE 2026-06-09 (commit). conversation_view tracks at-bottom + a LayoutBuilder re-jumps to the tail when the viewport height changes (interaction-zone resize, D-78), only when pinned. Tests: conversation_scroll_test.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 16:01:09', '2026-06-09 16:34:46', NULL, '6503bbef73fb77041d324369d61ea208', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM72SHY93S36VM9G6D8', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. @@ -3246,92 +860,8 @@ The glyphs already ship in assets/fonts/phosphor (codepoints.csv lines 150-151) RESOLUTION (2026-06-09): considered moving the re-open control into the center (pane edge or center status segment) to dodge the end-of-bar space contention; user chose to KEEP TOGGLES FIXED AT THE STATUS-BAR ENDS (best muscle memory). Final design = v3 mock: caret-line glyphs at fixed far-left/far-right status-bar cells (~24px reserved, side items shifted in), chevron-line flips per isCollapsed, firing existing sidebar.collapse / context.collapse. Design fully settled; ready to implement. -DONE 2026-06-09 (commit). Fixed ~24px caret-line toggles bookend the status bar (StatusbarHost), flip chevron per arrangement.isCollapsed, fire existing sidebar.collapse/context.collapse. Visual affordance only. Tests: app_collapse_toggle_test + updated app_statusbar_test.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 16:34:46', NULL, '2aa78ca6d9972c7680c81d904cb1a11b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-294', 'task', 'T-276', 'Collapse buttons for sidebar and context pane', 'Design and add collapse/expand controls for the left sidebar and the right context pane, along the lines of the reference screenshot (green arrows mark the two intended affordance locations — bottom-left of the sidebar and bottom-right of the context pane). - -Scope of this ticket: set up a Frame0 mock to talk through the design before implementing. - -Open questions to resolve in the mock: -- Affordance placement: footer/status-bar anchored (as the screenshot arrows suggest) vs. pane-edge chevron. -- Collapsed state: fully hidden vs. thin rail with a re-expand handle. -- Iconography (chevron direction) and hover/active states. -- Whether sidebar and context pane share one control pattern (parity) or differ. -- Keyboard/CLI parity (D-6): each collapse action needs a clide verb. - -Deliverable: Frame0 wireframe(s) of collapsed + expanded states for both panes, reviewed before any code. - -DESIGN DIRECTION (settled): anchor both toggles on the OUTER EDGES of the center (Claude conversation) pane — one on the left edge controlling the sidebar, one on the right edge controlling the context pane. The control stays fixed on the center-pane edge whether the adjacent pane is open or collapsed, so a single button both collapses an open pane and re-opens a collapsed one (chevron flips direction). This avoids needing a separate "re-expand" handle on the collapsed pane. - -Implications for the mock: -- Collapsed pane can be fully hidden (no thin rail needed) since the re-open control lives on the center edge. -- Sidebar and context pane share one mirrored control pattern (parity). -- Chevron direction reflects state: points outward to expand, inward to collapse. - -IMPLEMENTATION NOTE: the collapse logic already exists — no new toggle behaviour needed. Commands `sidebar.collapse` (ctrl+shift+1) and `context.collapse` (ctrl+shift+3) are registered in lib/builtin/default_layout/src/extension.dart, exposed in the command palette + menubar, and call arrangement.toggleCollapsed(Slots.sidebar|contextPanel), returning isCollapsed (D-051, D-054). - -So this ticket is scoped to the VISUAL AFFORDANCE only: -- Add the two edge-anchored toggle buttons on the center (Claude) pane''s outer edges. -- On click, invoke the existing `sidebar.collapse` / `context.collapse` commands (do NOT reimplement collapse). -- Read arrangement.isCollapsed(...) to flip the chevron direction per state. -- D-6 CLI/keyboard parity is already satisfied by the existing commands; this adds the mouse affordance. - -Mock: docs/design/wireframes/hud/pane-collapse-toggles.{json,png} — State A (open) + State B (collapsed). - -PLACEMENT REVISED: toggles do NOT float vertically-centered on the pane edges. They live in the BOTTOM STATUS BAR. Each toggle is horizontally pinned to the center pane''s left/right edge, so when a pane collapses the toggle slides along the status bar to that end (open: at the inner pane boundary; collapsed: at the far status-bar end — matching where the reference-screenshot arrows pointed). Still mirrored left/right for parity; chevron flips per isCollapsed. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -APPROVED (2026-06-09): pinned-to-edge status-bar placement confirmed by user. Design is settled; ready for implementation. - -PLACEMENT FINAL (supersedes "pinned-to-edge"): toggles are FIXED at the far-left and far-right ends of the bottom status bar in every state. They do not slide with the pane edge. Position is constant (muscle memory; always where the reference-screenshot arrows pointed); only the chevron flips per isCollapsed. Left end = sidebar toggle, right end = context toggle. Mirrored for parity. Buttons invoke the existing sidebar.collapse / context.collapse commands. - -Mock updated: docs/design/wireframes/hud/pane-collapse-toggles.{json,png}. - -SPACE NOTE: the status bar already hosts content at both ends (left: branch / skills count; right: Output / terminal). Fixed-end toggles therefore share that space. Resolution: reserve the OUTERMOST ~24px cell at each end for the toggle and shift the existing status items inward by that width — a constant reservation, not a dynamic fight (the toggle never moves or grows). The mock reflects this: status text begins after the left toggle and ends before the right toggle. - -GLYPH DECISION: use Phosphor caret-line icons (chevron + edge line), which read as "collapse to the edge": -- caret-line-left -> 0xe132 (CaretLineLeft) -- caret-line-right -> 0xe130 (CaretLineRight) -The glyphs already ship in assets/fonts/phosphor (codepoints.csv lines 150-151) — no new dependency. Add two consts to lib/widgets/src/icons/phosphor.dart (PhosphorIcons.caretLineLeft / caretLineRight) during implementation. Chevron-line direction flips per isCollapsed. - -RESOLUTION (2026-06-09): considered moving the re-open control into the center (pane edge or center status segment) to dodge the end-of-bar space contention; user chose to KEEP TOGGLES FIXED AT THE STATUS-BAR ENDS (best muscle memory). Final design = v3 mock: caret-line glyphs at fixed far-left/far-right status-bar cells (~24px reserved, side items shifted in), chevron-line flips per isCollapsed, firing existing sidebar.collapse / context.collapse. Design fully settled; ready to implement. - -DONE 2026-06-09 (commit). Fixed ~24px caret-line toggles bookend the status bar (StatusbarHost), flip chevron per arrangement.isCollapsed, fire existing sidebar.collapse/context.collapse. Visual affordance only. Tests: app_collapse_toggle_test + updated app_statusbar_test.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 16:34:46', NULL, '7c9522d4eb05a8d5c0c9ff1b0d28a90d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-297', 'bug', 'T-276', 'Conversation doesn''t re-scroll when the bottom input resizes (content hides behind it)', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. - -Trigger: anything that resizes the bottom interaction zone (D-78) — e.g. opening a permission dialog or the AskUserQuestion UI, which replace/expand the composer. The taller box shrinks the conversation viewport from the bottom, but the scroll offset isn''t adjusted, so content that was at the bottom edge is now occluded. - -Expected: when the interaction zone grows, the conversation re-anchors so the previously-visible bottom content stays visible above the box (preserve bottom-anchoring / keep the tail in view). Symmetric on shrink — no leftover gap when the box collapses back. - -Repro: -1. Scroll the Claude conversation to the bottom (tail in view). -2. Trigger a permission prompt or AskUserQuestion (interaction zone expands). -3. Observed: a strip of the last message/card is hidden behind the enlarged input box. -4. Expected: view scrolls so that content remains fully visible above the box. - -Notes: -- Likely the scroll controller doesn''t react to the composer/interaction-zone height change (no re-scroll on viewport-inset/size change). Audit the conversation view''s scroll handling around interaction-zone show/hide (D-78) and on keyboard/box resize. -- Affects the Claude conversation panel. - -DONE 2026-06-09 (commit). conversation_view tracks at-bottom + a LayoutBuilder re-jumps to the tail when the viewport height changes (interaction-zone resize, D-78), only when pinned. Tests: conversation_scroll_test.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 16:01:09', '2026-06-09 16:34:46', NULL, '2aefbb0ada37aeb34e3886a037f6687c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-297', 'bug', 'T-276', 'Conversation doesn''t re-scroll when the bottom input resizes (content hides behind it)', 'When the bottom input area changes height, the conversation view does not re-scroll to the new viewport bottom, so the last bit of content is left hidden behind the newly-sized box. - -Trigger: anything that resizes the bottom interaction zone (D-78) — e.g. opening a permission dialog or the AskUserQuestion UI, which replace/expand the composer. The taller box shrinks the conversation viewport from the bottom, but the scroll offset isn''t adjusted, so content that was at the bottom edge is now occluded. - -Expected: when the interaction zone grows, the conversation re-anchors so the previously-visible bottom content stays visible above the box (preserve bottom-anchoring / keep the tail in view). Symmetric on shrink — no leftover gap when the box collapses back. - -Repro: -1. Scroll the Claude conversation to the bottom (tail in view). -2. Trigger a permission prompt or AskUserQuestion (interaction zone expands). -3. Observed: a strip of the last message/card is hidden behind the enlarged input box. -4. Expected: view scrolls so that content remains fully visible above the box. - -Notes: -- Likely the scroll controller doesn''t react to the composer/interaction-zone height change (no re-scroll on viewport-inset/size change). Audit the conversation view''s scroll handling around interaction-zone show/hide (D-78) and on keyboard/box resize. -- Affects the Claude conversation panel. - -DONE 2026-06-09 (commit). conversation_view tracks at-bottom + a LayoutBuilder re-jumps to the tail when the viewport height changes (interaction-zone resize, D-78), only when pinned. Tests: conversation_scroll_test.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 16:01:09', '2026-06-09 16:34:46', NULL, 'c908227eea50bb4b9ef928b484c56efb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'task', 'T-276', 'Group consecutive edits to the same file into one collapsed card', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. +DONE 2026-06-09 (commit). Fixed ~24px caret-line toggles bookend the status bar (StatusbarHost), flip chevron per arrangement.isCollapsed, fire existing sidebar.collapse/context.collapse. Visual affordance only. Tests: app_collapse_toggle_test + updated app_statusbar_test.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 15:18:22', '2026-06-09 16:34:46', NULL, 'c138ffeffc36fcd39fbef562519377e9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM63TRGBFRWK9W2WA98', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'Group consecutive edits to the same file into one collapsed card', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. Behaviour: - Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). @@ -3357,35 +887,8 @@ STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header st - ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this. -DONE 2026-06-09 (commit). coalesceEditRuns folds consecutive same-file edits into one ''# edits'' ClideHolderCard; aggregate live status via new ClideStatusIndicator (running/success/error) + ClideSpinner (logo-mark, 3D Y rotation, reduced-motion aware), shared with the activity card. Per user: spinner is a self-contained component (not built on ConversationCard''s mark) with an AnimatedSwitcher seam for a richer spinner→check transition later. Tests: activity_cluster_test (coalesce), conversation_view_test (edits card), clide_status_indicator_test.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 15:43:58', '2026-06-09 16:34:46', NULL, '587bce6258997671b106a410cc87bc5a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-296', 'task', 'T-276', 'Group consecutive edits to the same file into one collapsed card', 'When Claude makes multiple subsequent edits to the SAME file, bundle them into a single collapsed holder card instead of rendering each edit as its own card — mirroring how we already fold meta/activity runs. - -Behaviour: -- Detect a run of consecutive edits targeting the same file_path and group them into one ClideHolderCard (the shared container from T-266; see _ActivityCard in lib/builtin/claude/src/conversation_view.dart:681). -- Collapsed (default): one-line ticker of the latest edit + a count label. -- Show the count as "# edits" (e.g. "3 edits" / "1 edit"), NOT "# steps". The existing activity card uses stepLabel = ''$count steps'' at conversation_view.dart:683; this run wants an edits-flavoured label. -- Expanded: every individual edit, each with its full report — bundle, do NOT drop or summarise away any information. All per-edit detail must remain reachable on expand. - -Notes: -- Reuse the existing ClideHolderCard / folding machinery rather than building a new card. -- Grouping breaks when the file_path changes or a non-edit step interleaves (consecutive-same-file only), matching the ''subsequent edits to the same file'' wording. -- Parity with the existing meta/activity folding (T-230) — same collapse/expand affordance, just an edits-labelled run. - -WORKED EXAMPLE (from user screenshot, clide_markdown.dart run): the current stream renders 11 stacked cards — 3x "Edit clide_markdown.dart", then a folded "Read … 2 steps" holder, then 7x "Edit clide_markdown.dart". With this feature it collapses to THREE cards: -- [3 edits] (the first edit run) -- [2 steps] (the existing Read holder — unchanged; this is what splits the edit run) -- [7 edits] (the second edit run) -Confirms the split rule: an interleaving non-edit step (here the folded Read run) breaks the consecutive-same-file edit grouping into two separate edit cards. Same-file edits with nothing between them collapse into one "# edits" card. - -STATUS INDICATOR (live tick reuse): the bundled edits card carries ONE header status indicator that reuses the existing per-step success tick, driven by the latest edit''s state: -- edit in flight -> spinner -- edit completed -> success check (the current green tick) -- next edit starts -> back to spinner -- ...repeat, settling on check when the final edit in the run completes (or the error glyph if one fails). -So the collapsed card''s indicator animates spinner<->check as the run grows, rather than showing a static tick. Each individual edit keeps its own tick in the expanded list (unchanged); this is the aggregate indicator on the holder header/ticker. Mirror the same treatment for the existing "# steps" activity card if it doesn''t already do this. - -DONE 2026-06-09 (commit). coalesceEditRuns folds consecutive same-file edits into one ''# edits'' ClideHolderCard; aggregate live status via new ClideStatusIndicator (running/success/error) + ClideSpinner (logo-mark, 3D Y rotation, reduced-motion aware), shared with the activity card. Per user: spinner is a self-contained component (not built on ConversationCard''s mark) with an AnimatedSwitcher seam for a richer spinner→check transition later. Tests: activity_cluster_test (coalesce), conversation_view_test (edits card), clide_status_indicator_test.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 15:43:58', '2026-06-09 16:34:46', NULL, '7d71f2e8bc6632dcc096d91d748d0cde', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-299', 'task', NULL, 'Automate supply-chain checks in CI (osv-scanner + native SHA verification)', 'Salvaged from gemini-report.md (external code-analysis). Today supply-chain checks are MANUAL — Makefile ''security'' target just prints ''Dart advisories reviewed manually before pubspec.yaml bumps''; there is no automated vuln scan or native-artifact integrity check. Automate both to harden the chain as we move to automated CI, complementing the existing prefer-zero-deps / exact-pin / licenses.yaml discipline (D-31, D-61, D-63). +DONE 2026-06-09 (commit). coalesceEditRuns folds consecutive same-file edits into one ''# edits'' ClideHolderCard; aggregate live status via new ClideStatusIndicator (running/success/error) + ClideSpinner (logo-mark, 3D Y rotation, reduced-motion aware), shared with the activity card. Per user: spinner is a self-contained component (not built on ConversationCard''s mark) with an AnimatedSwitcher seam for a richer spinner→check transition later. Tests: activity_cluster_test (coalesce), conversation_view_test (edits card), clide_status_indicator_test.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 15:43:58', '2026-06-09 16:34:46', NULL, 'ed946676507cc9223eaefad054fb9def', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7K1FN7KD8S5ZVX2VM', 'task', NULL, 'Automate supply-chain checks in CI (osv-scanner + native SHA verification)', 'Salvaged from gemini-report.md (external code-analysis). Today supply-chain checks are MANUAL — Makefile ''security'' target just prints ''Dart advisories reviewed manually before pubspec.yaml bumps''; there is no automated vuln scan or native-artifact integrity check. Automate both to harden the chain as we move to automated CI, complementing the existing prefer-zero-deps / exact-pin / licenses.yaml discipline (D-31, D-61, D-63). 1. Automate Dart dependency vulnerability scanning: - Run an automated scanner (e.g. Google osv-scanner) against pubspec.lock on every push/PR. @@ -3399,82 +902,15 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, Notes: - Both should be low-noise, runnable locally and in CI (candidate home: ci/security.sh + a Makefile target, surfaced via push-check). - Scope is automation only — the manual review discipline already exists; this makes it enforced rather than convention. -- Source report (gemini-report.md) is being removed from the repo once this ticket captures its only actionable content.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 16:42:47', '2026-06-09 16:42:47', NULL, '3a10dc76cf86083d128080c29a41ac18', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'epic', NULL, 'Native Claude integration: transcript-rendered conversation + tmux agent-team panels', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. +- Source report (gemini-report.md) is being removed from the repo once this ticket captures its only actionable content.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 16:42:47', '2026-06-09 16:42:47', NULL, '5eed19fc0a5b2671003c00dedbb2f3ea', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM53HQD6YV754GKBA60', 'story', NULL, 'image paste in Claude panel prompt bar', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:25:26', '2026-06-09 16:56:55', NULL, '3655929898ec949a983a88ba748d9f01', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM62FKQQD0B9B80PFY4', 'task', NULL, 'Team sidebar: account/team token budget (blocked on upstream)', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''. -Why: -1. No cross-widget text selection/copy in clide''s native widgets today (regression vs a terminal). -2. PTY/TUI rendering is the fragile, OS-variant part of the Claude pane. -3. Claude Code''s experimental tmux team mode (lead + N teammates, each a tmux pane) is the flagship capability to surface as real GUI panels, not scraped TUI. +2026-06-09: detached from T-132 (which is otherwise complete) and made the RESOLVER ticket for Q-34 (how + when to surface the budget given upstream doesn''t expose it). Stays in the backlog; revisit when a viable data path lands (upstream claude usage --json / endpoint per anthropics/claude-code#44328, or an API-key usage path).', 'backlog', 'low', NULL, NULL, NULL, '2026-05-23 20:48:38', '2026-06-09 17:16:34', NULL, '1a58f3b9021325310bc448e5a755f4f6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7D5DMYF5X92Q55CBW', 'task', '06FB0TNQM7W0NPAEG3KWNFY9DR', 'Persist + expose the activity-card fold level (T-230 follow-up)', 'Follow-up to T-230. The activity-card fold level is implemented as a switchable ConversationView.foldLevel parameter (FoldLevel none/tools/thinking/everything; groupConversation re-groups per level, unit-tested), but the live Claude pane currently uses the L1 default with no way for the user to change it. Finish acceptance #4: (1) read the fold level from a persisted user setting (via ctx.settings / the kernel settings service — see how other builtins read settings), defaulting to L1; (2) add a control to change it (e.g. a command ''claude.activity.fold-level'' cycling none->L1->L2->L3, and/or an entry in the Claude/settings UI); (3) thread it into the ConversationView built in claude_pane.dart (and team_panel_host.dart). The grouping + card + a11y are already done in activity_cluster.dart + conversation_view.dart. -Direction: run Claude headless in tmux; render its conversation natively from the transcript JSONL; lead panel (left, with composer) + teammate tiles (right) in a responsive auto-wrap grid (1->2->3 cols); team-meta sidebar with roster + token budget. - -Principle: Claude-centric first, CLI-first (D-6) a strong second. Accept isolated, version-pinned coupling to Claude Code internal contracts where it serves the Claude integration; preserve CLI/event surfaces where sensible. - -Acceptance: all child tickets done; D-record landed; Claude pane renders natively with working select+copy and no terminal; a tmux team surfaces as lead + auto-wrapping teammate tiles + meta sidebar; all fragile CC-internals parsing isolated and version-pinned. - -Source: plan iridescent-tinkering-umbrella (2026-05-22), grounded in an-idea.md + read-only validation of the transcript schema this session.', 'ready', 'high', NULL, NULL, NULL, '2026-05-22 15:45:15', '2026-06-09 16:54:28', NULL, 'e921f72c985f66e7d12b0a29b51bea7f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-241', 'task', NULL, 'Audit widget tests for ultrawide coverage (IDE runs wide)', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end.', 'ready', 'medium', NULL, NULL, 'D-23', '2026-06-05 10:02:31', '2026-06-09 16:55:49', NULL, 'd7f5c9aaf283c1cf04eac788ff7e5a19', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-44', 'story', NULL, 'image paste in Claude panel prompt bar', '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.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:25:26', '2026-06-09 16:56:55', NULL, '8921939af86b46ea8c2c0bda67fee119', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-242', 'task', NULL, 'Quiet test reporter: use failures-only in ci scripts (only failures + final count)', NULL, 'ready', 'medium', NULL, NULL, NULL, '2026-06-05 10:17:47', '2026-06-09 16:57:16', NULL, '188cdaef580de4316a8e5c60cf7ffb0e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'story', NULL, 'deep link protocol handler', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration.', 'ready', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-09 16:57:23', NULL, 'aef3c1acebb1677746e0639fc093370c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'task', NULL, 'Number-key shortcuts for interactive prompt buttons (CLI-style 1/2/3)', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. - -Refinement (2026-06-05, user): match the Claude Code CLI''s actual behavior for the text-field interaction. The number key triggers the button/option shortcut when it would be the FIRST character typed -- i.e. focus is NOT in a text field, OR focus IS in the note/Other field but that field is currently EMPTY. Once the field has any content, digits type normally (no shortcut). This supersedes the earlier ''never capture digits while a text field has focus'' gate: it''s better because focus often defaults into the (empty) note field, so the shortcut still fires there (muscle-memory case) while a digit mid-note still types. Implementation: intercept the digit at the prompt focus scope; consume+activate only when the focused editable (if any) is empty, else let it through to type. The CLI exhibits this exact ''footgun'' (digit-as-first-char in an empty field acts as the choice) and we intentionally mirror it.', 'ready', 'medium', NULL, NULL, 'D-78', '2026-06-05 09:38:46', '2026-06-09 16:57:35', NULL, 'ac02d830a7edcfd4bbd9dca45048d50f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'story', NULL, 'Make links in the Claude conversation panel clickable to open in OS URL handler', 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. - -## Behaviour -- Click (or ctrl/cmd-click) on a colorized link opens it via the OS default handler. -- Hover affordance (cursor change / underline) so it reads as clickable. -- Keep the existing colorization. - -## Notes -- Honour user/Claude parity (D-6) where relevant. -- Use the platform URL launcher; avoid pulling in an opinionated package if a thin native/url_launcher shim already exists in the tree. -- Guard against non-http schemes / malformed URLs.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:53', '2026-06-09 16:58:12', NULL, 'da14157e821ad0838e654a22878c9ddc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'epic', NULL, 'Native Claude integration: transcript-rendered conversation + tmux agent-team panels', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. - -Why: -1. No cross-widget text selection/copy in clide''s native widgets today (regression vs a terminal). -2. PTY/TUI rendering is the fragile, OS-variant part of the Claude pane. -3. Claude Code''s experimental tmux team mode (lead + N teammates, each a tmux pane) is the flagship capability to surface as real GUI panels, not scraped TUI. - -Direction: run Claude headless in tmux; render its conversation natively from the transcript JSONL; lead panel (left, with composer) + teammate tiles (right) in a responsive auto-wrap grid (1->2->3 cols); team-meta sidebar with roster + token budget. - -Principle: Claude-centric first, CLI-first (D-6) a strong second. Accept isolated, version-pinned coupling to Claude Code internal contracts where it serves the Claude integration; preserve CLI/event surfaces where sensible. - -Acceptance: all child tickets done; D-record landed; Claude pane renders natively with working select+copy and no terminal; a tmux team surfaces as lead + auto-wrapping teammate tiles + meta sidebar; all fragile CC-internals parsing isolated and version-pinned. - -Source: plan iridescent-tinkering-umbrella (2026-05-22), grounded in an-idea.md + read-only validation of the transcript schema this session. - -STATUS CORRECTED (2026-06-09): not superseded — substantially DELIVERED. 32/34 children done; the native Claude pane (transcript rendering, select/copy, no terminal), tmux team tiles + meta sidebar, composer, and D-75 all shipped. Re-flagged from ''ready'' (misleading — no startable work) to in_progress. Remaining, both non-blocking, kept under this epic: T-158 (team/account token budget — blocked on upstream /usage exposure) and T-235 (persist activity-card fold level, T-230 follow-up). Close when those land.', 'ready', 'high', NULL, NULL, NULL, '2026-05-22 15:45:15', '2026-06-09 17:06:47', NULL, '832c7217b64b6b2674baff7d93240857', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'epic', NULL, 'Native Claude integration: transcript-rendered conversation + tmux agent-team panels', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. - -Why: -1. No cross-widget text selection/copy in clide''s native widgets today (regression vs a terminal). -2. PTY/TUI rendering is the fragile, OS-variant part of the Claude pane. -3. Claude Code''s experimental tmux team mode (lead + N teammates, each a tmux pane) is the flagship capability to surface as real GUI panels, not scraped TUI. - -Direction: run Claude headless in tmux; render its conversation natively from the transcript JSONL; lead panel (left, with composer) + teammate tiles (right) in a responsive auto-wrap grid (1->2->3 cols); team-meta sidebar with roster + token budget. - -Principle: Claude-centric first, CLI-first (D-6) a strong second. Accept isolated, version-pinned coupling to Claude Code internal contracts where it serves the Claude integration; preserve CLI/event surfaces where sensible. - -Acceptance: all child tickets done; D-record landed; Claude pane renders natively with working select+copy and no terminal; a tmux team surfaces as lead + auto-wrapping teammate tiles + meta sidebar; all fragile CC-internals parsing isolated and version-pinned. - -Source: plan iridescent-tinkering-umbrella (2026-05-22), grounded in an-idea.md + read-only validation of the transcript schema this session. - -STATUS CORRECTED (2026-06-09): not superseded — substantially DELIVERED. 32/34 children done; the native Claude pane (transcript rendering, select/copy, no terminal), tmux team tiles + meta sidebar, composer, and D-75 all shipped. Re-flagged from ''ready'' (misleading — no startable work) to in_progress. Remaining, both non-blocking, kept under this epic: T-158 (team/account token budget — blocked on upstream /usage exposure) and T-235 (persist activity-card fold level, T-230 follow-up). Close when those land.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-22 15:45:15', '2026-06-09 17:06:47', NULL, '31d40a9794b4de3443e0802563fb0bfc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-235', 'task', 'T-132', 'Persist + expose the activity-card fold level (T-230 follow-up)', 'Follow-up to T-230. The activity-card fold level is implemented as a switchable ConversationView.foldLevel parameter (FoldLevel none/tools/thinking/everything; groupConversation re-groups per level, unit-tested), but the live Claude pane currently uses the L1 default with no way for the user to change it. Finish acceptance #4: (1) read the fold level from a persisted user setting (via ctx.settings / the kernel settings service — see how other builtins read settings), defaulting to L1; (2) add a control to change it (e.g. a command ''claude.activity.fold-level'' cycling none->L1->L2->L3, and/or an entry in the Claude/settings UI); (3) thread it into the ConversationView built in claude_pane.dart (and team_panel_host.dart). The grouping + card + a11y are already done in activity_cluster.dart + conversation_view.dart. - -DONE 2026-06-09. Persisted app-scoped setting kActivityFoldLevelKey (app.claude.activityFoldLevel); command claude.activity.fold-level cycles none→tools→thinking→everything (foldLevelFromName/nextFoldLevel in activity_cluster.dart); ClaudePane + team_panel_host read the setting and re-fold live via the settings notifier. Tests: foldLevelFromName/nextFoldLevel in activity_cluster_test.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-03 15:17:29', '2026-06-09 17:16:33', NULL, '40fd4797e688503fdc24c27b5d8ae5ba', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-235', 'task', 'T-132', 'Persist + expose the activity-card fold level (T-230 follow-up)', 'Follow-up to T-230. The activity-card fold level is implemented as a switchable ConversationView.foldLevel parameter (FoldLevel none/tools/thinking/everything; groupConversation re-groups per level, unit-tested), but the live Claude pane currently uses the L1 default with no way for the user to change it. Finish acceptance #4: (1) read the fold level from a persisted user setting (via ctx.settings / the kernel settings service — see how other builtins read settings), defaulting to L1; (2) add a control to change it (e.g. a command ''claude.activity.fold-level'' cycling none->L1->L2->L3, and/or an entry in the Claude/settings UI); (3) thread it into the ConversationView built in claude_pane.dart (and team_panel_host.dart). The grouping + card + a11y are already done in activity_cluster.dart + conversation_view.dart. - -DONE 2026-06-09. Persisted app-scoped setting kActivityFoldLevelKey (app.claude.activityFoldLevel); command claude.activity.fold-level cycles none→tools→thinking→everything (foldLevelFromName/nextFoldLevel in activity_cluster.dart); ClaudePane + team_panel_host read the setting and re-fold live via the settings notifier. Tests: foldLevelFromName/nextFoldLevel in activity_cluster_test.', 'done', 'low', NULL, NULL, NULL, '2026-06-03 15:17:29', '2026-06-09 17:16:34', NULL, '877368262db5a391fe1b37deb354155d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-158', 'task', NULL, 'Team sidebar: account/team token budget (blocked on upstream)', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-23 20:48:38', '2026-06-09 17:16:34', NULL, 'ac1714a8fedebb110163a4c2205d9944', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-158', 'task', NULL, 'Team sidebar: account/team token budget (blocked on upstream)', 'Show the account/subscription usage budget (5-hour + weekly limits, % used, reset times) in the Claude meta sidebar (T-141). BLOCKED: this data is not programmatically exposed under subscription (OAuth) auth as of claude 2.1.150 — verified empirically + via docs (2026-05-23). /usage is TUI-only (headless ''claude -p /usage'' returns only a one-liner); stats-cache.json has activity counts only; the stream-json rate_limit_event is undocumented + needs a billed turn; ''claude auth status --json'' shows plan only. A ''claude usage --json'' + a /v1/organizations/{org}/usage/subscription endpoint are an OPEN, unshipped feature request (GitHub anthropics/claude-code#44328). Revisit when #44328 ships or an API-key usage path exists. See project memory ''claude-usage-budget-not-exposed''. - -2026-06-09: detached from T-132 (which is otherwise complete) and made the RESOLVER ticket for Q-34 (how + when to surface the budget given upstream doesn''t expose it). Stays in the backlog; revisit when a viable data path lands (upstream claude usage --json / endpoint per anthropics/claude-code#44328, or an API-key usage path).', 'backlog', 'low', NULL, NULL, NULL, '2026-05-23 20:48:38', '2026-06-09 17:16:34', NULL, 'e137a4e7751d48048823fec2fd3d6d2a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'epic', NULL, 'Native Claude integration: transcript-rendered conversation + tmux agent-team panels', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. +DONE 2026-06-09. Persisted app-scoped setting kActivityFoldLevelKey (app.claude.activityFoldLevel); command claude.activity.fold-level cycles none→tools→thinking→everything (foldLevelFromName/nextFoldLevel in activity_cluster.dart); ClaudePane + team_panel_host read the setting and re-fold live via the settings notifier. Tests: foldLevelFromName/nextFoldLevel in activity_cluster_test.', 'done', 'low', NULL, NULL, NULL, '2026-06-03 15:17:29', '2026-06-09 17:16:34', NULL, 'eccaa5f62d3b6f50eb984a4b3d7bc169', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7W0NPAEG3KWNFY9DR', 'epic', NULL, 'Native Claude integration: transcript-rendered conversation + tmux agent-team panels', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. Why: 1. No cross-widget text selection/copy in clide''s native widgets today (regression vs a terminal). @@ -3491,28 +927,8 @@ Source: plan iridescent-tinkering-umbrella (2026-05-22), grounded in an-idea.md STATUS CORRECTED (2026-06-09): not superseded — substantially DELIVERED. 32/34 children done; the native Claude pane (transcript rendering, select/copy, no terminal), tmux team tiles + meta sidebar, composer, and D-75 all shipped. Re-flagged from ''ready'' (misleading — no startable work) to in_progress. Remaining, both non-blocking, kept under this epic: T-158 (team/account token budget — blocked on upstream /usage exposure) and T-235 (persist activity-card fold level, T-230 follow-up). Close when those land. -CLOSED 2026-06-09: all doable work delivered (native Claude pane, transcript rendering, select/copy, no terminal, tmux team tiles + meta sidebar, composer, activity-card folding incl. the persisted fold level T-235, D-75). The one blocked item — account/team token budget — was detached (T-158) and reframed as Q-34 (how + when to surface it given upstream doesn''t expose the data); T-158 is the resolver, parked in the backlog. Nothing actionable remains under this epic.', 'in_progress', 'high', NULL, NULL, NULL, '2026-05-22 15:45:15', '2026-06-09 17:23:25', NULL, '199062edd21d86aabf28297978ccf36c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-132', 'epic', NULL, 'Native Claude integration: transcript-rendered conversation + tmux agent-team panels', 'Replace the terminal-based Claude pane with native rendering driven by Claude Code''s transcript JSONL (with cross-widget text selection), and surface experimental tmux agent teams (lead + teammates) as tiled native panels plus a team-meta sidebar. The terminal is retained only as a general IDE tool. - -Why: -1. No cross-widget text selection/copy in clide''s native widgets today (regression vs a terminal). -2. PTY/TUI rendering is the fragile, OS-variant part of the Claude pane. -3. Claude Code''s experimental tmux team mode (lead + N teammates, each a tmux pane) is the flagship capability to surface as real GUI panels, not scraped TUI. - -Direction: run Claude headless in tmux; render its conversation natively from the transcript JSONL; lead panel (left, with composer) + teammate tiles (right) in a responsive auto-wrap grid (1->2->3 cols); team-meta sidebar with roster + token budget. - -Principle: Claude-centric first, CLI-first (D-6) a strong second. Accept isolated, version-pinned coupling to Claude Code internal contracts where it serves the Claude integration; preserve CLI/event surfaces where sensible. - -Acceptance: all child tickets done; D-record landed; Claude pane renders natively with working select+copy and no terminal; a tmux team surfaces as lead + auto-wrapping teammate tiles + meta sidebar; all fragile CC-internals parsing isolated and version-pinned. - -Source: plan iridescent-tinkering-umbrella (2026-05-22), grounded in an-idea.md + read-only validation of the transcript schema this session. - -STATUS CORRECTED (2026-06-09): not superseded — substantially DELIVERED. 32/34 children done; the native Claude pane (transcript rendering, select/copy, no terminal), tmux team tiles + meta sidebar, composer, and D-75 all shipped. Re-flagged from ''ready'' (misleading — no startable work) to in_progress. Remaining, both non-blocking, kept under this epic: T-158 (team/account token budget — blocked on upstream /usage exposure) and T-235 (persist activity-card fold level, T-230 follow-up). Close when those land. - -CLOSED 2026-06-09: all doable work delivered (native Claude pane, transcript rendering, select/copy, no terminal, tmux team tiles + meta sidebar, composer, activity-card folding incl. the persisted fold level T-235, D-75). The one blocked item — account/team token budget — was detached (T-158) and reframed as Q-34 (how + when to surface it given upstream doesn''t expose the data); T-158 is the resolver, parked in the backlog. Nothing actionable remains under this epic.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:45:15', '2026-06-09 17:23:25', NULL, 'bb12e25f5a36b67bcb1c4dddb4a750d1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-300', 'task', NULL, 'Clickable file references in Claude conversation open in editor', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:02:41', '2026-06-09 20:02:41', NULL, 'b01a26546b8842f6419cd79ce08930cd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-300', 'task', 'T-276', 'Clickable file references in Claude conversation open in editor', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:02:41', '2026-06-09 20:02:53', NULL, 'ef946533d6d38d0a21731090058eb682', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-300', 'task', 'T-276', 'Clickable file references in Claude conversation open in editor', 'Make file-path references rendered in the Claude conversation pane clickable: clicking a path opens that file in the editor. +CLOSED 2026-06-09: all doable work delivered (native Claude pane, transcript rendering, select/copy, no terminal, tmux team tiles + meta sidebar, composer, activity-card folding incl. the persisted fold level T-235, D-75). The one blocked item — account/team token budget — was detached (T-158) and reframed as Q-34 (how + when to surface it given upstream doesn''t expose the data); T-158 is the resolver, parked in the backlog. Nothing actionable remains under this epic.', 'done', 'high', NULL, NULL, NULL, '2026-05-22 15:45:15', '2026-06-09 17:23:25', NULL, 'd4abcdfdd0f3d79570f331c7385e36e5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7F2TBJJV1F2KP7XR8', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'Clickable file references in Claude conversation open in editor', 'Make file-path references rendered in the Claude conversation pane clickable: clicking a path opens that file in the editor. **Behavior** - Detect references to files in the workspace as the conversation markdown is rendered — bare paths (`lib/app.dart`), `file:line` forms (`lib/app.dart:42`, clickable per CLAUDE.md), and likely inline-code spans / markdown links pointing at repo paths. @@ -3522,10 +938,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, **Notes / constraints** - Markdown rendering is clide-owned custom CustomPaint/widgets (not a package), so detection + hit-testing lands in the conversation/markdown render path. - Honors User/Claude parity (D-6): the click maps to the existing `clide editor open` verb. -- Open questions to settle during design: how aggressive path detection should be (existence check vs. heuristic), handling of non-existent / external paths, and visual affordance (underline/hover) for a linkified ref.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:02:41', '2026-06-09 20:03:08', NULL, '21e72bc9ac78967ca6b340ade150bc68', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-301', 'task', NULL, 'Display in-repo file paths relative to repo root in Claude conversation', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:04:29', '2026-06-09 20:04:29', NULL, '1c14157cf62887b436a5b71012f6165a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-301', 'task', 'T-276', 'Display in-repo file paths relative to repo root in Claude conversation', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:04:29', '2026-06-09 20:04:31', NULL, 'b9ca8222d9f5904f9099f30d0e2cc1a9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-301', 'task', 'T-276', 'Display in-repo file paths relative to repo root in Claude conversation', 'When file paths to files inside the current repo appear in the Claude conversation, show them relative to the repo root rather than as absolute paths. +- Open questions to settle during design: how aggressive path detection should be (existence check vs. heuristic), handling of non-existent / external paths, and visual affordance (underline/hover) for a linkified ref.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:02:41', '2026-06-09 20:03:08', NULL, '1996bc502ccd18ba28203e362ce82bc7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6048VEGFDNEBHFMM4', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'Display in-repo file paths relative to repo root in Claude conversation', 'When file paths to files inside the current repo appear in the Claude conversation, show them relative to the repo root rather than as absolute paths. **DESIGN OPEN — needs discussion before implementation.** The exact presentation is undecided. @@ -3541,10 +955,9 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - Does this happen at render time only, or is the underlying text also normalized? **Related** -- Pairs with T-300 (clickable file references) — same conversation/markdown render path; likely share path-detection + workspace-root resolution.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:04:29', '2026-06-09 20:04:54', NULL, 'fdf0a8829db09aeb92850695246e407b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-242', 'task', NULL, 'Quiet test reporter: use failures-only in ci scripts (only failures + final count)', 'VERIFIED DONE 2026-06-09: failures-only is the default reporter across all gate scripts — ci/test.sh (REPORTER var, used in every flutter/dart test invocation), ci/test_core.sh, ci/test_a11y.sh — each with a TEST_REPORTER=expanded escape hatch for debugging. No work remaining.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-05 10:17:47', '2026-06-09 20:07:29', NULL, 'd203990cf8add84bd60e3d4b09074c88', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-242', 'task', NULL, 'Quiet test reporter: use failures-only in ci scripts (only failures + final count)', 'VERIFIED DONE 2026-06-09: failures-only is the default reporter across all gate scripts — ci/test.sh (REPORTER var, used in every flutter/dart test invocation), ci/test_core.sh, ci/test_a11y.sh — each with a TEST_REPORTER=expanded escape hatch for debugging. No work remaining.', 'done', 'medium', NULL, NULL, NULL, '2026-06-05 10:17:47', '2026-06-09 20:07:30', NULL, 'e1918868d0229214cf1175e3c4a2a6a7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'story', NULL, 'Make links in the Claude conversation panel clickable to open in OS URL handler', 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. +- Pairs with T-300 (clickable file references) — same conversation/markdown render path; likely share path-detection + workspace-root resolution.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:04:29', '2026-06-09 20:04:54', NULL, 'a5da33062dd9373142d5292a79de8d64', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4AE2GEF6BXSGKRMGC', 'task', NULL, 'Quiet test reporter: use failures-only in ci scripts (only failures + final count)', 'VERIFIED DONE 2026-06-09: failures-only is the default reporter across all gate scripts — ci/test.sh (REPORTER var, used in every flutter/dart test invocation), ci/test_core.sh, ci/test_a11y.sh — each with a TEST_REPORTER=expanded escape hatch for debugging. No work remaining.', 'done', 'medium', NULL, NULL, NULL, '2026-06-05 10:17:47', '2026-06-09 20:07:30', NULL, '05823664d6d7b3cefbbc3f6c86c199b0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM42VPM50VKAWXBCAM8', 'story', NULL, 'Make links in the Claude conversation panel clickable to open in OS URL handler', 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. ## Behaviour - Click (or ctrl/cmd-click) on a colorized link opens it via the OS default handler. @@ -3556,23 +969,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - Use the platform URL launcher; avoid pulling in an opinionated package if a thin native/url_launcher shim already exists in the tree. - Guard against non-http schemes / malformed URLs. -DONE 2026-06-09. http(s) links in the conversation open via OsBridge.openURL on click (hover underline + pointer); non-http inert. ClideMarkdown gained an onLinkTap hook + _urlLinkSpan; the 3 inline-interaction callbacks were bundled into ClideMarkdownHooks (single threaded param) — also fixed links/images only working in some markdown contexts. Tests: clide_markdown_test.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:53', '2026-06-09 20:17:04', NULL, '50b60ff602eaa27cb4d3025d7368d2d1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-253', 'story', NULL, 'Make links in the Claude conversation panel clickable to open in OS URL handler', 'The Claude conversation panel detects URLs and colorizes them, but they are not interactive. Make detected links clickable (a plain click, or control/cmd-click) so they are handed off to the OS URL opener. - -## Behaviour -- Click (or ctrl/cmd-click) on a colorized link opens it via the OS default handler. -- Hover affordance (cursor change / underline) so it reads as clickable. -- Keep the existing colorization. - -## Notes -- Honour user/Claude parity (D-6) where relevant. -- Use the platform URL launcher; avoid pulling in an opinionated package if a thin native/url_launcher shim already exists in the tree. -- Guard against non-http schemes / malformed URLs. - -DONE 2026-06-09. http(s) links in the conversation open via OsBridge.openURL on click (hover underline + pointer); non-http inert. ClideMarkdown gained an onLinkTap hook + _urlLinkSpan; the 3 inline-interaction callbacks were bundled into ClideMarkdownHooks (single threaded param) — also fixed links/images only working in some markdown contexts. Tests: clide_markdown_test.', 'done', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:53', '2026-06-09 20:17:04', NULL, '2a72ffcad16186e61da544faf08511e9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-302', 'task', NULL, 'Frame0 wireframe: settings screen design', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:24:54', '2026-06-09 20:24:54', NULL, '9feecb7a07a9bbbdd48db1e4c0c59b8e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-302', 'task', 'T-8', 'Frame0 wireframe: settings screen design', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:24:54', '2026-06-09 20:24:57', NULL, '29fbe1e10f41cd177653c1f8784b6b44', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-302', 'task', 'T-8', 'Frame0 wireframe: settings screen design', 'Design the app''s settings screen as a Frame0 wireframe before building `builtin.settings-ui` (see Tier 6 epic T-8). Output is a wireframe to align on layout/IA, not implementation. +DONE 2026-06-09. http(s) links in the conversation open via OsBridge.openURL on click (hover underline + pointer); non-http inert. ClideMarkdown gained an onLinkTap hook + _urlLinkSpan; the 3 inline-interaction callbacks were bundled into ClideMarkdownHooks (single threaded param) — also fixed links/images only working in some markdown contexts. Tests: clide_markdown_test.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-06 09:32:53', '2026-06-09 20:17:04', NULL, '4b6b10e6e323af31ce0e87055cf738d5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM7H8JWCP59WQVD0FW4', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'Frame0 wireframe: settings screen design', 'Design the app''s settings screen as a Frame0 wireframe before building `builtin.settings-ui` (see Tier 6 epic T-8). Output is a wireframe to align on layout/IA, not implementation. **Deliverable** - Frame0 wireframe authored via the frame0-wireframe skill: local JSON source-of-truth synced to Frame0, exported for review. @@ -3593,84 +991,22 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, **Constraints** - Follow clide visual language — pull theme tokens / control geometry from the ui-design skill so the wireframe maps cleanly to real widgets (no Material/Cupertino). -This is the design step; implementation of the actual settings UI is separate child work under T-8.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:24:54', '2026-06-09 20:25:49', NULL, '02c7fd8860f35097fffe573bcfab2739', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'bug', NULL, 'GLib-GIO-CRITICAL: GFileInfo created without standard::size on startup (Linux)', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: - - GLib-GIO-CRITICAL: GFileInfo created without standard::size - file ../gio/gfileinfo.c: line 1865 (g_file_info_get_size): should not be reached - -Repro: launch the app (make run / flutter run) on Linux. The pair fires early in boot — right after the primary pane binds its session and before the IPC server starts listening (observed ~0.3s apart, e.g. 15:53:15.570 and 15:53:15.832), on every launch. - -Cause: some code path calls g_file_info_get_size() on a GFileInfo that was created/queried WITHOUT requesting the G_FILE_ATTRIBUTE_STANDARD_SIZE (''standard::size'') attribute. A grep of clide''s own code (lib/, linux/runner/, native/) finds no direct g_file_info / g_file_query_info usage, so it is most likely inside GTK/GLib itself or a Flutter Linux plugin''s file enumeration (icon/thumbnail/mime probe, path lookups), not clide Dart/C++. The GTK file-chooser in linux/runner/clide_app.cc is on-demand only, so it is not the trigger (the warnings fire at boot). - -Impact: low — console noise at CRITICAL level; no observed functional breakage. But a size query that ''should not be reached'' may be reading a bogus/zero size somewhere worth confirming. - -Investigation: run with G_DEBUG=fatal-warnings (or gdb break on g_log/g_logv) to capture the stack at the warning and identify the library/plugin frame; check whether a Flutter plugin (file_selector, path_provider, url_launcher) or GTK icon/mime loading is responsible. If upstream/GTK, document + suppress-from-our-side or pin; if a plugin, file upstream. - -Env: Fedora, GTK Linux embedder, flutter run. - -UPDATE (2026-06-09): also fires MID-SESSION, not only at boot — contradicts the "fires early in boot, on every launch" framing above. Observed log: app booted 16:47:01, but the GLib-GIO-CRITICAL pair fired at 18:51:07.841 / 18:51:08.190 (~2h into the session), near pane/session activity. So the trigger is more likely a file-info code path tied to a user action or background file enumeration than pure startup. Re-scope the investigation to capture the stack when it fires mid-session (G_DEBUG=fatal-warnings / gdb break on g_log) rather than only at boot.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-08 17:12:49', '2026-06-09 20:26:44', NULL, '23781a4f86b92056beb8a9fda34daaec', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'task', NULL, 'Number-key shortcuts for interactive prompt buttons (CLI-style 1/2/3)', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. +This is the design step; implementation of the actual settings UI is separate child work under T-8.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 20:24:54', '2026-06-09 20:25:49', NULL, 'cebb365e52ecabe1de21f958aae69a9a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5QJC039TMDF5JGMXC', 'task', NULL, 'Number-key shortcuts for interactive prompt buttons (CLI-style 1/2/3)', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. Refinement (2026-06-05, user): match the Claude Code CLI''s actual behavior for the text-field interaction. The number key triggers the button/option shortcut when it would be the FIRST character typed -- i.e. focus is NOT in a text field, OR focus IS in the note/Other field but that field is currently EMPTY. Once the field has any content, digits type normally (no shortcut). This supersedes the earlier ''never capture digits while a text field has focus'' gate: it''s better because focus often defaults into the (empty) note field, so the shortcut still fires there (muscle-memory case) while a digit mid-note still types. Implementation: intercept the digit at the prompt focus scope; consume+activate only when the focused editable (if any) is empty, else let it through to type. The CLI exhibits this exact ''footgun'' (digit-as-first-char in an empty field acts as the choice) and we intentionally mirror it. -DONE 2026-06-09. ToolPromptCard: number keys pick the matching button/option (labels prefixed 1./2./3.), Enter confirms the primary action. Permission 1=Allow / 2=Allow&remember(if offered) else Deny / 3=Deny; AskUserQuestion 1..N select+toggle options + Other. Card autofocuses; _onKey self-guards on hasPrimaryFocus so a focused note field types digits normally. Permission/option actions shared between buttons + keys. Tests: prompt_card_test number-key group + updated label finders.', 'ready', 'medium', NULL, NULL, 'D-78', '2026-06-05 09:38:46', '2026-06-09 20:27:17', NULL, 'a17ee52067e66daa53751d8dc9047b34', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-240', 'task', NULL, 'Number-key shortcuts for interactive prompt buttons (CLI-style 1/2/3)', 'Muscle-memory gap from the Claude Code CLI: in clide''s interaction-zone prompts (D-78), the user reaches for number keys 1/2/3 to pick a button but has to click. Map number keys (1..N) to the visible buttons/options, and PREFIX each button label with its number for discoverability (CLI shows ''1. Allow / 2. ... / 3. No'').\n\nSurface: ToolPromptCard in lib/builtin/claude/src/prompt_card.dart. Two modes:\n- PERMISSION (_permission, ~line 119): buttons Allow (primary) / ''Allow & don''t ask again'' (only when permissionSuggestions present) / Deny. Map 1=Allow, 2=''Allow & don''t ask again'' (when shown) else Deny, 3=Deny when the middle one is shown. Match the CLI''s ordering/numbering.\n- ASKUSERQUESTION (_question / _optButton, ~line 280/294): each option per question gets a number (1..N) that selects (single) or toggles (multiSelect) it; keep Enter for Submit/Next and Esc/back as-is. Prefix option labels with the number alongside the existing radio/checkbox glyph (the ''○/●'' in _optButton).\n\nKEY CAVEAT (do this right): the prompt card hosts a free-text note field (_NoteField) and AskUserQuestion ''Other'' free-text. Number keys MUST NOT be captured while focus is in a text field (otherwise typing ''1'' triggers a button). Gate the shortcut on focus not being in an editable, i.e. intercept at the prompt''s Focus/FocusScope and bail when a text field has focus — same consumer-interception discipline as D-82 (Vim) / the editor. Keep the ClideButtons (clicks + AT) intact; the number key is an additional accelerator. Accessibility: include the number in the button''s semantics label.\n\nAcceptance: with a permission prompt or AskUserQuestion open and focus not in a text field, pressing 1/2/3 (and up to N for question options) activates the matching button/option; labels are number-prefixed; typing in a note/Other field is unaffected; mouse + screen-reader paths still work. +DONE 2026-06-09. ToolPromptCard: number keys pick the matching button/option (labels prefixed 1./2./3.), Enter confirms the primary action. Permission 1=Allow / 2=Allow&remember(if offered) else Deny / 3=Deny; AskUserQuestion 1..N select+toggle options + Other. Card autofocuses; _onKey self-guards on hasPrimaryFocus so a focused note field types digits normally. Permission/option actions shared between buttons + keys. Tests: prompt_card_test number-key group + updated label finders.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-05 09:38:46', '2026-06-09 20:27:17', NULL, '18039168ae44b4bd32d516ada0da1a46', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM531VE0C6N36AT12SR', 'task', NULL, 'Audit widget tests for ultrawide coverage (IDE runs wide)', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end. -Refinement (2026-06-05, user): match the Claude Code CLI''s actual behavior for the text-field interaction. The number key triggers the button/option shortcut when it would be the FIRST character typed -- i.e. focus is NOT in a text field, OR focus IS in the note/Other field but that field is currently EMPTY. Once the field has any content, digits type normally (no shortcut). This supersedes the earlier ''never capture digits while a text field has focus'' gate: it''s better because focus often defaults into the (empty) note field, so the shortcut still fires there (muscle-memory case) while a digit mid-note still types. Implementation: intercept the digit at the prompt focus scope; consume+activate only when the focused editable (if any) is empty, else let it through to type. The CLI exhibits this exact ''footgun'' (digit-as-first-char in an empty field acts as the choice) and we intentionally mirror it. - -DONE 2026-06-09. ToolPromptCard: number keys pick the matching button/option (labels prefixed 1./2./3.), Enter confirms the primary action. Permission 1=Allow / 2=Allow&remember(if offered) else Deny / 3=Deny; AskUserQuestion 1..N select+toggle options + Other. Card autofocuses; _onKey self-guards on hasPrimaryFocus so a focused note field types digits normally. Permission/option actions shared between buttons + keys. Tests: prompt_card_test number-key group + updated label finders.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-05 09:38:46', '2026-06-09 20:27:17', NULL, 'ccf823cb6864a42e1f87c3099ad7b16c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-241', 'task', NULL, 'Audit widget tests for ultrawide coverage (IDE runs wide)', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end. - -DONE 2026-06-09. Added the reusable setSurfaceSize(tester, width) helper to test/helpers/widget_harness.dart (the foundation the ticket asked for). Ultrawide (3440) cases added on the flagged width-sensitive surfaces: ClideMarquee (was narrow-only — now verified static when a line fits a wide slot) and the quick-open palette (verified width-capped, not stretched). Status bar already covers 600+3440 (T-239). AUDIT NOTES on the rest: overlays/pickers use fixed-width panels (ClideMenu/quick-open 480, anchored popovers) so they''re inherently capped/centered; tab strips (MultitabPane) and status items overflow only when NARROW (a small-screen concern, Q-26), not wide; RootLayout slots are explicit fixed/flex widths with drag-resize. The helper is now in place for any future width-sensitive widget. Not exhaustive by design (audit + reusable tooling + the high-risk surfaces).', 'ready', 'medium', NULL, NULL, 'D-23', '2026-06-05 10:02:31', '2026-06-09 20:34:30', NULL, '8bf99cb10e5a110880e8a15e4f2dcb17', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-241', 'task', NULL, 'Audit widget tests for ultrawide coverage (IDE runs wide)', 'Lesson from T-239: a width-PROPORTIONAL layout bug (status-bar right group drifting to mid-bar via a Spacer-vs-flex split) was invisible at the default 800px flutter_test surface and at 1200px, but glaring at 3440px. clide is an IDE — ultrawide (3440, 5120, ultrawide+vertical splits) is a VERY common real screen size, so width-sensitive layout that only ever gets tested at 800px hides exactly this class of bug.\n\nScope: review the widget/golden suite and add ultrawide coverage where layout is width-sensitive. Candidates: the bottom status bar (done — test/app_statusbar_test.dart now covers 600 + 3440), panel/slot layout + drag-resize (lib/app.dart RootLayout, SlotHost), tab strips (overflow/scroll at wide), ClideMarquee (T-160 — only tested narrow; also check it doesn''t mis-behave wide), conversation view / activity card, the command palette + quick-open overlays (max-width/centering on wide), modal pickers, status items.\n\nApproach: (1) add a shared test helper to pump at a given surface width via tester.view.physicalSize (note: a wide SizedBox under the default 800px surface is CLAMPED — must set view.physicalSize, see test/app_statusbar_test.dart pumpAt). (2) For layout-sensitive widgets, assert key positions/no-overflow at BOTH a normal and an ultrawide width. (3) Don''t blanket-add to every test — target width-sensitive layout (Row/Spacer/Expanded/Flexible/Align, max-width caps, centering). Note any widget that SHOULD cap/center on ultrawide (readability) vs fill. Relates to Q-26 (small-screen layout) — same responsive concern at the other end. - -DONE 2026-06-09. Added the reusable setSurfaceSize(tester, width) helper to test/helpers/widget_harness.dart (the foundation the ticket asked for). Ultrawide (3440) cases added on the flagged width-sensitive surfaces: ClideMarquee (was narrow-only — now verified static when a line fits a wide slot) and the quick-open palette (verified width-capped, not stretched). Status bar already covers 600+3440 (T-239). AUDIT NOTES on the rest: overlays/pickers use fixed-width panels (ClideMenu/quick-open 480, anchored popovers) so they''re inherently capped/centered; tab strips (MultitabPane) and status items overflow only when NARROW (a small-screen concern, Q-26), not wide; RootLayout slots are explicit fixed/flex widths with drag-resize. The helper is now in place for any future width-sensitive widget. Not exhaustive by design (audit + reusable tooling + the high-risk surfaces).', 'done', 'medium', NULL, NULL, 'D-23', '2026-06-05 10:02:31', '2026-06-09 20:34:30', NULL, '7e2a5500f0e91b9c54bf2ab67ecb0fed', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-303', 'task', NULL, 'macOS: deliver clide:// deep links via AppDelegate openURLs', 'Follow-up to T-56. The clide:// scheme is registered in macos/Runner/Info.plist (CFBundleURLTypes) and the parser+routing are done (parseArgv → editor.open → IPC). But macOS does NOT pass the URL as argv like Linux does (Exec %U); it delivers via the AppDelegate. Wire AppDelegate.application(_:open:) (or applicationDidFinishLaunching for a cold-start URL) to forward the clide:// URL string to Dart over a method channel (mirror the existing clide/window + clide/clipboard channels), and on the Dart side run it through parseArgv and dispatch to the local IPC (same path the CLI uses). Needs a real macOS machine to verify — deliberately not shipped blind in T-56. Refs: macos/Runner/AppDelegate.swift, lib/src/cli/argv_to_request.dart (_deepLinkToRequest).', 'backlog', 'low', NULL, NULL, NULL, '2026-06-09 20:41:22', '2026-06-09 20:41:22', NULL, 'c5e4137bb9eef55527d26525f0ea7b2f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'story', NULL, 'deep link protocol handler', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. - -DONE (Linux) 2026-06-09. clide://open?path=&line= → editor.open via parseArgv, routed through the existing CLI→IPC path into the running window (single-instance for free). Parser validates action/path/line; tests in argv_to_request_test. Scheme registered: linux/clide.desktop (x-scheme-handler/clide, Exec already %U) + macOS Info.plist CFBundleURLTypes. Linux works end-to-end. macOS URL DELIVERY (AppDelegate openURLs → method channel → Dart) deferred to T-303 — needs a macOS machine to verify, not shipped blind.', 'ready', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-09 20:41:22', NULL, '2c4a9860c2649adc4006a7af05834a3b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'story', NULL, 'deep link protocol handler', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. - -DONE (Linux) 2026-06-09. clide://open?path=&line= → editor.open via parseArgv, routed through the existing CLI→IPC path into the running window (single-instance for free). Parser validates action/path/line; tests in argv_to_request_test. Scheme registered: linux/clide.desktop (x-scheme-handler/clide, Exec already %U) + macOS Info.plist CFBundleURLTypes. Linux works end-to-end. macOS URL DELIVERY (AppDelegate openURLs → method channel → Dart) deferred to T-303 — needs a macOS machine to verify, not shipped blind.', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-09 20:41:22', NULL, '451a2c1b33398a8481275424a87291cf', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-56', 'story', NULL, 'deep link protocol handler', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. +DONE 2026-06-09. Added the reusable setSurfaceSize(tester, width) helper to test/helpers/widget_harness.dart (the foundation the ticket asked for). Ultrawide (3440) cases added on the flagged width-sensitive surfaces: ClideMarquee (was narrow-only — now verified static when a line fits a wide slot) and the quick-open palette (verified width-capped, not stretched). Status bar already covers 600+3440 (T-239). AUDIT NOTES on the rest: overlays/pickers use fixed-width panels (ClideMenu/quick-open 480, anchored popovers) so they''re inherently capped/centered; tab strips (MultitabPane) and status items overflow only when NARROW (a small-screen concern, Q-26), not wide; RootLayout slots are explicit fixed/flex widths with drag-resize. The helper is now in place for any future width-sensitive widget. Not exhaustive by design (audit + reusable tooling + the high-risk surfaces).', 'ready', 'medium', NULL, NULL, 'D-23', '2026-06-05 10:02:31', '2026-06-09 20:34:30', NULL, 'cad1d9909d676a7395a46320e9702560', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4K1MTP2MHRKKNXBFM', 'task', NULL, 'macOS: deliver clide:// deep links via AppDelegate openURLs', 'Follow-up to T-56. The clide:// scheme is registered in macos/Runner/Info.plist (CFBundleURLTypes) and the parser+routing are done (parseArgv → editor.open → IPC). But macOS does NOT pass the URL as argv like Linux does (Exec %U); it delivers via the AppDelegate. Wire AppDelegate.application(_:open:) (or applicationDidFinishLaunching for a cold-start URL) to forward the clide:// URL string to Dart over a method channel (mirror the existing clide/window + clide/clipboard channels), and on the Dart side run it through parseArgv and dispatch to the local IPC (same path the CLI uses). Needs a real macOS machine to verify — deliberately not shipped blind in T-56. Refs: macos/Runner/AppDelegate.swift, lib/src/cli/argv_to_request.dart (_deepLinkToRequest).', 'backlog', 'low', NULL, NULL, NULL, '2026-06-09 20:41:22', '2026-06-09 20:41:22', NULL, 'c1943138c8e1f3dedd400bc525400bf2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4BRWWPB4R06Q31XV0', 'story', NULL, 'deep link protocol handler', 'Register clide:// protocol handler. Support clide://open?path=/repo/file.md&line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration. DONE (Linux) 2026-06-09. clide://open?path=&line= → editor.open via parseArgv, routed through the existing CLI→IPC path into the running window (single-instance for free). Parser validates action/path/line; tests in argv_to_request_test. Scheme registered: linux/clide.desktop (x-scheme-handler/clide, Exec already %U) + macOS Info.plist CFBundleURLTypes. Linux works end-to-end. macOS URL DELIVERY (AppDelegate openURLs → method channel → Dart) deferred to T-303 — needs a macOS machine to verify, not shipped blind. -SECURITY REDESIGN 2026-06-09 (D-90): per user, the clide:// surface is now paranoid. The link no longer translates to a command in parseArgv — it routes the raw URL to a new builtin.deeplink handler gated by a DEFAULT-DENY allowlist (kDeepLinkSafeActions = {open} only — run/git/write/passthrough rejected) AND a mandatory confirmation prompt (''an external link wants to: … allow?'') before any action. The generic CLI-passthrough idea is deliberately NOT shipped (it would make a webpage a remote control); each new action is an explicit allowlist + handler addition. Tests: deep_link_test (allowlist boundary), extension_test (gating).', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-09 20:57:53', NULL, 'f77a269ef50bf0fc02ecd67d371f77b0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'bug', NULL, 'GLib-GIO-CRITICAL: GFileInfo created without standard::size on startup (Linux)', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: - - GLib-GIO-CRITICAL: GFileInfo created without standard::size - file ../gio/gfileinfo.c: line 1865 (g_file_info_get_size): should not be reached - -Repro: launch the app (make run / flutter run) on Linux. The pair fires early in boot — right after the primary pane binds its session and before the IPC server starts listening (observed ~0.3s apart, e.g. 15:53:15.570 and 15:53:15.832), on every launch. - -Cause: some code path calls g_file_info_get_size() on a GFileInfo that was created/queried WITHOUT requesting the G_FILE_ATTRIBUTE_STANDARD_SIZE (''standard::size'') attribute. A grep of clide''s own code (lib/, linux/runner/, native/) finds no direct g_file_info / g_file_query_info usage, so it is most likely inside GTK/GLib itself or a Flutter Linux plugin''s file enumeration (icon/thumbnail/mime probe, path lookups), not clide Dart/C++. The GTK file-chooser in linux/runner/clide_app.cc is on-demand only, so it is not the trigger (the warnings fire at boot). - -Impact: low — console noise at CRITICAL level; no observed functional breakage. But a size query that ''should not be reached'' may be reading a bogus/zero size somewhere worth confirming. - -Investigation: run with G_DEBUG=fatal-warnings (or gdb break on g_log/g_logv) to capture the stack at the warning and identify the library/plugin frame; check whether a Flutter plugin (file_selector, path_provider, url_launcher) or GTK icon/mime loading is responsible. If upstream/GTK, document + suppress-from-our-side or pin; if a plugin, file upstream. - -Env: Fedora, GTK Linux embedder, flutter run. - -UPDATE (2026-06-09): also fires MID-SESSION, not only at boot — contradicts the "fires early in boot, on every launch" framing above. Observed log: app booted 16:47:01, but the GLib-GIO-CRITICAL pair fired at 18:51:07.841 / 18:51:08.190 (~2h into the session), near pane/session activity. So the trigger is more likely a file-info code path tied to a user action or background file enumeration than pure startup. Re-scope the investigation to capture the stack when it fires mid-session (G_DEBUG=fatal-warnings / gdb break on g_log) rather than only at boot.', 'ready', 'low', NULL, NULL, NULL, '2026-06-08 17:12:49', '2026-06-09 21:04:10', NULL, 'b6dcadfb27c0456901c6d99a819529a7', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'bug', NULL, 'GLib-GIO-CRITICAL: GFileInfo created without standard::size on startup (Linux)', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: - - GLib-GIO-CRITICAL: GFileInfo created without standard::size - file ../gio/gfileinfo.c: line 1865 (g_file_info_get_size): should not be reached - -Repro: launch the app (make run / flutter run) on Linux. The pair fires early in boot — right after the primary pane binds its session and before the IPC server starts listening (observed ~0.3s apart, e.g. 15:53:15.570 and 15:53:15.832), on every launch. - -Cause: some code path calls g_file_info_get_size() on a GFileInfo that was created/queried WITHOUT requesting the G_FILE_ATTRIBUTE_STANDARD_SIZE (''standard::size'') attribute. A grep of clide''s own code (lib/, linux/runner/, native/) finds no direct g_file_info / g_file_query_info usage, so it is most likely inside GTK/GLib itself or a Flutter Linux plugin''s file enumeration (icon/thumbnail/mime probe, path lookups), not clide Dart/C++. The GTK file-chooser in linux/runner/clide_app.cc is on-demand only, so it is not the trigger (the warnings fire at boot). - -Impact: low — console noise at CRITICAL level; no observed functional breakage. But a size query that ''should not be reached'' may be reading a bogus/zero size somewhere worth confirming. - -Investigation: run with G_DEBUG=fatal-warnings (or gdb break on g_log/g_logv) to capture the stack at the warning and identify the library/plugin frame; check whether a Flutter plugin (file_selector, path_provider, url_launcher) or GTK icon/mime loading is responsible. If upstream/GTK, document + suppress-from-our-side or pin; if a plugin, file upstream. - -Env: Fedora, GTK Linux embedder, flutter run. - -UPDATE (2026-06-09): also fires MID-SESSION, not only at boot — contradicts the "fires early in boot, on every launch" framing above. Observed log: app booted 16:47:01, but the GLib-GIO-CRITICAL pair fired at 18:51:07.841 / 18:51:08.190 (~2h into the session), near pane/session activity. So the trigger is more likely a file-info code path tied to a user action or background file enumeration than pure startup. Re-scope the investigation to capture the stack when it fires mid-session (G_DEBUG=fatal-warnings / gdb break on g_log) rather than only at boot.', 'in_progress', 'low', NULL, NULL, NULL, '2026-06-08 17:12:49', '2026-06-09 21:04:33', NULL, '431bd3dcef2ce7bc67322f0f7da432c4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'bug', NULL, 'GLib-GIO-CRITICAL: GFileInfo created without standard::size on startup (Linux)', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: +SECURITY REDESIGN 2026-06-09 (D-90): per user, the clide:// surface is now paranoid. The link no longer translates to a command in parseArgv — it routes the raw URL to a new builtin.deeplink handler gated by a DEFAULT-DENY allowlist (kDeepLinkSafeActions = {open} only — run/git/write/passthrough rejected) AND a mandatory confirmation prompt (''an external link wants to: … allow?'') before any action. The generic CLI-passthrough idea is deliberately NOT shipped (it would make a webpage a remote control); each new action is an explicit allowlist + handler addition. Tests: deep_link_test (allowlist boundary), extension_test (gating).', 'done', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-09 20:57:53', NULL, '26038fc931f2848f7327905c101de662', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM6SK3D1QYACBPHHBZ0', 'bug', NULL, 'GLib-GIO-CRITICAL: GFileInfo created without standard::size on startup (Linux)', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: GLib-GIO-CRITICAL: GFileInfo created without standard::size file ../gio/gfileinfo.c: line 1865 (g_file_info_get_size): should not be reached @@ -3697,60 +1033,8 @@ Fix (both, per user direction): 1. Switched pickDirectory to GtkFileChooserNative — portal-backed, runs out-of-process in sandboxed/Flatpak builds so the noise never enters our process; nicer Wayland picker. (Verified: outside a sandbox GtkFileChooserNative falls back to the in-process chooser and STILL warns on this box — portal is only used under Flatpak/snap or GTK_USE_PORTAL=1.) 2. Added clide_gio_log_filter in clide_app.cc, installed via g_log_set_handler("GLib-GIO", CRITICAL, ...) in clide_app_startup. Drops ONLY messages containing "g_file_info_get_size" or "without standard::size"; forwards every other GLib-GIO log to the default handler. This is the universal fix (works in every run mode). -Verification: standalone reproducer with both the native chooser AND the filter — size warnings gone, an unrelated GLib-GIO CRITICAL still passes through (filter is scoped, not a blanket mute). `make build-linux` green. CHANGELOG updated under Fixed.', 'in_progress', 'low', NULL, NULL, NULL, '2026-06-08 17:12:49', '2026-06-09 21:21:04', NULL, '670ae8945133f10860258b538ab7951a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-287', 'bug', NULL, 'GLib-GIO-CRITICAL: GFileInfo created without standard::size on startup (Linux)', 'On Linux desktop startup, two GLib-GIO-CRITICAL warnings fire to stderr: - - GLib-GIO-CRITICAL: GFileInfo created without standard::size - file ../gio/gfileinfo.c: line 1865 (g_file_info_get_size): should not be reached - -Repro: launch the app (make run / flutter run) on Linux. The pair fires early in boot — right after the primary pane binds its session and before the IPC server starts listening (observed ~0.3s apart, e.g. 15:53:15.570 and 15:53:15.832), on every launch. - -Cause: some code path calls g_file_info_get_size() on a GFileInfo that was created/queried WITHOUT requesting the G_FILE_ATTRIBUTE_STANDARD_SIZE (''standard::size'') attribute. A grep of clide''s own code (lib/, linux/runner/, native/) finds no direct g_file_info / g_file_query_info usage, so it is most likely inside GTK/GLib itself or a Flutter Linux plugin''s file enumeration (icon/thumbnail/mime probe, path lookups), not clide Dart/C++. The GTK file-chooser in linux/runner/clide_app.cc is on-demand only, so it is not the trigger (the warnings fire at boot). - -Impact: low — console noise at CRITICAL level; no observed functional breakage. But a size query that ''should not be reached'' may be reading a bogus/zero size somewhere worth confirming. - -Investigation: run with G_DEBUG=fatal-warnings (or gdb break on g_log/g_logv) to capture the stack at the warning and identify the library/plugin frame; check whether a Flutter plugin (file_selector, path_provider, url_launcher) or GTK icon/mime loading is responsible. If upstream/GTK, document + suppress-from-our-side or pin; if a plugin, file upstream. - -Env: Fedora, GTK Linux embedder, flutter run. - -UPDATE (2026-06-09): also fires MID-SESSION, not only at boot — contradicts the "fires early in boot, on every launch" framing above. Observed log: app booted 16:47:01, but the GLib-GIO-CRITICAL pair fired at 18:51:07.841 / 18:51:08.190 (~2h into the session), near pane/session activity. So the trigger is more likely a file-info code path tied to a user action or background file enumeration than pure startup. Re-scope the investigation to capture the stack when it fires mid-session (G_DEBUG=fatal-warnings / gdb break on g_log) rather than only at boot. - -RESOLVED (2026-06-09). - -Root cause CONFIRMED by reproducer: the Open Workspace folder picker. `gtk_file_chooser_dialog_new(..., SELECT_FOLDER, ...)` at linux/runner/clide_app.cc:222 builds a GtkPlacesSidebar that enumerates bookmarks/recent and internally creates GFileInfo objects WITHOUT G_FILE_ATTRIBUTE_STANDARD_SIZE, then calls g_file_info_get_size() on them — GTK''s own bug. A standalone repro (gtk3, SELECT_FOLDER dialog, auto-cancel) emitted the exact warning pair ~0.3s apart, matching the user''s mid-session log (18:51:07.841 / 18:51:08.190). The original "fires at boot" framing was wrong; the picker is opened on demand. Ruled OUT the T-138 clipboard channel: gtk_clipboard_wait_for_uris/wait_for_image emit nothing for text/image/file-uri clipboard states under G_DEBUG=fatal-warnings. - -No public GTK API exists to fix at source (the sidebar''s enumeration is private; can''t inject the missing attribute). - -Fix (both, per user direction): -1. Switched pickDirectory to GtkFileChooserNative — portal-backed, runs out-of-process in sandboxed/Flatpak builds so the noise never enters our process; nicer Wayland picker. (Verified: outside a sandbox GtkFileChooserNative falls back to the in-process chooser and STILL warns on this box — portal is only used under Flatpak/snap or GTK_USE_PORTAL=1.) -2. Added clide_gio_log_filter in clide_app.cc, installed via g_log_set_handler("GLib-GIO", CRITICAL, ...) in clide_app_startup. Drops ONLY messages containing "g_file_info_get_size" or "without standard::size"; forwards every other GLib-GIO log to the default handler. This is the universal fix (works in every run mode). - -Verification: standalone reproducer with both the native chooser AND the filter — size warnings gone, an unrelated GLib-GIO CRITICAL still passes through (filter is scoped, not a blanket mute). `make build-linux` green. CHANGELOG updated under Fixed.', 'done', 'low', NULL, NULL, NULL, '2026-06-08 17:12:49', '2026-06-09 21:21:08', NULL, '8d35f85f76f618f0e324dd1f396db8e4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-298', 'bug', 'T-276', 'Bottom input box is misaligned when the status bar is hidden', 'The bottom input/prompt box (the composer, shown with the edit-pencil affordance on the right) is aligned weird when the status bar is not visible. - -Symptom: with the status bar hidden, the input box''s alignment/padding looks off (see screenshot — single-line box spanning full width, content not sitting where it should relative to the window bottom). - -Likely cause: the composer''s vertical alignment / bottom padding assumes the status bar is present below it and reserves/relies on that space. When the status bar is hidden, that assumption breaks and the box no longer sits flush/centered correctly. - -Expected: the input box sits correctly flush to the window bottom (or with consistent padding) regardless of whether the status bar is visible. Layout should not depend on the status bar being present. - -Notes: -- Audit the bottom-of-window layout stack (composer / interaction zone + status bar). The composer should bottom-anchor independently of the status bar''s visibility. -- Related to the bottom interaction zone work (D-78) and may interact with T-297 (scroll on input resize). Also relevant once the status-bar collapse toggles (T-294) can change what''s in the bar.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-09 16:06:41', '2026-06-09 21:21:30', NULL, 'cc9ab1dee5d58863b3277c896e3e4461', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-298', 'bug', 'T-276', 'Bottom input box is misaligned when the status bar is hidden', 'The bottom input/prompt box (the composer, shown with the edit-pencil affordance on the right) is aligned weird when the status bar is not visible. - -Symptom: with the status bar hidden, the input box''s alignment/padding looks off (see screenshot — single-line box spanning full width, content not sitting where it should relative to the window bottom). - -Likely cause: the composer''s vertical alignment / bottom padding assumes the status bar is present below it and reserves/relies on that space. When the status bar is hidden, that assumption breaks and the box no longer sits flush/centered correctly. - -Expected: the input box sits correctly flush to the window bottom (or with consistent padding) regardless of whether the status bar is visible. Layout should not depend on the status bar being present. - -Notes: -- Audit the bottom-of-window layout stack (composer / interaction zone + status bar). The composer should bottom-anchor independently of the status bar''s visibility. -- Related to the bottom interaction zone work (D-78) and may interact with T-297 (scroll on input resize). Also relevant once the status-bar collapse toggles (T-294) can change what''s in the bar.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-09 16:06:41', '2026-06-09 21:22:26', NULL, 'c5773b9623efafd5e269a1618f243f66', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-304', 'bug', NULL, 'Activity-card spinner too small to make out — enlarge it', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 21:22:28', '2026-06-09 21:22:28', NULL, '95e2fdd86dbe98e3f911637bcb844072', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-304', 'bug', 'T-276', 'Activity-card spinner too small to make out — enlarge it', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 21:22:28', '2026-06-09 21:22:31', NULL, '20c85ebff9e0a45f7e27ee099b7a2563', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-304', 'bug', 'T-276', 'Activity-card spinner too small to make out — enlarge it', 'The logo-mark spinner shown on in-progress activity/holder cards in the Claude conversation is too small to read as a spinner — it reads as a static speck. Enlarge it so the running state is legible at a glance. +Verification: standalone reproducer with both the native chooser AND the filter — size warnings gone, an unrelated GLib-GIO CRITICAL still passes through (filter is scoped, not a blanket mute). `make build-linux` green. CHANGELOG updated under Fixed.', 'done', 'low', NULL, NULL, NULL, '2026-06-08 17:12:49', '2026-06-09 21:21:08', NULL, 'bb5703a93c41803651ecd4c1210b23bb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4KS233FGZE9H7ABWR', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Activity-card spinner too small to make out — enlarge it', 'The logo-mark spinner shown on in-progress activity/holder cards in the Claude conversation is too small to read as a spinner — it reads as a static speck. Enlarge it so the running state is legible at a glance. **Where** - `ClideSpinner` (lib/widgets/src/clide_spinner.dart) — defaults to size 14; renders the logo SVG at width/height = size. @@ -3763,16 +1047,8 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, - Check the other ClideSpinner/StatusIndicator consumers (status surfaces) so the bump doesn''t bloat unrelated spots — may warrant sizing the cards explicitly rather than changing the shared default. **Acceptance** -- The in-progress spinner on conversation activity cards is comfortably distinguishable as a spinning indicator; success/error glyphs stay aligned at the same footprint.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 21:22:28', '2026-06-09 21:22:44', NULL, '4d335acecf697af397d391dab086bd5d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-280', 'bug', NULL, 'app_test ''Open Folder non-repo'' hangs 10min — runAsync/ReceivePort teardown wedge', 'Pre-existing (reproduces at base commit 3a78dfa, predates the T-267 conversation-rendering epic). The widget test `test/app_test.dart` › "Open Folder on a non-repo path surfaces the ''no git repo'' dialog" times out after 10 minutes; teardown is wedged on `_RawReceivePort._handleMessage`. - -Bisected (each a 45s-timeout repro, all on this box): -- A bare `Process.run` inside `tester.runAsync` (no app, no extensions) hangs → `Process.run`-in-`runAsync` leaks its exit ReceivePort here. -- `pumpApp` + empty `runAsync`, and `pumpApp` + a 1.5s real delay → both PASS (boot + runAsync alone is fine). -- The full openFolder tap flow hangs even when project validation is stubbed to a synchronous, pure-Dart `.git` walk (no subprocess) AND `runAsync` is removed — so the wedge is not solely the git subprocess; something in the booted-app + extensions + open-folder command path holds a native port that teardown waits on forever. - -Quarantined with `skip:` so the suite/gate stays green. Real fix: find the leaked native async resource (likely a Process/Isolate/FakeDaemonClient port reachable from the open-folder command or app boot under the test harness) and ensure it''s drained/cancelled before teardown — or drive the "no git repo" assertion without booting the resource. Then remove the skip.', 'ready', 'high', NULL, NULL, NULL, '2026-06-08 11:29:22', '2026-06-09 21:32:29', NULL, '9a47d2033458934d77d1110e1ac98cfe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-298', 'bug', 'T-276', 'Bottom input box is misaligned when the status bar is hidden', 'The bottom input/prompt box (the composer, shown with the edit-pencil affordance on the right) is aligned weird when the status bar is not visible. +- The in-progress spinner on conversation activity cards is comfortably distinguishable as a spinning indicator; success/error glyphs stay aligned at the same footprint.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-09 21:22:28', '2026-06-09 21:22:44', NULL, 'c29f22db34a367ee5eb7234a75e75616', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM50S6WRD81V3Z0NH1M', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Bottom input box is misaligned when the status bar is hidden', 'The bottom input/prompt box (the composer, shown with the edit-pencil affordance on the right) is aligned weird when the status bar is not visible. Symptom: with the status bar hidden, the input box''s alignment/padding looks off (see screenshot — single-line box spanning full width, content not sitting where it should relative to the window bottom). @@ -3782,12 +1058,12 @@ Expected: the input box sits correctly flush to the window bottom (or with consi Notes: - Audit the bottom-of-window layout stack (composer / interaction zone + status bar). The composer should bottom-anchor independently of the status bar''s visibility. -- Related to the bottom interaction zone work (D-78) and may interact with T-297 (scroll on input resize). Also relevant once the status-bar collapse toggles (T-294) can change what''s in the bar.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 16:06:41', '2026-06-09 21:36:05', NULL, '162be954ae0cc51fa9db0c36b0173cbe', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); -INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-280', 'bug', NULL, 'app_test ''Open Folder non-repo'' hangs 10min — runAsync/ReceivePort teardown wedge', 'Pre-existing (reproduces at base commit 3a78dfa, predates the T-267 conversation-rendering epic). The widget test `test/app_test.dart` › "Open Folder on a non-repo path surfaces the ''no git repo'' dialog" times out after 10 minutes; teardown is wedged on `_RawReceivePort._handleMessage`. +- Related to the bottom interaction zone work (D-78) and may interact with T-297 (scroll on input resize). Also relevant once the status-bar collapse toggles (T-294) can change what''s in the bar.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 16:06:41', '2026-06-09 21:36:05', NULL, 'a82415278c5d2653d6e7092a0fbbb6e8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5YNEQM1WY25BV7TD8', 'bug', NULL, 'app_test ''Open Folder non-repo'' hangs 10min — runAsync/ReceivePort teardown wedge', 'Pre-existing (reproduces at base commit 3a78dfa, predates the T-267 conversation-rendering epic). The widget test `test/app_test.dart` › "Open Folder on a non-repo path surfaces the ''no git repo'' dialog" times out after 10 minutes; teardown is wedged on `_RawReceivePort._handleMessage`. Bisected (each a 45s-timeout repro, all on this box): - A bare `Process.run` inside `tester.runAsync` (no app, no extensions) hangs → `Process.run`-in-`runAsync` leaks its exit ReceivePort here. - `pumpApp` + empty `runAsync`, and `pumpApp` + a 1.5s real delay → both PASS (boot + runAsync alone is fine). - The full openFolder tap flow hangs even when project validation is stubbed to a synchronous, pure-Dart `.git` walk (no subprocess) AND `runAsync` is removed — so the wedge is not solely the git subprocess; something in the booted-app + extensions + open-folder command path holds a native port that teardown waits on forever. -Quarantined with `skip:` so the suite/gate stays green. Real fix: find the leaked native async resource (likely a Process/Isolate/FakeDaemonClient port reachable from the open-folder command or app boot under the test harness) and ensure it''s drained/cancelled before teardown — or drive the "no git repo" assertion without booting the resource. Then remove the skip.', 'done', 'high', NULL, NULL, NULL, '2026-06-08 11:29:22', '2026-06-09 21:42:59', NULL, '9154fcc7560d18aa35f4b718076203df', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +Quarantined with `skip:` so the suite/gate stays green. Real fix: find the leaked native async resource (likely a Process/Isolate/FakeDaemonClient port reachable from the open-folder command or app boot under the test harness) and ensure it''s drained/cancelled before teardown — or drive the "no git repo" assertion without booting the resource. Then remove the skip.', 'done', 'high', NULL, NULL, NULL, '2026-06-08 11:29:22', '2026-06-09 21:42:59', NULL, 'c49e75c2d73ddec10469bf2405dde626', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);