spike T-134: Claude Code team/transcript internals (pinned to 2.1.148)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 31s

Findings note for the tmux agent-team work (epic T-132), validated from
real on-disk artifacts (42 past team configs, real team + subagent
transcripts, current-version .meta.json) plus a synthetic control-mode
test and the tmux manual — no live team run / quota needed.

Key results: teammates get tmux panes (config tmuxPaneId) and write
transcripts at <munged>/<sid>/subagents/agent-<hex>.jsonl + .meta.json;
tmux 3.6a has no %pane-died (use %window-close/%layout-change), and
polling list-panes -a is the reliable lifecycle baseline; the real risk
is identity linkage — pane/teammate (config: name@team, agentType,
%pane) joins to the transcript only via agentType (config <-> .meta.json),
ambiguous for same-type teammates, so T-139 needs a joinedAt/mtime or
lead-transcript tiebreaker.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-22 19:52:00 +02:00
co-authored by Claude
parent 0eee456426
commit 3f2e5ad9a9
3 changed files with 102 additions and 0 deletions
@@ -2109,3 +2109,6 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by,
INSERT INTO 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;
+21
View File
@@ -2448,3 +2448,24 @@ Refinement (2026-05-22, refinement-agent review): READY. Verified against real t
- 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/<munged>/<sid>.jsonl (lead) and <sid>/subagents/agent-<id>.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/<munged>/; poll mtime and switch the tail when a newer session appears.
- Subagent transcripts: <munged>/<session-uuid>/subagents/agent-<id>.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);