T-127: replace InProcessClient with socket loopback
test / unit + widget + golden + a11y (push) Failing after 2m16s
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 29s

Fourth slice of T-99. The UI's DaemonClient now talks to its own
IpcServer through the same per-workspace Unix socket the C `clide`
client uses — one transport, one wire contract, no second path
through the dispatch tree.

Changes:
* lib/kernel/src/ipc/in_process.dart deleted. Nothing imports it.
* DaemonClient.socketPath becomes mutable + new `reconnectAt(path)`
  method swaps an active client onto a different socket without
  restart. Project switch in main.dart uses it — the dispatcher
  + IpcServer are rebuilt for the new workspace, and the client
  reconnects to the new path.
* main.dart's daemonClientFactory now builds a real DaemonClient
  pointed at workspaceSocketPath(workRoot); swapIpcServer kicks
  off server.start() then client.start() in sequence.
* lib/test_app.dart's pane.spawn smoke test uses dispatcher.dispatch
  directly instead of InProcessClient — same coverage, no dead-end
  import.
* DaemonClient client_test gets a reconnectAt round-trip test.

T-128 (delete IsolateClient + Backend + backend_entry.dart) unblocked.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-19 12:03:44 +02:00
co-authored by Claude
parent 88ed4bf391
commit 70c293b163
8 changed files with 130 additions and 48 deletions
+40
View File
@@ -276,5 +276,45 @@ void main() {
await Future<void>.delayed(const Duration(milliseconds: 100));
expect(flips, contains(true));
});
test('reconnectAt swaps socket paths and re-binds (T-127)', () async {
final pathA = await _tmpSocket();
final daemonA = _TestDaemon(pathA);
await daemonA.start();
addTearDown(daemonA.close);
final bus = DaemonBus();
addTearDown(bus.dispose);
final client = _build(pathA, bus);
addTearDown(client.dispose);
await client.start();
await daemonA.waitForClient();
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(client.isConnected, isTrue);
expect(client.socketPath, pathA);
// Spin up a SECOND daemon on a different socket and move the
// client over to it.
final pathB = await _tmpSocket();
final daemonB = _TestDaemon(pathB);
await daemonB.start();
addTearDown(daemonB.close);
await client.reconnectAt(pathB);
await daemonB.waitForClient();
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(client.socketPath, pathB);
expect(client.isConnected, isTrue);
// A request goes to the NEW daemon — verify by reading the
// line off daemonB.lines.
final lineFuture = daemonB.lines.first;
final responseFuture = client.request('ping');
final reqLine = await lineFuture;
final reqJson = jsonDecode(reqLine) as Map<String, Object?>;
daemonB.send(IpcResponse.ok(id: reqJson['id'] as String, data: const {'pong': true}).encode());
final resp = await responseFuture;
expect(resp.ok, isTrue);
});
});
}