Commit Graph
15 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 6d0ebab721 chore: adopt Dart 3.9 toolchain — honest floor + tall-style reformat (T-353)
Raise the declared minimums in pubspec.yaml to what our deps already
require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist
0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is
the binding floor. Pin the exact build toolchain in .fvmrc (Flutter
3.44.1).

Moving to the Dart 3.9 language level switches `dart format` to the new
"tall" style and enables two new lints. This commit is the resulting
mechanical churn, isolated from any behaviour change:
  - whole-tree `dart format` reformat (tall style)
  - `dart fix` for unnecessary_underscores + use_null_aware_elements

No runtime behaviour change; `make test` green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:11:53 +02:00
jpmschweitzerandClaude d7c935977e fix daemon-not-connected race on startup
The socket-loopback DaemonClient (T-127) raced the UI on first launch:
panels queried before the socket finished connecting and cached a
"daemon not connected" error, and the Claude pane's spawn gate tripped,
leaving an empty terminal. Three fixes in the startup/connection path:

- DaemonClient.request() now waits briefly (5s) for an in-flight
  connection instead of failing instantly, gated on _started so a
  never-started client still fails fast. start() sets _started
  synchronously so the gate is armed before the UI builds.
- swapIpcServer reuses the live server when the opened project matches
  the workspace it already serves, instead of tearing it down — the
  project-open flow fired right as the Claude pane spawned, dropping
  the connection mid-spawn.
- _connect bails if already connected, so start() arming the reconnect
  loop and swapIpcServer's reconnectAt can't open a second socket
  (which had been double-delivering events).

This whole orchestration had no automated coverage — integration tests
stub a FakeDaemonClient. Adds a real wait-then-connect client test.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-22 15:39:59 +02:00
jpmschweitzerandClaude f987cd3bb1 T-128: delete IsolateClient / Backend / backend_entry.dart
test / unit + widget + golden + a11y (push) Failing after 37s
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
Fifth slice of T-99. After T-127 the socket-loopback DaemonClient
is the only IPC path; the isolate-backed third implementation
(IsolateClient + Backend + backend_entry.dart) was never wired
through and has no remaining references. Removed wholesale; the
single service-registration site lives in main.dart's
buildDispatcher.

flutter analyze + the kernel and ipc suites stay green.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-19 12:06:54 +02:00
jpmschweitzerandClaude 70c293b163 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>
2026-05-19 12:03:44 +02:00
jpmschweitzerandClaude Opus 4.7 889058db1b test sweep: cover kernel/src/{events,ipc} (T-91)
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
test / dart doc (lib API) (push) Failing after 1m2s
Three new test files + a small DaemonClient dispose-safety fix:

- test/kernel/src/events/types_test.dart (7 tests): every ClideEvent
  subclass's subsystem / kind / payload contract + the
  ClideEventEnvelope v1 JSON shape.
- test/kernel/src/events/message_bus_test.dart (6 tests): Message
  shape, MessageBus publish/subscribe/dispose, filter-by-publisher,
  filter-by-channel, intersection.
- test/kernel/src/ipc/client_test.dart (9 tests): real Unix-socket
  roundtrip via a _TestDaemon helper — connect + correlate request/
  response, event forwarding to the DaemonBus, malformed-line skip,
  daemon-disconnect failing pending requests, stop cleanup, dispose,
  connect-failure-then-reconnect, daemon-sent-Request warn-and-skip,
  DaemonConnectionChanged emission.

Fix in lib/kernel/src/ipc/client.dart: _setConnected now skips
notifyListeners / event emit when _disposed. The socket stream's
onDone can fire after dispose runs, which previously hit
ChangeNotifier's "used after disposed" assertion. State flip stays
unconditional so stop()'s explicit transition still works.

Coverage: ipc/client.dart 14% -> 92% (79/86; remaining 7 lines are
the socket onError callback + 1 const ctor phantom); events/types
.dart 95% (37/39 — 2 const-ctor phantoms); events/message_bus.dart
100%; events/bus.dart stays 100%.

Total coverage 71.93% -> 73.34%; floor bumped to 73.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 19:06:17 +02:00
Jeroen SchweitzerandClaude Opus 4.6 a6eca2561b remove dissolved daemon, retire ptyc, fix golden cross-platform
Complete three overdue cleanups discovered during macOS health check:

