share DaemonBus between backend and kernel, fix terminal spawn timing
test / unit + widget + golden + a11y (push) Failing after 28s
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

The IsolateClient and KernelServices now share the same DaemonBus.
Previously, backend events (pane.output, git.changed) went to a
separate bus that widgets couldn't see.

ClaudePane._spawnWhenReady waits for ProjectOpened before sending
pane.spawn, preventing "No project active" errors.

Recents loaded before runApp so the welcome screen shows them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-04-26 17:17:17 +02:00
co-authored by Claude Opus 4.6
parent 243a92798c
commit baf90270d1
4 changed files with 131 additions and 103 deletions
+20 -1
View File
@@ -57,7 +57,7 @@ class _ClaudePaneState extends State<ClaudePane> {
_terminal = Terminal(maxLines: _maxLines);
_terminal.onOutput = _onOutput;
_terminal.onResize = _onResize;
WidgetsBinding.instance.addPostFrameCallback((_) => _spawn());
WidgetsBinding.instance.addPostFrameCallback((_) => _spawnWhenReady());
}
@override
@@ -78,6 +78,25 @@ class _ClaudePaneState extends State<ClaudePane> {
super.dispose();
}
Future<void> _spawnWhenReady() async {
if (!mounted) return;
final kernel = ClideKernel.of(context);
if (!kernel.project.isOpen) {
// Wait for a project to open before spawning.
final c = Completer<void>();
late final StreamSubscription<ProjectOpened> sub;
sub = kernel.events.on<ProjectOpened>().listen((_) {
sub.cancel();
if (!c.isCompleted) c.complete();
});
await c.future.timeout(const Duration(seconds: 10), onTimeout: () {
sub.cancel();
});
if (!mounted) return;
}
return _spawn();
}
Future<void> _spawn() async {
if (!mounted) return;
final ipc = _ipc();
+2 -1
View File
@@ -107,9 +107,10 @@ class KernelServices {
Toolchain? toolchain,
Future<void> Function(String path)? onProjectOpen,
Future<String?> Function(String path)? onValidateProject,
DaemonBus? sharedBus,
}) async {
final log = Logger();
final events = DaemonBus();
final events = sharedBus ?? DaemonBus();
final messages = MessageBus();
final settings = SettingsStore(appDir: appDir);
+9 -1
View File
@@ -57,11 +57,12 @@ Future<void> main() async {
// Phase 1: resolve toolchain (binary availability only, no workspace).
// Phase 2: openProject() initializes services when a project opens.
const workspace = String.fromEnvironment('CLIDE_WORKSPACE');
final sharedBus = DaemonBus();
final backend = kIsWeb ? null : await Backend.spawn(
hintRoot: workspace.isNotEmpty ? workspace : null,
clientFactory: (backendPort) => IsolateClient(
log: Logger(),
events: DaemonBus(),
events: sharedBus,
backendPort: backendPort,
),
);
@@ -82,6 +83,7 @@ Future<void> main() async {
onValidateProject: backend != null
? (path) => backend.validateProject(path)
: null,
sharedBus: backend != null ? sharedBus : null,
);
// Register every built-in. Tier 0 activates only the four that do
@@ -120,6 +122,12 @@ Future<void> main() async {
await services.extensions.activateAll();
// Load recents before runApp so the welcome screen shows them.
// project.open triggers backend.openProject which initializes services.
if (!kIsWeb) {
await services.project.loadRecents();
}
runApp(ClideApp(services: services));
}