`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>
watchdog.dart doesn't import pty_log.dart (only references the pattern), so the
[IsolateCrumbFile] doc references couldn't resolve and the dart-doc gate fails
on the warning. Plain-code-font instead of a doc link — no import added just for
a cross-reference.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A main-isolate Timer would freeze WITH the main isolate and tell us nothing,
so the watchdog runs in its own isolate: it fsyncs a heartbeat every ~500ms
(so the last on-disk heartbeat bounds a freeze to ~500ms) and every ~2s samples
this process's thread / handle-or-fd / child-host / RSS counts. A monotonically
climbing child or thread count is the leak signature the soak couldn't
reproduce on CI but a real freeze would show. Output is JSON-lines in
clide-watchdog.log, bounded by the same truncate-on-cap scheme as the crumb
files.
- watchdog.dart (Flutter-free, tested): ResourceSample, ResourceSampler
(forPlatform), PosixResourceSampler (/proc/self: Threads, fd count, task
children, ProcessInfo.currentRss), WatchdogFile (bounded fsynced JSON-lines),
runWatchdog (the loop, bounded by maxTicks for tests), watchdogEntry (the
sendable Isolate.spawn entry).
- watchdog_windows.dart (coverage:ignore — Win32 FFI, validated only at runtime
on Windows): one CreateToolhelp32Snapshot for thread + conhost/OpenConsole
child count, GetProcessHandleCount, ProcessInfo.currentRss. Exhaustively
defensive: any failure yields a -1 field, snapshot handle always closed, never
throws.
- main.dart: spawn the watchdog at boot (desktop only), non-fatal. Per-line
fsync means the OS reaping the isolate at exit loses nothing.
Tests: ResourceSample.toJson, Posix sampler against real /proc, WatchdogFile
(JSON shape, cap, disabled), runWatchdog (immediate baseline tick). Coverage
gate 95.08%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>