summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/web-snapshots/README.md7
-rw-r--r--test/web-snapshots/source-list.golden306
-rw-r--r--test/web_harness.mjs136
-rw-r--r--test/web_snapshot.mjs41
4 files changed, 315 insertions, 175 deletions
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 <zig-out/web>");
+ 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 <ms> <text> | stable <quiet-ms> <timeout-ms>
// snap <label> | snapstyle <label>
@@ -183,10 +185,7 @@ async function runScript(source) {
}
async function dispatchTouch(phase, id, col, row, active) {
- const point = await evaluate(`({
- x: Module._pardes_web_test_cell_x(${JSON.stringify(col)}),
- y: Module._pardes_web_test_cell_y(${JSON.stringify(row)})
- })`);
+ const point = await evaluate(`pardes.test.cellPoint(${JSON.stringify(col)}, ${JSON.stringify(row)})`);
const touch = { x: point.x, y: point.y, id, radiusX: 1, radiusY: 1, force: 1 };
if (phase === "down") {
if (active) throw new Error("only one finger is supported by this web test");
@@ -207,29 +206,24 @@ async function dispatchTouch(phase, id, col, row, active) {
async function grid() {
return evaluate(`(() => {
- const cols = Module._pardes_web_test_cols();
- const rows = Module._pardes_web_test_rows();
+ const frame = pardes.test.grid();
+ const { cols, rows, cursorX, cursorY } = frame;
const lines = [];
const styleRows = [];
for (let y = 0; y < rows; y++) {
let line = "";
const styles = [];
for (let x = 0; x < cols; x++) {
- const cp = Module._pardes_web_test_cell(x, y) || 32;
- line += String.fromCodePoint(cp);
- styles.push([
- Module._pardes_web_test_cell_fg(x, y),
- Module._pardes_web_test_cell_bg(x, y),
- Module._pardes_web_test_cell_attrs(x, y),
- ]);
+ const cell = frame.cells[y * cols + x];
+ line += cell.text || " ";
+ styles.push([cell.fg, cell.bg, cell.attrs]);
}
lines.push(line.replace(/ +$/, ""));
styleRows.push(styles);
}
return {
cols, rows, lines, styleRows,
- cursorX: Module._pardes_web_test_cursor_x(),
- cursorY: Module._pardes_web_test_cursor_y(),
+ cursorX, cursorY,
text: lines.join("\\n"),
};
})()`);
@@ -301,7 +295,14 @@ async function waitStable(quiet, timeout) {
async function waitForRuntime(timeout) {
await waitUntil(async () => {
try {
- return await evaluate(`typeof Module?._pardes_web_test_cols === "function" && Module._pardes_web_test_cols() > 0`);
+ return await evaluate(`(() => {
+ const app = globalThis.pardes;
+ if (!app?.test?.grid()?.cols || document.querySelector("canvas")) return false;
+ const want = app.runtime.gridSize();
+ const frame = app.test.grid();
+ return frame.cols === want.cols && frame.rows === want.rows &&
+ document.querySelectorAll(".pardes-row").length === want.rows;
+ })()`);
} catch {
return false;
}
@@ -388,8 +389,10 @@ async function serveDirectory(root) {
}
const contents = await fsp.readFile(filename);
const contentType = filename.endsWith(".wasm") ? "application/wasm"
- : filename.endsWith(".js") ? "text/javascript; charset=utf-8"
- : "text/html; charset=utf-8";
+ : filename.endsWith(".js") || filename.endsWith(".mjs") ? "text/javascript; charset=utf-8"
+ : filename.endsWith(".css") ? "text/css; charset=utf-8"
+ : filename.endsWith(".ttf") ? "font/ttf"
+ : "text/html; charset=utf-8";
response.writeHead(200, { "content-type": contentType, "cache-control": "no-store" });
response.end(contents);
} catch {