D-56 daemon dissolution: delete bin/clide.dart, DaemonServer,
and orphaned tests (test/cli/, subprocess_test, in_process_test).
Update stale "clide --daemon" references in i18n catalogs, error
messages, editor_commands, CI scripts, and decision records.

ptyc retirement: delete ptyc/ source tree, PtySession, scm_rights.
Remove from Toolchain resolution, ToolCheck gate, backend
serialization, testmode harness, Makefile, CI, and sandbox
entitlements. PTY spawning uses NativePty (Dart FFI forkpty) since
the terminal was absorbed in-tree. D-5 amended.

Golden tests: wire the existing but never-applied clideGoldenConfig
via flutter_test_config.dart. Disable CI goldens (Skia anti-aliasing
differs between macOS/Linux even with Ahem). Keep platform-keyed
goldens only — goldens/linux/ and goldens/macos/ each run on their
own OS.

Test suite: 826 pass, 0 fail on macOS (was 829 pass, 11 fail).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 18:40:01 +02:00
jpmschweitzerandClaude 1fb4786f18 sweep remaining analyze infos to zero
test / unit + widget + golden + a11y (push) Failing after 35s
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 1m0s
Clears the 19 misc lint hits left after the test_app print sweep
+ libc.dart suppression. By rule:

- `withOpacity(α)` → `withValues(alpha: α)` (deprecated_member_use)
  in `painter.dart:187` and `terminal_view.dart:318`.
- `Pointer.elementAt(n)` → `Pointer + n` (deprecated_member_use)
  in `native_pty.dart:306` and `session.dart:187`.
- Brace single-statement for/if bodies in `native_pty.dart`
  (×3) and `decisions_view.dart` (curly_braces_in_flow_control_
  structures).
- `IsolateClient` and `InProcessClient` constructors switched to
  `super.log` / `super.events` parameters (use_super_parameters);
  associated unused imports of `kernel/src/log.dart` and
  `kernel/src/events/bus.dart` removed in the same files.
- `InProcessClient._dispatcher` field + getter/setter pair folded
  into a single mutable public `dispatcher` field
  (unnecessary_getters_setters).
- `_buildDispatcher` local in `lib/main.dart` renamed to
  `buildDispatcher` (no_leading_underscores_for_local_identifiers).
- `_onTapDown(_)` in `terminal_view.dart` typed as
  `TapDownDetails _` (strict_top_level_inference).
- `operator []=(...)` in `circular_buffer.dart` given an explicit
  `void` return type (strict_top_level_inference).
- `CustomKeyboardListener` and `TerminalGestureDetector` callsites
  reordered so `child:` lands last (sort_child_properties_last).
- `CustomTextEdit` constructor declared `const`
  (prefer_const_constructors_in_immutables).
- `LinkedHashMap<K, V>()` in `paragraph_cache.dart` collapsed to a
  `<K, V>{}` literal (prefer_collection_literals); the now-unused
  `dart:collection` import dropped.

Project analyze: 19 → 0 issues. `make test` stays green; coverage
unchanged at 52.72%.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 23:51:46 +02:00
jpmschweitzerandClaude 26cc1b3154 drop redundant single-symbol imports across the tree
12 imports flagged by `unnecessary_import` because the symbols
they bring in are also re-exported by the umbrella import already
present in the same file:

- bin/clide.dart: src/git/client.dart, src/pql/client.dart
  (covered by package:clide/clide.dart).
- lib/builtin/decisions/, lib/builtin/tickets/ (4 files):
  kernel/src/events/message_bus.dart (covered by kernel.dart).
- lib/kernel/src/ipc/in_process.dart: src/daemon/dispatcher.dart
  (covered by clide.dart).
- lib/main.dart: kernel/src/toolchain.dart (covered by kernel.dart).
- test/builtin/ipc_status/widget_test.dart:
  builtin/ipc_status/src/status_item.dart (covered by
  ipc_status.dart).
- test/daemon/{git,pql}_commands_test.dart: src/git/client.dart and
  src/pql/client.dart (covered by clide.dart).
- test/widgets/multitab_pane_test.dart: widgets/src/icons/x.dart
  (covered by widgets.dart).

