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
+29 -5
View File
@@ -11,13 +11,15 @@ import 'package:flutter/foundation.dart';
class DaemonClient extends ChangeNotifier {
DaemonClient({
required this.socketPath,
required String socketPath,
required Logger log,
required DaemonBus events,
}) : _log = log,
}) : _socketPath = socketPath,
_log = log,
_events = events;
final String socketPath;
String _socketPath;
String get socketPath => _socketPath;
final Logger _log;
final DaemonBus _events;
@@ -47,6 +49,28 @@ class DaemonClient extends ChangeNotifier {
_setConnected(false);
}
/// Point the client at a different socket path and reconnect.
/// Used on project switch — the workspace-derived socket path
/// (D-70) changes when the user opens a different project, so the
/// client follows. Cancels the reconnect timer, closes the live
/// socket (failing in-flight requests with `disconnect`), updates
/// the path, and re-arms the connect loop. Idempotent if the new
/// path equals the current one.
Future<void> reconnectAt(String newPath) async {
if (newPath == _socketPath && _connected) return;
_socketPath = newPath;
_reconnectTimer?.cancel();
_reconnectTimer = null;
final s = _socket;
_socket = null;
await s?.close();
_failPending('socket path changed');
_setConnected(false);
_disposed = false;
_backoff = const Duration(milliseconds: 200);
await _connect();
}
Future<IpcResponse> request(
String cmd, {
Map<String, Object?> args = const {},
@@ -72,12 +96,12 @@ class DaemonClient extends ChangeNotifier {
Future<void> _connect() async {
if (_disposed) return;
try {
final addr = InternetAddress(socketPath, type: InternetAddressType.unix);
final addr = InternetAddress(_socketPath, type: InternetAddressType.unix);
final socket = await Socket.connect(addr, 0);
_socket = socket;
_backoff = const Duration(milliseconds: 200);
_setConnected(true);
_log.info('ipc', 'connected to $socketPath');
_log.info('ipc', 'connected to $_socketPath');
socket.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).listen(
_handleLine,
onDone: _handleDisconnect,
-29
View File
@@ -1,29 +0,0 @@
import 'package:clide/clide.dart';
import 'package:clide/kernel/src/ipc/client.dart';
class InProcessClient extends DaemonClient {
InProcessClient({
required super.log,
required super.events,
required this.dispatcher,
}) : super(socketPath: '');
DaemonDispatcher dispatcher;
int _nextReqId = 0;
@override
bool get isConnected => true;
@override
Future<void> start() async {}
@override
Future<void> stop() async {}
@override
Future<IpcResponse> request(String cmd, {Map<String, Object?> args = const {}}) {
final id = '${_nextReqId++}';
final req = IpcRequest(id: id, cmd: cmd, args: args);
return dispatcher.dispatch(req);
}
}