feat(web): fence dart:ffi behind web stubs so the WASM build compiles (T-438, D-100)
`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>
This commit is contained in:
@@ -101,7 +101,11 @@ String logDirectory([Map<String, String>? env]) {
|
||||
String fnv1a64Hex(String s) {
|
||||
// Desktop-only (the IPC server is desktop-only per D-56). Dart VM
|
||||
// ints are 64-bit; arithmetic wraps modulo 2^64 naturally.
|
||||
var h = 0xcbf29ce484222325;
|
||||
// FNV-1a 64-bit offset basis. Split into two 32-bit halves so the dart2js
|
||||
// web fallback accepts it — a 0xcbf2…2325 literal "can't be represented
|
||||
// exactly in JavaScript" (T-438). Correct on the VM/wasm (int64); the web
|
||||
// build never hashes a socket path (the IPC server is desktop-only).
|
||||
var h = (0xcbf29ce4 << 32) | 0x84222325;
|
||||
const prime = 0x100000001b3;
|
||||
final bytes = utf8.encode(s);
|
||||
for (final b in bytes) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/// Desktop PTY backend selection (T-438 web fence, D-100).
|
||||
///
|
||||
/// [pty_session.dart] imports this only when `dart.library.ffi` is available;
|
||||
/// the web build gets [pty_backend_web.dart] instead, so the FFI-backed
|
||||
/// [NativePty]/[WindowsPty] never enter the wasm compile graph.
|
||||
library;
|
||||
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'native_pty.dart';
|
||||
import 'pty_log.dart';
|
||||
import 'pty_session.dart';
|
||||
import 'windows_pty.dart';
|
||||
|
||||
/// Spawn a PTY child using the platform backend — ConPTY on Windows
|
||||
/// ([WindowsPty]), `posix_openpt` + `posix_spawn` elsewhere ([NativePty]).
|
||||
PtySession startPtyBackend({
|
||||
required String executable,
|
||||
List<String> arguments = const [],
|
||||
required int columns,
|
||||
required int rows,
|
||||
String? workingDirectory,
|
||||
Map<String, String> environment = const {},
|
||||
PtyLog log = PtyLog.none,
|
||||
}) {
|
||||
if (Platform.isWindows) {
|
||||
return WindowsPty.start(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
log: log,
|
||||
);
|
||||
}
|
||||
return NativePty.start(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
log: log,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/// Web stub for the PTY backend (T-438 web fence, D-100).
|
||||
///
|
||||
/// The web/WASM target has no PTY — spawning a child under a pseudo-terminal is
|
||||
/// desktop-only (it needs `dart:ffi`). [pty_session.dart] selects this when
|
||||
/// `dart.library.ffi` is absent, keeping [NativePty]/[WindowsPty] out of the
|
||||
/// wasm graph. The web build is a UI/e2e surface, not a functional desktop
|
||||
/// replacement, so a terminal is never spawned there; calling this is a bug.
|
||||
library;
|
||||
|
||||
import 'pty_log.dart';
|
||||
import 'pty_session.dart';
|
||||
|
||||
PtySession startPtyBackend({
|
||||
required String executable,
|
||||
List<String> arguments = const [],
|
||||
required int columns,
|
||||
required int rows,
|
||||
String? workingDirectory,
|
||||
Map<String, String> environment = const {},
|
||||
PtyLog log = PtyLog.none,
|
||||
}) => throw UnsupportedError('PTY sessions are not available on the web target.');
|
||||
@@ -8,12 +8,13 @@
|
||||
/// in → EOF on child exit → close() reaps.
|
||||
library;
|
||||
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'native_pty.dart';
|
||||
// Web fence (T-438, D-100): the FFI-backed backends are reachable only when
|
||||
// `dart.library.ffi` is available; the web build gets a throwing stub, so
|
||||
// `dart:ffi` never enters the wasm compile graph.
|
||||
import 'pty_backend_web.dart' if (dart.library.ffi) 'pty_backend_io.dart';
|
||||
import 'pty_log.dart';
|
||||
import 'windows_pty.dart';
|
||||
|
||||
abstract interface class PtySession {
|
||||
/// OS process id of the spawned child.
|
||||
@@ -51,25 +52,12 @@ PtySession startPtySession({
|
||||
String? workingDirectory,
|
||||
Map<String, String> environment = const {},
|
||||
PtyLog log = PtyLog.none,
|
||||
}) {
|
||||
if (Platform.isWindows) {
|
||||
return WindowsPty.start(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
log: log,
|
||||
);
|
||||
}
|
||||
return NativePty.start(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
log: log,
|
||||
);
|
||||
}
|
||||
}) => startPtyBackend(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
log: log,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user