Mechanical change — every removed line was already a no-op for
symbol resolution; the umbrella imports define the public surface
each file is actually using.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 23:46:24 +02:00
jpmschweitzerandClaude 7b30748bf5 rewire daemon services on project open
test / unit + widget + golden + a11y (push) Failing after 27s
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 daemon dispatcher (pql, git, files, editor) was built once at
boot against cwd and never updated. Opening a project from the
welcome screen or directory picker had no effect on the daemon
services — pql couldn't find its index, git operated on the wrong
repo.

Now onProjectOpen rebuilds the full dispatcher against the new
workspace root and swaps it into the InProcessClient. The IPC
client's dispatcher field is no longer final.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 22:29:03 +02:00
jpmschweitzerandClaude 9c7ec008dc dart format whole tree
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 21:51:59 +02:00
Jeroen SchweitzerandClaude Opus 4.6 45e8132a41 async recvFd + backend terminal test (sandbox blocks ptyc exec)
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
PtySession.spawn() now runs recvFd in a child isolate via
Isolate.spawn so the blocking FFI call doesn't stall the backend
isolate's event loop.

Terminal testmode test spawns a real backend isolate, opens a project,
and sends pane.spawn via IPC — the exact same path the full app uses.
Currently fails: ptyc successfully starts but its fork+execvp is
blocked by the macOS sandbox ("Operation not permitted"). The SBPL
allows /bin/zsh exec from the app process, but ptyc's child process
may not inherit the exec permission, or the PTYC_SOCK_FD is not
inherited by the ptyc child (Dart Process.start fd inheritance on
macOS).

IsolateClient.events getter exposed for test access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 15:55:29 +02:00
Jeroen SchweitzerandClaude Opus 4.6 a89910dc36 move daemon services to backend isolate
The merged UI/platform thread on macOS (Flutter 3.41) freezes on any
synchronous work — file I/O, Process.run, even isolate spawning during
early frames. No timing workaround (Timer, addPostFrameCallback,
Future.delayed) was reliable.

Fix: spawn a backend isolate that owns the DaemonDispatcher, GitClient,
PqlClient, FilesService, EditorRegistry, and Toolchain resolution. The
main isolate stays free for rendering. Communication uses SendPort with
the existing IPC message protocol (IpcRequest/IpcResponse/IpcEvent) —
zero new serialization.

New files:
  backend.dart       — spawns isolate, manages SendPort/ReceivePort
  backend_entry.dart — isolate entry point, boots all services
  isolate_client.dart — replaces InProcessClient for production

Toolchain now exposes resolveToolchainPaths() as a top-level function
with self-contained PATH expansion (no module-level state that would
prevent isolate message passing).

ClideTestApp gains boot-sequence tests: compute(), Isolate.run(),
sequential Process.run, and the full resolve+exec chain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 13:53:33 +02:00
jpmschweitzerandClaude Opus 4.6 3e1bb27432 split EventBus into DaemonBus + MessageBus
DaemonBus (was EventBus): typed events for system/IPC layer.
MessageBus: channel-based pub/sub for UI/extension coordination.
Messages carry publisher (auto-stamped from extension ID),
channel (required), timestamp, and payload. Subscribe by
publisher, channel, or both — zero collision across extensions.

Extension context gains publish() and subscribe() convenience
methods that auto-stamp the extension's ID as publisher.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 15:25:07 +02:00
jpmschweitzerandClaude Opus 4.6 289d064639 wire in-process dispatcher — no daemon socket needed
InProcessClient dispatches IPC requests directly to handlers in the
same process, bypassing the unix socket entirely. All subsystem
registries (pane, files, editor, git, pql) are created in the
Flutter app's boot sequence and registered on a DaemonDispatcher.
Events bridge to the kernel EventBus via _BusEventSink. The app
no longer needs `clide --daemon` running.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 00:51:33 +02:00
jpmschweitzerandClaude Opus 4.6 46329700d5 dissolve app/ into repo root (D-056)
Single Flutter package at the repo root. All code, tests, assets,
and platform directories moved from app/ to root. Package renamed
from clide_app to clide — all imports rewritten. Merged pubspec
combines core (ffi) and app (flutter, yaml, xterm) dependencies.
Makefile simplified: no APP_PRESENT conditionals, no cd, no daemon
lifecycle. 317 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 00:37:20 +02:00