a73x

c38ea811

test: exercise client core wasm canary

a73x   2026-08-16 14:46

Commit message
test: exercise client core wasm canary

build.zig
Old New
@@ -598,13 +598,15 @@ pub fn build(b: *std.Build) void {
598 wasm_core_mod.addImport("client_core", wasm_mods[comptime idxOf("client_core")].?); 598 wasm_core_mod.addImport("client_core", wasm_mods[comptime idxOf("client_core")].?);
599 wasm_core_mod.addImport("replica", wasm_mods[comptime idxOf("replica")].?); 599 wasm_core_mod.addImport("replica", wasm_mods[comptime idxOf("replica")].?);
600 wasm_core_mod.addImport("keymap", wasm_mods[comptime idxOf("keymap")].?); 600 wasm_core_mod.addImport("keymap", wasm_mods[comptime idxOf("keymap")].?);
601 // Compile the semantic decoder as its own wasm object as well as making 601 // Compile a tiny, never-embedded canary that calls the semantic decoder
602 // it available to wasm_core. The object is a build-only cleanliness 602 // through typed mode and clipboard result paths. This build-only object
603 // guard: it forces this module's source and protocol import to undergo 603 // forces the production API and its validation helpers through wasm
604 // wasm semantic analysis without adding runtime exports or behavior. 604 // semantic analysis without adding runtime exports or behavior.
605 const client_core_wasm_check_mod = wasmMod(b, wasm_target, "src/client_core_wasm_check.zig");
606 client_core_wasm_check_mod.addImport("client_core", client_core_wasm_mod);
605 const client_core_wasm_guard = b.addObject(.{ 607 const client_core_wasm_guard = b.addObject(.{
606 .name = "client_core_wasm_guard", 608 .name = "client_core_wasm_check",
607 .root_module = client_core_wasm_mod, 609 .root_module = client_core_wasm_check_mod,
608 }); 610 });
609 const wasm_exe = b.addExecutable(.{ .name = "mux_core", .root_module = wasm_core_mod }); 611 const wasm_exe = b.addExecutable(.{ .name = "mux_core", .root_module = wasm_core_mod });
610 // A wasm reactor, not a command: no _start, and the exports must 612 // A wasm reactor, not a command: no _start, and the exports must
src/client_core.zig
Old New
@@ -172,11 +172,11 @@ test "client core refuses BEL as the only invalid clipboard alphabet byte" {
172 172
173 test "client core accepts digits plus slash and plus in clipboard alphabet" { 173 test "client core accepts digits plus slash and plus in clipboard alphabet" {
174 var core = ClientCore{}; 174 var core = ClientCore{};
175 const payload = [_]u8{ 0, 'c', 'A', 'B', '0', '1', '2', '+', '/', '=' }; 175 const payload = [_]u8{ 0, 'c', 'A', 'B', '0', '1', '2', '9', '+', '/', '=' };
176 176
177 switch (core.receive(.term_event, &payload)) { 177 switch (core.receive(.term_event, &payload)) {
178 .effect => |effect| switch (effect) { 178 .effect => |effect| switch (effect) {
179 .clipboard_set => |set| try std.testing.expectEqualStrings("AB012+/=", set.base64), 179 .clipboard_set => |set| try std.testing.expectEqualStrings("AB0129+/=", set.base64),
180 else => return error.ExpectedClipboard, 180 else => return error.ExpectedClipboard,
181 }, 181 },
182 else => return error.ExpectedClipboard, 182 else => return error.ExpectedClipboard,
src/client_core_wasm_check.zig
Old New
@@ -0,0 +1,32 @@
1 const client_core = @import("client_core");
2
3 /// Compile-only canary: keep a real typed mode receive call in the wasm
4 /// build, without exporting or embedding this check in muxweb.
5 export fn clientCoreWasmModesCheck() void {
6 var core = client_core.ClientCore{};
7 const payload = [_]u8{ 1, 0, 0, 0 };
8 const result = core.receive(.term_modes, &payload);
9 switch (result) {
10 .state => |state| switch (state) {
11 .terminal_modes => |modes| if (!modes.bracketed_paste) unreachable,
12 },
13 else => unreachable,
14 }
15 }
16
17 /// Compile-only canary for clipboard event decoding and validation. The
18 /// payload exercises the accepted target and alphabet through the public API.
19 export fn clientCoreWasmClipboardCheck() void {
20 var core = client_core.ClientCore{};
21 const payload = [_]u8{ 0, 'c', 'A', 'B', '0', '1', '2', '9', '+', '/', '=' };
22 const result = core.receive(.term_event, &payload);
23 switch (result) {
24 .effect => |effect| switch (effect) {
25 .clipboard_set => |set| {
26 if (set.target != 'c' or set.base64.len != 9) unreachable;
27 },
28 else => unreachable,
29 },
30 else => unreachable,
31 }
32 }