route DaemonClient through a DaemonTransport seam (T-331)

The UI's backend client connected straight to the workspace unix
socket, hard-coding the local shape. It now talks JSON-lines through a
DaemonTransport (new lib/src/ipc/transport.dart, Flutter-free), with
LocalSocketTransport reproducing today's connect byte-for-byte — zero
behavior change, proven by the untouched client test suite plus new
seam tests driving the client over an in-memory transport.

This is the slot the SSH-remote backend (T-329/Q-23) plugs into:
request correlation, reconnect/backoff, and event forwarding live
above the seam and won't change when the endpoint is remote.
main.dart's swapIpcServer becomes swapBackend per the same plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 03:04:58 +02:00
co-authored by Claude Fable 5
parent c3dd1d3e3f
commit 051ceea3b2
10 changed files with 311 additions and 59 deletions
@@ -4146,3 +4146,4 @@ Plan:
DECSTR (`CSI ! p`, soft reset) is a separate, smaller follow-up — same intermediates mechanism, maps to a subset of the existing reset paths; file separately if wanted.
Done when: claude/vim cursor-shape changes (insert vs normal mode) render as bar vs block in the terminal pane.', NULL, '2026-06-12 00:52:40', '2026-06-12 00:52:40', '2026-06-12 00:52:40', NULL, 'fd38b932757c81de62d693d72b883448', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DKQQJ583944DG8561VQ3G', 'status', 'backlog', 'done', NULL, '2026-06-12 01:04:52', '2026-06-12 01:04:52', '2026-06-12 01:04:52', NULL, '66a692e143b6a4be1b4845825e9e16ad', 2) ON CONFLICT(hash) DO NOTHING;
+1
View File
@@ -4429,3 +4429,4 @@ Plan:
DECSTR (`CSI ! p`, soft reset) is a separate, smaller follow-up — same intermediates mechanism, maps to a subset of the existing reset paths; file separately if wanted.
Done when: claude/vim cursor-shape changes (insert vs normal mode) render as bar vs block in the terminal pane.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-12 00:52:25', '2026-06-12 00:52:40', NULL, '5c07c49f5ad28c005a77ba26434e66d7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DKQQJ583944DG8561VQ3G', 'story', '06FB3DHCTP001YCHFP39XER0ZM', 'Phase 1 — backend transport seam (DaemonTransport, prove locally)', 'Model-independent backbone. Make the local app talk to its backend through a DaemonTransport abstraction with ZERO behavior change, so a remote transport can slot in later. New lib/src/ipc/transport.dart: DaemonTransport interface + LocalTransport (wraps today''s unix-socket connect). DaemonClient takes a transport instead of a hard-coded socket path; its reconnect loop generalizes to ''re-establish via transport''. lib/main.dart: swapIpcServer (~:119) becomes swapBackend — local binds the in-process IpcServer as today; seam ready for a remote branch. Wire protocol unchanged — JSON-lines IpcRequest/IpcResponse/IpcEvent (envelope.dart) already serialize over IPC; event stream, tail --events, events --since cursor-pull ride the same stream untouched. Verify: make test-core (IPC) + testmode skill — local path behaves identically through the seam, no regression. Depends on Phase 0 (T-330).', 'done', 'high', NULL, NULL, NULL, '2026-06-10 13:26:25', '2026-06-12 01:04:52', NULL, 'c36521dfda73828837c32d44eb2f6a4e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);