a73x

a1215774

test: verify harness catches up with the wall header; runtime reads hashed

a73x   2026-08-18 19:14

Commit message
test: verify harness catches up with the wall header; runtime reads hashed

The wasm-ABI check's FakeElement predates the dynamic wall: no
dataset, one hardcoded button, a header-order pin from the old markup
— Tile could not even construct. It stayed green because verify.js
reads web/mux.js and web/index.html at runtime and neither was a
declared build input, so the mux.js changes replayed a stale cached
pass; both are now addFileInput'd, the same lesson this step already
paid for once with verify.js itself. Buttons are parsed out of the
markup now rather than hardcoded, so the next header change lands in
assertions, not in a constructor throw.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

build.zig
Old New
@@ -671,6 +671,12 @@ pub fn build(b: *std.Build) void {
671 // doctoring one, exactly as the fix round asked.) 671 // doctoring one, exactly as the fix round asked.)
672 verify.addFileArg(b.path("web/verify.js")); 672 verify.addFileArg(b.path("web/verify.js"));
673 verify.addFileArg(wasm_exe.getEmittedBin()); 673 verify.addFileArg(wasm_exe.getEmittedBin());
674 // Hashed as INPUTS though they are not argv: verify.js reads both at
675 // runtime (the shell's call list and the page), so without these a
676 // mux.js change replays a stale cached pass — which is exactly how
677 // the dataset.tileId harness gap shipped green.
678 verify.addFileInput(b.path("web/mux.js"));
679 verify.addFileInput(b.path("web/index.html"));
674 verify.setName("verify wasm ABI (web/verify.js)"); 680 verify.setName("verify wasm ABI (web/verify.js)");
675 // stdio is the assertion: a failing check exits non-zero. 681 // stdio is the assertion: a failing check exits non-zero.
676 verify.expectExitCode(0); 682 verify.expectExitCode(0);
web/verify.js
Old New
@@ -338,6 +338,9 @@ function browserShell(source) {
338 this.type = ''; 338 this.type = '';
339 this.rect = { left: 0, top: 0, width: 640, height: 480 }; 339 this.rect = { left: 0, top: 0, width: 640, height: 480 };
340 this.capturedPointers = new Set(); 340 this.capturedPointers = new Set();
341 // Real DOM nodes carry one; Tile stamps its id here so reorder can
342 // read the wall's order back out of the document.
343 this.dataset = {};
341 } 344 }
342 set innerHTML(value) { 345 set innerHTML(value) {
343 this._innerHTML = value; 346 this._innerHTML = value;
@@ -346,15 +349,20 @@ function browserShell(source) {
346 const header = new FakeElement('header'); 349 const header = new FakeElement('header');
347 const label = new FakeElement('span'); 350 const label = new FakeElement('span');
348 label.className = 'label'; 351 label.className = 'label';
349 const button = new FakeElement('button');
350 button.className = 'copy-request';
351 button.type = /<button[^>]*\btype="([^"]+)"/.exec(value)?.[1] ?? '';
352 button.textContent = /<button[^>]*>([^<]*)<\/button>/.exec(value)?.[1] ?? '';
353 const badge = new FakeElement('span'); 352 const badge = new FakeElement('span');
354 badge.className = 'badge connecting'; 353 badge.className = 'badge connecting';
355 badge.textContent = 'connecting'; 354 badge.textContent = 'connecting';
356 header.appendChild(label); 355 header.appendChild(label);
357 if (value.includes('<button')) header.appendChild(button); 356 // Every button in the markup, classes read from the markup: a
357 // hardcoded single .copy-request is how the .close/.spawn buttons
358 // arrived unconstructable and Tile broke only under this harness.
359 for (const m of value.matchAll(/<button([^>]*)>([^<]*)<\/button>/g)) {
360 const button = new FakeElement('button');
361 button.className = /\bclass="([^"]+)"/.exec(m[1])?.[1] ?? '';
362 button.type = /\btype="([^"]+)"/.exec(m[1])?.[1] ?? '';
363 button.textContent = m[2];
364 header.appendChild(button);
365 }
358 header.appendChild(badge); 366 header.appendChild(badge);
359 this.appendChild(header); 367 this.appendChild(header);
360 } 368 }
@@ -737,7 +745,7 @@ async function verifyClipboardShell(shell, html) {
737 check('hidden fallback preserves terminal Tab encoding', terminalKeys.join('|'), '2,0,0'); 745 check('hidden fallback preserves terminal Tab encoding', terminalKeys.join('|'), '2,0,0');
738 746
739 const header = click.tile.el.querySelector('header'); 747 const header = click.tile.el.querySelector('header');
740 check('tile header places copy button between label and badge', header.children.map((el) => el.className).join('|'), 'label|copy-request on|badge connecting'); 748 check('tile header places copy button between label and badge', header.children.map((el) => el.className).join('|'), 'label|copy-request on|spawn|close|badge connecting');
741 check('tile copy control is a real button', click.tile.copyButton.tagName, 'BUTTON'); 749 check('tile copy control is a real button', click.tile.copyButton.tagName, 'BUTTON');
742 check('tile copy control has button type', click.tile.copyButton.type, 'button'); 750 check('tile copy control has button type', click.tile.copyButton.type, 'button');
743 check('tile header does not interpolate label into innerHTML', click.tile.el.innerHTML.includes('<unsafe-label>'), false); 751 check('tile header does not interpolate label into innerHTML', click.tile.el.innerHTML.includes('<unsafe-label>'), false);