From d59397d4146ffa23adbbe6dc99c64ebb22a93b2d Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sat, 1 Aug 2026 22:44:19 -0300 Subject: add vanilla DOM web backend --- test/web-snapshots/README.md | 7 +- test/web-snapshots/source-list.golden | 306 +++++++++++++++++----------------- test/web_harness.mjs | 136 +++++++++++++++ test/web_snapshot.mjs | 41 ++--- 4 files changed, 315 insertions(+), 175 deletions(-) create mode 100644 test/web_harness.mjs (limited to 'test') diff --git a/test/web-snapshots/README.md b/test/web-snapshots/README.md index d2ea84f8..c6564ba0 100644 --- a/test/web-snapshots/README.md +++ b/test/web-snapshots/README.md @@ -13,6 +13,7 @@ To recapture it: The visible `.body` must exactly match the command plus every path reported by Git (and the final prompt). The base64 PTY stream must contain the command -itself. `source-list.snap` then drives that dump through Chromium: it checks the -full listing, taps `build.zig`, snapshots tree-sitter style runs, resizes the -pane separator, and finally verifies body scrolling. +itself. `source-list.snap` then drives the freestanding WASM + DOM build through +Chromium: it checks the full listing, taps `build.zig`, snapshots packed cell +styles (including Tree-sitter's Zig highlighting), resizes the pane separator, +and finally verifies body scrolling. diff --git a/test/web-snapshots/source-list.golden b/test/web-snapshots/source-list.golden index d4321fbb..35f86de6 100644 --- a/test/web-snapshots/source-list.golden +++ b/test/web-snapshots/source-list.golden @@ -1,6 +1,6 @@ == snap launcher grid=120x37 cursor=4,23 -|Kill Newcol Tutor Debug Colors NextColor Crt Dump -| TTY /home/goblin/00-projects/0x4200.cafe/02-pardes-code Del +|Kill Newcol Tutor Debug NextColor Dump Find Grep Help +|$ /home/goblin/00-projects/0x4200.cafe/02-pardes-code Del | $ git ls-files '*.zig' | build.zig | src/crt.zig @@ -36,155 +36,155 @@ | | | -== snap source-open grid=120x37 cursor=7,25 -|Kill Newcol Tutor Debug Colors NextColor Crt Dump -| TTY /home/goblin/00-projects/0x4200.cafe/02-pardes-code Del -| $ git ls-files '*.zig' -| build.zig -| src/crt.zig -| src/deck.zig -| src/dump.zig -| src/gui.zig -| src/image.zig -| src/look.zig -| src/main.zig -| src/modal.zig -| src/pardes.zig -| src/petscii.zig -| src/syntax.zig -| src/tracy.zig -| src/tty.zig -| test/deck/replay.zig -| test/deck/schema.zig -| test/e2e_harness.zig -| test/hxdiff.zig -| test/snapshot.zig -| uucode_config.zig -| -| NOR build.zig Save Del -| 1 const std = @import("std"); -| 2 -| 3 pub const Platform = enum { tty, gui, web }; -| 4 pub const TreeSitterGrammars = enum { disabled, zig, minimal, full }; -| 5 const GhosttyArtifact = enum { ghostty, lib }; -| 6 -| 7 pub fn build(b: *std.Build) void { -| 8 // Default target is the Steam Deck (deckcap's trick): x86_64 linux-gnu -| 9 // with the glibc version pinned low, so a binary built on a rolling- -| 10 // release host runs on SteamOS — a native build references the host's -| 11 // newer versioned libm/libc symbols and dies with "GLIBC_2.4x not found" -| 12 // on the deck. Override with -Dtarget= as usual. +== snap source-open grid=120x37 cursor=7,2 +|Kill Newcol Tutor Debug NextColor Dump Find Grep Help +| build.zig Save Del $ /home/goblin/00-projects/0x4200.cafe/02-pardes-code Del +| 1 const std = @import("std"); $ git ls-files '*.zig' +| 2 const snap_build = @import("build/snap.zig"); build.zig +| 3 src/crt.zig +| 4 pub const Platform = enum { tty, gui, web }; src/deck.zig +| 5 src/dump.zig +| 6 /// The ZLS the language backend links, spelled once. src/gui.zig +| 7 /// build.zig.zon pins (0.16.x branch) and is passed src/image.zig +| 8 /// `-Dversion-string` (its build.zig otherwise shell src/look.zig +| 9 /// which fails on a fetched package that has no .git src/main.zig +| 10 const zls_version = "0.16.1-dev+3e0d0820"; src/modal.zig +| 11 pub const TreeSitterGrammars = enum { disabled, zig, src/pardes.zig +| 12 src/petscii.zig +| 13 pub fn build(b: *std.Build) void { src/syntax.zig +| 14 // Default target is the Steam Deck (deckcap's tr src/tracy.zig +| 15 // with the glibc version pinned low, so a binary src/tty.zig +| 16 // release host runs on SteamOS — a native build test/deck/replay.zig +| 17 // newer versioned libm/libc symbols and dies wit test/deck/schema.zig +| 18 // on the deck. Override with -Dtarget= as usual. test/e2e_harness.zig +| 19 const target = b.standardTargetOptions(.{ .defaul test/hxdiff.zig +| 20 .cpu_arch = .x86_64, test/snapshot.zig +| 21 .os_tag = .linux, uucode_config.zig +| 22 .abi = .gnu, +| 23 .glibc_version = .{ .major = 2, .minor = 38, +| 24 } }); +| 25 const requested_optimize = b.standardOptimizeOpti +| 26 const platform = b.option(Platform, "platform", " +| 27 const static = b.option(bool, "static", "statical +| 28 const dump_path = b.option([]const u8, "dump", "d +| 29 const is_web = platform == .web; +| 30 const is_web_target = target.result.cpu.arch == . +| 31 // wasm: size is the budget +| 32 const optimize = if (is_web) .ReleaseSmall else r +| 33 // The vendored C is never what we are debugging, +| 34 // the app: 90% of a Debug startup is tree-sitter +| 35 // (perf: ts_query__perform_analysis + ts_lookahe == style source-open -|0: 0-48 #969696,#2c2a3e, 49-119 d,#2c2a3e, -|1: 0-1 d,#383c54, 2-60 #969696,#2c2a3e, 61-119 d,#2c2a3e, -|2: 0-1 d,#525252, 2-119 d,d, -|3: 0-1 d,#525252, 2-119 d,d, -|4: 0-1 d,#525252, 2-119 d,d, -|5: 0-1 d,#525252, 2-119 d,d, -|6: 0-1 d,#525252, 2-119 d,d, -|7: 0-1 d,#525252, 2-119 d,d, -|8: 0-1 d,#525252, 2-119 d,d, -|9: 0-1 d,#525252, 2-119 d,d, -|10: 0-1 d,#525252, 2-119 d,d, -|11: 0-1 d,#525252, 2-119 d,d, -|12: 0-1 d,#525252, 2-119 d,d, -|13: 0-1 d,#525252, 2-119 d,d, -|14: 0-1 d,#525252, 2-119 d,d, -|15: 0-1 d,#525252, 2-119 d,d, -|16: 0-1 d,#525252, 2-119 d,d, -|17: 0-1 d,#525252, 2-119 d,d, -|18: 0-1 d,#525252, 2-119 d,d, -|19: 0-1 d,#525252, 2-119 d,d, -|20: 0-1 d,#525252, 2-119 d,d, -|21: 0-1 d,#525252, 2-119 d,d, -|22: 0-1 d,#525252, 2-119 d,d, -|23: 0-1 d,#525252, 2-119 d,d, -|24: 0-1 d,#6e4fd0, 2-23 #969696,#2c2a3e, 24-119 d,#2c2a3e, -|25: 0-1 d,#525252, 2-6 #969696,#2c2a3e, 7-18 d,d, 19-25 #cc70d8,d,b 26-26 d,d, 27-31 #8ab87a,d, 32-119 d,d, -|26: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-119 d,d, -|27: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-27 d,d, 28-31 #cc70d8,d,b 32-119 d,d, -|28: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-37 d,d, 38-41 #cc70d8,d,b 42-119 d,d, -|29: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-30 d,d, 31-34 #cc70d8,d,b 35-119 d,d, -|30: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-119 d,d, -|31: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-10 d,d, 11-12 #cc70d8,d,b 13-119 d,d, -|32: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-10 d,d, 11-81 #f0f0f0,d,b 82-119 d,d, -|33: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-10 d,d, 11-79 #f0f0f0,d,b 80-119 d,d, -|34: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-10 d,d, 11-80 #f0f0f0,d,b 81-119 d,d, -|35: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-10 d,d, 11-83 #f0f0f0,d,b 84-119 d,d, -|36: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-119 d,d, -== snap separator-resized grid=120x37 cursor=7,21 -|Kill Newcol Tutor Debug Colors NextColor Crt Dump -| TTY /home/goblin/00-projects/0x4200.cafe/02-pardes-code Del -| src/dump.zig -| src/gui.zig -| src/image.zig -| src/look.zig -| src/main.zig -| src/modal.zig -| src/pardes.zig -| src/petscii.zig -| src/syntax.zig -| src/tracy.zig -| src/tty.zig -| test/deck/replay.zig -| test/deck/schema.zig -| test/e2e_harness.zig -| test/hxdiff.zig -| test/snapshot.zig -| uucode_config.zig -|╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ -| NOR build.zig Save Del -| 1 const std = @import("std"); -| 2 -| 3 pub const Platform = enum { tty, gui, web }; -| 4 pub const TreeSitterGrammars = enum { disabled, zig, minimal, full }; -| 5 const GhosttyArtifact = enum { ghostty, lib }; -| 6 -| 7 pub fn build(b: *std.Build) void { -| 8 // Default target is the Steam Deck (deckcap's trick): x86_64 linux-gnu -| 9 // with the glibc version pinned low, so a binary built on a rolling- -| 10 // release host runs on SteamOS — a native build references the host's -| 11 // newer versioned libm/libc symbols and dies with "GLIBC_2.4x not found" -| 12 // on the deck. Override with -Dtarget= as usual. -| 13 const target = b.standardTargetOptions(.{ .default_target = .{ -| 14 .cpu_arch = .x86_64, -| 15 .os_tag = .linux, -| 16 .abi = .gnu, -== snap source-scrolled grid=120x37 cursor=0,0 -|Kill Newcol Tutor Debug Colors NextColor Crt Dump -| TTY /home/goblin/00-projects/0x4200.cafe/02-pardes-code Del -| src/dump.zig -| src/gui.zig -| src/image.zig -| src/look.zig -| src/main.zig -| src/modal.zig -| src/pardes.zig -| src/petscii.zig -| src/syntax.zig -| src/tracy.zig -| src/tty.zig -| test/deck/replay.zig -| test/deck/schema.zig -| test/e2e_harness.zig -| test/hxdiff.zig -| test/snapshot.zig -| uucode_config.zig -| -| NOR build.zig Save Del -| 10 // release host runs on SteamOS — a native build references the host's -| 11 // newer versioned libm/libc symbols and dies with "GLIBC_2.4x not found" -| 12 // on the deck. Override with -Dtarget= as usual. -| 13 const target = b.standardTargetOptions(.{ .default_target = .{ -| 14 .cpu_arch = .x86_64, -| 15 .os_tag = .linux, -| 16 .abi = .gnu, -| 17 .glibc_version = .{ .major = 2, .minor = 38, .patch = 0 }, -| 18 } }); -| 19 const requested_optimize = b.standardOptimizeOption(.{}); -| 20 const platform = b.option(Platform, "platform", "which shell to build (tty, gui, web)") orelse .tty; -| 21 const static = b.option(bool, "static", "statically link") orelse false; -| 22 const dump_path = b.option([]const u8, "dump", "dump .zon embedded into the web shell (-Dplatform=web)"); -| 23 const emcc_path = b.option([]const u8, "emcc", "emcc for the web shell's final link") orelse "/usr/lib/emscri -| 24 const is_emscripten = target.result.os.tag == .emscripten; -| 25 // wasm: size is the budget +|0: 0-52 #969696,#2c2a3e, 53-119 d,#2c2a3e, +|1: 0-1 #ffffff,#6e4fd0, 2-19 #969696,#2c2a3e, 20-59 d,#2c2a3e, 60-61 #ffffff,#383c54, 62-116 #969696,#2c2a3e, 117-119 d,#2c2a3e, +|2: 0-0 d,#525252, 1-1 d,d, 2-6 #969696,#2c2a3e, 7-18 d,d, 19-25 #cc70d8,d,b 26-26 d,d, 27-31 #8ab87a,d, 32-59 d,d, 60-60 d,#525252, 61-119 d,d, +|3: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-25 d,d, 26-32 #cc70d8,d,b 33-33 d,d, 34-49 #8ab87a,d, 50-59 d,d, 60-60 d,#525252, 61-119 d,d, +|4: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|5: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-27 d,d, 28-31 #cc70d8,d,b 32-59 d,d, 60-60 d,#525252, 61-119 d,d, +|6: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|7: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|8: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|9: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|10: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|11: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-26 d,d, 27-47 #8ab87a,d, 48-59 d,d, 60-60 d,#525252, 61-119 d,d, +|12: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-37 d,d, 38-41 #cc70d8,d,b 42-59 d,d, 60-60 d,#525252, 61-119 d,d, +|13: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|14: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-12 #cc70d8,d,b 13-59 d,d, 60-60 d,#525252, 61-119 d,d, +|15: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|16: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|17: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|18: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|19: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|20: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|21: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|22: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|23: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|24: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-43 d,d, 44-44 #d0c070,d, 45-55 d,d, 56-57 #d0c070,d, 58-59 d,d, 60-60 d,#525252, 61-119 d,d, +|25: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|26: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|27: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-46 d,d, 47-56 #8ab87a,d, 57-58 d,d, 59-59 #8ab87a,d, 60-60 d,#525252, 61-119 d,d, +|28: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-40 d,d, 41-48 #8ab87a,d, 49-50 d,d, 51-59 #8ab87a,d, 60-60 d,#525252, 61-119 d,d, +|29: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-49 d,d, 50-55 #8ab87a,d, 56-57 d,d, 58-59 #8ab87a,d, 60-60 d,#525252, 61-119 d,d, +|30: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|31: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-59 d,d, 60-60 d,#525252, 61-119 d,d, +|32: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-37 #f0f0f0,d,b 38-59 d,d, 60-60 d,#525252, 61-119 d,d, +|33: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-27 d,d, 28-29 #cc70d8,d,b 30-53 d,d, 54-57 #cc70d8,d,b 58-59 d,d, 60-60 d,#525252, 61-119 d,d, +|34: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|35: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +|36: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-10 d,d, 11-59 #f0f0f0,d,b 60-60 d,#525252, 61-119 d,d, +== snap separator-resized grid=120x37 cursor=7,2 +|Kill Newcol Tutor Debug NextColor Dump Find Grep Help +| build.zig Save Del ╎$ /home/goblin/00-projects/0x4200.cafe/02-pardes-code Del +| 1 const std = @import("std"); ╎ $ git ls-files '*.zig' +| 2 const snap_build = @import("build/snap.zig"); ╎ build.zig +| 3 ╎ src/crt.zig +| 4 pub const Platform = enum { tty, gui, web }; ╎ src/deck.zig +| 5 ╎ src/dump.zig +| 6 /// The ZLS the language backend links, spelled once.╎ src/gui.zig +| 7 /// build.zig.zon pins (0.16.x branch) and is passed ╎ src/image.zig +| 8 /// `-Dversion-string` (its build.zig otherwise shell╎ src/look.zig +| 9 /// which fails on a fetched package that has no .git╎ src/main.zig +| 10 const zls_version = "0.16.1-dev+3e0d0820"; ╎ src/modal.zig +| 11 pub const TreeSitterGrammars = enum { disabled, zig, ╎ src/pardes.zig +| 12 ╎ src/petscii.zig +| 13 pub fn build(b: *std.Build) void { ╎ src/syntax.zig +| 14 // Default target is the Steam Deck (deckcap's tr╎ src/tracy.zig +| 15 // with the glibc version pinned low, so a binary╎ src/tty.zig +| 16 // release host runs on SteamOS — a native build ╎ test/deck/replay.zig +| 17 // newer versioned libm/libc symbols and dies wit╎ test/deck/schema.zig +| 18 // on the deck. Override with -Dtarget= as usual.╎ test/e2e_harness.zig +| 19 const target = b.standardTargetOptions(.{ .defaul╎ test/hxdiff.zig +| 20 .cpu_arch = .x86_64, ╎ test/snapshot.zig +| 21 .os_tag = .linux, ╎ uucode_config.zig +| 22 .abi = .gnu, ╎ +| 23 .glibc_version = .{ .major = 2, .minor = 38, ╎ +| 24 } }); ╎ +| 25 const requested_optimize = b.standardOptimizeOpti╎ +| 26 const platform = b.option(Platform, "platform", "╎ +| 27 const static = b.option(bool, "static", "statical╎ +| 28 const dump_path = b.option([]const u8, "dump", "d╎ +| 29 const is_web = platform == .web; ╎ +| 30 const is_web_target = target.result.cpu.arch == .╎ +| 31 // wasm: size is the budget ╎ +| 32 const optimize = if (is_web) .ReleaseSmall else r╎ +| 33 // The vendored C is never what we are debugging,╎ +| 34 // the app: 90% of a Debug startup is tree-sitter╎ +| 35 // (perf: ts_query__perform_analysis + ts_lookahe╎ +== snap source-scrolled grid=120x37 cursor=7,2 +|Kill Newcol Tutor Debug NextColor Dump Find Grep Help +| build.zig Save Del ╎$ /home/goblin/00-projects/0x4200.cafe/02-pardes-code Del +| 1 const std = @import("std"); ╎ $ git ls-files '*.zig' +| 2 const snap_build = @import("build/snap.zig"); ╎ build.zig +| 3 ╎ src/crt.zig +| 4 pub const Platform = enum { tty, gui, web }; ╎ src/deck.zig +| 5 ╎ src/dump.zig +| 6 /// The ZLS the language backend links, spelled once.╎ src/gui.zig +| 7 /// build.zig.zon pins (0.16.x branch) and is passed ╎ src/image.zig +| 8 /// `-Dversion-string` (its build.zig otherwise shell╎ src/look.zig +| 9 /// which fails on a fetched package that has no .git╎ src/main.zig +| 10 const zls_version = "0.16.1-dev+3e0d0820"; ╎ src/modal.zig +| 11 pub const TreeSitterGrammars = enum { disabled, zig, ╎ src/pardes.zig +| 12 ╎ src/petscii.zig +| 13 pub fn build(b: *std.Build) void { ╎ src/syntax.zig +| 14 // Default target is the Steam Deck (deckcap's tr╎ src/tracy.zig +| 15 // with the glibc version pinned low, so a binary╎ src/tty.zig +| 16 // release host runs on SteamOS — a native build ╎ test/deck/replay.zig +| 17 // newer versioned libm/libc symbols and dies wit╎ test/deck/schema.zig +| 18 // on the deck. Override with -Dtarget= as usual.╎ test/e2e_harness.zig +| 19 const target = b.standardTargetOptions(.{ .defaul╎ test/hxdiff.zig +| 20 .cpu_arch = .x86_64, ╎ test/snapshot.zig +| 21 .os_tag = .linux, ╎ uucode_config.zig +| 22 .abi = .gnu, ╎ +| 23 .glibc_version = .{ .major = 2, .minor = 38, ╎ +| 24 } }); ╎ +| 25 const requested_optimize = b.standardOptimizeOpti╎ +| 26 const platform = b.option(Platform, "platform", "╎ +| 27 const static = b.option(bool, "static", "statical╎ +| 28 const dump_path = b.option([]const u8, "dump", "d╎ +| 29 const is_web = platform == .web; ╎ +| 30 const is_web_target = target.result.cpu.arch == .╎ +| 31 // wasm: size is the budget ╎ +| 32 const optimize = if (is_web) .ReleaseSmall else r╎ +| 33 // The vendored C is never what we are debugging,╎ +| 34 // the app: 90% of a Debug startup is tree-sitter╎ +| 35 // (perf: ts_query__perform_analysis + ts_lookahe╎ diff --git a/test/web_harness.mjs b/test/web_harness.mjs new file mode 100644 index 00000000..bcd45746 --- /dev/null +++ b/test/web_harness.mjs @@ -0,0 +1,136 @@ +#!/usr/bin/env node +// Dependency-free JS harness: instantiate the real freestanding core in Node, +// then render its packed frame through app.mjs against a deliberately tiny DOM +// double. Chrome is reserved for the separate browser/e2e snapshot harness. + +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +if (process.argv.length !== 3) { + console.error("usage: node test/web_harness.mjs "); + process.exit(2); +} + +async function main() { + const webDir = path.resolve(process.argv[2]); + const wasmBytes = await fs.readFile(path.join(webDir, "pardes.wasm")); + const module = await WebAssembly.compile(wasmBytes); + assert.deepEqual(WebAssembly.Module.imports(module), [], "the freestanding core must have no JS or libc imports"); + const { exports: wasm } = await WebAssembly.instantiate(module, {}); + + for (const name of [ + "memory", "pardes_init", "pardes_frame", "pardes_frame_ptr", "pardes_key", + "pardes_mouse", "pardes_output", "pardes_lsp_response", "pardes_file_changed", + "pardes_effect_next", "pardes_deinit", + ]) assert.ok(wasm[name], `missing wasm export ${name}`); + + assert.equal(wasm.pardes_init(80, 24), 0, `wasm init error ${wasm.pardes_last_error()}`); + assert.equal(wasm.pardes_frame(), 80 * 24); + assert.ok(wasm.pardes_frame_ptr() > 0); + + const { DomRenderer } = await import(pathToFileURL(path.join(webDir, "app.mjs"))); + const document = new FakeDocument(); + const root = document.createElement("main"); + root.ownerDocument = document; + const renderer = new DomRenderer(root, wasm); + const first = renderer.render(); + + assert.equal(first.cols, 80); + assert.equal(first.rows, 24); + assert.equal(root.children.length, 24); + assert.equal(root.children[0].children.length, 80); + assert.ok(first.cells.some((cell) => cell.text !== " "), "dump rendered an empty frame"); + assert.ok(root.walk().every((node) => node.tagName !== "CANVAS"), "renderer created a canvas"); + + const firstCell = renderer.cells[0]; + const second = renderer.render(); + assert.equal(renderer.cells[0], firstCell, "stable frames should reuse DOM nodes"); + assert.equal(second.cells.length, 80 * 24); + + wasm.pardes_mouse(2, 0, 5, 3, 0); + wasm.pardes_mouse(2, 1, 5, 3, 0); + const looked = renderer.render(); + assert.match(looked.cells.map((cell) => cell.text).join(""), /build\.zig Save Del/, "right-click LOOK did not open the embedded source"); + const sourceRows = Array.from({ length: looked.rows }, (_, row) => + looked.cells.slice(row * looked.cols, (row + 1) * looked.cols)); + const codeRows = sourceRows.filter((row) => row.map((cell) => cell.text).join("").match(/^\s*\d+\s/)); + const codeColors = new Set(codeRows.flatMap((row) => row.slice(5).map((cell) => `${cell.fg}:${cell.attrs & 1}`))); + assert.ok(codeColors.size >= 3, "Tree-sitter did not apply distinct syntax styles to build.zig"); + let sawWatch = false; + for (let index = 0; index < 32; index += 1) { + const kind = wasm.pardes_effect_next(); + if (!kind) break; + if (kind === 9) sawWatch = true; + } + assert.ok(sawWatch, "JS effect drain did not receive the opened file watch request"); + + wasm.pardes_resize(41, 13); + const resized = renderer.render(); + assert.equal(resized.cols, 41); + assert.equal(resized.rows, 13); + assert.equal(root.children.length, 13); + assert.equal(root.children[0].children.length, 41); + + wasm.pardes_deinit(); + console.error("web harness ok: freestanding wasm + Tree-sitter + packed DOM renderer"); +} + +class FakeDocument { + constructor() { + this.body = new FakeElement("body", this); + } + + createElement(tagName) { + return new FakeElement(tagName, this); + } +} + +class FakeElement { + constructor(tagName, ownerDocument) { + this.tagName = tagName.toUpperCase(); + this.ownerDocument = ownerDocument; + this.children = []; + this.attributes = new Map(); + this.dataset = {}; + this.style = new FakeStyle(); + this.classList = new FakeClassList(this); + this.className = ""; + this.textContent = ""; + } + + setAttribute(name, value) { + this.attributes.set(name, String(value)); + } + + replaceChildren(...children) { + this.children = children; + } + + *walk() { + yield this; + for (const child of this.children) yield* child.walk(); + } +} + +class FakeStyle { + setProperty(name, value) { + this[name] = value; + } +} + +class FakeClassList { + constructor(element) { + this.element = element; + this.names = new Set(); + } + + toggle(name, force) { + if (force) this.names.add(name); + else this.names.delete(name); + this.element.className = [...this.names].join(" "); + } +} + +await main(); diff --git a/test/web_snapshot.mjs b/test/web_snapshot.mjs index 7c0c7462..4c50956e 100644 --- a/test/web_snapshot.mjs +++ b/test/web_snapshot.mjs @@ -1,5 +1,7 @@ #!/usr/bin/env node -// Drive the Emscripten build in a real headless Chromium using .snap scripts. +// Drive the freestanding WASM + DOM build in a real headless Chromium using +// .snap scripts. This is an end-to-end browser test: Chrome creates genuine +// touch events and the JS shell translates them into core events. // Shared commands retain test/snapshot.zig's spelling and output format: // wait | stable // snap