icon.show resolves Phosphor glyphs by name (injected resolver) or a 0xNNNN
codepoint, reads a --file JSON array of {icon,label,description,color}
entries, validates colors via parseSvgColor (hex or CSS name), and
publishes on the `icon` bus channel. Honest userError on an unknown glyph,
a bad color, or a malformed payload. A trailing stringList positional is
now variadic so `icon show gear folder gauge` collects every token.
Flutter-free, dart-tested. Card widget + wiring next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A `claude` started with CLAUDE_CONFIG_DIR=<dir> looks for clide's /ide
discovery lock under <dir>/ide, not ~/.claude/ide — so today a per-repo
account session (T-476) can't reach clide's IDE bridge.
McpServer now reconciles a SET of lock paths instead of one: the default
~/.claude/ide always, plus the bound account's <dir>/ide when this workspace
is bound (boundConfigDir, injected from main.dart via the AccountRegistry).
syncDiscoveryLocks() writes/removes locks to match the active set; it runs on
start, once post-boot when the registry becomes resolvable, and on every
accountActionChannel event, so binding/unbinding adds/removes the account lock
at runtime. stop() sweeps every path written — no orphans. Lock content +
0600 are identical across dirs.
Acceptance 1-4 and 6 are covered by mcp_server_test (multi-dir write, no-binding
regression, dynamic add/remove, full cleanup); #5 (a live claude resolving the
bridge end-to-end) is manual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The runtime socket dir accumulated orphaned *.sock nodes from crashed
instances — only the current workspace's own path was ever cleaned. Add a
best-effort startup sweep that probes every *.sock in the dir and unlinks
only the dead ones; live instances (something answers) and unresponsive
nodes (possibly hung) are left alone. Runs before bind, alongside the
existing per-workspace stale-unlink. Never blocks our own startup on a
sweep failure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`flutter build web --wasm` had been broken since the tree-sitter/PTY dart:ffi
pivot. Per D-100 (resolving Q-50: keep the web "happy accident" alive), every
native binding now sits behind a `dart.library.ffi` conditional import with a
graceful web stub. Desktop builds are unchanged — no fidelity loss; the web
target degrades (no terminal, native git, or syntax highlighting).
Discriminator is `dart.library.ffi`, not `dart.library.io` — dart2wasm provides
dart:io, so FFI is the only blocker.
Fences:
- PTY: pty_session → pty_backend_io / pty_backend_web (stub throws).
- tree-sitter: pure types → syntax_result.dart; tree_sitter_service is now a
facade over _ffi/_stub; tree_sitter_boot_io/stub fences TreeSitterLib.init().
- watchdog: watchdog_windows_stub (all -1 sampler).
- claude ABI probe: native_abi_io/stub (was `dart:ffi show Abi`).
- testmode fd-check: fd_check_io/stub.
Also dart2js-safe: the 64-bit FNV literals in session_naming.dart + paths.dart
(the dual JS fallback rejected them) — split into 32-bit halves, dropped a
no-op 64-bit mask. Desktop/wasm hash values unchanged.
CI: added a `web-wasm` job (flutter build web --wasm) so the fence can't rot.
Two FFI-constructing tree-sitter tests import _ffi.dart directly (the analyzer
resolves the conditional facade to the stub branch).
Verified: `flutter build web --wasm` → built; `flutter analyze` clean;
`make test` green. Full Playwright e2e harness wiring is the tracked follow-on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the observability loop: the log + breadcrumb + watchdog files are now
collected by CI so a wedged run leaves downloadable evidence instead of
nothing.
- logDirectory(): CLIDE_LOG_DIR overrides the per-platform default, so CI can
point the logs at an uploadable workspace dir (and tests at a temp dir).
Now takes an injectable env map; tested.
- test_app.dart: when CLIDE_LOG_DIR is set, the testmode harness tees its
logger to a FileLogSink + spawns the watchdog (off by default — normal
run-testmode keeps the stderr-only path, no isolate). _say breadcrumbs each
test into the file.
- conpty_orphan_probe.dart: with CLIDE_LOG_DIR set it passes a verbose PtyLog,
so when soak-conpty-kill.ps1 force-kills the parent, the reader/waiter
isolates' LAST crumb is fsynced to disk — naming what the wedged isolate was
doing at the instant of death.
- bundle-smoke job: runs the real release app with CLIDE_LOG=debug +
CLIDE_LOG_DIR, uploads clide-logs (watchdog heartbeat/sample + FileLogSink)
in an always() step.
- windows-soak kill-probe job: sets CLIDE_LOG_DIR, uploads the FFI crumbs.
Coverage gate 95.08%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First increment of the observability epic (T-425), the productive pivot after
the ConPTY freeze refused to reproduce on CI: if we can't reproduce it, make
the next occurrence leave evidence.
- FileLogSink (lib/kernel/src/file_log_sink.dart): synchronous, crash-survivable
LogSink. Appends each record as one JSON line to a size-rotated file; fsyncs
warn/error + risky-source (pty/ffi/conpty/watchdog) records immediately so the
last breadcrumb is on disk before a hard death, batches the rest on a timer.
Never throws. Flutter-free → unit-tested under dart test against a temp dir.
- logDirectory() (paths.dart): persistent per-platform log dir (LOCALAPPDATA /
~/Library/Logs / $XDG_STATE_HOME) — durable across reboot, unlike the
ephemeral socketDirectory.
- resolveLogLevel() (log.dart): the requested dev/prod toggle. CLIDE_LOG
dart-define → CLIDE_LOG env → app.log.level setting → warn(release)/info(debug).
Lenient parse; an invalid source falls through.
- Boot wiring (facade.boot + main.dart): FileLogSink leads the sink chain (so a
crash records before the volatile stderr/ring sinks) and the resolved level
sets Logger.minLevel.
Tests: FileLogSink (JSON shape, error/stack, rotation cap, append-across-restart,
timer-cancel), resolveLogLevel precedence + fall-through, logDirectory per-OS.
Coverage gate 95.11%.
Follow-ups under T-425: live toggle CLI/command/chip (T-433), FFI breadcrumbs
(T-434), watchdog isolate (T-435), CI artifact wiring (T-436).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings windows-support up to date with main (T-404/405/406, T-413–416,
T-421, the T-422 workspace-lifecycle epic, and the 2.4.0 release).
Conflict resolutions:
- terminal_pane.dart: keep the Windows PowerShell shell selection and
main's workspace-cwd fix (T-381) together.
- tool_check.dart: accept main's deletion (dead, unreferenced code).
- CHANGELOG.md: keep both Unreleased sections.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the runtime up on Windows without disturbing the POSIX paths.
PTY: introduce a platform-neutral PtySession contract with a factory
that picks NativePty (posix_openpt/posix_spawn) or the new WindowsPty
(ConPTY via CreatePseudoConsole). The pane registry programs against
the interface; NativePty now implements it.
IPC: the per-workspace AF_UNIX socket lives under %LOCALAPPDATA% and
is hashed from a canonical workspace key (backslash + ASCII-folded
case) so the Dart server and the C client agree despite NTFS case-
insensitivity. The C client grows a Win32 shim (winsock afunix);
chmod is a no-op on Windows where the per-user ACL is the gate.
Toolchain: PATH probing splits on ';' and tries PATHEXT extensions;
the shell defaults to PowerShell (pwsh, then powershell); tmux is
treated as optional since it has no Windows build; dugite falls back
to PATH git for now.
Build: add `make build-windows`, a clide-cli MSVC build wrapped by
ci/build_cli_windows.sh, and a ConPTY smoke-test suite that self-
skips off-platform.
Co-Authored-By: Claude <noreply@anthropic.com>
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>
The async onData handler never paused its subscription, so pipelined
requests interleaved mid-handler — violating D-72's serial-dispatch
contract — while the shared StringBuffer could re-frame underneath an
in-flight await and the per-chunk utf8.decode corrupted runes split
across reads. One `await for` over a persistent Utf8Decoder +
LineSplitter fixes framing, decoding, and serialization at once.
Tests: two frames pipelined in one write dispatch strictly in order;
a frame split mid-rune across writes decodes intact.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
D-71's threat model — another user on the same host must not drive my
IDE — was enforced with 0600 on the unix socket and then bypassed
wholesale by the unauthenticated localhost SSE port, which since D-86
serves every clide verb as a tool. The server now mints 32 bytes of
CSPRNG token per start, publishes it via the /ide discovery lock
file's authToken slot (the field Claude Code's client reads), chmods
the lock to 0600, and rejects any request that doesn't present the
token in x-claude-code-ide-authorization with 401.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
The catch-all "dispatch threw" log omitted the request command, so a
handler exception couldn't be correlated to what caused it (PTY/IPC
audit item #26). Track the resolved cmd across the request handler and
include it in the error log.
The audit's other two items were already satisfied: errno/signal magic
numbers are centralized in PosixErrno + ffi/libc.dart, and the IPC
server already logs through the kernel Logger (no stderr.writeln left).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
External MCP clients (Cursor, Windsurf, Copilot) can now drive clide. The
MCP server's tools/list is generated from the co-registered command+schema
registry (D-74) that already feeds the CLI and palette — the full
mcp__clide__* namespace with no hand-maintained second surface (D-86). Each
command's CommandSchema maps to a JSON-Schema inputSchema; tools/call routes
mcp__clide__<cmd> to dispatcher.dispatch and renders the IpcResponse as MCP
content (data as JSON, errors with isError).
register() gains a mcpExpose flag (default true); pane.tail opts out as a
poor request/response fit. tail/events are server-intercepted so they're
naturally absent. The two /ide stubs (getDiagnostics, executeCode) are left
as stubs — making them real (analyzer hook, Jupyter eval) is out of scope
per the ticket. Transport unchanged (SSE, D-73).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The one-shot request/response complement to the never-returning
`tail --events` stream — what an agent poll loop wants. The IPC server now
keeps a single global, arrival-ordered event log keyed by a monotonic
cursor (alongside the per-subsystem replay ring), bounded by eventLogDepth
with drop-oldest back-pressure (D-85: producer never blocks).
`clide events [--since <cursor>] [--filter X]` returns events after the
cursor, a high-water `cursor` to poll from next, and `gap: true` (+
oldestCursor) when the requested cursor predates the retained window so a
caller detects loss instead of silently missing dropped events. Repeated
polls neither drop nor duplicate. No on-disk persistence.
`events` is handled in the IPC server like `tail` (not the dispatcher);
added to the argv umbrella set. bindingWhen/CommandContribution untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 23s
Per D-74: commands register an argument schema beside their handler
instead of hand-validating args inline. DaemonDispatcher accumulates a
cmd->schema registry and, before invoking a handler, normalises the
argv-translator shape ({positional, flags}) into named args, coerces
types, and checks per-arg constraints (charset/pattern, leading-dash
rejection, numeric range, list caps). Violations return userError so no
handler sees malformed input. Schema adoption is opt-in per command —
unschema'd commands dispatch unchanged.
panel.resize adopts a schema (dropping the _ResizeArgs hand-lift from
T-119); git.checkout and git.push gain schemas that reject leading-dash
refs at the dispatcher and, via positional ordering, fix the C-client
CLI path — `clide git checkout <branch>` now reaches the handler, where
the positional token previously never mapped to `branch`. The T-104
validateGitRef + count/path caps stay in place as defense-in-depth
because the git client is reachable directly from the UI, not only
through the dispatcher.
Co-Authored-By: Claude <noreply@anthropic.com>
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
test / dart doc (lib API) (push) Failing after 27s
Seventh slice of T-99. clide now advertises itself to Claude Code's
/ide command and serves a working MCP endpoint over HTTP+SSE per
D-73 (the Q-33 transport decision, locked in this commit).
What lands:
* D-73 — MCP transport for /ide is SSE over HTTP. Resolves Q-33;
references D-68 + D-72.
* lib/src/ipc/mcp_server.dart — McpServer class. localhost HTTP
listener on a random port; GET /sse opens a long-lived SSE stream
with an initial endpoint event carrying the session id; POST
/messages?sessionId=... accepts JSON-RPC requests and replies via
the matching SSE stream. JSON-RPC handlers for initialize,
tools/list, tools/call.
* Discovery file at $HOME/.claude/ide/<pid>.lock with the workspace
+ url so `/ide` can find us. Removed on stop.
* The two /ide minimum tools (mcp__ide__getDiagnostics,
mcp__ide__executeCode) ship as stubs — real implementations need
the analyzer integration / a clide eval surface, both follow-ups.
* main.dart starts the MCP server alongside the unix IPC server on
daemonClientFactory and project switch. Failure non-fatal — the
UI runs without MCP.
* 12 server tests cover lifecycle (start/stop, lock file), unknown
paths, full JSON-RPC round-trip for all four methods, error
responses, and edge cases (unknown session, malformed JSON,
notification without id).
The "Claude Code's /ide discovers and connects" smoke is deferred to
T-131 wrap-up since it needs a real Claude Code session against the
running app — out of scope for unit/widget tests.
Co-Authored-By: Claude <noreply@anthropic.com>
Sixth slice of T-99. Long-lived event subscription path, the second
half of D-6.
Wire shape:
- Client sends `{cmd:"tail", args:{flags:{events:true, filter:X}}}`.
- Server responds with `{ok:true, data:{streaming:true, filter:X}}`.
- Server pushes `{type:"event", subsystem, kind, ts, data}` lines
until the client closes.
Server (lib/src/ipc/server.dart):
- Takes a DaemonBus, subscribes to DaemonEvent on start.
- Per-subsystem ring buffer (replayDepth=16 per D-6) populated on
every emit.
- `tail --events` connection: send ack, replay matching events from
ring, register the client for future fanout.
- _argv envelope now unwrapped at the server layer so the streaming
check sees the inner `tail` cmd (not just `_argv`).
- Broken subscriber writes drop the subscriber cleanly; the bus
doesn't block on a stalled client.
Client (native/clide-cli/clide.c):
- Sniffs `data.streaming:true` in the ack. If set, loops reading
JSON-line events to stdout (with fflush per line) until EOF.
Tests:
- test/ipc/server_streaming_test.dart — 8 cases covering ack shape,
filter, replay buffer (size + ordering), multi-subscriber fanout,
broken-subscriber cleanup.
- test/cli/clide_cli_e2e_test.dart gets a tail --events test that
spawns the C client, emits two events on the bus, asserts they
print on stdout.
T-99 children remaining: T-130 (MCP), T-131 (wrap-up).
Co-Authored-By: Claude <noreply@anthropic.com>
Third slice of T-99. After this `clide status` actually does
something when typed in a shell.
* native/clide-cli/clide.c — ~250 LOC C. Walks CWD up to .git,
hashes the workspace root with FNV-1a 64-bit (byte-for-byte
identical to the Dart side, pinned via reference vectors in
paths_test.dart), opens the per-workspace socket, and ships argv
across the wire as `{cmd:"_argv", args:{argv:[...]}}`.
* lib/src/cli/argv_dispatch.dart — registers the `_argv` sentinel
command on the dispatcher. The handler runs the T-125 parser on
the embedded argv and either re-dispatches the unwrapped request
through the same dispatcher or returns the pre-built error
response. Keeps the parser in Dart so the C side stays dumb.
* lib/src/ipc/paths.dart — fnv1a64Hex hoisted to a public helper +
fixed to format as unsigned (Dart `int` is signed int64; the high
bit lit a leading minus that broke the cross-language compare).
Reference-vector tests added against the FNV reference.
* `make clide-cli` builds it via the host `cc`; output lands at
native/<platform>/clide and is gitignored. Test
test/cli/clide_cli_e2e_test.dart compiles + exercises the full
round-trip; skips cleanly when no cc is on PATH.
* CONTRIBUTING.md gets a "C clide shell client" section.
T-128 (delete legacy IPC) unblocked.
Co-Authored-By: Claude <noreply@anthropic.com>
First slice of T-99 (the D-56-path-a IPC server). What this lands:
* lib/src/ipc/paths.dart rewritten — `workspaceSocketPath(root)`
returns the per-workspace path per D-70 (FNV-1a 64-bit hash, hex,
no crypto dep — D-70 amended in this commit to record the hash
choice). Old `defaultSocketPath()` removed; the lone fallback in
facade.dart kept with a clear placeholder pending T-127.
* lib/src/ipc/server.dart — IpcServer class. ServerSocket.listen
accept loop (D-72), 0600 socket + 0700 parent (D-71), stale-node
probe + unlink on start, refuses to clobber a live listener.
* lib/main.dart — IpcServer started after the first dispatcher is
built and swapped on project open (workspace path changes).
Failure logged but non-fatal so the UI still works without IPC.
* 11 server tests + 5 path tests cover socket modes, multi-conn,
stale unlink, live-conflict, idempotent start/stop.
T-99 children downstream of T-124 (T-125 / T-126 / T-127 / T-130)
are now unblocked.
Co-Authored-By: Claude <noreply@anthropic.com>
test / unit + widget + golden + a11y (push) Failing after 31s
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 1m7s
Seven small consultant findings, one commit:
1. TreeSitterLib stores last dlopen error + path in static fields
instead of swallowing them. Callers that observe a null instance
can now read the diagnostic.
2. Drop the Cmsghdr alias in libc.dart — back-compat shim with no
callers; CLAUDE.md forbids those in a solo repo.
3. Drop EditorController._events field + the unused_field
suppression. The constructor still subscribes via `events.on<...>`
for _eventSub; the field itself was speculative retention.
4. Replace inline hex / errno literals in native_pty.dart with
PosixErrno.{eintr,ebadf,epipe} and new libc.{pollin, pollAnyErr,
sighup, sigkill, sigwinch}. PosixErrno gains eintr.
5. ExtensionManager records activate/deactivate exceptions in a
`_failed` map exposed as `failedExtensions` + `didFail(id)`.
Listeners are notified on entry/exit; cleared on a clean
activate. UI surfaces the degraded state instead of pretending
everything is fine.
6. file_tree_view imports FileEntry via the clide.dart barrel
instead of `package:clide/src/files/listing.dart` directly — the
leak the consultant flagged (barrel already re-exports it).
7. test_app branch in main.dart wrapped in `if (kDebugMode)` so
release tree-shaker elides the test harness from shipping
binaries. Source import stays; tree-shake handles the rest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Mechanical `dart format` sweep across files that drifted from the
formatter's output (mostly trailing-comma and line-wrap differences
from a Dart SDK / formatter version bump). No semantic changes.
Caught because the pre-push gate now actually fires.
Co-Authored-By: Claude <noreply@anthropic.com>
pane.spawn (via PtyException.errno) and editor.open (via
FileSystemException.osError.errorCode) now route ENOENT to
not_found, EACCES/EPERM to user_error with a permissions hint,
EISDIR/ENOTDIR/EEXIST to distinct user-error/conflict, and
EMFILE/ENFILE to tool_error with a "fd limit hit" hint. The
mapping lives in lib/src/ipc/errno_mapping.dart so other handlers
can adopt the same surface as they pick up errno-bearing failures.
Co-Authored-By: Claude <noreply@anthropic.com>
Three hardening fixes:
- 60s per-request timeout (configurable via DaemonServer constructor)
prevents a misbehaving handler from blocking the connection's
read pipeline indefinitely. On timeout the client gets a clean
tool_error response.
- broadcast() and the per-request response writeln are wrapped in
try/catch with stderr logging. Previously write failures silently
dropped clients with no diagnostic; events going missing was
invisible.
- start() probes for a live daemon before unlinking a stale socket.
If something answers within 200ms, refuse to start. Previously
two daemons racing to bind would let the second rip the first's
live socket out.
Co-Authored-By: Claude <noreply@anthropic.com>
`bin/clide` gains the single-word shortcuts CLAUDE.md's tier 2 spells
out: open, active, insert, replace-selection, save. Each maps the
flat positional argv into the canonical editor.* IPC shape. Insert
and replace-selection accept a lone `-` to read text from stdin so
piping works (`pbpaste | clide replace-selection -`).
`clide tail --events` is the subscribe mode. Same socket as the
request side; the client just reads + filters events. --filter
SUBSYSTEM or SUBSYSTEM:ID narrows the stream. Exits cleanly on
SIGINT.
defaultSocketPath() now respects CLIDE_SOCKET_PATH before XDG — the
existing override callers always had this up their sleeve (via
XDG_RUNTIME_DIR manipulation) but making it explicit unblocks
parallel test runs where each test needs its own daemon socket. The
new end-to-end CLI suite does exactly that: 5 tests spin up real
daemon subprocesses and exercise the shortcut surface through the
live IPC stack.
74 core tests pass; round-trip verified by hand (open README.md →
insert → tail --events captures editor.opened / edited /
selection-changed / saved).
Co-Authored-By: Claude <noreply@anthropic.com>
Implements the Tier-1 pane subsystem from D-006: spawn / list / focus /
close / write / resize / tail commands, plus pane.spawned / output /
exit / resized / focused / closed events. PaneRegistry owns per-pane
PtySession lifecycles and id generation (p_N); a DaemonEventSink seam
keeps pane code decoupled from the IPC server package.
DaemonServer.broadcast() fans events out to every connected client.
Per-client subsystem/id filtering (`tail --filter pane:p_7`) is
deferred — Tier 1 broadcasts everything and the subscriber discards.
Panes carry a `kind:` field (terminal | claude). Step 7 (builtin.claude)
adds the claude-specific pane flow on top of this generic substrate —
the subsystem itself stays neutral.
14 new core tests: registry unit coverage (spawn → pane.spawned event,
output → base64 events, write/resize/close round-trips, idempotent
close, claude kind on the wire) plus dispatcher coverage (argv
validation, unknown-id → not-found, text vs bytes_b64, etc). All 37
core tests pass in ~3s under test-core.
Co-Authored-By: Claude <noreply@anthropic.com>
First real content for the `clide` Dart package at the repo root.
One AOT-compiled binary (ADR 0005) with two modes:
* `clide --daemon` long-running unix-socket server; listens on
`$XDG_RUNTIME_DIR/clide-$USER.sock` with stale-socket
reclaim, accepts JSON-lines request/response traffic, clean
SIGTERM shutdown unlinks the socket file.
* `clide <subcommand>` one-shot; opens the socket, sends a
request, writes the response JSON to stdout, exits with the
dispatcher's error code per ADR 0006 (0/1/2/3/4). Unknown
subcommands forward to the daemon so extensions can register
their own without CLI changes.
Tier 0 handlers: `ping` (returns pong + version + UTC ts) and
`version`. Both are covered by `test/ipc/` + `test/daemon/`; the
subprocess test builds `bin/clide`, starts it, pings it, SIGTERMs
it, and asserts the socket file disappears.
Co-Authored-By: Claude <noreply@anthropic.com>