Subprocess Wiring¶
BxpProcessClient is the single entry point for all binary calls. Every call
goes through the in-process FFI bridge (bxp-gui-bridge.{dll,so,dylib}) on every
platform — there is no dart:io Process.start path. The bridge offers two call
shapes: in-process inspect / eval, and a native-code bxp-cli subprocess proxy
(both detailed below).
Transport paths¶
The bridge is the single backend on every platform — there is no
Process.start path. Every entry point is a bridge_* call, and the whole
C-ABI surface is below: in-proc runs inside the GUI process against
bxp-core, proxy spawns bxp-cli and drains its pipes in native code, and
lifecycle covers handles, buffers and the version probe.
| Export | Served | Purpose |
|---|---|---|
bridge_version() |
lifecycle | NUL-terminated semver string, matching build.zig.zon. The GUI's library-probe uses it to refuse a mismatched build. |
bridge_run(argv, out) |
proxy | One-shot spawn: run bxp-cli, drain stdout and stderr into the caller's buffer, return the exit code. Used for the --version probe; the native drain sidesteps the Dart pipe truncation of dart-lang/sdk#1727. |
bridge_run_streaming(argv, callbacks) |
proxy | Streaming spawn: per-batch stdout / stderr callbacks plus an exit callback. Carries the bxp-cli --trace BXTB frame stream behind every dry-run and full run. |
bridge_cancel(handle) |
lifecycle | Cooperative cancel for a streaming handle. The child leads its own process group, so this reaches its grandchildren too. |
bridge_ack(handle) |
lifecycle | Backpressure acknowledgement — releases one queue permit, so a slow Dart consumer cannot be outrun by a fast producer. |
bridge_free(ptr, len) |
lifecycle | Returns a response buffer to the bridge allocator. Every buffer the bridge hands out is freed through here, never by Dart. |
bridge_eval_expr(expr, row, out) |
in-proc | Parse and evaluate one expression, returning the value or {error, offset, length}. Drives the expression editor's per-keystroke validation, which is why it must not pay a ~50 ms spawn. |
bridge_eval_expr_trace(expr, row, out) |
in-proc | The same with a per-call NDJSON trace plus a terminal sentinel — the ExprPlayground's step-through view. |
bridge_inspect(request, out) |
in-proc | The stateless inspect ops behind one JSON request envelope: docs, config, list_templates, fetch_template, eval_batch. Result JSON comes back in the out buffer. |
bridge_verify_minisign(file, sig, pubkey) |
in-proc | Verifies a minisign signature over a file — the release SHA256SUMS — against a base64 public key. Ed25519 + Blake2b-512 through the zig-libs minisign module, no heap allocation, no Dart crypto dependency; returns 0 for authentic and non-zero to refuse. |
The table is generated from bxp-gui-bridge/src/ops.zig, which a compile-time
check holds to the library's actual pub export fns in both directions — so an
export cannot be added, renamed or removed without this page following.
The bridge is implemented as a Zig shared library that links the
bxp-core/inspect + expr modules directly (in-proc paths) and spawns the
bxp-cli subprocess (proxy paths). For the two-cause rationale behind the in-proc / proxy split see
internals/index.md — "Why the bridge exists".
The C-ABI surface and Debug→ReleaseSafe rewrite landmine live in
bxp-gui-bridge/CLAUDE.md. The proxy path's pipe
drain is hardened against several subprocess-reaping hazards — the Dart VM's own
child reaper closing fds mid-read, an inherited SIGCHLD=SIG_IGN, and ECHILD
on wait — by joining the stream readers before reaping; see that file for the
ordering.
Mandatory on every platform. Library probe failure at startup is fatal
(synthetic error surfaced through the normal startup gate, which also parses
the docs catalog). There is no subprocess fallback — Windows can't use
Process.start (dart-lang/sdk#1727) and the Linux/macOS Process.start route
was removed when the bridge became the single backend.
Reloading bridge changes. dlopen mmaps the file at process start, so
editing a .so/.dylib and mcp__dart__hot_reload does NOT pick it up. After
zig build in bxp-gui-bridge/, fully stop and relaunch the Flutter app.
Binary resolution¶
Resolved in this order:
- Env override —
$BXP_CLI_PATH. If set and non-empty, used absolutely (missing file → fatal error, no fallthrough). ($BXP_EXAMPLES_PATHis the analogous override for locating the bundledbxp-cli.examples.jsontemplate catalog.) - Bundle sibling —
<name>next to the Flutter executable inside the app bundle. - Dev-tree fallback — walks up from the exe dir until it finds a
bxp-gui/segment, then looks for<monorepo-root>/<name>/zig-out/bin/<name>. This makesflutter run -d linuxwork without copying binaries after a bundle wipe.
Client methods¶
| Method | Backend call | Notes |
|---|---|---|
validateConfig(path) |
bridge_inspect {config} |
Returns annotated JSON with $err_*/$warn_* siblings |
getDocs() |
bridge_inspect {docs} |
Cached at startup; drives FnDoc tooltips + SchemaGate |
listTemplates(path) |
bridge_inspect {list_templates} |
{templates:[…]} → List<TemplateInfo> (id + io shape) |
validateExpr(text) |
bridge_eval_expr |
Returns {error, offset, length} on failure |
traceExpr(text, …) |
bridge_eval_expr_trace |
NDJSON stream of per-call values |
runWithBtrace(...) |
bridge_run_streaming → bxp-cli --trace=bin |
BXTB frame stream → in-store reader |
getVersion(name) |
bridge_run → bxp-cli --version |
Writes to stdout |
Linux dev-tree gotcha¶
The Linux CMake config copies the bxp-gui-bridge library (and bxp-cli) into
the bundle at build time. After rebuilding the bridge, either run a clean Flutter
build or rely on the dev-tree fallback (option 3 above) which reads directly from
bxp-gui-bridge/zig-out/.