Three shipped bugs now share one shape: the JS bridge declares a surface, native silently doesn't serve it, and the app finds out in production. The menu action-name mismatch and dead click selector (#27/#29), and global shortcuts that register successfully but can never fire (#47). The bridge is injected per-build, so a page cannot even rely on window.craft.x existing to mean anything — reachability of a JS method is not proof of a native handler.
A survey of the current bridge found more of the same, still live:
tray.destroy— JS sends it,bridge_tray.zig:117-137has no branch →UnknownAction.fs.watch/onChange— watch registers, but nothing emitscraft:fs:change; no delivery path exists.power.onSleep/onWake,network.onChange, all threebluetooth.onDevice*— never emitted.touchbar:action,serial:data,speechRecognition:partial|final— never emitted.spotlight.getIndexingStatus— a hardcoded JS reply (craft-bridge.js:1028), never asks native.- SDK emits
--sidebar-config-file(index.ts:456) which the native binary does not implement — craft drops the config and renders its placeholder. window.craft.updateris a Sparkle client (bridge_updater.zig:69) and nothing links Sparkle — it should report itself unavailable up front; the working updater is the TypeScriptAutoUpdater.
Ask: a craft.capabilities() (or craft.introspect()) call answered by the zig side, generated from the actual dispatch tables — namespaces, actions, and which event channels have a live emitter. Then: (a) apps can feature-detect honestly, (b) a conformance test can diff the JS surface against it and fail CI the day a new surface ships half-wired, closing this bug class permanently. Downstream apps currently build HTTP probe endpoints to learn a fraction of this (stacksjs/harness /native-probe).
🤖 Generated with Claude Code