From 6d060fdfb23bdf3a7674d264f82362de725e76a9 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 14:25:20 +0700
Subject: [PATCH 01/33] widgets: catalogue all 82 widget folders and the
factory law -- 28 tools carry 22833 hand lines, 2775 of them pure logic
(Closes #10938)
specs/widgets/catalog.t27 pins the census measured at trinity origin/main.
specs/t27f/widget_factory.t27 states six stages, the t27c-seal build key and the port gate.
specs/t27f/frontend.t27 registers SF_WIDGET_FACTORY. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/t27f/frontend.t27 | 25 +++-
specs/t27f/widget_factory.t27 | 223 +++++++++++++++++++++++++++++++++
specs/widgets/catalog.t27 | 227 ++++++++++++++++++++++++++++++++++
3 files changed, 470 insertions(+), 5 deletions(-)
create mode 100644 specs/t27f/widget_factory.t27
create mode 100644 specs/widgets/catalog.t27
diff --git a/specs/t27f/frontend.t27 b/specs/t27f/frontend.t27
index 40fb2aa79c..d744a1ab65 100644
--- a/specs/t27f/frontend.t27
+++ b/specs/t27f/frontend.t27
@@ -55,10 +55,11 @@ pub const SF_PARTICLE_LIVE : u32 = 10; // T27F Live, particle_field.t27 + partic
pub const SF_UI_PLAN : u32 = 11; // trusted-widget UI plan, ui_plan.t27
pub const SF_DASHBOARD : u32 = 12; // corpus and session health, t27mcp/dashboard.t27, tool t27_dashboard
pub const SF_PETAL_MENU : u32 = 13; // T27 OS home: the 27-petal triangle menu, petal_menu.t27
-pub const SF_COUNT : u32 = 14;
+pub const SF_WIDGET_FACTORY : u32 = 14; // widgets as specs: logic, plan, page, data, card -- widget_factory.t27, catalogue widgets/catalog.t27
+pub const SF_COUNT : u32 = 15;
pub const SF_NONE : u32 = 255;
-pub const SURFACE_NAMES : str = "graph,outline,map_still,graph_page,hot,check,preview,particle_reel,still,map_video,particle_live,ui_plan,dashboard,petal_menu";
+pub const SURFACE_NAMES : str = "graph,outline,map_still,graph_page,hot,check,preview,particle_reel,still,map_video,particle_live,ui_plan,dashboard,petal_menu,widget_factory";
/// What draws a surface today.
pub const ST_LIVE : u8 = 0;
@@ -67,7 +68,7 @@ pub const ST_PARTIAL : u8 = 2;
pub const ST_BLOCKED : u8 = 3;
pub const ST_SPEC : u8 = 4;
-pub const SURFACE_STATUS : [14]u8 = [0, 0, 0, 0, 2, 3, 4, 1, 1, 4, 4, 4, 0, 4];
+pub const SURFACE_STATUS : [15]u8 = [0, 0, 0, 0, 2, 3, 4, 1, 1, 4, 4, 4, 0, 4, 4];
/// Where a surface is made: an MCP tool, a t27.ai page, the Railway lab, the TRI CLAW App, nowhere.
pub const MK_MCP : u8 = 0;
@@ -76,7 +77,7 @@ pub const MK_LAB : u8 = 2;
pub const MK_APP : u8 = 3;
pub const MK_NONE : u8 = 4;
-pub const SURFACE_MAKER : [14]u8 = [0, 0, 0, 1, 0, 3, 3, 2, 2, 4, 4, 4, 0, 4];
+pub const SURFACE_MAKER : [15]u8 = [0, 0, 0, 1, 0, 3, 3, 2, 2, 4, 4, 4, 0, 4, 4];
/// What a caller wants shown.
pub const I_SPEC_SHAPE : u32 = 0; // the structure of one spec
@@ -90,7 +91,8 @@ pub const I_HEALTH : u32 = 7; // the state of the corpus and the session
pub const I_LIVE_AGENT : u32 = 8; // an agent's work as a live particle field
pub const I_APP : u32 = 9; // an application screen built from trusted widgets
pub const I_OS_HOME : u32 = 10; // the T27 OS home screen: 27 petals, a petal opens its menu
-pub const I_COUNT : u32 = 11;
+pub const I_WIDGET : u32 = 11; // a shareable widget made from a spec, and the census of the ones that exist
+pub const I_COUNT : u32 = 12;
/// The drawing tools TRI CLAW's chat agent carries. service.t27 gives its coder persona CODER_TOOLS
/// for writing a spec; these are for showing one.
@@ -179,6 +181,9 @@ pub fn route(intent: u32, host: u8) -> u32 {
if (intent == I_OS_HOME) {
return SF_PETAL_MENU;
}
+ if (intent == I_WIDGET) {
+ return SF_WIDGET_FACTORY;
+ }
return SF_NONE;
}
@@ -322,3 +327,13 @@ test tri_claw_carries_every_drawing_tool {
assert claw_visual_tool(CLAW_VISUAL_COUNT) == TOOL_COUNT;
assert TOOLS[claw_visual_tool(0)].name == "t27f_graph";
}
+
+
+test the_widget_factory_is_a_spec_and_nothing_makes_it_yet {
+ assert route(I_WIDGET, HOST_CLAUDE_CODE_DESKTOP) == SF_WIDGET_FACTORY;
+ assert route(I_WIDGET, HOST_SITE) == SF_WIDGET_FACTORY;
+ assert SURFACE_STATUS[SF_WIDGET_FACTORY] == ST_SPEC;
+ assert pick(I_WIDGET, HOST_CLAUDE_CODE_DESKTOP) == SF_NONE;
+ assert claim_ok(SF_WIDGET_FACTORY, true) == false;
+ assert surface_tool(SF_WIDGET_FACTORY) == TOOL_COUNT;
+}
diff --git a/specs/t27f/widget_factory.t27 b/specs/t27f/widget_factory.t27
new file mode 100644
index 0000000000..f757c21c6f
--- /dev/null
+++ b/specs/t27f/widget_factory.t27
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/t27f/widget_factory.t27 -- the widget factory: how a widget goes from a spec to a page, its
+; numbers and its card with no hand-written line in between, and the gate that lets a hand-written
+; widget be retired. Owner, 2026-10-19: catalogue every widget, rewrite the hand-written ones in
+; .t27, make working with them easy, put the experience into an epic for tri CLI and TRI CLAW, and
+; build the factory natively in t27f, t27b and t27c seal. The census is specs/widgets/catalog.t27:
+; 28 tools, 22833 hand-written lines, of which 2775 are pure logic that ports as it stands.
+; --
+; The factory in six stages. Each is a function of the stage before and nothing else:
+; spec the widget's .t27 (words, rules, tests, data sources). The hand column of the catalog is
+; everything that is NOT this stage.
+; logic the pure functions of the tool (parse, grade, layout), written in .t27 and generated to
+; JavaScript by gen-ts. The spec's own test blocks are the fixed answers; a data builder no
+; longer re-imports a hand file "so the page cannot drift from the spec".
+; plan what is drawn, as a ui_plan.t27 plan: widget ids from an allowlist, no markup, no script.
+; The primitives below extend ui_plan's catalog; they are not a second catalog.
+; page one trusted renderer draws a plan. It replaces 28 tool.js files with one program.
+; data the tool's data builder, a spec compiled by t27b to a native binary (t27b is the
+; native path; it is the reason a 696-line test-touch.mjs does not need Node).
+; card the share picture, drawn by t27f as a still (SF_STILL) from the same spec, not by a
+; Python script beside it.
+; --
+; THE SEAL IS THE ACCELERATOR. t27c seal gives every spec a hash of its parse. A built artefact
+; records the seal of the spec it was built from. needs_build() is false when the seals match, so a
+; one-spec edit rebuilds one widget of 82 and skips the other 81. The renderer's own version is part
+; of the key: a renderer change rebuilds every page, never every card.
+; --
+; THE PORT GATE. A widget moves up one stage at a time, and only when (a) the new stage's output is
+; byte-identical to the hand-written output it replaces, (b) the spec's tests pass, and (c) the
+; hand-written lines do not grow. (c) is the same rule as t27b's "net <= 0" and FROZEN_HASH ports.
+; --
+; Not claimed: no stage past `spec` exists as a program today. gen-ts has not been run on a tool's
+; logic; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
+; PROPOSED from reading the 28 tools by name, not measured; wave 1 (catalog.t27, 11 tools of at
+; most 700 hand lines) measures them and may rename or merge them.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module t27f_widget_factory;
+
+use widgets::catalog::wave_of;
+use widgets::catalog::PORT_HAND;
+use widgets::catalog::PORT_WORDS;
+use widgets::catalog::PORT_LOGIC;
+use widgets::catalog::PORT_DRAW;
+use widgets::catalog::PORT_SEALED;
+use widgets::catalog::PORT_STAGES;
+use t27f::ui_plan::widget_id;
+use t27f::ui_plan::WIDGET_UNKNOWN;
+use t27mcp::service::home_of;
+use t27mcp::service::has_prefix;
+use t27mcp::service::H_T27;
+
+/// The stages, in order. A stage reads only the one before it.
+pub const ST_SPEC : u8 = 0;
+pub const ST_LOGIC : u8 = 1;
+pub const ST_PLAN : u8 = 2;
+pub const ST_PAGE : u8 = 3;
+pub const ST_DATA : u8 = 4;
+pub const ST_CARD : u8 = 5;
+pub const ST_COUNT : u8 = 6;
+pub const STAGE_NAMES : [6]str = ["spec", "logic", "plan", "page", "data", "card"];
+
+/// The tool of the toolchain that makes each stage. t27c is the compiler and the seal; gen-ts
+/// writes the logic; t27b compiles the data builder native; t27f draws plan, page and card.
+pub const MAKER_T27C : u8 = 0;
+pub const MAKER_GEN_TS : u8 = 1;
+pub const MAKER_T27F : u8 = 2;
+pub const MAKER_T27B : u8 = 3;
+pub const STAGE_MAKER : [6]u8 = [0, 1, 2, 2, 3, 2];
+
+/// Draw primitives the factory adds to ui_plan's catalog (which already has metrics_table and
+/// text_block). PROPOSED, see the header. None of them may be a forbidden name.
+pub const PRIMITIVE_COUNT : u32 = 8;
+pub const PRIMITIVES : [8]str = ["bars", "treemap", "timeline", "grid_map", "graph", "waves", "calc", "tabs"];
+pub const FORBIDDEN : [6]str = ["script", "eval", "html", "iframe", "raw_js", "function"];
+
+/// The verbs tri CLI and the tools TRI CLAW carries. `tri widget` exists today (trinity, the
+/// widget-forge skill); these are what it gains. Every tool is a t27f_ name so that service.t27
+/// mounts it into TRI CLAW's /mcp with the other t27 tools.
+pub const TRI_VERB_COUNT : u32 = 5;
+pub const TRI_VERBS : [5]str = ["widget list", "widget port", "widget make", "widget seal", "widget gate"];
+pub const CLAW_TOOL_COUNT : u32 = 3;
+pub const CLAW_TOOLS : [3]str = ["t27f_widgets", "t27f_widget_make", "t27f_widget_port_plan"];
+
+/// A stage is the next of a port stage only by one step.
+pub fn next_port(stage: u8) -> u8 {
+ if (stage + 1 >= PORT_STAGES) {
+ return PORT_STAGES;
+ }
+ return stage + 1;
+}
+
+/// The port gate. before and after are the widget's hand-written lines at the two ends of the move.
+pub fn port_gate(from: u8, to: u8, byte_identical: bool, tests_pass: bool, hand_before: u32, hand_after: u32) -> bool {
+ if (to >= PORT_STAGES) {
+ return false;
+ }
+ if (to != next_port(from)) {
+ return false;
+ }
+ if (byte_identical == false) {
+ return false;
+ }
+ if (tests_pass == false) {
+ return false;
+ }
+ return hand_after <= hand_before;
+}
+
+/// The key a built artefact is filed under: the spec's seal and the renderer version it was drawn by.
+pub fn build_key(spec_seal: u32, renderer: u32, uses_renderer: bool) -> u32 {
+ if (uses_renderer) {
+ return spec_seal * 31 + renderer;
+ }
+ return spec_seal * 31;
+}
+
+/// Build only when the key of what exists is not the key of what is wanted.
+pub fn needs_build(want: u32, have: u32) -> bool {
+ return want != have;
+}
+
+/// Of n widgets, how many are skipped when `changed` of them have a new key.
+pub fn skipped(n: u32, changed: u32) -> u32 {
+ if (changed >= n) {
+ return 0;
+ }
+ return n - changed;
+}
+
+/// A primitive name is allowed when it is not a forbidden word.
+pub fn primitive_ok(name: str) -> bool {
+ var f : u32 = 0;
+ while (f < 6) {
+ if (FORBIDDEN[f as usize] == name) { return false; }
+ f = f + 1;
+ }
+ return true;
+}
+
+/// How many hand-written lines a wave-1 tool of `hand` lines may keep after its logic is ported:
+/// none of the logic file, since gen-ts writes it. A pure bookkeeping rule the epic's issues quote.
+pub fn after_logic(hand: u32, logic: u32) -> u32 {
+ if (logic >= hand) {
+ return 0;
+ }
+ return hand - logic;
+}
+
+test the_stages_are_six_in_order_with_a_maker_each {
+ assert ST_COUNT == 6;
+ assert STAGE_NAMES[ST_SPEC] == "spec";
+ assert STAGE_NAMES[ST_CARD] == "card";
+ assert STAGE_MAKER[ST_LOGIC] == MAKER_GEN_TS;
+ assert STAGE_MAKER[ST_DATA] == MAKER_T27B;
+ assert STAGE_MAKER[ST_SPEC] == MAKER_T27C;
+ assert STAGE_MAKER[ST_PAGE] == MAKER_T27F;
+ assert STAGE_MAKER[ST_CARD] == MAKER_T27F;
+}
+
+test a_port_moves_one_stage_at_a_time {
+ assert next_port(PORT_WORDS) == PORT_LOGIC;
+ assert next_port(PORT_LOGIC) == PORT_DRAW;
+ assert next_port(PORT_SEALED) == PORT_STAGES;
+ assert port_gate(PORT_WORDS, PORT_LOGIC, true, true, 1000, 800);
+ assert port_gate(PORT_WORDS, PORT_DRAW, true, true, 1000, 800) == false;
+ assert port_gate(PORT_DRAW, PORT_LOGIC, true, true, 1000, 800) == false;
+ assert port_gate(PORT_SEALED, PORT_STAGES, true, true, 1000, 0) == false;
+}
+
+test a_port_needs_identical_output_green_tests_and_no_growth {
+ assert port_gate(PORT_HAND, PORT_WORDS, true, true, 500, 500);
+ assert port_gate(PORT_HAND, PORT_WORDS, false, true, 500, 400) == false;
+ assert port_gate(PORT_HAND, PORT_WORDS, true, false, 500, 400) == false;
+ assert port_gate(PORT_HAND, PORT_WORDS, true, true, 500, 501) == false;
+ assert port_gate(PORT_LOGIC, PORT_DRAW, true, true, 800, 0);
+}
+
+test the_seal_skips_what_did_not_change {
+ const k1 : u32 = build_key(1000, 7, true);
+ assert needs_build(k1, k1) == false;
+ assert needs_build(build_key(1001, 7, true), k1);
+ assert needs_build(build_key(1000, 8, true), k1);
+ assert needs_build(build_key(1000, 8, false), build_key(1000, 7, false)) == false;
+ assert skipped(82, 1) == 81;
+ assert skipped(82, 0) == 82;
+ assert skipped(82, 82) == 0;
+ assert skipped(5, 9) == 0;
+}
+
+test the_primitives_extend_ui_plan_and_hold_no_code {
+ var i : u32 = 0;
+ while (i < PRIMITIVE_COUNT) {
+ assert primitive_ok(PRIMITIVES[i as usize]);
+ assert widget_id(PRIMITIVES[i as usize]) == WIDGET_UNKNOWN;
+ i = i + 1;
+ }
+ assert widget_id("metrics_table") != WIDGET_UNKNOWN;
+ assert widget_id("text_block") != WIDGET_UNKNOWN;
+ assert widget_id("script") == WIDGET_UNKNOWN;
+ assert primitive_ok("script") == false;
+ assert primitive_ok("bars");
+}
+
+test every_claw_tool_is_a_t27_name_the_service_mounts {
+ var i : u32 = 0;
+ while (i < CLAW_TOOL_COUNT) {
+ assert has_prefix(CLAW_TOOLS[i as usize], "t27f_");
+ assert home_of(CLAW_TOOLS[i as usize]) == H_T27;
+ i = i + 1;
+ }
+ assert TRI_VERB_COUNT == 5;
+ assert TRI_VERBS[0] == "widget list";
+}
+
+test the_logic_stage_removes_exactly_the_logic_file {
+ assert after_logic(1000, 51) == 949;
+ assert after_logic(686, 0) == 686;
+ assert after_logic(10, 20) == 0;
+ assert wave_of(after_logic(1432, 370)) == 3;
+ assert wave_of(after_logic(1000, 51)) == 2;
+ assert port_gate(PORT_WORDS, PORT_LOGIC, true, true, 1432, after_logic(1432, 370));
+}
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
new file mode 100644
index 0000000000..35f526116f
--- /dev/null
+++ b/specs/widgets/catalog.t27
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/catalog.t27 -- the census of every widget in every t27 project, and how much of each
+; is still written by hand. Owner, 2026-10-19: find all widgets in all projects, catalogue them,
+; rewrite the hand-written ones in .t27, make them easy to work with, move the lesson into an epic
+; for tri CLI and TRI CLAW, and build a widget factory natively in t27f, t27b and t27c seal.
+; This file is the catalogue. The factory that retires the hand-written column is
+; specs/t27f/widget_factory.t27; the shipped gallery (what a reader may share) is
+; specs/widgets/gallery.t27 in gHashTag/trinity and is NOT copied here -- this spec counts, it does
+; not list titles.
+; --
+; MEASURED 2026-10-19 at gHashTag/trinity origin/main, wc -l of every file, nothing estimated:
+; apps/website/public/widgets// 82 widget folders, app.t27.ai/game/widgets previews them
+; apps/website/specs/widgets/.t27 the words and numbers of each (SAY_ and K_ constants)
+; apps/website/scripts/widget-data/ the data builders (.mjs) and the card renderers (.py)
+; apps/queen/QueenUI/Widgets/ 105 SwiftUI views, 42052 lines
+; apps/website/src/components/ 11 React *Widget*.tsx, 4902 lines
+; Of the 82 folders, 54 are LESSONS (27 basics-*, 27 golden-*): a 3-line tool.js and a 47-48 line
+; page, both generated from the spec. They are already native. The other 28 are TOOLS and carry
+; hand-written JavaScript, CSS, a Node data builder and a Python card renderer.
+; --
+; What the census found, which no one had written down:
+; 1. A tool is TWO layers. 12 of the 28 have a pure logic file beside tool.js (rule.js, bitparse.js,
+; fsmparse.js, lut.js, treemap.js, xdc.js, statparse.js, ...): 2775 lines of functions of
+; numbers and text, no DOM. They port to .t27 and gen-ts as they stand, and the spec's test
+; blocks replace the fixed answers the data builder re-checks today (gatle.mjs imports rule.js
+; "so the page cannot drift from the spec" -- one source would make that true by construction).
+; 2. The other 8920 lines are DOM drawing. Every tool.js opens with the same helpers (fill, el,
+; link, a stylesheet link) and then builds tables, bars, maps and timelines. That is a renderer,
+; not 28 programs: ui_plan.t27 already decides that a plan is data and a trusted renderer draws
+; it. The factory extends that catalog instead of adding a second one.
+; 3. 23 tools have a Node data builder (5572 lines) and 24 a Python card renderer (3415 lines).
+; The card is a picture of the page; t27f draws pictures from specs already (SF_STILL).
+; --
+; Not claimed: that any hand column has been rewritten. Every tool is at PORT_WORDS today: its
+; words live in its spec, its logic and drawing still live in JavaScript. A tool moves up only when
+; its output is byte-identical to the hand version, and the factory spec states that gate.
+; The Swift and React widgets are counted, not classified: they are another renderer's targets.
+; ASCII only (L3), English only (LANG-EN).
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widgets_catalog;
+
+pub const KIND : str = "widgets-catalog";
+pub const ID : str = "widgets/catalog";
+pub const MEASURED : str = "2026-10-19";
+pub const MEASURED_AT : str = "gHashTag/trinity origin/main";
+
+; --- What the gallery serves, by kind (gallery.t27: WIDGET_COUNT) -------------------------------
+pub const GALLERY_CASTS : u32 = 104;
+pub const GALLERY_PLAYERS : u32 = 8;
+pub const GALLERY_TABS : u32 = 1;
+pub const GALLERY_TOOLS : u32 = 82;
+pub const GALLERY_COUNT : u32 = 195;
+
+; --- The 82 tool folders ----------------------------------------------------------------------
+pub const LESSON_BASICS : u32 = 27;
+pub const LESSON_GOLDEN : u32 = 27;
+pub const TOOL_COUNT : u32 = 28;
+
+; --- Other renderers' widgets, counted not classified ------------------------------------------
+pub const SWIFT_FILES : u32 = 105;
+pub const SWIFT_LINES : u32 = 42052;
+pub const REACT_FILES : u32 = 11;
+pub const REACT_LINES : u32 = 4902;
+
+; --- The 28 tools, in alphabetical order; every array below is TOOL_COUNT long -----------------
+pub const TOOL_IDS : [28]str = ["bit-autopsy", "bit-diff", "build-receipt", "cost-curves", "fasm-skyline", "fits-on", "fsm-sketch", "gatle", "gf16-calc", "idcode", "jtag-verdict", "lut-depth", "lut-explain", "lut-treemap", "pin-map", "placement-map", "race-the-bee", "resource-bars", "slack-waterfall", "stat-card", "synth-smells", "t27-cell", "t27-vs-nvfp4", "t27c-stages", "test-touch", "test-waves", "uart-bucket", "vcd-wrapped"];
+
+; Hand-written lines per tool. LOGIC is the pure parser/rule file beside tool.js (0 when the tool
+; has none), DRAW is tool.js. CSS is tool.css. DATA is scripts/widget-data/.mjs, CARD is
+; scripts/widget-data/-card.py (0 when the tool has neither). SPEC is specs/widgets/.t27.
+pub const LOGIC_LINES : [28]u32 = [289, 371, 0, 0, 122, 0, 379, 51, 0, 0, 0, 97, 370, 289, 207, 0, 0, 0, 0, 127, 192, 0, 0, 0, 0, 0, 0, 281];
+pub const DRAW_LINES : [28]u32 = [376, 274, 221, 273, 533, 232, 361, 309, 511, 316, 171, 241, 588, 322, 340, 379, 105, 172, 369, 318, 304, 187, 192, 364, 146, 726, 228, 362];
+pub const CSS_LINES : [28]u32 = [57, 50, 127, 65, 43, 51, 56, 98, 114, 96, 61, 89, 128, 85, 151, 32, 51, 33, 42, 48, 76, 78, 78, 169, 89, 34, 72, 78];
+pub const DATA_LINES : [28]u32 = [184, 124, 329, 395, 266, 209, 294, 411, 0, 153, 0, 187, 242, 252, 187, 0, 208, 0, 0, 163, 203, 144, 221, 109, 696, 144, 177, 274];
+pub const CARD_LINES : [28]u32 = [103, 72, 201, 131, 153, 126, 194, 131, 0, 121, 117, 150, 104, 252, 206, 0, 89, 0, 0, 147, 148, 85, 101, 163, 133, 189, 129, 170];
+pub const SPEC_LINES : [28]u32 = [183, 148, 139, 246, 196, 187, 197, 188, 193, 180, 159, 197, 304, 233, 189, 125, 110, 121, 135, 201, 201, 134, 168, 153, 183, 124, 129, 198];
+
+; Port stage of one tool. A tool moves up only through widget_factory.t27's port_gate.
+pub const PORT_HAND : u8 = 0; // words and rules in JavaScript
+pub const PORT_WORDS : u8 = 1; // words and numbers in the spec (every tool today)
+pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
+pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
+pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
+pub const PORT_STAGES : u8 = 5;
+pub const TOOL_PORT : [28]u8 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+
+; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
+; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
+pub const WAVE_1_MAX : u32 = 700;
+pub const WAVE_2_MAX : u32 = 1000;
+
+pub fn gallery_sum() -> u32 {
+ return GALLERY_CASTS + GALLERY_PLAYERS + GALLERY_TABS + GALLERY_TOOLS;
+}
+
+pub fn folder_sum() -> u32 {
+ return LESSON_BASICS + LESSON_GOLDEN + TOOL_COUNT;
+}
+
+pub fn hand_lines(i: u32) -> u32 {
+ return LOGIC_LINES[i] + DRAW_LINES[i] + CSS_LINES[i] + DATA_LINES[i] + CARD_LINES[i];
+}
+
+pub fn wave_of(lines: u32) -> u32 {
+ if (lines <= WAVE_1_MAX) {
+ return 1;
+ }
+ if (lines <= WAVE_2_MAX) {
+ return 2;
+ }
+ return 3;
+}
+
+pub fn has_logic_file(i: u32) -> bool {
+ return LOGIC_LINES[i] > 0;
+}
+
+pub fn sum_lines(a: [28]u32) -> u32 {
+ var s : u32 = 0;
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ s = s + a[i];
+ i = i + 1;
+ }
+ return s;
+}
+
+pub fn total_hand() -> u32 {
+ return sum_lines(LOGIC_LINES) + sum_lines(DRAW_LINES) + sum_lines(CSS_LINES) + sum_lines(DATA_LINES) + sum_lines(CARD_LINES);
+}
+
+pub fn count_with(a: [28]u32) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ if (a[i] > 0) {
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ return n;
+}
+
+pub fn count_wave(w: u32) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ if (wave_of(hand_lines(i)) == w) {
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ return n;
+}
+
+pub fn count_port(stage: u8) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ if (TOOL_PORT[i] == stage) {
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ return n;
+}
+
+test the_gallery_kinds_add_up_to_its_count {
+ assert gallery_sum() == GALLERY_COUNT;
+ assert GALLERY_COUNT == 195;
+}
+
+test the_tool_folders_are_lessons_plus_tools {
+ assert folder_sum() == GALLERY_TOOLS;
+}
+
+test every_tool_has_an_id_and_a_spec {
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ assert SPEC_LINES[i] > 100;
+ assert DRAW_LINES[i] > 0;
+ assert CSS_LINES[i] > 0;
+ i = i + 1;
+ }
+ assert TOOL_IDS[0] == "bit-autopsy";
+ assert TOOL_IDS[7] == "gatle";
+ assert TOOL_IDS[27] == "vcd-wrapped";
+}
+
+test the_hand_written_column_is_the_measured_total {
+ assert sum_lines(LOGIC_LINES) == 2775;
+ assert sum_lines(DRAW_LINES) == 8920;
+ assert sum_lines(CSS_LINES) == 2151;
+ assert sum_lines(DATA_LINES) == 5572;
+ assert sum_lines(CARD_LINES) == 3415;
+ assert sum_lines(SPEC_LINES) == 4921;
+ assert total_hand() == 22833;
+}
+
+test twelve_tools_split_logic_from_drawing {
+ assert count_with(LOGIC_LINES) == 12;
+ assert has_logic_file(7);
+ assert has_logic_file(2) == false;
+ assert LOGIC_LINES[7] == 51;
+ assert count_with(DATA_LINES) == 23;
+ assert count_with(CARD_LINES) == 24;
+}
+
+test the_waves_cover_every_tool_once {
+ assert count_wave(1) == 11;
+ assert count_wave(2) == 8;
+ assert count_wave(3) == 9;
+ assert count_wave(1) + count_wave(2) + count_wave(3) == TOOL_COUNT;
+ assert hand_lines(17) == 205;
+ assert hand_lines(7) == 1000;
+ assert wave_of(hand_lines(7)) == 2;
+ assert hand_lines(26) == 606;
+ assert hand_lines(12) == 1432;
+}
+
+test no_tool_has_left_the_words_stage_yet {
+ assert count_port(PORT_WORDS) == TOOL_COUNT;
+ assert count_port(PORT_LOGIC) == 0;
+ assert count_port(PORT_DRAW) == 0;
+ assert count_port(PORT_SEALED) == 0;
+}
From 9d714ffd965e31dfd526206d77f6d55cb1a35734 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 14:35:31 +0700
Subject: [PATCH 02/33] widgets: gatle rules ported to .t27 -- 3 tests, 19
asserts; gen-js lowers no fn bodies, so the logic stage waits (Closes #10938)
specs/widgets/gatle_rule.t27 replaces rule.js as the single source. widget_factory.t27
records that gen-js/gen-ts emit declarations only. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/t27f/widget_factory.t27 | 19 +++++-
specs/widgets/gatle_rule.t27 | 113 ++++++++++++++++++++++++++++++++++
2 files changed, 130 insertions(+), 2 deletions(-)
create mode 100644 specs/widgets/gatle_rule.t27
diff --git a/specs/t27f/widget_factory.t27 b/specs/t27f/widget_factory.t27
index f757c21c6f..934d3a159f 100644
--- a/specs/t27f/widget_factory.t27
+++ b/specs/t27f/widget_factory.t27
@@ -29,8 +29,8 @@
; byte-identical to the hand-written output it replaces, (b) the spec's tests pass, and (c) the
; hand-written lines do not grow. (c) is the same rule as t27b's "net <= 0" and FROZEN_HASH ports.
; --
-; Not claimed: no stage past `spec` exists as a program today. gen-ts has not been run on a tool's
-; logic; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
+; Not claimed: no stage past `spec` exists as a program today. gatle's rules are ported to
+; specs/widgets/gatle_rule.t27 and pass, but gen-js/gen-ts do not lower fn bodies, so no JS is generated; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
; PROPOSED from reading the 28 tools by name, not measured; wave 1 (catalog.t27, 11 tools of at
; most 700 hand lines) measures them and may rename or merge them.
; phi^2 + 1/phi^2 = 3 | TRINITY
@@ -82,6 +82,17 @@ pub const TRI_VERBS : [5]str = ["widget list", "widget port", "widget make", "wi
pub const CLAW_TOOL_COUNT : u32 = 3;
pub const CLAW_TOOLS : [3]str = ["t27f_widgets", "t27f_widget_make", "t27f_widget_port_plan"];
+/// MEASURED 2026-10-19 with t27c 0.5.2 on specs/widgets/gatle_rule.t27: gen-js and gen-ts emit
+/// declarations (consts, structs) and skip every fn body -- "this backend lowers declarations, not
+/// bodies". Until a body-lowering JS backend exists the logic stage writes a tested .t27 rule
+/// (the single source) but cannot generate the JavaScript the page runs. Flip this when it can.
+pub const GEN_JS_LOWERS_BODIES : bool = false;
+
+/// The logic stage can produce its artefact only when the backend lowers bodies.
+pub fn logic_stage_ready() -> bool {
+ return GEN_JS_LOWERS_BODIES;
+}
+
/// A stage is the next of a port stage only by one step.
pub fn next_port(stage: u8) -> u8 {
if (stage + 1 >= PORT_STAGES) {
@@ -213,6 +224,10 @@ test every_claw_tool_is_a_t27_name_the_service_mounts {
assert TRI_VERBS[0] == "widget list";
}
+test the_logic_stage_waits_for_a_body_lowering_backend {
+ assert logic_stage_ready() == false;
+}
+
test the_logic_stage_removes_exactly_the_logic_file {
assert after_logic(1000, 51) == 949;
assert after_logic(686, 0) == 686;
diff --git a/specs/widgets/gatle_rule.t27 b/specs/widgets/gatle_rule.t27
new file mode 100644
index 0000000000..c082dc8ccb
--- /dev/null
+++ b/specs/widgets/gatle_rule.t27
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/gatle_rule.t27 -- the rules of Gatle as pure functions: the first widget logic file
+; ported from JavaScript (trinity apps/website/public/widgets/gatle/rule.js, 51 lines) to .t27.
+; Epic gHashTag/t27#10937. Until now rule.js was hand-written and gatle.mjs re-imported it to check
+; it against the fixed answers in gatle.t27, "so the page cannot drift from the spec". Here the
+; rules and their fixed answers are one file, so there is nothing to drift.
+; Ported: dayNumber, dayIndex, puzzleNumber, grade (heat and dir), emoji_code. Not yet ported:
+; parseHash and formatHash (URL text handling) -- the next step, once gen-js of str ops is measured.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_gatle_rule;
+
+pub const DAY_MS : u64 = 86400000;
+
+pub const HEAT_CLOSE : u8 = 0;
+pub const HEAT_WARM : u8 = 1;
+pub const HEAT_COLD : u8 = 2;
+pub const DIR_HIT : u8 = 0;
+pub const DIR_UP : u8 = 1;
+pub const DIR_DOWN : u8 = 2;
+
+pub const EMOJI_HEAT : [3]u32 = [129001, 129000, 128997];
+pub const ARROWS : [2]u32 = [11014, 11015];
+
+/// Whole UTC days since 1970-01-01 for a time in milliseconds.
+pub fn day_number(ms: u64) -> u64 {
+ return ms / DAY_MS;
+}
+
+/// The puzzle a UTC day gets: days since the epoch, modulo the pool size (never negative).
+pub fn day_index(day: u64, epoch_day: u64, size: u64) -> u64 {
+ if (day >= epoch_day) {
+ return (day - epoch_day) % size;
+ }
+ return (size - ((epoch_day - day) % size)) % size;
+}
+
+/// The running number a day's puzzle is shared under: the epoch day is #1.
+pub fn puzzle_number(day: u64, epoch_day: u64) -> u64 {
+ return day - epoch_day + 1;
+}
+
+/// |guess - truth| * 100, integer arithmetic only.
+pub fn off100(guess: u64, truth: u64) -> u64 {
+ if (guess > truth) {
+ return (guess - truth) * 100;
+ }
+ return (truth - guess) * 100;
+}
+
+/// close within close_pct percent of the truth (boundary counts), warm within warm_pct, else cold.
+pub fn heat(guess: u64, truth: u64, close_pct: u64, warm_pct: u64) -> u8 {
+ const off : u64 = off100(guess, truth);
+ if (off <= truth * close_pct) {
+ return HEAT_CLOSE;
+ }
+ if (off <= truth * warm_pct) {
+ return HEAT_WARM;
+ }
+ return HEAT_COLD;
+}
+
+/// hit when close, else up (the truth is higher) or down.
+pub fn dir(guess: u64, truth: u64, close_pct: u64, warm_pct: u64) -> u8 {
+ if (heat(guess, truth, close_pct, warm_pct) == HEAT_CLOSE) {
+ return DIR_HIT;
+ }
+ if (truth > guess) {
+ return DIR_UP;
+ }
+ return DIR_DOWN;
+}
+
+/// The code point of the heat square of one graded guess.
+pub fn heat_code(h: u8) -> u32 {
+ return EMOJI_HEAT[h as usize];
+}
+
+/// The code point of the arrow of one graded guess; 0 for a hit (no arrow).
+pub fn arrow_code(d: u8) -> u32 {
+ if (d == DIR_HIT) {
+ return 0;
+ }
+ return ARROWS[(d - 1) as usize];
+}
+
+test day_index_on_two_fixed_dates {
+ assert day_number(1791072000000) == 20730;
+ assert day_index(20730, 20730, 37) == 0;
+ assert day_index(20819, 20730, 37) == 15;
+ assert puzzle_number(20730, 20730) == 1;
+ assert puzzle_number(20819, 20730) == 90;
+ assert day_index(20729, 20730, 37) == 36;
+}
+
+test close_is_within_ten_percent_boundary_included {
+ assert heat(180, 200, 10, 50) == HEAT_CLOSE;
+ assert heat(179, 200, 10, 50) == HEAT_WARM;
+ assert heat(220, 200, 10, 50) == HEAT_CLOSE;
+ assert heat(221, 200, 10, 50) == HEAT_WARM;
+ assert heat(1, 200, 10, 50) == HEAT_COLD;
+ assert dir(180, 200, 10, 50) == DIR_HIT;
+ assert dir(179, 200, 10, 50) == DIR_UP;
+ assert dir(221, 200, 10, 50) == DIR_DOWN;
+}
+
+test the_share_codes_follow_the_grade {
+ assert heat_code(HEAT_CLOSE) == 129001;
+ assert heat_code(HEAT_COLD) == 128997;
+ assert arrow_code(DIR_HIT) == 0;
+ assert arrow_code(DIR_UP) == 11014;
+ assert arrow_code(DIR_DOWN) == 11015;
+}
From 749e318e9404dac896e89c7f861fd59c0497308a Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 14:39:00 +0700
Subject: [PATCH 03/33] widgets: gallery rules as scalar .t27 -- 195 widgets,
refusal verdicts and embed sizes run native under t27b, 3 of 3 (Closes
#10938)
specs/widgets/gallery_rules.t27 holds the decisions of scripts/widgets-from-spec.mjs
(trinity); the gallery data stays in trinity gallery.t27. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/gallery_rules.t27 | 146 ++++++++++++++++++++++++++++++++
1 file changed, 146 insertions(+)
create mode 100644 specs/widgets/gallery_rules.t27
diff --git a/specs/widgets/gallery_rules.t27 b/specs/widgets/gallery_rules.t27
new file mode 100644
index 0000000000..1d31e5c1f6
--- /dev/null
+++ b/specs/widgets/gallery_rules.t27
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/gallery_rules.t27 -- the rules of the widget gallery (app.t27.ai/game/widgets) as
+; scalar functions, so t27b can run them native. Epic gHashTag/t27#10937, owner 2026-10-19: the
+; gallery is integrated natively into t27b and T27F.
+; The gallery is 195 widgets: 104 casts, 8 players, 1 tab, 82 tools; by category 95 fpga, 92
+; compiler, 8 game. Its data is gHashTag/trinity apps/website/specs/widgets/gallery.t27 and stays
+; there (this file does not copy 195 rows). Its generator, scripts/widgets-from-spec.mjs (269
+; lines), does two jobs: I/O (read each widget's page for og tags, read meta.json) and RULES (which
+; widget is refused, how tall its embed is, whether its preview carries ?embed=1). The rules are
+; here; the I/O stays a thin reader that hands this file booleans. verdict() is the generator's
+; problems list as one number, in the generator's order.
+; Not claimed: the generator still runs in node today; this file is what replaces its decisions.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widgets_gallery_rules;
+
+pub const K_CAST : u8 = 0;
+pub const K_PLAYER : u8 = 1;
+pub const K_TAB : u8 = 2;
+pub const K_TOOL : u8 = 3;
+pub const K_KINDS : u8 = 4;
+
+pub const GALLERY_COUNT : u32 = 195;
+pub const KIND_COUNTS : [4]u32 = [104, 8, 1, 82];
+pub const CAT_COUNTS : [3]u32 = [95, 92, 8];
+
+pub const EMBED_WIDTH : u32 = 640;
+pub const PLAYER_EMBED_HEIGHT : u32 = 360;
+pub const CAST_EMBED_HEIGHT : u32 = 460;
+
+pub const V_OK : u8 = 0;
+pub const V_TAB_VIEW_MISSING : u8 = 1;
+pub const V_PAGE_MISSING : u8 = 2;
+pub const V_META_MISSING : u8 = 3;
+pub const V_IMAGE_NOT_LOCAL : u8 = 4;
+pub const V_CAST_META_MISSING : u8 = 5;
+pub const V_CAST_BAD_EXIT : u8 = 6;
+pub const V_TOOL_HOOK_MISSING : u8 = 7;
+pub const V_PLAYER_EMBED_MISSING : u8 = 8;
+pub const V_UNKNOWN_KIND : u8 = 9;
+
+/// Embed query a kind carries: 0 none, 1 "?embed=1", 2 "&embed=1" (a tab already has a query).
+pub fn embed_query(kind: u8) -> u8 {
+ if (kind == K_TOOL) {
+ return 1;
+ }
+ if (kind == K_TAB) {
+ return 2;
+ }
+ return 0;
+}
+
+pub fn embed_height(kind: u8) -> u32 {
+ if (kind == K_PLAYER) {
+ return PLAYER_EMBED_HEIGHT;
+ }
+ return CAST_EMBED_HEIGHT;
+}
+
+/// A tab has no page of its own to preview; every other kind previews its framed page.
+pub fn has_preview(kind: u8) -> bool {
+ return kind != K_TAB;
+}
+
+/// Why the generator would refuse one widget, or V_OK. The booleans are what its reader found.
+pub fn verdict(kind: u8, view_ok: bool, page_ok: bool, meta_ok: bool, image_local: bool, cast_meta: bool, bad_exit: bool, hook_ok: bool, player_ok: bool) -> u8 {
+ if (kind >= K_KINDS) {
+ return V_UNKNOWN_KIND;
+ }
+ if (kind == K_TAB) {
+ if (view_ok) {
+ return V_OK;
+ }
+ return V_TAB_VIEW_MISSING;
+ }
+ if (page_ok == false) {
+ return V_PAGE_MISSING;
+ }
+ if (meta_ok == false) {
+ return V_META_MISSING;
+ }
+ if (image_local == false) {
+ return V_IMAGE_NOT_LOCAL;
+ }
+ if (kind == K_CAST) {
+ if (cast_meta == false) {
+ return V_CAST_META_MISSING;
+ }
+ if (bad_exit) {
+ return V_CAST_BAD_EXIT;
+ }
+ return V_OK;
+ }
+ if (kind == K_TOOL) {
+ if (hook_ok == false) {
+ return V_TOOL_HOOK_MISSING;
+ }
+ return V_OK;
+ }
+ if (player_ok == false) {
+ return V_PLAYER_EMBED_MISSING;
+ }
+ return V_OK;
+}
+
+pub fn kinds_sum() -> u32 {
+ return KIND_COUNTS[0] + KIND_COUNTS[1] + KIND_COUNTS[2] + KIND_COUNTS[3];
+}
+
+pub fn cats_sum() -> u32 {
+ return CAT_COUNTS[0] + CAT_COUNTS[1] + CAT_COUNTS[2];
+}
+
+test the_gallery_counts_agree_by_kind_and_by_category {
+ assert kinds_sum() == GALLERY_COUNT;
+ assert cats_sum() == GALLERY_COUNT;
+}
+
+test embed_size_and_query_follow_the_kind {
+ assert embed_height(K_PLAYER) == 360;
+ assert embed_height(K_CAST) == 460;
+ assert embed_height(K_TOOL) == 460;
+ assert embed_height(K_TAB) == 460;
+ assert embed_query(K_TOOL) == 1;
+ assert embed_query(K_TAB) == 2;
+ assert embed_query(K_CAST) == 0;
+ assert embed_query(K_PLAYER) == 0;
+ assert has_preview(K_TAB) == false;
+ assert has_preview(K_TOOL);
+}
+
+test a_widget_is_refused_for_the_first_rule_it_breaks {
+ assert verdict(K_CAST, true, true, true, true, true, false, false, false) == V_OK;
+ assert verdict(K_CAST, true, false, true, true, true, false, false, false) == V_PAGE_MISSING;
+ assert verdict(K_CAST, true, true, false, true, true, false, false, false) == V_META_MISSING;
+ assert verdict(K_CAST, true, true, true, false, true, false, false, false) == V_IMAGE_NOT_LOCAL;
+ assert verdict(K_CAST, true, true, true, true, false, false, false, false) == V_CAST_META_MISSING;
+ assert verdict(K_CAST, true, true, true, true, true, true, false, false) == V_CAST_BAD_EXIT;
+ assert verdict(K_TOOL, true, true, true, true, false, false, false, false) == V_TOOL_HOOK_MISSING;
+ assert verdict(K_TOOL, true, true, true, true, false, false, true, false) == V_OK;
+ assert verdict(K_PLAYER, true, true, true, true, false, false, false, false) == V_PLAYER_EMBED_MISSING;
+ assert verdict(K_PLAYER, true, true, true, true, false, false, false, true) == V_OK;
+ assert verdict(K_TAB, false, false, false, false, false, false, false, false) == V_TAB_VIEW_MISSING;
+ assert verdict(K_TAB, true, false, false, false, false, false, false, false) == V_OK;
+ assert verdict(K_KINDS, true, true, true, true, true, false, true, true) == V_UNKNOWN_KIND;
+}
From ddcabdf0db17afaa6f67eddd7579d1528d87804c Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 14:58:14 +0700
Subject: [PATCH 04/33] widgets: the gallery reader as pure .t27 -- meta tags,
entities, localOf, exit codes and hud views over bytes; 8 of 8 under t27c and
t27b, byte-identical to metaOf on all 337 corpus files (Closes #10938)
specs/widgets/page_reader.t27 replaces the reading half of widgets-from-spec.mjs. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/page_reader.t27 | 430 ++++++++++++++++++++++++++++++++++
1 file changed, 430 insertions(+)
create mode 100644 specs/widgets/page_reader.t27
diff --git a/specs/widgets/page_reader.t27 b/specs/widgets/page_reader.t27
new file mode 100644
index 0000000000..60098ba81d
--- /dev/null
+++ b/specs/widgets/page_reader.t27
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/page_reader.t27 -- the gallery's reader as pure rules over bytes: what a widget's
+; page and recording say, without touching a disk. Epic gHashTag/t27#10937; owner 2026-10-19: the
+; gallery goes native into t27b and T27F. Replaces, rule for rule, the reading half of
+; apps/website/scripts/widgets-from-spec.mjs in gHashTag/trinity: metaOf (the og and twitter tags
+; of a share page), unescape, localOf (an absolute t27.ai address to its file), framed, the cast's
+; exit_codes test, and hudViews (which Queen board views exist, for the one tab).
+; --
+; The pattern is source_text.t27's (T27bSourceText): the glue passes a file's BYTES and gets back
+; offsets; nothing here reads a file. A sub-text is returned as its two byte offsets packed in one
+; i64 (start * 2^32 + end), NO_TEXT when there is none, so the whole module is scalar-and-slice and
+; t27b can run it. A text with HTML entities is read through text_len and text_byte, which unescape
+; on the fly: no buffer, no allocation.
+; --
+; Exact semantics kept from the generator, so the swap changes nothing a reader can see:
+; - a meta tag matches only as ` i64 {
+ return (lo as i64) * PACK + (hi as i64);
+}
+
+pub fn lo_of(p: i64) -> usize {
+ return (p / PACK) as usize;
+}
+
+pub fn hi_of(p: i64) -> usize {
+ return (p % PACK) as usize;
+}
+
+/// JavaScript's \s for the ASCII range: space, tab, line feed, vertical tab, form feed, carriage return.
+pub fn is_space(b: u8) -> bool {
+ return b == B_SPACE or (b >= B_TAB and b <= B_CR);
+}
+
+/// Do the bytes of w sit at s[p ..], inside s[.. hi]?
+pub fn match_in(s: []const u8, p: usize, hi: usize, w: []const u8) -> bool {
+ if (p + w.len > hi) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (s[p + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// The first offset at or after `from` where w starts, or NO_TEXT.
+pub fn find_from(s: []const u8, from: usize, w: []const u8) -> i64 {
+ var p : usize = from;
+ while (p + w.len <= s.len) {
+ if (match_in(s, p, s.len, w)) { return p as i64; }
+ p = p + 1;
+ }
+ return NO_TEXT;
+}
+
+/// The offset of the next `"` at or after p, or NO_TEXT.
+pub fn next_quote(s: []const u8, p0: usize) -> i64 {
+ var p : usize = p0;
+ while (p < s.len) {
+ if (s[p] == B_QUOTE) { return p as i64; }
+ p = p + 1;
+ }
+ return NO_TEXT;
+}
+
+pub fn skip_space(s: []const u8, p0: usize) -> usize {
+ var p : usize = p0;
+ while (p < s.len and is_space(s[p])) { p = p + 1; }
+ return p;
+}
+
+/// One ` i64 {
+ var p : usize = skip_space(html, p0);
+ if (p == p0) { return NO_TEXT; }
+ if (match_in(html, p, html.len, "property=")) {
+ p = p + 9;
+ } else {
+ if (match_in(html, p, html.len, "name=")) {
+ p = p + 5;
+ } else {
+ return NO_TEXT;
+ }
+ }
+ if (p >= html.len or html[p] != B_QUOTE) { return NO_TEXT; }
+ p = p + 1;
+ if (!match_in(html, p, html.len, key)) { return NO_TEXT; }
+ p = p + key.len;
+ if (key.len == 0 or p >= html.len or html[p] != B_QUOTE) { return NO_TEXT; }
+ p = p + 1;
+ const q : usize = skip_space(html, p);
+ if (q == p) { return NO_TEXT; }
+ if (!match_in(html, q, html.len, "content=")) { return NO_TEXT; }
+ const v : usize = q + 8;
+ if (v >= html.len or html[v] != B_QUOTE) { return NO_TEXT; }
+ const e : i64 = next_quote(html, v + 1);
+ if (e < 0) { return NO_TEXT; }
+ return pack(v + 1, e as usize);
+}
+
+/// The packed content of the meta tag with this key; the last one wins, as in metaOf.
+pub fn meta_value(html: []const u8, key: []const u8) -> i64 {
+ var found : i64 = NO_TEXT;
+ var at : i64 = find_from(html, 0, "= 0) {
+ const after : usize = (at as usize) + 5;
+ const r : i64 = meta_at(html, after, key);
+ if (r >= 0) { found = r; }
+ at = find_from(html, after, " bool {
+ return meta_value(html, key) >= 0;
+}
+
+/// Which of the tags a gallery widget needs the page has.
+pub fn page_flags(html: []const u8) -> u32 {
+ var f : u32 = 0;
+ if (has_meta(html, "og:title")) { f = f + F_TITLE; }
+ if (has_meta(html, "og:description")) { f = f + F_LINE; }
+ if (has_meta(html, "og:image")) { f = f + F_IMAGE; }
+ if (has_meta(html, "t27:hook")) { f = f + F_HOOK; }
+ if (has_meta(html, "twitter:player")) { f = f + F_PLAYER; }
+ return f;
+}
+
+/// A share page has its title, line and image, and none is empty.
+pub fn words_ok(html: []const u8) -> bool {
+ var k : u32 = 0;
+ while (k < 3) {
+ var key : []const u8 = "og:title";
+ if (k == 1) { key = "og:description"; }
+ if (k == 2) { key = "og:image"; }
+ const v : i64 = meta_value(html, key);
+ if (v < 0) { return false; }
+ if (hi_of(v) == lo_of(v)) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// The length of the HTML entity at s[p ..] inside s[.. hi], 0 when there is none of the six.
+pub fn entity_len(s: []const u8, p: usize, hi: usize) -> usize {
+ if (p >= hi or s[p] != B_AMP) { return 0; }
+ if (match_in(s, p, hi, "&")) { return 5; }
+ if (match_in(s, p, hi, "<")) { return 4; }
+ if (match_in(s, p, hi, ">")) { return 4; }
+ if (match_in(s, p, hi, """)) { return 6; }
+ if (match_in(s, p, hi, "'")) { return 5; }
+ if (match_in(s, p, hi, "'")) { return 6; }
+ return 0;
+}
+
+pub fn entity_byte(s: []const u8, p: usize, hi: usize) -> u8 {
+ if (match_in(s, p, hi, "&")) { return B_AMP; }
+ if (match_in(s, p, hi, "<")) { return B_LT; }
+ if (match_in(s, p, hi, ">")) { return B_GT; }
+ if (match_in(s, p, hi, """)) { return B_QUOTE; }
+ return B_APOS;
+}
+
+/// The length of s[lo .. hi] once its entities are replaced, in one pass.
+pub fn text_len(s: []const u8, lo: usize, hi: usize) -> usize {
+ var p : usize = lo;
+ var n : usize = 0;
+ while (p < hi) {
+ const e : usize = entity_len(s, p, hi);
+ if (e > 0) { p = p + e; } else { p = p + 1; }
+ n = n + 1;
+ }
+ return n;
+}
+
+/// The k-th byte of s[lo .. hi] once its entities are replaced; 0 past the end.
+pub fn text_byte(s: []const u8, lo: usize, hi: usize, k: usize) -> u8 {
+ var p : usize = lo;
+ var n : usize = 0;
+ while (p < hi) {
+ const e : usize = entity_len(s, p, hi);
+ if (n == k) {
+ if (e > 0) { return entity_byte(s, p, hi); }
+ return s[p];
+ }
+ if (e > 0) { p = p + e; } else { p = p + 1; }
+ n = n + 1;
+ }
+ return 0;
+}
+
+/// localOf: the packed path of url after origin, up to the first ? or #; NO_TEXT when url is not
+/// under origin.
+pub fn local_of(url: []const u8, origin: []const u8) -> i64 {
+ if (!match_in(url, 0, url.len, origin)) { return NO_TEXT; }
+ var e : usize = origin.len;
+ while (e < url.len and url[e] != B_QUERY and url[e] != B_HASH) { e = e + 1; }
+ return pack(origin.len, e);
+}
+
+/// framed: a path that ends in a slash names a directory and gets index.html appended.
+pub fn is_dir(path: []const u8, lo: usize, hi: usize) -> bool {
+ return hi > lo and path[hi - 1] == B_SLASH;
+}
+
+/// The cast's exit codes: true when exit_codes holds anything but the number 0. A token is a run of
+/// bytes between spaces, commas and quotes; "exit_codes" absent or empty is clean.
+pub fn bad_exit(json: []const u8) -> bool {
+ const k : i64 = find_from(json, 0, "exit_codes");
+ if (k < 0) { return false; }
+ var p : usize = (k as usize) + 10;
+ while (p < json.len and json[p] != B_OPEN) { p = p + 1; }
+ if (p >= json.len) { return false; }
+ p = p + 1;
+ var len : usize = 0;
+ var zero : bool = true;
+ while (p < json.len and json[p] != B_CLOSE) {
+ const b : u8 = json[p];
+ if (is_space(b) or b == B_COMMA or b == B_QUOTE) {
+ if (len > 0 and (len != 1 or !zero)) { return true; }
+ len = 0;
+ zero = true;
+ } else {
+ if (b != B_ZERO) { zero = false; }
+ len = len + 1;
+ }
+ p = p + 1;
+ }
+ if (len > 0 and (len != 1 or !zero)) { return true; }
+ return false;
+}
+
+/// hudViews: is `view` one of the quoted names in `export const HUD_VIEWS ... = [ ... ] as const`?
+pub fn view_listed(src: []const u8, view: []const u8) -> bool {
+ const k : i64 = find_from(src, 0, "HUD_VIEWS");
+ if (k < 0) { return false; }
+ const eq : i64 = find_from(src, (k as usize) + 9, "=");
+ if (eq < 0) { return false; }
+ const open : i64 = find_from(src, (eq as usize) + 1, "[");
+ if (open < 0) { return false; }
+ var close : i64 = find_from(src, (open as usize) + 1, "]");
+ var ok : bool = false;
+ while (close >= 0 and !ok) {
+ const a : usize = skip_space(src, (close as usize) + 1);
+ if (match_in(src, a, src.len, "as const")) { ok = true; } else { close = find_from(src, (close as usize) + 1, "]"); }
+ }
+ if (!ok) { return false; }
+ var p : usize = (open as usize) + 1;
+ while (p < (close as usize)) {
+ if (src[p] == B_QUOTE) {
+ const e : i64 = next_quote(src, p + 1);
+ if (e < 0 or (e as usize) > (close as usize)) { return false; }
+ if (e as usize - p - 1 == view.len and match_in(src, p + 1, e as usize, view)) { return true; }
+ p = (e as usize) + 1;
+ } else {
+ p = p + 1;
+ }
+ }
+ return false;
+}
+
+/// The decision about one widget: the generator's problems list as gallery_rules' verdict. The glue
+/// answers what only a disk knows -- page_ok (the index.html exists), image_file_ok (the og:image is
+/// a file under public/), player_file_ok (the twitter:player is a page under public/), cast_meta
+/// (meta.json exists), view_ok (hudViews lists the tab's view) -- and passes the bytes.
+pub fn judge(kind: u8, html: []const u8, cast_json: []const u8, origin: []const u8, page_ok: bool, image_file_ok: bool, player_file_ok: bool, cast_meta: bool, view_ok: bool) -> u8 {
+ const img : i64 = meta_value(html, "og:image");
+ var image_local : bool = false;
+ if (img >= 0) {
+ image_local = local_of(html[lo_of(img) .. hi_of(img)], origin) >= 0 and image_file_ok;
+ }
+ const flags : u32 = page_flags(html);
+ const hook_ok : bool = (flags / F_HOOK) % 2 == 1;
+ return verdict(kind, view_ok, page_ok, words_ok(html), image_local, cast_meta, bad_exit(cast_json), hook_ok, player_file_ok);
+}
+
+test a_meta_value_is_the_content_of_the_last_matching_tag {
+ const h : []const u8 = "";
+ const v : i64 = meta_value(h, "og:title");
+ assert v >= 0;
+ assert hi_of(v) - lo_of(v) == 6;
+ assert h[lo_of(v)] == 83;
+ assert meta_value(h, "og:image") == NO_TEXT;
+ assert hi_of(meta_value(h, "description")) - lo_of(meta_value(h, "description")) == 1;
+}
+
+test a_meta_tag_must_be_spelled_the_way_the_generator_matches {
+ assert meta_value("", "og:title") >= 0;
+ assert meta_value("", "og:title") >= 0;
+ assert meta_value("", "og:title") == NO_TEXT;
+ assert meta_value("", "og:title") == NO_TEXT;
+ assert meta_value("", "og:title") == NO_TEXT;
+ assert meta_value("", "og:title") == NO_TEXT;
+ assert meta_value("", "og:title") >= 0;
+ assert meta_value("", "og:title") == NO_TEXT;
+}
+
+test the_page_flags_say_which_tags_a_share_page_has {
+ const p : []const u8 = "";
+ assert page_flags(p) == F_TITLE + F_LINE + F_IMAGE + F_HOOK;
+ assert words_ok(p);
+ assert page_flags("") == F_PLAYER;
+ assert words_ok("") == false;
+ assert words_ok("") == false;
+}
+
+test entities_unescape_in_one_pass {
+ const s : []const u8 = "a & b <c> "d" it's 'e' & &bogus;";
+ assert text_len(s, 0, s.len) == 36;
+ assert text_byte(s, 0, s.len, 2) == 38;
+ assert text_byte(s, 0, s.len, 6) == 60;
+ assert text_byte(s, 0, s.len, 16) == 39;
+ assert text_byte(s, 0, s.len, 29) == 38;
+ assert text_byte(s, 0, s.len, 30) == 98;
+ assert text_byte(s, 0, s.len, 23) == 38;
+ assert text_byte(s, 0, s.len, 24) == 97;
+ assert text_byte(s, 0, s.len, 999) == 0;
+ assert text_len("plain", 0, 5) == 5;
+ assert text_len("&", 0, 4) == 4;
+ assert text_len("", 0, 0) == 0;
+}
+
+test an_address_is_local_when_it_sits_under_the_origin {
+ const u : []const u8 = "https://t27.ai/widgets/gatle/card.png?v=9e7908d83118";
+ const r : i64 = local_of(u, "https://t27.ai/");
+ assert r >= 0;
+ assert lo_of(r) == 15;
+ assert hi_of(r) == 37;
+ assert local_of("https://other.example/x.png", "https://t27.ai/") == NO_TEXT;
+ assert hi_of(local_of("https://t27.ai/term/a/#frag", "https://t27.ai/")) == 22;
+ assert lo_of(local_of("https://t27.ai/", "https://t27.ai/")) == 15;
+ assert hi_of(local_of("https://t27.ai/", "https://t27.ai/")) == 15;
+ assert is_dir("term/x7-board/", 0, 14);
+ assert is_dir("widgets/gatle/card.png", 0, 22) == false;
+ assert is_dir("", 0, 0) == false;
+}
+
+test a_cast_is_bad_when_any_exit_code_is_not_zero {
+ assert bad_exit("{ \"exit_codes\": [ 0, 0, 0 ], \"gif\": true }") == false;
+ assert bad_exit("{ \"exit_codes\": [0] }") == false;
+ assert bad_exit("{ \"exit_codes\": [0, 1] }");
+ assert bad_exit("{ \"exit_codes\": [ 2 ] }");
+ assert bad_exit("{ \"exit_codes\": [ 00 ] }");
+ assert bad_exit("{ \"exit_codes\": [ 10 ] }");
+ assert bad_exit("{ \"exit_codes\": [ \"0\", \"0\" ] }") == false;
+ assert bad_exit("{ \"exit_codes\": [] }") == false;
+ assert bad_exit("{ \"gif\": true }") == false;
+ assert bad_exit("") == false;
+}
+
+test the_tab_view_must_be_listed_in_the_hud {
+ const src : []const u8 = "export const HUD_VIEWS: readonly string[] = [ \"board\", \"widgets\", \"wars\" ] as const";
+ assert view_listed(src, "widgets");
+ assert view_listed(src, "board");
+ assert view_listed(src, "wars");
+ assert view_listed(src, "widget") == false;
+ assert view_listed(src, "nope") == false;
+ assert view_listed("const X = [ \"widgets\" ]", "widgets") == false;
+ assert view_listed("", "widgets") == false;
+}
+
+test judge_turns_what_was_read_into_gallery_rules_verdicts {
+ const page : []const u8 = "";
+ const origin : []const u8 = "https://t27.ai/";
+ assert judge(K_TOOL, page, "", origin, true, true, false, false, false) == V_OK;
+ assert judge(K_TOOL, page, "", origin, false, true, false, false, false) != V_OK;
+ assert judge(K_TOOL, page, "", origin, true, false, false, false, false) != V_OK;
+ assert judge(K_TOOL, "", "", origin, true, true, false, false, false) != V_OK;
+ assert judge(K_CAST, page, "{ \"exit_codes\": [0, 0] }", origin, true, true, false, true, false) == V_OK;
+ assert judge(K_CAST, page, "{ \"exit_codes\": [0, 1] }", origin, true, true, false, true, false) != V_OK;
+ assert judge(K_CAST, page, "{ \"exit_codes\": [0] }", origin, true, true, false, false, false) != V_OK;
+}
From def022a40db7a354a1900672a2be86934f789d9f Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 15:18:48 +0700
Subject: [PATCH 05/33] widgets: the logic stage runs through wasm --
gatle_rule built with gen-c and zig, 866178 comparisons against rule.js, 0
mismatches (Closes #10938)
gen-js lowers no fn bodies (#10943), but the path the Queen's app.wasm takes works: 490 bytes.
Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/t27f/widget_factory.t27 | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/specs/t27f/widget_factory.t27 b/specs/t27f/widget_factory.t27
index 934d3a159f..6ffefd4b06 100644
--- a/specs/t27f/widget_factory.t27
+++ b/specs/t27f/widget_factory.t27
@@ -10,7 +10,7 @@
; spec the widget's .t27 (words, rules, tests, data sources). The hand column of the catalog is
; everything that is NOT this stage.
; logic the pure functions of the tool (parse, grade, layout), written in .t27 and generated to
-; JavaScript by gen-ts. The spec's own test blocks are the fixed answers; a data builder no
+; wasm (t27c gen-c, then zig cc --target=wasm32-wasi), not JavaScript: gen-js lowers no bodies. The spec's own test blocks are the fixed answers; a data builder no
; longer re-imports a hand file "so the page cannot drift from the spec".
; plan what is drawn, as a ui_plan.t27 plan: widget ids from an allowlist, no markup, no script.
; The primitives below extend ui_plan's catalog; they are not a second catalog.
@@ -30,7 +30,7 @@
; hand-written lines do not grow. (c) is the same rule as t27b's "net <= 0" and FROZEN_HASH ports.
; --
; Not claimed: no stage past `spec` exists as a program today. gatle's rules are ported to
-; specs/widgets/gatle_rule.t27 and pass, but gen-js/gen-ts do not lower fn bodies, so no JS is generated; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
+; specs/widgets/gatle_rule.t27 and built to wasm (0 mismatches over 866178 comparisons against rule.js), but the widget page still loads the hand-written rule.js: the trinity side is not changed; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
; PROPOSED from reading the 28 tools by name, not measured; wave 1 (catalog.t27, 11 tools of at
; most 700 hand lines) measures them and may rename or merge them.
; phi^2 + 1/phi^2 = 3 | TRINITY
@@ -83,14 +83,18 @@ pub const CLAW_TOOL_COUNT : u32 = 3;
pub const CLAW_TOOLS : [3]str = ["t27f_widgets", "t27f_widget_make", "t27f_widget_port_plan"];
/// MEASURED 2026-10-19 with t27c 0.5.2 on specs/widgets/gatle_rule.t27: gen-js and gen-ts emit
-/// declarations (consts, structs) and skip every fn body -- "this backend lowers declarations, not
-/// bodies". Until a body-lowering JS backend exists the logic stage writes a tested .t27 rule
-/// (the single source) but cannot generate the JavaScript the page runs. Flip this when it can.
+/// declarations only and skip every fn body (#10943), so the logic stage does not go through them.
+/// It goes through the path the Queen's app.wasm already takes: `t27c gen-c`, then
+/// `zig cc --target=wasm32-wasi -Oz -mexec-model=reactor` with one -Wl,--export per function.
+/// gatle_rule.t27 became a 490-byte wasm; 866178 comparisons against the hand-written rule.js
+/// (random and boundary inputs, every function) gave 0 mismatches. Byte slices and out buffers pass
+/// through the module's linear memory; module-level `var` arrays and `[]u8` out parameters work.
+pub const LOGIC_VIA_WASM : bool = true;
pub const GEN_JS_LOWERS_BODIES : bool = false;
-/// The logic stage can produce its artefact only when the backend lowers bodies.
+/// The logic stage can produce its artefact when some route lowers bodies.
pub fn logic_stage_ready() -> bool {
- return GEN_JS_LOWERS_BODIES;
+ return LOGIC_VIA_WASM or GEN_JS_LOWERS_BODIES;
}
/// A stage is the next of a port stage only by one step.
@@ -224,8 +228,10 @@ test every_claw_tool_is_a_t27_name_the_service_mounts {
assert TRI_VERBS[0] == "widget list";
}
-test the_logic_stage_waits_for_a_body_lowering_backend {
- assert logic_stage_ready() == false;
+test the_logic_stage_runs_through_wasm_while_gen_js_lowers_no_bodies {
+ assert logic_stage_ready();
+ assert LOGIC_VIA_WASM;
+ assert GEN_JS_LOWERS_BODIES == false;
}
test the_logic_stage_removes_exactly_the_logic_file {
From d4d8ccd60095f2f276aa44627d23261e530681cf Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 15:21:31 +0700
Subject: [PATCH 06/33] widgets: html_out -- the one HTML writer for native
widget views, bounded buffer, escaping, en-US grouped numbers; 4 of 4 under
t27c and t27b (Closes #10938)
Stage "page" of the widget factory: a view writes HTML into a fixed buffer and the host sets innerHTML.
Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/html_out.t27 | 243 +++++++++++++++++++++++++++++++++++++
1 file changed, 243 insertions(+)
create mode 100644 specs/widgets/html_out.t27
diff --git a/specs/widgets/html_out.t27 b/specs/widgets/html_out.t27
new file mode 100644
index 0000000000..d1a05d1c5d
--- /dev/null
+++ b/specs/widgets/html_out.t27
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/html_out.t27 -- the HTML writer every native widget view uses. Epic gHashTag/t27#10937,
+; stage "page" of specs/t27f/widget_factory.t27. A tool.js builds its page with el()/h() helpers and
+; DOM calls (28 files, 8920 lines); a native view writes HTML text into one module-level buffer and the
+; host sets it as innerHTML. This file is that writer, once: reset, put a byte, put text (raw or
+; HTML-escaped), put a number, open/close a tag, put an attribute. No allocation: the buffer is fixed
+; at OUT_CAP and a write past the end sets OVERFLOW and drops the byte, so a view can never write
+; outside it. The host reads out_len() bytes starting at out_at(0..).
+; ASCII only (L3), English only (LANG-EN).
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widgets_html_out;
+
+pub const OUT_CAP : u32 = 65536;
+pub var OUT : [65536]u8 = [0; 65536];
+pub var OUT_LEN : u32 = 0;
+pub var OVERFLOW : u32 = 0;
+
+pub const B_LT : u8 = 60;
+pub const B_GT : u8 = 62;
+pub const B_AMP : u8 = 38;
+pub const B_QUOTE : u8 = 34;
+pub const B_APOS : u8 = 39;
+pub const B_SPACE : u8 = 32;
+pub const B_SLASH : u8 = 47;
+pub const B_EQ : u8 = 61;
+
+pub fn out_reset() -> u32 {
+ OUT_LEN = 0;
+ OVERFLOW = 0;
+ return 0;
+}
+
+pub fn out_len() -> u32 {
+ return OUT_LEN;
+}
+
+pub fn out_at(i: u32) -> u8 {
+ return OUT[i as usize];
+}
+
+pub fn out_overflowed() -> bool {
+ return OVERFLOW != 0;
+}
+
+pub fn put(b: u8) -> u32 {
+ if (OUT_LEN >= OUT_CAP) {
+ OVERFLOW = 1;
+ return OUT_LEN;
+ }
+ OUT[OUT_LEN as usize] = b;
+ OUT_LEN = OUT_LEN + 1;
+ return OUT_LEN;
+}
+
+/// Raw bytes, as the caller wrote them (markup the view itself owns).
+pub fn raw(s: []const u8) -> u32 {
+ var i : usize = 0;
+ while (i < s.len) {
+ put(s[i]);
+ i = i + 1;
+ }
+ return OUT_LEN;
+}
+
+/// Text from outside the view (data, words): & < > " ' are written as entities.
+pub fn text(s: []const u8) -> u32 {
+ var i : usize = 0;
+ while (i < s.len) {
+ const b : u8 = s[i];
+ if (b == B_AMP) {
+ raw("&");
+ } else {
+ if (b == B_LT) {
+ raw("<");
+ } else {
+ if (b == B_GT) {
+ raw(">");
+ } else {
+ if (b == B_QUOTE) {
+ raw(""");
+ } else {
+ if (b == B_APOS) {
+ raw("'");
+ } else {
+ put(b);
+ }
+ }
+ }
+ }
+ }
+ i = i + 1;
+ }
+ return OUT_LEN;
+}
+
+/// An unsigned decimal.
+pub fn num(n: u64) -> u32 {
+ if (n == 0) {
+ return put(48);
+ }
+ var digits : [20]u8 = [0; 20];
+ var k : usize = 0;
+ var v : u64 = n;
+ while (v > 0) {
+ digits[k] = ((v % 10) as u8) + 48;
+ v = v / 10;
+ k = k + 1;
+ }
+ while (k > 0) {
+ k = k - 1;
+ put(digits[k]);
+ }
+ return OUT_LEN;
+}
+
+/// An unsigned decimal with thousands separated by commas, as toLocaleString('en-US') writes it.
+pub fn num_grouped(n: u64) -> u32 {
+ var digits : [20]u8 = [0; 20];
+ var k : usize = 0;
+ var v : u64 = n;
+ if (v == 0) {
+ return put(48);
+ }
+ while (v > 0) {
+ digits[k] = ((v % 10) as u8) + 48;
+ v = v / 10;
+ k = k + 1;
+ }
+ while (k > 0) {
+ k = k - 1;
+ put(digits[k]);
+ if (k > 0 and k % 3 == 0) {
+ put(44);
+ }
+ }
+ return OUT_LEN;
+}
+
+/// u32 {
+ put(B_LT);
+ return raw(tag);
+}
+
+pub fn open_end() -> u32 {
+ return put(B_GT);
+}
+
+/// name="value" with the value escaped, preceded by a space.
+pub fn attr(name: []const u8, value: []const u8) -> u32 {
+ put(B_SPACE);
+ raw(name);
+ put(B_EQ);
+ put(B_QUOTE);
+ text(value);
+ return put(B_QUOTE);
+}
+
+pub fn close(tag: []const u8) -> u32 {
+ put(B_LT);
+ put(B_SLASH);
+ raw(tag);
+ return put(B_GT);
+}
+
+/// {0} {1} ... filled from up to three values, as fmt() does in the tool.js files. A placeholder with
+/// no value writes nothing. The template is the view's own words, written raw; each value is escaped.
+pub fn fill3(t: []const u8, a: []const u8, b: []const u8, c: []const u8) -> u32 {
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] <= 50) {
+ if (t[i + 1] == 48) { text(a); }
+ if (t[i + 1] == 49) { text(b); }
+ if (t[i + 1] == 50) { text(c); }
+ i = i + 3;
+ } else {
+ put(t[i]);
+ i = i + 1;
+ }
+ }
+ return OUT_LEN;
+}
+
+pub fn same_bytes(a: u32, b: []const u8) -> bool {
+ var i : usize = 0;
+ if (OUT_LEN != (b.len as u32) + a) { return false; }
+ while (i < b.len) {
+ if (OUT[(a as usize) + i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+test the_writer_escapes_text_and_leaves_markup_alone {
+ assert out_reset() == 0;
+ open("p");
+ attr("title", "a \"b\" & c");
+ open_end();
+ text("x < y & 'z'");
+ close("p");
+ assert same_bytes(0, "
x < y & 'z'
");
+ assert out_overflowed() == false;
+}
+
+test numbers_are_decimal_and_grouped_like_en_us {
+ out_reset();
+ num(0);
+ put(32);
+ num(1234567);
+ put(32);
+ num_grouped(0);
+ put(32);
+ num_grouped(999);
+ put(32);
+ num_grouped(1000);
+ put(32);
+ num_grouped(215360);
+ put(32);
+ num_grouped(1234567890);
+ assert same_bytes(0, "0 1234567 0 999 1,000 215,360 1,234,567,890");
+}
+
+test fill_puts_values_in_the_template_and_escapes_them {
+ out_reset();
+ fill3("{0} of {1}, {2}{3}", "Gatle", "<6>", "end");
+ assert same_bytes(0, "Gatle of <6>, end{3}");
+}
+
+test the_buffer_never_writes_past_its_end {
+ out_reset();
+ var i : u32 = 0;
+ while (i < OUT_CAP + 10) {
+ put(65);
+ i = i + 1;
+ }
+ assert out_len() == OUT_CAP;
+ assert out_overflowed();
+ assert out_at(OUT_CAP - 1) == 65;
+ out_reset();
+ assert out_len() == 0;
+ assert out_overflowed() == false;
+}
From 45af0f32afe9df39b54322f9ea38dfa998f47374 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 15:48:08 +0700
Subject: [PATCH 07/33] widgets: jtag-verdict is the first widget drawn
natively -- words, rules and page in one .t27, a 9269-byte wasm, 3015 DOM
comparisons against tool.js, 0 differences (Closes #10938)
specs/widgets/jtag_verdict.t27 holds the trinity spec's words and a native page (w_init, w_input,
w_click, w_hash, w_render, w_hash_out); html_out.t27 becomes a stateless writer (a pub var does not
cross a use). catalog.t27 records gatle at PORT_LOGIC and jtag-verdict at PORT_DRAW. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/catalog.t27 | 22 +-
specs/widgets/html_out.t27 | 285 ++++++--------
specs/widgets/jtag_verdict.t27 | 689 +++++++++++++++++++++++++++++++++
3 files changed, 824 insertions(+), 172 deletions(-)
create mode 100644 specs/widgets/jtag_verdict.t27
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
index 35f526116f..f74c118276 100644
--- a/specs/widgets/catalog.t27
+++ b/specs/widgets/catalog.t27
@@ -31,9 +31,13 @@
; 3. 23 tools have a Node data builder (5572 lines) and 24 a Python card renderer (3415 lines).
; The card is a picture of the page; t27f draws pictures from specs already (SF_STILL).
; --
-; Not claimed: that any hand column has been rewritten. Every tool is at PORT_WORDS today: its
-; words live in its spec, its logic and drawing still live in JavaScript. A tool moves up only when
-; its output is byte-identical to the hand version, and the factory spec states that gate.
+; Stages reached, each with a differential against the hand-written version on the trinity side:
+; gatle is at PORT_LOGIC (specs/widgets/gatle_rule.t27: 866178 comparisons vs rule.js, 0 mismatches)
+; and jtag-verdict at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM comparisons vs tool.js,
+; 0 mismatches). Every other tool is at PORT_WORDS: its words live in its spec, its logic and drawing
+; still live in JavaScript. A stage is reached on the t27 side first; the page in trinity keeps
+; loading the hand-written file until that repo's migration PR deletes it. A tool moves up only when
+; its output is identical to the hand version, and the factory spec states that gate.
; The Swift and React widgets are counted, not classified: they are another renderer's targets.
; ASCII only (L3), English only (LANG-EN).
; phi^2 + 1/phi^2 = 3 | TRINITY
@@ -83,7 +87,7 @@ pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
pub const PORT_STAGES : u8 = 5;
-pub const TOOL_PORT : [28]u8 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+pub const TOOL_PORT : [28]u8 = [1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
@@ -219,9 +223,11 @@ test the_waves_cover_every_tool_once {
assert hand_lines(12) == 1432;
}
-test no_tool_has_left_the_words_stage_yet {
- assert count_port(PORT_WORDS) == TOOL_COUNT;
- assert count_port(PORT_LOGIC) == 0;
- assert count_port(PORT_DRAW) == 0;
+test two_tools_have_left_the_words_stage_with_a_differential {
+ assert count_port(PORT_WORDS) == TOOL_COUNT - 2;
+ assert count_port(PORT_LOGIC) == 1;
+ assert count_port(PORT_DRAW) == 1;
assert count_port(PORT_SEALED) == 0;
+ assert TOOL_PORT[7] == PORT_LOGIC;
+ assert TOOL_PORT[10] == PORT_DRAW;
}
diff --git a/specs/widgets/html_out.t27 b/specs/widgets/html_out.t27
index d1a05d1c5d..2aff837aa0 100644
--- a/specs/widgets/html_out.t27
+++ b/specs/widgets/html_out.t27
@@ -1,107 +1,82 @@
// SPDX-License-Identifier: Apache-2.0
; specs/widgets/html_out.t27 -- the HTML writer every native widget view uses. Epic gHashTag/t27#10937,
; stage "page" of specs/t27f/widget_factory.t27. A tool.js builds its page with el()/h() helpers and
-; DOM calls (28 files, 8920 lines); a native view writes HTML text into one module-level buffer and the
-; host sets it as innerHTML. This file is that writer, once: reset, put a byte, put text (raw or
-; HTML-escaped), put a number, open/close a tag, put an attribute. No allocation: the buffer is fixed
-; at OUT_CAP and a write past the end sets OVERFLOW and drops the byte, so a view can never write
-; outside it. The host reads out_len() bytes starting at out_at(0..).
+; DOM calls (28 files, 8920 lines); a native view writes HTML text into the buffer the host gave it
+; (a region of the wasm's linear memory) and the host sets it as innerHTML. This file is that writer,
+; once: put a byte, put text (raw or HTML-escaped), put a number, put an attribute, fill a template.
+; --
+; STATELESS ON PURPOSE. Every function takes the buffer o and the length written so far n and returns
+; the new length. There is no module-level state because t27c's splicer does not carry a `pub var`
+; across `use` (measured: a view that imported OUT and OUT_LEN got "undeclared identifier"), and
+; follows only the names a module itself mentions, not the names an imported function mentions: so a
+; view imports every function it calls, and these functions call only each other through the
+; ANCHOR in the view (see jtag_verdict.t27, html_out_anchor). A write past the end of o returns OVER,
+; and every function returns OVER at once when it is given OVER, so a view can never write outside o
+; and checks once, at the end, with ok(n).
; ASCII only (L3), English only (LANG-EN).
; phi^2 + 1/phi^2 = 3 | TRINITY
module widgets_html_out;
-pub const OUT_CAP : u32 = 65536;
-pub var OUT : [65536]u8 = [0; 65536];
-pub var OUT_LEN : u32 = 0;
-pub var OVERFLOW : u32 = 0;
-
-pub const B_LT : u8 = 60;
-pub const B_GT : u8 = 62;
-pub const B_AMP : u8 = 38;
-pub const B_QUOTE : u8 = 34;
-pub const B_APOS : u8 = 39;
-pub const B_SPACE : u8 = 32;
-pub const B_SLASH : u8 = 47;
-pub const B_EQ : u8 = 61;
-
-pub fn out_reset() -> u32 {
- OUT_LEN = 0;
- OVERFLOW = 0;
- return 0;
-}
-
-pub fn out_len() -> u32 {
- return OUT_LEN;
-}
+pub const OVER : u32 = 4294967295;
+pub const H_LT : u8 = 60;
+pub const H_GT : u8 = 62;
+pub const H_AMP : u8 = 38;
+pub const H_QUOTE : u8 = 34;
+pub const H_APOS : u8 = 39;
+pub const H_SPACE : u8 = 32;
+pub const H_SLASH : u8 = 47;
+pub const H_EQ : u8 = 61;
-pub fn out_at(i: u32) -> u8 {
- return OUT[i as usize];
+pub fn ok(n: u32) -> bool {
+ return n != OVER;
}
-pub fn out_overflowed() -> bool {
- return OVERFLOW != 0;
-}
-
-pub fn put(b: u8) -> u32 {
- if (OUT_LEN >= OUT_CAP) {
- OVERFLOW = 1;
- return OUT_LEN;
- }
- OUT[OUT_LEN as usize] = b;
- OUT_LEN = OUT_LEN + 1;
- return OUT_LEN;
+pub fn put(o: []u8, n: u32, b: u8) -> u32 {
+ if (n >= (o.len as u32)) { return OVER; }
+ o[n as usize] = b;
+ return n + 1;
}
/// Raw bytes, as the caller wrote them (markup the view itself owns).
-pub fn raw(s: []const u8) -> u32 {
+pub fn raw(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = n0;
var i : usize = 0;
while (i < s.len) {
- put(s[i]);
+ n = put(o, n, s[i]);
i = i + 1;
}
- return OUT_LEN;
+ return n;
}
-/// Text from outside the view (data, words): & < > " ' are written as entities.
-pub fn text(s: []const u8) -> u32 {
+/// One byte of text, escaped: & < > " ' become entities.
+pub fn put_escaped(o: []u8, n: u32, b: u8) -> u32 {
+ if (b == H_AMP) { return raw(o, n, "&"); }
+ if (b == H_LT) { return raw(o, n, "<"); }
+ if (b == H_GT) { return raw(o, n, ">"); }
+ if (b == H_QUOTE) { return raw(o, n, """); }
+ if (b == H_APOS) { return raw(o, n, "'"); }
+ return put(o, n, b);
+}
+
+/// Text from outside the view (data, words).
+pub fn text(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = n0;
var i : usize = 0;
while (i < s.len) {
- const b : u8 = s[i];
- if (b == B_AMP) {
- raw("&");
- } else {
- if (b == B_LT) {
- raw("<");
- } else {
- if (b == B_GT) {
- raw(">");
- } else {
- if (b == B_QUOTE) {
- raw(""");
- } else {
- if (b == B_APOS) {
- raw("'");
- } else {
- put(b);
- }
- }
- }
- }
- }
+ n = put_escaped(o, n, s[i]);
i = i + 1;
}
- return OUT_LEN;
+ return n;
}
/// An unsigned decimal.
-pub fn num(n: u64) -> u32 {
- if (n == 0) {
- return put(48);
- }
+pub fn num(o: []u8, n0: u32, v0: u64) -> u32 {
+ var n : u32 = n0;
+ if (v0 == 0) { return put(o, n, 48); }
var digits : [20]u8 = [0; 20];
var k : usize = 0;
- var v : u64 = n;
+ var v : u64 = v0;
while (v > 0) {
digits[k] = ((v % 10) as u8) + 48;
v = v / 10;
@@ -109,19 +84,18 @@ pub fn num(n: u64) -> u32 {
}
while (k > 0) {
k = k - 1;
- put(digits[k]);
+ n = put(o, n, digits[k]);
}
- return OUT_LEN;
+ return n;
}
/// An unsigned decimal with thousands separated by commas, as toLocaleString('en-US') writes it.
-pub fn num_grouped(n: u64) -> u32 {
+pub fn num_grouped(o: []u8, n0: u32, v0: u64) -> u32 {
+ var n : u32 = n0;
+ if (v0 == 0) { return put(o, n, 48); }
var digits : [20]u8 = [0; 20];
var k : usize = 0;
- var v : u64 = n;
- if (v == 0) {
- return put(48);
- }
+ var v : u64 = v0;
while (v > 0) {
digits[k] = ((v % 10) as u8) + 48;
v = v / 10;
@@ -129,115 +103,98 @@ pub fn num_grouped(n: u64) -> u32 {
}
while (k > 0) {
k = k - 1;
- put(digits[k]);
- if (k > 0 and k % 3 == 0) {
- put(44);
- }
+ n = put(o, n, digits[k]);
+ if (k > 0 and k % 3 == 0) { n = put(o, n, 44); }
}
- return OUT_LEN;
-}
-
-/// u32 {
- put(B_LT);
- return raw(tag);
-}
-
-pub fn open_end() -> u32 {
- return put(B_GT);
+ return n;
}
/// name="value" with the value escaped, preceded by a space.
-pub fn attr(name: []const u8, value: []const u8) -> u32 {
- put(B_SPACE);
- raw(name);
- put(B_EQ);
- put(B_QUOTE);
- text(value);
- return put(B_QUOTE);
-}
-
-pub fn close(tag: []const u8) -> u32 {
- put(B_LT);
- put(B_SLASH);
- raw(tag);
- return put(B_GT);
-}
-
-/// {0} {1} ... filled from up to three values, as fmt() does in the tool.js files. A placeholder with
-/// no value writes nothing. The template is the view's own words, written raw; each value is escaped.
-pub fn fill3(t: []const u8, a: []const u8, b: []const u8, c: []const u8) -> u32 {
+pub fn attr(o: []u8, n0: u32, name: []const u8, value: []const u8) -> u32 {
+ var n : u32 = put(o, n0, H_SPACE);
+ n = raw(o, n, name);
+ n = put(o, n, H_EQ);
+ n = put(o, n, H_QUOTE);
+ n = text(o, n, value);
+ return put(o, n, H_QUOTE);
+}
+
+/// {0} {1} {2} filled from k values (k up to 3), as fill() does in the tool.js files: a placeholder
+/// whose index is not below k stays as written. The template and each value are escaped as text.
+pub fn fill(o: []u8, n0: u32, t: []const u8, a: []const u8, b: []const u8, c: []const u8, k: u32) -> u32 {
+ var n : u32 = n0;
var i : usize = 0;
while (i < t.len) {
- if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] <= 50) {
- if (t[i + 1] == 48) { text(a); }
- if (t[i + 1] == 49) { text(b); }
- if (t[i + 1] == 50) { text(c); }
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] < 48 + (k as u8) and t[i + 1] <= 50) {
+ if (t[i + 1] == 48) { n = text(o, n, a); }
+ if (t[i + 1] == 49) { n = text(o, n, b); }
+ if (t[i + 1] == 50) { n = text(o, n, c); }
i = i + 3;
} else {
- put(t[i]);
+ n = put_escaped(o, n, t[i]);
i = i + 1;
}
}
- return OUT_LEN;
+ return n;
}
-pub fn same_bytes(a: u32, b: []const u8) -> bool {
+pub fn same_bytes(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (n != (want.len as u32)) { return false; }
var i : usize = 0;
- if (OUT_LEN != (b.len as u32) + a) { return false; }
- while (i < b.len) {
- if (OUT[(a as usize) + i] != b[i]) { return false; }
+ while (i < want.len) {
+ if (o[i] != want[i]) { return false; }
i = i + 1;
}
return true;
}
test the_writer_escapes_text_and_leaves_markup_alone {
- assert out_reset() == 0;
- open("p");
- attr("title", "a \"b\" & c");
- open_end();
- text("x < y & 'z'");
- close("p");
- assert same_bytes(0, "
x < y & 'z'
");
- assert out_overflowed() == false;
+ var o : [128]u8 = [0; 128];
+ var n : u32 = raw(o[0..], 0, "
");
+ n = text(o[0..], n, "x < y & 'z'");
+ n = raw(o[0..], n, "
");
+ assert same_bytes(o[0..], n, "
x < y & 'z'
");
+ assert ok(n);
}
test numbers_are_decimal_and_grouped_like_en_us {
- out_reset();
- num(0);
- put(32);
- num(1234567);
- put(32);
- num_grouped(0);
- put(32);
- num_grouped(999);
- put(32);
- num_grouped(1000);
- put(32);
- num_grouped(215360);
- put(32);
- num_grouped(1234567890);
- assert same_bytes(0, "0 1234567 0 999 1,000 215,360 1,234,567,890");
+ var o : [128]u8 = [0; 128];
+ var n : u32 = num(o[0..], 0, 0);
+ n = put(o[0..], n, 32);
+ n = num(o[0..], n, 1234567);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 0);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 999);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 1000);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 215360);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 1234567890);
+ assert same_bytes(o[0..], n, "0 1234567 0 999 1,000 215,360 1,234,567,890");
}
test fill_puts_values_in_the_template_and_escapes_them {
- out_reset();
- fill3("{0} of {1}, {2}{3}", "Gatle", "<6>", "end");
- assert same_bytes(0, "Gatle of <6>, end{3}");
+ var o : [128]u8 = [0; 128];
+ var n : u32 = fill(o[0..], 0, "{0} of {1}, {2}{3}", "Gatle", "<6>", "end", 3);
+ assert same_bytes(o[0..], n, "Gatle of <6>, end{3}");
+ n = fill(o[0..], 0, "{0}{1} it's", "x", "", "", 1);
+ assert same_bytes(o[0..], n, "x{1} it's");
}
test the_buffer_never_writes_past_its_end {
- out_reset();
- var i : u32 = 0;
- while (i < OUT_CAP + 10) {
- put(65);
- i = i + 1;
- }
- assert out_len() == OUT_CAP;
- assert out_overflowed();
- assert out_at(OUT_CAP - 1) == 65;
- out_reset();
- assert out_len() == 0;
- assert out_overflowed() == false;
+ var o : [8]u8 = [0; 8];
+ var n : u32 = raw(o[0..], 0, "12345678");
+ assert ok(n);
+ assert n == 8;
+ n = raw(o[0..], n, "9");
+ assert ok(n) == false;
+ n = text(o[0..], n, "more");
+ assert n == OVER;
+ assert o[7] == 56;
+ var p : [4]u8 = [0; 4];
+ assert text(p[0..], 0, "<<") == OVER;
}
diff --git a/specs/widgets/jtag_verdict.t27 b/specs/widgets/jtag_verdict.t27
new file mode 100644
index 0000000000..144177d227
--- /dev/null
+++ b/specs/widgets/jtag_verdict.t27
@@ -0,0 +1,689 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/jtag-verdict.t27 -- one IR capture and one DR word, and what they say about the link
+; Source of truth for public/widgets/jtag-verdict/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, every number the tool relies on is a K_ constant.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader types the raw IR capture and the raw DR word a JTAG probe shifted out
+; (or picks one of the seven vectors below). The page runs the same five checks, in the same order,
+; as t27 specs/port/tools/jtag/tdo_verdict.t27 and `tri fpga-jtag --decode HEX --ir HEX`: all ones,
+; all zeros, the IR capture rule, bit 0 of the DR word, and only then the IDCODE itself. It shows
+; which check decided, the bits it looked at, and the command that prints the same verdict.
+
+; THE RULE: IEEE 1149.1 Capture-IR loads binary 01 into the two least significant IR cells, so
+; the first two bits out of TDO after an IR scan are 1 then 0 on every compliant TAP. Above them
+; the device may put anything: Xilinx 7-series BSDL files give INSTRUCTION_CAPTURE as XXXX01, the
+; upper four being status. 0x35 (110101) is one value of that shape and is used here as the
+; example; it is not a bench reading. A DR word with bit 0 clear is BYPASS when the IR capture
+; holds 01, and noise when it does not: the same even word gets two different verdicts.
+
+; DATA PROVENANCE: the seven vectors are the test vectors of t27 specs/port/tools/jtag/
+; tdo_verdict.t27 at commit 60166a56194b5730dea355e704e98bc4f540cf14 (gHashTag/t27#8032, issue
+; #8030), and the tests below assert the same verdicts. 0x13636093 is the raw IDCODE read from our
+; XC7A200T on 2026-10-04 (openFPGALoader 1.1.1 --detect --verbose-level 2, see the idcode widget);
+; 0x03631093 is the XC7A100T entry of openFPGALoader v1.1.1 src/part.hpp.
+
+; WHAT IT DOES NOT CLAIM: that it talks to a cable. Nothing is read from hardware; the values are
+; typed. A clean verdict says the shift looks sane, not that the board is configured or healthy.
+; The device table has two entries; the idcode widget carries openFPGALoader's full list.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_jtag_verdict;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::page_reader::match_in;
+
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "jtag-verdict";
+pub const TITLE : str = "JTAG verdict: IR capture and DR word, BYPASS or a broken shift";
+pub const DESCRIPTION : str = "Type the IR capture and the DR word a JTAG probe shifted out. Five checks in a fixed order say stuck line, broken shift, BYPASS or a real IDCODE, and which bit decided.";
+pub const IMAGE_ALT : str = "JTAG verdict card: the same even DR word 0x13636092 read twice. With IR capture 0x35, whose low two bits are 01, the verdict is BYPASS: the TAP is alive. With IR capture 0x34, low bits 00, the verdict is broken shift: the DR word means nothing yet. Below, the five checks in order, from t27 spec tdo_verdict.t27 with 8 tests.";
+pub const HOOK : str = "Same DR word, two verdicts: IR 0x35 says BYPASS, IR 0x34 says your shift is broken. Type what your probe read and see which bit decides.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [4]str = ["t27 specs/port/tools/jtag/tdo_verdict.t27 at 60166a56194b5730dea355e704e98bc4f540cf14 (gHashTag/t27#8032, issue #8030): the order of the checks and the seven test vectors", "IEEE Std 1149.1 Capture-IR: binary 01 in the two least significant instruction register cells; Xilinx 7-series BSDL INSTRUCTION_CAPTURE XXXX01", "Bench 2026-10-04 UTC: openFPGALoader 1.1.1 --detect --verbose-level 2 on our XC7A200T: raw IDCODE 0x13636093 (the idcode widget's record)", "openFPGALoader v1.1.1 src/part.hpp: 0x03636093 xc7a200, 0x03631093 xc7a100"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+pub const K_CARD_WIDTH : u16 = 1200;
+pub const K_CARD_HEIGHT : u16 = 630;
+
+; --- Numbers the tool relies on ------------------------------------------------------------
+; Capture-IR: the low two bits must read 01.
+pub const K_IR_MASK : u32 = 3;
+pub const K_IR_LOW : u32 = 1;
+; 0xFFFFFFFF: TDO floating high, nothing drives it.
+pub const K_ALL_ONES : u32 = 4294967295;
+; Verdict codes, the same numbers as tdo_verdict.t27; they index SAY_VERDICT_NAMES from 1.
+pub const K_V_STUCK_HIGH : u8 = 1;
+pub const K_V_STUCK_LOW : u8 = 2;
+pub const K_V_BROKEN_SHIFT : u8 = 3;
+pub const K_V_BYPASS : u8 = 4;
+pub const K_V_IDCODE : u8 = 5;
+; IDCODE lookup ignores the version nibble: 0x0FFFFFFF.
+pub const K_VERSION_MASK : u32 = 268435455;
+pub const K_KNOWN_IDCODE : [2]u32 = [56844435, 56823955];
+; The seven vectors: IR capture, DR word, expected verdict.
+pub const K_CASE_IR : [7]u32 = [53, 53, 52, 54, 63, 0, 63];
+pub const K_CASE_DR : [7]u32 = [325279891, 325279890, 325279890, 325279891, 4294967295, 0, 325279891];
+pub const K_CASE_WANT : [7]u8 = [5, 4, 3, 3, 1, 2, 3];
+; The vector the page opens on: the even word behind a good IR capture.
+pub const K_START_CASE : u8 = 1;
+
+; --- Words on screen ------------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_LOADING : str = "Loading";
+pub const SAY_IR_LABEL : str = "IR capture (hex)";
+pub const SAY_DR_LABEL : str = "DR word (hex)";
+pub const SAY_IR_HINT : str = "The bits TDO shifted out during the IR scan, e.g. 0x35";
+pub const SAY_DR_HINT : str = "The 32 bits shifted out of the data register, e.g. 0x13636093";
+pub const SAY_CASES_LABEL : str = "The spec's test vectors";
+pub const SAY_CASE_NAMES : [7]str = ["real IDCODE", "BYPASS", "even word, IR 0x34", "odd word, IR 0x36", "all ones", "all zeros", "real-looking word, IR 0x3F"];
+pub const SAY_VERDICT_NAMES : [5]str = ["stuck high", "stuck low", "broken shift", "BYPASS", "IDCODE"];
+pub const SAY_VERDICT_WHY : [5]str = ["Every DR bit reads 1: nothing drives TDO. Check the cable's target voltage, board power, the ribbon and its pin 1, then the TDO wire.", "DR and IR both read 0: TDO is held low. Suspect the cable's own buffer before the board.", "IR capture {0} ends in {1}, not 01. Capture-IR always loads 01, so the shift itself is broken: TCK, the TMS order, or the TDO path. The DR word means nothing yet.", "IR capture {0} ends in 01, so the TAP shifts correctly, and DR bit 0 is 0: BYPASS is selected, not IDCODE. Load the IDCODE instruction, or this was read one bit late.", "IR capture {0} ends in 01 and DR bit 0 is 1: a real IDCODE."];
+pub const SAY_LADDER_TITLE : str = "The checks, in the order they run";
+pub const SAY_LADDER_STEPS : [5]str = ["DR is all ones", "DR is 0 and IR is 0", "IR does not end in 01", "DR bit 0 is 0", "otherwise"];
+pub const SAY_LADDER_DECIDED : str = "decided here";
+pub const SAY_LADDER_PASSED : str = "passed";
+pub const SAY_LADDER_SKIPPED : str = "not reached";
+pub const SAY_BITS_TITLE : str = "The bits that decide";
+pub const SAY_BITS_IR : str = "IR, low bit first";
+pub const SAY_BITS_DR : str = "DR bit 0";
+pub const SAY_BITS_IR_RULE : str = "must read 1 then 0";
+pub const SAY_DEVICE_KNOWN : str = "Masked IDCODE {0}: {1}";
+pub const SAY_DEVICE_NAMES : [2]str = ["XC7A200T", "XC7A100T"];
+pub const SAY_DEVICE_UNKNOWN : str = "Masked IDCODE {0}: not in this page's two-entry table.";
+pub const SAY_DECODER_LINK : str = "Take the word apart in the IDCODE decoder";
+pub const SAY_CLI_TITLE : str = "The same verdict on the command line";
+pub const SAY_CLI_LINE : str = "tri fpga-jtag --decode {0} --ir {1}";
+pub const SAY_CLI_EXIT : str = "exit {0}";
+pub const SAY_BAD_HEX : str = "Not a hex number that fits in 32 bits: {0}";
+pub const SAY_SELF_CHECK : str = "This page against the spec's {0} vectors: {1} agree.";
+pub const SAY_NOTE : str = "Nothing is read from a cable: the values are typed. 0x35 is an example of the XXXX01 shape, not a bench reading. A clean verdict says the shift looks sane, not that the board is configured.";
+pub const SAY_SPEC_LINK : str = "The spec: tdo_verdict.t27, 8 tests";
+pub const SAY_SPEC_HREF : str = "https://github.com/gHashTag/t27/pull/8032";
+pub const SAY_DECODER_HREF : str = "../idcode/#id={0}";
+; The page keeps the two words in its address: #ir=0x35&dr=0x13636092.
+pub const SAY_HASH_IR : str = "ir";
+pub const SAY_HASH_DR : str = "dr";
+pub const SAY_CARD_TITLE : str = "JTAG verdict";
+pub const SAY_CARD_BRAND : str = "Trinity S3AI";
+pub const SAY_CARD_HEAD : str = "Same DR word, two verdicts";
+pub const SAY_CARD_IR_LINE : str = "IR capture {0}, low bits {1}";
+pub const SAY_CARD_SPEC : str = "t27 spec tdo_verdict.t27, 8 tests";
+pub const SAY_CARD_URL : str = "t27.ai/widgets/jtag-verdict/";
+
+test the_card_is_x_shaped {
+ assert K_CARD_WIDTH == 1200;
+ assert K_CARD_HEIGHT == 630;
+}
+
+test the_page_sends_nothing {
+ assert SENDS_NOTHING == true;
+ assert READS_LOCAL_FILES == false;
+ assert CATEGORY == "fpga";
+}
+
+test the_vectors_cover_every_verdict {
+ assert K_CASE_WANT[0] == K_V_IDCODE;
+ assert K_CASE_WANT[1] == K_V_BYPASS;
+ assert K_CASE_WANT[2] == K_V_BROKEN_SHIFT;
+ assert K_CASE_WANT[4] == K_V_STUCK_HIGH;
+ assert K_CASE_WANT[5] == K_V_STUCK_LOW;
+ assert K_CASE_DR[4] == K_ALL_ONES;
+}
+
+test one_even_word_two_verdicts {
+ assert K_CASE_DR[1] == K_CASE_DR[2];
+ assert K_CASE_IR[1] % 4 == K_IR_LOW;
+ assert K_CASE_IR[2] % 4 != K_IR_LOW;
+ assert K_CASE_DR[1] % 2 == 0;
+ assert K_CASE_WANT[1] != K_CASE_WANT[2];
+}
+
+test the_ir_rule_decides_the_broken_shifts {
+ assert K_CASE_IR[3] % 4 != K_IR_LOW;
+ assert K_CASE_IR[6] % 4 != K_IR_LOW;
+ assert K_CASE_DR[6] % 2 == 1;
+ assert K_CASE_IR[0] % 4 == K_IR_LOW;
+ assert K_CASE_DR[0] % 2 == 1;
+}
+
+test the_bench_word_masks_to_the_200t {
+ assert K_CASE_DR[0] % (K_VERSION_MASK + 1) == K_KNOWN_IDCODE[0];
+ assert K_IR_MASK == 3;
+}
+
+test the_page_opens_on_bypass {
+ assert K_V_IDCODE == 5;
+ assert K_START_CASE < 7;
+ assert K_CASE_WANT[K_START_CASE] == K_V_BYPASS;
+}
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/jtag-verdict/tool.js (171 lines of hand-written JavaScript).
+; The words and numbers above are unchanged. The page is these functions, built to wasm (t27c gen-c,
+; then zig cc --target=wasm32-wasi) and driven by the one host glue every native widget shares: it
+; forwards input, click and hashchange events, then sets the bytes w_render() wrote as the page's
+; innerHTML, keeping the focused input. Host protocol, all exports of this module:
+; w_init(hash) the page opens; hash is the address's #fragment bytes (empty when none)
+; w_input(id, text) a field changed: id 0 is the IR field, 1 the DR field; its whole text
+; w_click(i) preset button i was pressed
+; w_hash(hash) the address changed
+; w_render(dst) write the page's HTML into dst, return its length (0 on overflow)
+; w_hash_out(dst) the address to show after the last event (length 0: leave it)
+; The state is the two field texts; the rest is derived on every render, as tool.js's update() did.
+; Not carried over from JavaScript: Unicode white space around a typed number (its \s) is not ASCII;
+; field text is cut at FIELD_CAP bytes; a malformed %-escape in the address is read as no value
+; (decodeURIComponent would throw).
+; MEASURED 2026-10-19: t27c gen, a 6-line Zig export wrapper and `zig build-exe -target
+; wasm32-freestanding -O ReleaseSmall` give a 9269-byte wasm. A browser harness loaded the original
+; page (tool.js) and this one side by side and compared their DOM (tag, attributes, text, the two
+; input values, the address) after 3015 actions: the initial page, the 7 preset buttons, 3000 typed
+; values (valid hex with and without 0x, spaces and underscores, 9-digit overflow, empty, quotes,
+; markup, random ASCII) and 7 hash changes, plus 7 page loads that carry an address: 0 differences.
+; Changing one text node of the native page makes the harness report a difference (control).
+; Not carried over from the page: nothing else; focus after a preset click is kept by the host.
+
+pub const FIELD_CAP : u32 = 255;
+pub var IR_TEXT : [255]u8 = [0; 255];
+pub var IR_LEN : u32 = 0;
+pub var DR_TEXT : [255]u8 = [0; 255];
+pub var DR_LEN : u32 = 0;
+pub var PRESSED : [7]u8 = [0; 7];
+pub var HASH_NEW : [64]u8 = [0; 64];
+pub var HASH_NEW_LEN : u32 = 0;
+pub var HEX_A : [16]u8 = [0; 16];
+pub var HEX_B : [16]u8 = [0; 16];
+pub var BIN2 : [2]u8 = [0; 2];
+pub var DECODED : [64]u8 = [0; 64];
+pub const NOT_HEX : i64 = -1;
+
+/// The five checks, in the order of specs/port/tools/jtag/tdo_verdict.t27 (written in an older dialect
+/// that cannot be imported; the seven vectors above are its test vectors and the tests below hold this to them).
+pub fn jtag_verdict(ir: u32, dr: u32) -> u8 {
+ if (dr == K_ALL_ONES) { return K_V_STUCK_HIGH; }
+ if (dr == 0 and ir == 0) { return K_V_STUCK_LOW; }
+ if ((ir & K_IR_MASK) != K_IR_LOW) { return K_V_BROKEN_SHIFT; }
+ if ((dr & 1) == 0) { return K_V_BYPASS; }
+ return K_V_IDCODE;
+}
+
+fn is_ws(b: u8) -> bool {
+ return b == 32 or (b >= 9 and b <= 13);
+}
+
+fn hex_val(b: u8) -> i64 {
+ if (b >= 48 and b <= 57) { return (b - 48) as i64; }
+ if (b >= 97 and b <= 102) { return (b - 97) as i64 + 10; }
+ if (b >= 65 and b <= 70) { return (b - 65) as i64 + 10; }
+ return -1;
+}
+
+/// parseHex: trim, drop white space and underscores, optional 0x, one to eight hex digits; else -1.
+pub fn parse_hex(s: []const u8) -> i64 {
+ var pos : u32 = 0;
+ var first : u8 = 0;
+ var digits : u32 = 0;
+ var val : i64 = 0;
+ var i : usize = 0;
+ while (i < s.len) {
+ const b : u8 = s[i];
+ i = i + 1;
+ if (is_ws(b) or b == 95) { continue; }
+ if (pos == 1 and first == 48 and (b == 120 or b == 88)) {
+ digits = 0;
+ val = 0;
+ pos = pos + 1;
+ continue;
+ }
+ const h : i64 = hex_val(b);
+ if (h < 0) { return NOT_HEX; }
+ if (pos == 0) { first = b; }
+ digits = digits + 1;
+ if (digits <= 8) { val = val * 16 + h; }
+ pos = pos + 1;
+ }
+ if (digits < 1 or digits > 8) { return NOT_HEX; }
+ return val;
+}
+
+/// hexN: "0x" and the lowercase hex digits of v, at least min_digits of them; written to dst, length returned.
+pub fn hex_to(dst: []u8, v: u32, min_digits: u32) -> u32 {
+ dst[0] = 48;
+ dst[1] = 120;
+ var count : u32 = 1;
+ var t : u32 = v / 16;
+ while (t > 0) {
+ count = count + 1;
+ t = t / 16;
+ }
+ var width : u32 = count;
+ if (min_digits > width) { width = min_digits; }
+ var k : u32 = width;
+ while (k > 0) {
+ k = k - 1;
+ var d : u32 = v;
+ var sh : u32 = 0;
+ while (sh < k) {
+ d = d / 16;
+ sh = sh + 1;
+ }
+ const nib : u32 = d % 16;
+ if (nib < 10) { dst[(2 + width - 1 - k) as usize] = (nib as u8) + 48; } else { dst[(2 + width - 1 - k) as usize] = (nib as u8) + 87; }
+ }
+ return 2 + width;
+}
+
+fn ir_value() -> i64 {
+ return parse_hex(IR_TEXT[0..(IR_LEN as usize)]);
+}
+
+fn dr_value() -> i64 {
+ return parse_hex(DR_TEXT[0..(DR_LEN as usize)]);
+}
+
+fn set_text(which: u32, s: []const u8) -> u32 {
+ var n : usize = s.len;
+ if (n > (FIELD_CAP as usize)) { n = FIELD_CAP as usize; }
+ var i : usize = 0;
+ while (i < n) {
+ if (which == 0) { IR_TEXT[i] = s[i]; } else { DR_TEXT[i] = s[i]; }
+ i = i + 1;
+ }
+ if (which == 0) { IR_LEN = n as u32; } else { DR_LEN = n as u32; }
+ return 0;
+}
+
+fn set_field_hex(which: u32, v: u32, digits: u32) -> u32 {
+ const n : u32 = hex_to(HEX_A[0..], v, digits);
+ return set_text(which, HEX_A[0..(n as usize)]);
+}
+
+/// The two fields are good when both parse; then the pressed preset and the new address follow.
+fn recompute(write_hash: bool) -> u32 {
+ HASH_NEW_LEN = 0;
+ const ir : i64 = ir_value();
+ const dr : i64 = dr_value();
+ if (ir < 0 or dr < 0) { return 0; }
+ var i : u32 = 0;
+ while (i < 7) {
+ PRESSED[i as usize] = 0;
+ if (K_CASE_IR[i as usize] == (ir as u32) and K_CASE_DR[i as usize] == (dr as u32)) { PRESSED[i as usize] = 1; }
+ i = i + 1;
+ }
+ if (write_hash) {
+ var n : u32 = 0;
+ HASH_NEW[0] = 35;
+ n = 1;
+ var k : usize = 0;
+ while (k < SAY_HASH_IR.len) { HASH_NEW[n as usize] = SAY_HASH_IR[k]; n = n + 1; k = k + 1; }
+ HASH_NEW[n as usize] = 61;
+ n = n + 1;
+ n = n + hex_to(HASH_NEW[(n as usize)..], ir as u32, 2);
+ HASH_NEW[n as usize] = 38;
+ n = n + 1;
+ k = 0;
+ while (k < SAY_HASH_DR.len) { HASH_NEW[n as usize] = SAY_HASH_DR[k]; n = n + 1; k = k + 1; }
+ HASH_NEW[n as usize] = 61;
+ n = n + 1;
+ n = n + hex_to(HASH_NEW[(n as usize)..], dr as u32, 8);
+ HASH_NEW_LEN = n;
+ }
+ return 0;
+}
+
+/// fromHash's get(): the hex value of #key=... or &key=... in the address, -1 when absent or unreadable.
+fn hash_get(h: []const u8, key: []const u8) -> i64 {
+ var p : usize = 0;
+ while (p + 1 + key.len < h.len) {
+ if ((h[p] == 35 or h[p] == 38) and match_in(h, p + 1, h.len, key) and h[p + 1 + key.len] == 61) {
+ var a : usize = p + 2 + key.len;
+ var e : usize = a;
+ while (e < h.len and h[e] != 38) { e = e + 1; }
+ if (e == a) { return NOT_HEX; }
+ var n : usize = 0;
+ var i : usize = a;
+ while (i < e) {
+ if (n >= 64) { return NOT_HEX; }
+ if (h[i] == 37) {
+ if (i + 2 >= e) { return NOT_HEX; }
+ const hi : i64 = hex_val(h[i + 1]);
+ const lo : i64 = hex_val(h[i + 2]);
+ if (hi < 0 or lo < 0) { return NOT_HEX; }
+ DECODED[n] = ((hi * 16 + lo) as u8);
+ i = i + 3;
+ } else {
+ DECODED[n] = h[i];
+ i = i + 1;
+ }
+ n = n + 1;
+ }
+ return parse_hex(DECODED[0..n]);
+ }
+ p = p + 1;
+ }
+ return NOT_HEX;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ const ir : i64 = hash_get(h, SAY_HASH_IR);
+ const dr : i64 = hash_get(h, SAY_HASH_DR);
+ var start : u32 = K_START_CASE as u32;
+ if (ir >= 0) { set_field_hex(0, ir as u32, 2); } else { set_field_hex(0, K_CASE_IR[start as usize], 2); }
+ if (dr >= 0) { set_field_hex(1, dr as u32, 8); } else { set_field_hex(1, K_CASE_DR[start as usize], 8); }
+ return recompute(false);
+}
+
+pub fn w_input(id: u32, s: []const u8) -> u32 {
+ set_text(id, s);
+ return recompute(true);
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (i >= 7) { return 0; }
+ set_field_hex(0, K_CASE_IR[i as usize], 2);
+ set_field_hex(1, K_CASE_DR[i as usize], 8);
+ return recompute(true);
+}
+
+pub fn w_hash(h: []const u8) -> u32 {
+ const ir : i64 = hash_get(h, SAY_HASH_IR);
+ const dr : i64 = hash_get(h, SAY_HASH_DR);
+ if (ir < 0 or dr < 0) { return recompute(false); }
+ set_field_hex(0, ir as u32, 2);
+ set_field_hex(1, dr as u32, 8);
+ return recompute(false);
+}
+
+pub fn w_hash_out(dst: []u8) -> u32 {
+ var i : u32 = 0;
+ while (i < HASH_NEW_LEN) {
+ dst[i as usize] = HASH_NEW[i as usize];
+ i = i + 1;
+ }
+ return HASH_NEW_LEN;
+}
+
+fn agree_count() -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < 7) {
+ if (jtag_verdict(K_CASE_IR[i as usize], K_CASE_DR[i as usize]) == K_CASE_WANT[i as usize]) { n = n + 1; }
+ i = i + 1;
+ }
+ return n;
+}
+
+fn bit_cells(dst: []u8, n0: u32, v: u32, count: u32) -> u32 {
+ var n : u32 = n0;
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < count) {
+ n = raw(dst, n, "");
+ n = put(dst, n, (((v >> i) % 2) as u8) + 48);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "");
+ return n;
+}
+
+fn field(dst: []u8, n0: u32, id: u32, label: []const u8, hint: []const u8) -> u32 {
+ var n : u32 = n0;
+ if (id == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ROUND_NOTE);
+ n = raw(dst, n, "
");
+ n = storage_html(dst, n, d, r);
+ n = raw(dst, n, "");
+ n = limits_html(dst, n, d, r);
+ n = raw(dst, n, "");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call.
+/// Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ return n;
+}
From 6a2fc5ad56d8bc0be17c46cae0ed133b0e183d4f Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 17:21:44 +0700
Subject: [PATCH 30/33] widgets: build-receipt drawn natively -- 32541-byte
wasm, 951 DOM comparisons, 0 differences (Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/build_receipt.t27 | 1388 +++++++++++++++++++++++++++++++
1 file changed, 1388 insertions(+)
create mode 100644 specs/widgets/build_receipt.t27
diff --git a/specs/widgets/build_receipt.t27 b/specs/widgets/build_receipt.t27
new file mode 100644
index 0000000000..6da77f9198
--- /dev/null
+++ b/specs/widgets/build_receipt.t27
@@ -0,0 +1,1388 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/build-receipt.t27 -- one open-flow build, t27 spec to XC7A200T bitstream, as a receipt
+; Source of truth for public/widgets/build-receipt/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, every number the tool relies on is a K_ constant or a field
+; of receipt.json written by the same script run.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT SHOWS: a shop-style receipt for one real build on one machine. Each stage of the open flow
+; is a line item with its wall time, its tool and version, the size of the file it wrote and that
+; file's sha256: the t27 compiler (WebAssembly, in node) turns specs/ternary/stream_ternary_mac.t27
+; into Verilog, yosys synthesises it, nextpnr-xilinx places and routes it on the xc7a200tfbg676-1
+; chipdb, prjxray's fasm2frames and xc7frames2bit write the bitstream, and the last line hashes it.
+; Under the items: the TOTAL, a line that no vendor tool and no licence was used, the exact commands,
+; and how to reproduce it. A barcode is drawn from the bitstream's sha256.
+
+; DATA PROVENANCE: scripts/widget-data/build-receipt.mjs ran every stage in /tmp/widget-receipt and
+; wrote receipt.json and the generated block below; it then re-checked that the stage times sum to
+; the total within rounding and that the stage count matches the data. The datapath is generated
+; from the t27 spec; the board wrapper (clock from STARTUPE2's CFGMCLK, two LFSRs as stimulus, three
+; LEDs) is hand-written Verilog, scripts/widget-data/build-receipt-top.v, and the receipt says so.
+; Outside the total, bitwalk (gHashTag/t27 specs/xilinx7, generated by the same compiler's rust
+; target) writes a second .bit from the same frames to check xc7frames2bit's payload.
+
+; WHAT IT DOES NOT CLAIM: that the bitstream was loaded into a board (it was not: no JTAG was run),
+; that the design does anything useful on one, or that the times hold on another machine. The run
+; shared the machine with other work; its load average at start is printed on the receipt. No Vivado
+; was run, so there is no Vivado time to compare against.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_build_receipt;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "build-receipt";
+pub const TITLE : str = "Build receipt: t27 spec to XC7A200T bitstream, no Vivado";
+pub const DESCRIPTION : str = "One real open-flow build itemised like a shop receipt: each stage from a t27 spec to an Artix-7 200T bitstream with its time, tool version, output size and sha256.";
+pub const IMAGE_ALT : str = "A white thermal-paper receipt on black. Header: Trinity S3AI open flow, build receipt. Six line items with times: t27 to Verilog 0.288 s, synthesis 29.608 s, place and route 57.238 s, FASM to frames 15.979 s, frames to .bit 0.635 s, sha256 0.016 s. TOTAL 103.764 s. Vivado licence 0. Below: a barcode drawn from the bitstream sha256 and the first hex digits of that sha.";
+pub const HOOK : str = "From a ternary spec to an Artix-7 200T bitstream with no Vivado: every stage timed, versioned and hashed.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [7]str = ["scripts/widget-data/build-receipt.mjs run, 2026-10-04 on an Apple M1 Pro: every stage time (process.hrtime), cpu and peak memory (/usr/bin/time -l), output size and sha256 (node crypto), written to receipt.json", "public/t27/t27_compiler.wasm in node, targets.verilog of specs/ternary/stream_ternary_mac.t27", "Yosys 0.67+post b8e7da6f40: yosys -V; synth_xilinx -flatten -abc9 -arch xc7", "nextpnr-xilinx 0.9.2-107-g7037c948 with ~/openxc7-src/chipdb/xc7a200tfbg676-1.bin: fmax and utilisation from its log, --freq 80 --seed 1", "prjxray c9f02d85 fasm2frames.py and xc7frames2bit with prjxray-db ab1fc60 artix7 xc7a200tfbg676-1 part.yaml", "bitwalk cross-check: gHashTag/t27 29b78e7fe specs/xilinx7, rust target of the same compiler, rustc 1.98.1", "LED pins R23, T23, J26 LVCMOS33: gHashTag/t27 fpga/vsa/gf16_heartbeat_top.xdc (QMTech Wukong V1)"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Numbers the tool relies on ------------------------------------------------------------
+pub const K_DATA_FILE : str = "./receipt.json";
+; The open flow has six stages: gen, synth, pnr, frames, bit, sha256.
+pub const K_STAGE_PLAN : u8 = 6;
+; Each stage time is rounded to the millisecond on its own and the total once, from the exact sum.
+; So the items may miss the total by at most half a millisecond each: K_STAGE_PLAN / 2, rounded up.
+pub const K_ROUND_TOL_MS : u8 = 3;
+
+; --- The run: written by the script, re-checked by it and by the tests below ---------------
+; --- BEGIN generated by scripts/widget-data/build-receipt.mjs run: do not edit by hand ---
+; Run of 2026-10-04T17:41:38Z on Apple M1 Pro, 8 cores, load 151.2 at start.
+pub const K_STAGE_COUNT : u8 = 6;
+pub const K_STAGES_OK : u8 = 6;
+pub const K_STAGE_MS : [6]u32 = [288, 29608, 57238, 15979, 635, 16];
+pub const K_TOTAL_MS : u32 = 103764;
+pub const K_BIT_BYTES : u32 = 9730757;
+pub const K_VENDOR_TOOLS_RUN : u8 = 0;
+; --- END generated ---
+
+pub const SAY_STAGE_NAMES : [6]str = ["t27 to Verilog", "synthesis", "place and route", "FASM to frames", "frames to .bit", "sha256 of the .bit"];
+
+; --- Words on the receipt -----------------------------------------------------------------
+; Templates fill {name} slots from receipt.json; tool.js adds no words of its own.
+pub const SAY_SHOP : str = "TRINITY S3AI OPEN FLOW";
+pub const SAY_RECEIPT : str = "build receipt no. {no}";
+pub const SAY_WHEN : str = "{when} UTC";
+pub const SAY_MACHINE : str = "{cpu}, {cores} cores, macOS {macos}";
+pub const SAY_LOAD : str = "load average {load} at start: the machine was shared";
+pub const SAY_LABEL_SPEC : str = "spec";
+pub const SAY_LABEL_PART : str = "part";
+pub const SAY_LABEL_BOARD : str = "board";
+pub const SAY_WRAPPER : str = "datapath {name} is generated from the spec; the board wrapper is {lines} lines of hand-written Verilog";
+pub const SAY_COL_ITEM : str = "ITEM";
+pub const SAY_COL_TIME : str = "TIME";
+pub const SAY_OUT : str = "wrote {file}, {size}";
+pub const SAY_SHA : str = "sha256 {sha}";
+pub const SAY_CPU : str = "cpu {cpu} s, peak memory {rss} MB";
+pub const SAY_FAILED : str = "FAILED, exit {exit}";
+pub const SAY_NOT_RUN : str = "not run";
+pub const SAY_ITEMS : str = "ITEMS {ok} of {count}";
+pub const SAY_TOTAL : str = "TOTAL";
+pub const SAY_NO_TOTAL : str = "NO TOTAL: a stage failed after {s} in all";
+pub const SAY_VIVADO : str = "Vivado licence";
+pub const SAY_VENDOR : str = "vendor tools run";
+pub const SAY_END_TO_END : str = "end to end, with the script's own work: {s}";
+pub const SAY_FMAX : str = "fmax {clock} {mhz} MHz, target {target} MHz";
+pub const SAY_PASS : str = "PASS";
+pub const SAY_FAIL : str = "FAIL";
+pub const SAY_UTIL_TITLE : str = "used on the 200T";
+pub const SAY_FILE_SHA : str = "sha256 of the .bit file";
+pub const SAY_FILE_SHA_NOTE : str = "changes every run: the file header holds a path, a date and a time";
+pub const SAY_PAYLOAD_SHA : str = "sha256 from the sync word on, {size}";
+pub const SAY_SAME_PAYLOAD : str = "same payload as the {n} earlier run(s) of the same inputs";
+pub const SAY_OTHER_PAYLOAD : str = "payload differs from an earlier run of the same inputs";
+pub const SAY_EARLIER : str = "earlier run {when}: total {s}, load {load}";
+pub const SAY_CROSS_OK : str = "cross-check: bitwalk, t27's own writer, wrote the same payload from the same frames in {ms} ms (not in the total)";
+pub const SAY_CROSS_BAD : str = "cross-check: bitwalk wrote a different payload from the same frames";
+pub const SAY_CROSS_SKIP : str = "cross-check: bitwalk was not run";
+pub const SAY_NOT_PROGRAMMED : str = "Not loaded into any board: no JTAG was run.";
+pub const SAY_BARCODE : str = "Barcode drawn from the sha256 of the .bit file";
+pub const SAY_FOOTER : str = "phi^2 + 1/phi^2 = 3 | keep the sha, not the receipt";
+pub const SAY_SHOW_CMDS : str = "show commands";
+pub const SAY_HIDE_CMDS : str = "hide commands";
+pub const SAY_VERIFY_TITLE : str = "Verify it yourself";
+pub const SAY_VERIFY_STEPS : [5]str = ["Install the openXC7 tools: yosys, nextpnr-xilinx with the xc7a200tfbg676-1 chipdb, and prjxray with its database.", "Clone gHashTag/trinity, then run: cd apps/website && node scripts/widget-data/build-receipt.mjs run", "Compare the payload sha256 (from the sync word on), not the file sha256: the file header holds a path and a time.", "Run node scripts/widget-data/build-receipt.mjs check: it re-adds the stage times and recounts the stages.", "Expect other times: these were taken once, on one shared laptop."];
+pub const SAY_SPEC_DIFF : str = "the spec and receipt.json disagree on {what}";
+pub const SAY_LOADING : str = "printing the receipt";
+pub const SAY_ERROR : str = "could not read the receipt data";
+pub const SAY_UNITS : [4]str = ["B", "kB", "MB", "GB"];
+pub const SAY_SECONDS : str = "{s} s";
+
+; --- Tests: the generator evaluates these before it writes the page ----------------------
+test the_items_add_up_to_the_total {
+ assert K_STAGE_MS[0] + K_STAGE_MS[1] + K_STAGE_MS[2] + K_STAGE_MS[3] + K_STAGE_MS[4] + K_STAGE_MS[5] <= K_TOTAL_MS + K_ROUND_TOL_MS;
+ assert K_STAGE_MS[0] + K_STAGE_MS[1] + K_STAGE_MS[2] + K_STAGE_MS[3] + K_STAGE_MS[4] + K_STAGE_MS[5] + K_ROUND_TOL_MS >= K_TOTAL_MS;
+ assert K_ROUND_TOL_MS == (K_STAGE_PLAN + 1) / 2;
+}
+
+test the_stage_count_matches_the_data {
+ assert K_STAGE_COUNT == K_STAGE_PLAN;
+ assert K_STAGES_OK <= K_STAGE_COUNT;
+ assert SAY_STAGE_NAMES[5] == "sha256 of the .bit";
+}
+
+test no_vendor_tool_was_run {
+ assert K_VENDOR_TOOLS_RUN == 0;
+ assert K_BIT_BYTES > 0;
+}
+
+test the_page_sends_nothing {
+ assert READS_LOCAL_FILES == false;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+ assert K_DATA_FILE == "./receipt.json";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/build-receipt/tool.js (221 lines of hand-written JavaScript).
+; The words and numbers above are unchanged. The host protocol is race_the_bee.t27's:
+; w_init(hash) the page opens: "printing the receipt"
+; w_data(i, bytes) file i of K_DATA_FILE has been fetched; w_data_failed(i) when it could not be
+; w_click(i) the "show commands" button (the only one) was pressed
+; w_render(dst) the page's HTML, length (0 on overflow)
+; The receipt is drawn from the bytes of receipt.json (json_scan.t27) and the values are written the way
+; tool.js's String() / toFixed(3) / Math.round / template literals write them (js_val.t27, js_num.t27).
+; Where tool.js throws while it draws (receipt.stages no array, a null stage, a machine that is a string
+; or a number, a payload digest that is no string, a utilisation row that is no array, earlier runs with a
+; null entry, commands that are no array) its catch shows SAY_ERROR; so does this page (data_ok).
+; The spec-versus-data check (specDiff) reads the K_ constants of this very file.
+; NOT carried over: String.prototype.toLowerCase / toUpperCase beyond ASCII and Latin-1 (the tool-name
+; test and the receipt number); Object.entries order for integer-like keys of `utilisation`, and duplicate keys; a
+; non-empty string as `utilisation` or `earlier_runs`, and a string as a row of `utilisation`;
+; the data file is cut at DATA_CAP bytes (the page then says it could not be read); a digest longer than
+; 293 hex digits is cut in the barcode and one longer than 511 bytes everywhere.
+; MEASURED 2026-10-19: t27c 0.5.2 gen + a 5-line Zig export wrapper + zig build-exe wasm32-freestanding
+; ReleaseSmall give a 32541-byte wasm. The original page (tool.js) and this one ran in two happy-dom
+; windows against the real 6673-byte receipt.json and 188 variants of it, 951 comparisons of tag,
+; attributes (the barcode svg included) and text, the button pressed up to 12 times in a row (569 presses)
+; and the page before the data arrives and after: no fmax / a failing one / an odd one, utilisation empty /
+; absent / an array / short rows / odd keys, stages not run / failed (with and without exit and error) /
+; with odd status words, outputs missing / partial / byte sizes at every unit boundary (0, 999, 1000,
+; 999999, 1e9, 1.5e12, negative, a string, 7e21), cpu and rss forms, ms of every form (a string makes the
+; sum a string, as in JavaScript), an extra / fewer / duplicate / one stage, every kind of spec-versus-data
+; disagreement (the warning line), a missing / null bit, digests of every shape (upper case, short, empty,
+; non-hex, 200 digits, a number, Latin-1 letters), cross-check ok / bad / skipped / missing, earlier runs
+; none / many / odd stamps, an incomplete receipt, vendor tools of every form, machine and design missing /
+; partial / numbers, commands empty / markup / nested, version and tool names with and without a match,
+; markup and unicode in every string, 15 data files the page cannot draw (a root that is an array or null,
+; stages missing / an object / with a null, a number and a string in it, a machine that is a string or a
+; number, a payload that is a number or an array, a utilisation row that is a number or null, an earlier
+; run that is null, commands that are a string or an object): all end on the error line in both; 70 files
+; with random numbers and strings in every field, 8 long sessions, a 404 and a body that is not JSON:
+; 0 differences. A negative control (one node of the native DOM changed) is caught.
+; Not compared: the stylesheet (happy-dom); the cases under "NOT carried over".
+
+pub const DATA_CAP : u32 = 32768;
+pub var DATA : [32768]u8 = [0; 32768];
+pub var DATA_LEN : u32 = 0;
+pub var DATA_STATE : u8 = 0;
+pub var CMDS_ON : bool = false;
+
+pub fn w_init(h: []const u8) -> u32 {
+ DATA_STATE = 0;
+ DATA_LEN = 0;
+ CMDS_ON = false;
+ return 0;
+}
+
+fn nullish(d: []const u8, sp: i64) -> bool {
+ const k : u8 = kind(d, sp);
+ return k == J_NONE or k == J_NULL;
+}
+
+/// receipt.stages is an array, every stage is non-null, and the rest of render() would not throw.
+fn data_ok(d: []const u8, r: i64) -> bool {
+ if (kind(d, r) != J_OBJECT) { return false; }
+ const st : i64 = obj_get(d, r, "stages");
+ if (kind(d, st) != J_ARRAY) { return false; }
+ const cnt : u32 = arr_len(d, st);
+ var i : u32 = 0;
+ while (i < cnt) {
+ if (nullish(d, arr_at(d, st, i))) { return false; }
+ i = i + 1;
+ }
+ const m : i64 = obj_get(d, r, "machine");
+ if (jv_truthy(d, m) and kind(d, m) != J_OBJECT and kind(d, m) != J_ARRAY) { return false; }
+ const bit : i64 = obj_get(d, r, "bit");
+ var pay : i64 = obj_get(d, bit, "payload_sha256");
+ if (nullish(d, pay)) { pay = obj_get(d, bit, "sha256"); }
+ if (!nullish(d, pay) and kind(d, pay) != J_STRING) { return false; }
+ const ut : i64 = obj_get(d, r, "utilisation");
+ if (jv_truthy(d, ut)) {
+ const k : u8 = kind(d, ut);
+ if (k == J_STRING) { return false; }
+ if (k == J_OBJECT or k == J_ARRAY) {
+ if (!entries_ok(d, ut)) { return false; }
+ }
+ }
+ if (jv_truthy(d, bit)) {
+ const er : i64 = obj_get(d, r, "earlier_runs");
+ if (jv_truthy(d, er) and kind(d, er) == J_STRING) { return false; }
+ if (kind(d, er) == J_OBJECT and jv_truthy(d, obj_get(d, er, "length"))) { return false; }
+ if (kind(d, er) == J_ARRAY) {
+ const ec : u32 = arr_len(d, er);
+ var j : u32 = 0;
+ while (j < ec) {
+ if (nullish(d, arr_at(d, er, j))) { return false; }
+ j = j + 1;
+ }
+ }
+ }
+ const cm : i64 = obj_get(d, r, "commands");
+ if (jv_truthy(d, cm) and kind(d, cm) != J_ARRAY) { return false; }
+ return true;
+}
+
+/// Every entry of the object / array `ut` is an array (the `[used, total]` destructuring does not throw).
+fn entries_ok(d: []const u8, ut: i64) -> bool {
+ if (kind(d, ut) == J_ARRAY) {
+ const c : u32 = arr_len(d, ut);
+ var i : u32 = 0;
+ while (i < c) {
+ const v : i64 = arr_at(d, ut, i);
+ if (kind(d, v) != J_ARRAY) { return false; }
+ i = i + 1;
+ }
+ return true;
+ }
+ // an object: walk its members in file order
+ const hi : usize = span_hi(ut);
+ var p : usize = skip_ws(d, span_lo(ut) + 1);
+ while (p < hi and d[p] == 34) {
+ const ke : usize = string_end(d, p);
+ const q : usize = skip_ws(d, ke);
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ if (kind(d, jspan(v, ve)) != J_ARRAY) { return false; }
+ p = skip_ws(d, ve);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ return true;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (s.len > (DATA_CAP as usize)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < s.len) {
+ DATA[k] = s[k];
+ k = k + 1;
+ }
+ DATA_LEN = s.len as u32;
+ DATA_STATE = 1;
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (!jv_valid(d)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ if (!data_ok(d, root(d))) { DATA_STATE = 2; }
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ DATA_STATE = 2;
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (i == 0 and DATA_STATE == 1) { CMDS_ON = !CMDS_ON; }
+ return 0;
+}
+
+// --- small writers ------------------------------------------------------------------------------
+
+///
0) {
+ n = put(dst, n, 32);
+ n = raw(dst, n, cls);
+ }
+ return raw(dst, n, "\">");
+}
+
+fn rule_html(dst: []u8, n0: u32) -> u32 {
+ return raw(dst, n0, "");
+}
+
+/// line(left, right, cls) up to the left text.
+fn line_open(dst: []u8, n0: u32, cls: []const u8) -> u32 {
+ var n : u32 = div_open(dst, n0, "br-line", cls);
+ return raw(dst, n, "");
+}
+
+fn line_mid(dst: []u8, n0: u32) -> u32 {
+ return raw(dst, n0, "");
+}
+
+fn line_close(dst: []u8, n0: u32) -> u32 {
+ return raw(dst, n0, "
");
+}
+
+fn put_all(dst: []u8, n0: u32, s: []const u8, l: u32) -> u32 {
+ var n : u32 = n0;
+ var i : u32 = 0;
+ while (i < l) {
+ n = put_escaped(dst, n, s[i as usize]);
+ i = i + 1;
+ }
+ return n;
+}
+
+/// The text of a textContent / child assignment: null and undefined add nothing.
+fn tc_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return n0; }
+ return jv_text(dst, n0, d, sp);
+}
+
+/// (ms / 1000).toFixed(3) in SAY_SECONDS.
+fn secs_text(dst: []u8, n0: u32, ms: f64) -> u32 {
+ var vb : [128]u8 = [0; 128];
+ const vn : u32 = jv_vput_fixed(vb[0..], 0, "s", ms / 1000.0, 3);
+ return jv_fill(dst, n0, SAY_SECONDS, vb[0..], vn, false);
+}
+
+/// `x ?? 0` then Number(): the coerced number of a field with a default of 0.
+fn num_or0(d: []const u8, sp: i64) -> f64 {
+ if (nullish(d, sp)) { return 0.0; }
+ return jv_num(d, sp);
+}
+
+/// Trim JavaScript white space off buf[0..l); the new start in lo and end in hi (written back through the arrays).
+fn trim_range(buf: []const u8, l: u32, out: []u32) -> u32 {
+ var lo : u32 = 0;
+ var hi : u32 = l;
+ var w : u32 = 1;
+ while (lo < hi and w > 0) {
+ w = jv_ws_at(buf, lo, hi);
+ lo = lo + w;
+ }
+ w = 1;
+ while (hi > lo and w > 0) {
+ w = jv_ws_before(buf, lo, hi);
+ hi = hi - w;
+ }
+ out[0] = lo;
+ out[1] = hi;
+ return 0;
+}
+
+/// size(bytes) as raw bytes into out, trimmed: `12.34 MB (12345678 B)` / `4492 B`; the length.
+fn size_raw(out: []u8, d: []const u8, sp: i64) -> u32 {
+ var tmp : [512]u8 = [0; 512];
+ var t : u32 = 0;
+ var v : f64 = jv_num(d, sp);
+ var i : u32 = 0;
+ const last : u32 = (SAY_UNITS.len as u32) - 1;
+ while (v >= 1000.0 and i < last) {
+ v = v / 1000.0;
+ i = i + 1;
+ }
+ if (i == 0) {
+ t = jv_str(tmp[0..], t, d, sp);
+ } else {
+ t = js_to_fixed(tmp[0..], t, v, 2);
+ }
+ t = put(tmp[0..], t, 32);
+ t = jn_raw(tmp[0..], t, SAY_UNITS[i as usize]);
+ if (i != 0) {
+ t = jn_raw(tmp[0..], t, " (");
+ t = jv_str(tmp[0..], t, d, sp);
+ t = put(tmp[0..], t, 32);
+ t = jn_raw(tmp[0..], t, SAY_UNITS[0]);
+ t = put(tmp[0..], t, 41);
+ }
+ var rg : [2]u32 = [0; 2];
+ trim_range(tmp[0..], t, rg[0..]);
+ var n : u32 = 0;
+ var q : u32 = rg[0];
+ while (q < rg[1]) {
+ n = jn_put(out, n, tmp[q as usize]);
+ q = q + 1;
+ }
+ return n;
+}
+
+fn size_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ var tmp : [600]u8 = [0; 600];
+ const l : u32 = size_raw(tmp[0..], d, sp);
+ return put_all(dst, n0, tmp[0..], l);
+}
+
+/// toLowerCase for ASCII and Latin-1 (UTF-8 C3 80..9E except 97), in place; the length does not change.
+fn lower_in_place(buf: []u8, l: u32) -> u32 {
+ var i : u32 = 0;
+ while (i < l) {
+ const c : u8 = buf[i as usize];
+ if (c >= 65 and c <= 90) { buf[i as usize] = c + 32; }
+ if (c == 195 and i + 1 < l) {
+ const c2 : u8 = buf[(i + 1) as usize];
+ if (c2 >= 128 and c2 <= 158 and c2 != 151) { buf[(i + 1) as usize] = c2 + 32; }
+ i = i + 1;
+ }
+ i = i + 1;
+ }
+ return l;
+}
+
+/// toolLine(s): the version alone when it already names the tool, else `tool | version`.
+fn tool_line(dst: []u8, n0: u32, d: []const u8, s: i64) -> u32 {
+ var ver : [512]u8 = [0; 512];
+ var vl : u32 = 0;
+ const vv : i64 = obj_get(d, s, "version");
+ if (!nullish(d, vv)) { vl = jv_str(ver[0..], 0, d, vv); }
+ var tool : [512]u8 = [0; 512];
+ const tl : u32 = jv_str(tool[0..], 0, d, obj_get(d, s, "tool"));
+ var fl : u32 = 0;
+ while (fl < tl and tool[fl as usize] != 32) { fl = fl + 1; }
+ var lver : [512]u8 = [0; 512];
+ var ltool : [512]u8 = [0; 512];
+ var c : u32 = 0;
+ while (c < vl) {
+ lver[c as usize] = ver[c as usize];
+ c = c + 1;
+ }
+ c = 0;
+ while (c < fl) {
+ ltool[c as usize] = tool[c as usize];
+ c = c + 1;
+ }
+ lower_in_place(lver[0..], vl);
+ lower_in_place(ltool[0..], fl);
+ // does lower(ver) include lower(first)?
+ var found : bool = fl == 0;
+ var a : u32 = 0;
+ while (!found and a + fl <= vl) {
+ var m : bool = true;
+ var b : u32 = 0;
+ while (m and b < fl) {
+ if (lver[(a + b) as usize] != ltool[b as usize]) { m = false; }
+ b = b + 1;
+ }
+ if (m) { found = true; }
+ a = a + 1;
+ }
+ if (found) { return put_all(dst, n0, ver[0..], vl); }
+ var n : u32 = put_all(dst, n0, tool[0..], tl);
+ n = jn_raw(dst, n, " | ");
+ return put_all(dst, n, ver[0..], vl);
+}
+
+fn hexval(b: u8) -> u32 {
+ if (b >= 48 and b <= 57) { return (b - 48) as u32; }
+ if (b >= 97 and b <= 102) { return (b - 97) as u32 + 10; }
+ if (b >= 65 and b <= 70) { return (b - 65) as u32 + 10; }
+ return 99;
+}
+
+/// barcode(hex): one bar and one gap per hex digit, widths 1 to 4, with a guard at each end.
+fn barcode_html(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ var tmp : [512]u8 = [0; 512];
+ var l : u32 = 0;
+ if (jv_truthy(d, sp)) { l = jv_str(tmp[0..], 0, d, sp); }
+ var parts : [600]u32 = [0; 600];
+ var pc : u32 = 0;
+ parts[0] = 1; parts[1] = 1; parts[2] = 1; parts[3] = 1;
+ pc = 4;
+ var i : u32 = 0;
+ while (i < l and pc < 590) {
+ const hv : u32 = hexval(tmp[i as usize]);
+ if (hv < 16) {
+ parts[pc as usize] = hv / 4 + 1;
+ parts[(pc + 1) as usize] = hv % 4 + 1;
+ pc = pc + 2;
+ }
+ i = i + 1;
+ }
+ parts[pc as usize] = 1; parts[(pc + 1) as usize] = 1; parts[(pc + 2) as usize] = 1; parts[(pc + 3) as usize] = 1;
+ pc = pc + 4;
+ var width : u32 = 12;
+ i = 0;
+ while (i < pc) {
+ width = width + parts[i as usize];
+ i = i + 1;
+ }
+ var n : u32 = raw(dst, n0, "");
+ var x : u32 = 6;
+ i = 0;
+ while (i < pc) {
+ if (i % 2 == 0) {
+ n = raw(dst, n, "");
+ }
+ x = x + parts[i as usize];
+ i = i + 1;
+ }
+ return raw(dst, n, "");
+}
+
+// --- the stages -----------------------------------------------------------------------------------
+
+fn stage_item(dst: []u8, n0: u32, d: []const u8, s: i64, ix: u32) -> u32 {
+ const status : i64 = obj_get(d, s, "status");
+ var n : u32 = raw(dst, n0, "
");
+ // line(name, ...)
+ n = line_open(dst, n, "");
+ n = js_str_u64(dst, n, (ix + 1) as u64);
+ n = put(dst, n, 32);
+ if (ix < (SAY_STAGE_NAMES.len as u32)) { n = text(dst, n, SAY_STAGE_NAMES[ix as usize]); } else { n = jv_text(dst, n, d, obj_get(d, s, "id")); }
+ n = line_mid(dst, n);
+ if (jv_is(d, status, "not run")) {
+ n = text(dst, n, SAY_NOT_RUN);
+ n = line_close(dst, n);
+ n = div_open(dst, n, "br-sub", "");
+ n = tc_text(dst, n, d, obj_get(d, s, "tool"));
+ n = raw(dst, n, "
");
+ return n;
+ }
+ n = secs_text(dst, n, num_or0(d, obj_get(d, s, "ms")));
+ n = line_close(dst, n);
+ n = div_open(dst, n, "br-sub", "");
+ n = tool_line(dst, n, d, s);
+ n = raw(dst, n, "");
+ if (failed) {
+ n = div_open(dst, n, "br-sub", "br-bad");
+ var vb : [256]u8 = [0; 256];
+ var vn : u32 = 0;
+ const ex : i64 = obj_get(d, s, "exit");
+ if (nullish(d, ex)) { vn = jv_vput_str(vb[0..], 0, "exit", ""); } else { vn = jv_vput_val(vb[0..], 0, "exit", d, ex); }
+ n = jv_fill(dst, n, SAY_FAILED, vb[0..], vn, false);
+ n = raw(dst, n, "");
+ if (jv_truthy(d, obj_get(d, s, "error"))) {
+ n = raw(dst, n, "
");
+ n = jv_text(dst, n, d, obj_get(d, s, "error"));
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "");
+ }
+ const out : i64 = obj_get(d, s, "output");
+ if (jv_truthy(d, out)) {
+ n = div_open(dst, n, "br-sub", "br-detail");
+ var ob : [1024]u8 = [0; 1024];
+ var on : u32 = jv_vput_val(ob[0..], 0, "file", d, obj_get(d, out, "file"));
+ var szn : [600]u8 = [0; 600];
+ const tmpn : u32 = size_raw(szn[0..], d, obj_get(d, out, "bytes"));
+ on = jv_vput_str(ob[0..], on, "size", szn[0..(tmpn as usize)]);
+ n = jv_fill(dst, n, SAY_OUT, ob[0..], on, false);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-hex br-detail");
+ var hb : [512]u8 = [0; 512];
+ var hn : u32 = jv_vput_val(hb[0..], 0, "sha", d, obj_get(d, out, "sha256"));
+ n = jv_fill(dst, n, SAY_SHA, hb[0..], hn, false);
+ n = raw(dst, n, "");
+ }
+ const cpu : i64 = obj_get(d, s, "cpu_s");
+ if (!nullish(d, cpu)) {
+ n = div_open(dst, n, "br-sub", "br-faint br-detail");
+ var cb : [256]u8 = [0; 256];
+ var cn : u32 = jv_vput_val(cb[0..], 0, "cpu", d, cpu);
+ cn = jv_vput_f64(cb[0..], cn, "rss", js_round(num_or0(d, obj_get(d, s, "rss_mb"))));
+ n = jv_fill(dst, n, SAY_CPU, cb[0..], cn, false);
+ n = raw(dst, n, "");
+ }
+ return raw(dst, n, "");
+}
+
+/// The names specDiff() lists: where the generated constants of this spec and receipt.json disagree.
+fn spec_diff(dst: []u8, n0: u32, d: []const u8, r: i64) -> u32 {
+ const st : i64 = obj_get(d, r, "stages");
+ const cnt : u32 = arr_len(d, st);
+ var n : u32 = n0;
+ var any : bool = false;
+ const sc : i64 = obj_get(d, r, "stage_count");
+ if (!(kind(d, sc) == J_NUMBER and jv_num(d, sc) == (K_STAGE_COUNT as f64) and jv_num(d, sc) == (cnt as f64))) {
+ n = raw(dst, n, "K_STAGE_COUNT");
+ any = true;
+ }
+ const so : i64 = obj_get(d, r, "stages_ok");
+ if (!(kind(d, so) == J_NUMBER and jv_num(d, so) == (K_STAGES_OK as f64))) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "K_STAGES_OK");
+ any = true;
+ }
+ var msbad : bool = false;
+ if ((K_STAGE_MS.len as u32) != cnt) { msbad = true; }
+ var sum : f64 = 0.0;
+ // reduce((a, b) => a + b, 0): numbers add; once a string (or object / array) is met the rest is concatenated
+ var sbuf : [1100]u8 = [0; 1100];
+ var sl : u32 = 0;
+ var strmode : bool = false;
+ var i : u32 = 0;
+ while (i < cnt) {
+ const ms : i64 = obj_get(d, arr_at(d, st, i), "ms");
+ const mk : u8 = kind(d, ms);
+ var mv : f64 = 0.0;
+ var is_num : bool = true;
+ if (!nullish(d, ms)) {
+ mv = jv_num(d, ms);
+ is_num = mk == J_NUMBER;
+ }
+ if (i < (K_STAGE_MS.len as u32)) {
+ if (!is_num or mv != (K_STAGE_MS[i as usize] as f64)) { msbad = true; }
+ }
+ if (!strmode and (mk == J_STRING or mk == J_OBJECT or mk == J_ARRAY)) {
+ strmode = true;
+ sl = js_str_num(sbuf[0..], 0, sum);
+ }
+ if (strmode) {
+ if (nullish(d, ms)) { sl = jn_raw(sbuf[0..], sl, "0"); } else { sl = jv_str(sbuf[0..], sl, d, ms); }
+ } else {
+ sum = sum + mv;
+ }
+ i = i + 1;
+ }
+ if (strmode) { sum = jv_number_of_text(sbuf[0..], sl); }
+ if (msbad) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "K_STAGE_MS");
+ any = true;
+ }
+ const tm : i64 = obj_get(d, r, "total_ms");
+ if (!(kind(d, tm) == J_NUMBER and jv_num(d, tm) == (K_TOTAL_MS as f64))) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "K_TOTAL_MS");
+ any = true;
+ }
+ const bit : i64 = obj_get(d, r, "bit");
+ if (jv_truthy(d, bit)) {
+ const bb : i64 = obj_get(d, bit, "bytes");
+ if (!(kind(d, bb) == J_NUMBER and jv_num(d, bb) == (K_BIT_BYTES as f64))) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "K_BIT_BYTES");
+ any = true;
+ }
+ }
+ var gap : f64 = sum - jv_num(d, tm);
+ if (gap < 0.0) { gap = 0.0 - gap; }
+ if (gap > (K_ROUND_TOL_MS as f64)) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "total_ms");
+ }
+ return n;
+}
+
+fn member_value(d: []const u8, ut: i64, ix: u32, key_out: []u8, key_len: []u32) -> i64 {
+ const hi : usize = span_hi(ut);
+ var p : usize = skip_ws(d, span_lo(ut) + 1);
+ var at : u32 = 0;
+ while (p < hi and d[p] == 34) {
+ const ke : usize = string_end(d, p);
+ const q : usize = skip_ws(d, ke);
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ if (at == ix) {
+ key_len[0] = jv_str_to(key_out, 0, d, jspan(p, ke));
+ return jspan(v, ve);
+ }
+ at = at + 1;
+ p = skip_ws(d, ve);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ return NO_SPAN;
+}
+
+fn member_count(d: []const u8, ut: i64) -> u32 {
+ const hi : usize = span_hi(ut);
+ var p : usize = skip_ws(d, span_lo(ut) + 1);
+ var at : u32 = 0;
+ while (p < hi and d[p] == 34) {
+ const ke : usize = string_end(d, p);
+ const q : usize = skip_ws(d, ke);
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ at = at + 1;
+ p = skip_ws(d, ve);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ return at;
+}
+
+fn paper_html(dst: []u8, n0: u32, d: []const u8, r: i64) -> u32 {
+ const bit : i64 = obj_get(d, r, "bit");
+ const mach : i64 = obj_get(d, r, "machine");
+ const des : i64 = obj_get(d, r, "design");
+ const sha : i64 = obj_get(d, bit, "sha256");
+ var n : u32 = raw(dst, n0, "");
+ // the receipt number: the payload digest (else the file digest), first 8 characters, upper case
+ var pay : i64 = obj_get(d, bit, "payload_sha256");
+ if (nullish(d, pay)) { pay = sha; }
+ var nb : [64]u8 = [0; 64];
+ var nl : u32 = 0;
+ if (kind(d, pay) == J_STRING) {
+ var raw8 : [512]u8 = [0; 512];
+ const rl : u32 = jv_str(raw8[0..], 0, d, pay);
+ var units : u32 = 0;
+ var q : u32 = 0;
+ while (q < rl and units < 8 and nl < 56) {
+ const c : u8 = raw8[q as usize];
+ var w : u32 = 1;
+ var u : u32 = 1;
+ if (c >= 192) { w = 2; }
+ if (c >= 224) { w = 3; }
+ if (c >= 240) { w = 4; u = 2; }
+ if (units + u > 8) { break; }
+ if (c >= 97 and c <= 122) {
+ nb[nl as usize] = c - 32;
+ nl = nl + 1;
+ } else {
+ if (c == 195 and q + 1 < rl) {
+ // Latin-1: a..o / o..y with a diaeresis etc. go up by 32 (not the division sign); y with diaeresis and sharp s are special
+ const c2 : u8 = raw8[(q + 1) as usize];
+ if (c2 == 159) {
+ nb[nl as usize] = 83;
+ nb[(nl + 1) as usize] = 83;
+ nl = nl + 2;
+ } else {
+ if (c2 == 191) {
+ nb[nl as usize] = 197;
+ nb[(nl + 1) as usize] = 184;
+ nl = nl + 2;
+ } else {
+ nb[nl as usize] = c;
+ if (c2 >= 160 and c2 <= 190 and c2 != 183) { nb[(nl + 1) as usize] = c2 - 32; } else { nb[(nl + 1) as usize] = c2; }
+ nl = nl + 2;
+ }
+ }
+ } else {
+ if (c == 194 and q + 1 < rl and raw8[(q + 1) as usize] == 181) {
+ nb[nl as usize] = 206;
+ nb[(nl + 1) as usize] = 156;
+ nl = nl + 2;
+ } else {
+ var z : u32 = 0;
+ while (z < w and q + z < rl) {
+ nb[(nl + z) as usize] = raw8[(q + z) as usize];
+ z = z + 1;
+ }
+ nl = nl + z;
+ }
+ }
+ }
+ q = q + w;
+ units = units + u;
+ }
+ }
+ var vb : [512]u8 = [0; 512];
+ var vn : u32 = jv_vput_str(vb[0..], 0, "no", nb[0..(nl as usize)]);
+ n = div_open(dst, n, "br-center", "br-shop");
+ n = text(dst, n, SAY_SHOP);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-center", "");
+ n = jv_fill(dst, n, SAY_RECEIPT, vb[0..], vn, false);
+ n = raw(dst, n, "");
+ // when
+ n = div_open(dst, n, "br-center", "");
+ var wb : [1200]u8 = [0; 1200];
+ var wtmp : [1100]u8 = [0; 1100];
+ const wl : u32 = utc_raw(wtmp[0..], d, obj_get(d, r, "when_utc"));
+ var wn : u32 = jv_vput_str(wb[0..], 0, "when", wtmp[0..(wl as usize)]);
+ n = jv_fill(dst, n, SAY_WHEN, wb[0..], wn, false);
+ n = raw(dst, n, "");
+ // machine
+ n = div_open(dst, n, "br-center", "br-faint");
+ var mb : [1024]u8 = [0; 1024];
+ var mn : u32 = 0;
+ if (kind(d, mach) == J_OBJECT) {
+ mn = jv_vput_defined(mb[0..], mn, "cpu", d, obj_get(d, mach, "cpu"));
+ mn = jv_vput_defined(mb[0..], mn, "cores", d, obj_get(d, mach, "cores"));
+ mn = jv_vput_defined(mb[0..], mn, "macos", d, obj_get(d, mach, "macos"));
+ }
+ n = jv_fill(dst, n, SAY_MACHINE, mb[0..], mn, false);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-center", "br-faint");
+ var lb : [256]u8 = [0; 256];
+ var ln : u32 = 0;
+ const la : i64 = obj_get(d, mach, "loadavg_1m_at_start");
+ if (nullish(d, la)) { ln = jv_vput_str(lb[0..], 0, "load", ""); } else { ln = jv_vput_val(lb[0..], 0, "load", d, la); }
+ n = jv_fill(dst, n, SAY_LOAD, lb[0..], ln, false);
+ n = raw(dst, n, "");
+ n = rule_html(dst, n);
+ // design
+ n = div_open(dst, n, "br-sub", "");
+ n = text(dst, n, SAY_LABEL_SPEC);
+ n = raw(dst, n, ": ");
+ n = nn_text(dst, n, d, obj_get(d, des, "spec"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "");
+ n = text(dst, n, SAY_LABEL_PART);
+ n = raw(dst, n, ": ");
+ n = nn_text(dst, n, d, obj_get(d, des, "part"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-detail");
+ n = text(dst, n, SAY_LABEL_BOARD);
+ n = raw(dst, n, ": ");
+ n = nn_text(dst, n, d, obj_get(d, des, "board"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-faint br-detail");
+ var db : [1024]u8 = [0; 1024];
+ var dn : u32 = 0;
+ dn = jv_vput_nn(db[0..], dn, "name", d, obj_get(d, des, "datapath"));
+ dn = jv_vput_nn(db[0..], dn, "lines", d, obj_get(d, des, "wrapper_lines"));
+ n = jv_fill(dst, n, SAY_WRAPPER, db[0..], dn, false);
+ n = raw(dst, n, "");
+ n = rule_html(dst, n);
+ n = line_open(dst, n, "br-head");
+ n = text(dst, n, SAY_COL_ITEM);
+ n = line_mid(dst, n);
+ n = text(dst, n, SAY_COL_TIME);
+ n = line_close(dst, n);
+ // items
+ const st : i64 = obj_get(d, r, "stages");
+ const cnt : u32 = arr_len(d, st);
+ var i : u32 = 0;
+ while (i < cnt) {
+ n = stage_item(dst, n, d, arr_at(d, st, i), i);
+ i = i + 1;
+ }
+ n = rule_html(dst, n);
+ // totals
+ n = div_open(dst, n, "br-line", "");
+ var tb : [256]u8 = [0; 256];
+ var tn : u32 = jv_vput_val(tb[0..], 0, "ok", d, obj_get(d, r, "stages_ok"));
+ tn = jv_vput_val(tb[0..], tn, "count", d, obj_get(d, r, "stage_count"));
+ n = jv_fill(dst, n, SAY_ITEMS, tb[0..], tn, false);
+ n = raw(dst, n, "");
+ const total_ms : f64 = jv_num(d, obj_get(d, r, "total_ms"));
+ if (jv_truthy(d, obj_get(d, r, "complete"))) {
+ n = line_open(dst, n, "br-total");
+ n = text(dst, n, SAY_TOTAL);
+ n = line_mid(dst, n);
+ n = secs_text(dst, n, total_ms);
+ n = line_close(dst, n);
+ } else {
+ n = div_open(dst, n, "br-sub", "br-bad br-total-miss");
+ var sb : [256]u8 = [0; 256];
+ var stmp : [200]u8 = [0; 200];
+ const sl : u32 = secs_text_raw(stmp[0..], total_ms);
+ const sn : u32 = jv_vput_str(sb[0..], 0, "s", stmp[0..(sl as usize)]);
+ n = jv_fill(dst, n, SAY_NO_TOTAL, sb[0..], sn, false);
+ n = raw(dst, n, "");
+ }
+ var vt : [64]u8 = [0; 64];
+ var vtl : u32 = 0;
+ const vend : i64 = obj_get(d, r, "vendor_tools_run");
+ if (nullish(d, vend)) { vtl = jn_raw(vt[0..], 0, "0"); } else { vtl = jv_str(vt[0..], 0, d, vend); }
+ n = line_open(dst, n, "br-big");
+ n = text(dst, n, SAY_VIVADO);
+ n = line_mid(dst, n);
+ n = put_all(dst, n, vt[0..], vtl);
+ n = line_close(dst, n);
+ n = line_open(dst, n, "");
+ n = text(dst, n, SAY_VENDOR);
+ n = line_mid(dst, n);
+ n = put_all(dst, n, vt[0..], vtl);
+ n = line_close(dst, n);
+ n = div_open(dst, n, "br-sub", "br-faint");
+ var eb : [256]u8 = [0; 256];
+ var etmp : [200]u8 = [0; 200];
+ const el : u32 = secs_text_raw(etmp[0..], num_or0(d, obj_get(d, r, "wall_ms_end_to_end")));
+ const en : u32 = jv_vput_str(eb[0..], 0, "s", etmp[0..(el as usize)]);
+ n = jv_fill(dst, n, SAY_END_TO_END, eb[0..], en, false);
+ n = raw(dst, n, "");
+ n = rule_html(dst, n);
+ // the design that came out
+ const fm : i64 = obj_get(d, r, "fmax");
+ if (jv_truthy(d, fm)) {
+ const pass : bool = jv_truthy(d, obj_get(d, fm, "pass"));
+ if (pass) { n = line_open(dst, n, ""); } else { n = line_open(dst, n, "br-bad"); }
+ var fb : [512]u8 = [0; 512];
+ var fn_ : u32 = jv_vput_val(fb[0..], 0, "clock", d, obj_get(d, fm, "clock"));
+ fn_ = jv_vput_val(fb[0..], fn_, "mhz", d, obj_get(d, fm, "mhz"));
+ fn_ = jv_vput_val(fb[0..], fn_, "target", d, obj_get(d, fm, "target_mhz"));
+ n = jv_fill(dst, n, SAY_FMAX, fb[0..], fn_, false);
+ n = line_mid(dst, n);
+ if (pass) { n = text(dst, n, SAY_PASS); } else { n = text(dst, n, SAY_FAIL); }
+ n = line_close(dst, n);
+ }
+ const ut : i64 = obj_get(d, r, "utilisation");
+ var has_entries : bool = false;
+ if (jv_truthy(d, ut)) {
+ if (kind(d, ut) == J_ARRAY) { has_entries = arr_len(d, ut) > 0; }
+ if (kind(d, ut) == J_OBJECT) { has_entries = member_count(d, ut) > 0; }
+ }
+ if (has_entries) {
+ n = div_open(dst, n, "br-sub", "");
+ n = text(dst, n, SAY_UTIL_TITLE);
+ n = raw(dst, n, "");
+ var ecount : u32 = 0;
+ if (kind(d, ut) == J_ARRAY) { ecount = arr_len(d, ut); } else { ecount = member_count(d, ut); }
+ var e : u32 = 0;
+ while (e < ecount) {
+ var kb : [512]u8 = [0; 512];
+ var kl : [1]u32 = [0; 1];
+ var val : i64 = NO_SPAN;
+ if (kind(d, ut) == J_ARRAY) {
+ val = arr_at(d, ut, e);
+ kl[0] = js_str_u64(kb[0..], 0, e as u64);
+ } else {
+ val = member_value(d, ut, e, kb[0..], kl[0..]);
+ }
+ n = line_open(dst, n, "br-small");
+ n = raw(dst, n, " ");
+ n = put_all(dst, n, kb[0..], kl[0]);
+ n = line_mid(dst, n);
+ n = jv_text(dst, n, d, arr_at(d, val, 0));
+ n = raw(dst, n, " / ");
+ n = jv_text(dst, n, d, arr_at(d, val, 1));
+ n = line_close(dst, n);
+ e = e + 1;
+ }
+ }
+ if (jv_truthy(d, fm) or jv_truthy(d, ut)) { n = rule_html(dst, n); }
+ // the bitstream
+ if (jv_truthy(d, bit)) {
+ n = div_open(dst, n, "br-sub", "");
+ n = text(dst, n, SAY_FILE_SHA);
+ n = raw(dst, n, ": ");
+ n = size_text(dst, n, d, obj_get(d, bit, "bytes"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-hex");
+ n = tc_text(dst, n, d, obj_get(d, bit, "sha256"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-faint");
+ n = text(dst, n, SAY_FILE_SHA_NOTE);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "");
+ var pb : [600]u8 = [0; 600];
+ var pstr : [600]u8 = [0; 600];
+ const pl : u32 = size_raw(pstr[0..], d, obj_get(d, bit, "payload_bytes"));
+ const pn : u32 = jv_vput_str(pb[0..], 0, "size", pstr[0..(pl as usize)]);
+ n = jv_fill(dst, n, SAY_PAYLOAD_SHA, pb[0..], pn, false);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-hex");
+ n = tc_text(dst, n, d, obj_get(d, bit, "payload_sha256"));
+ n = raw(dst, n, "");
+ const er : i64 = obj_get(d, r, "earlier_runs");
+ var ec : u32 = 0;
+ if (kind(d, er) == J_ARRAY) { ec = arr_len(d, er); }
+ if (ec > 0) {
+ const same : bool = jv_truthy(d, obj_get(d, r, "payload_same_as_earlier"));
+ if (same) { n = div_open(dst, n, "br-sub", ""); } else { n = div_open(dst, n, "br-sub", "br-bad"); }
+ if (same) {
+ var sb2 : [64]u8 = [0; 64];
+ const sn2 : u32 = jv_vput_f64(sb2[0..], 0, "n", ec as f64);
+ n = jv_fill(dst, n, SAY_SAME_PAYLOAD, sb2[0..], sn2, false);
+ } else {
+ n = text(dst, n, SAY_OTHER_PAYLOAD);
+ }
+ n = raw(dst, n, "");
+ var x : u32 = 0;
+ while (x < ec) {
+ const e2 : i64 = arr_at(d, er, x);
+ n = div_open(dst, n, "br-sub", "br-faint");
+ var xb : [1600]u8 = [0; 1600];
+ var xt : [1100]u8 = [0; 1100];
+ var xtl : u32 = utc_raw(xt[0..], d, obj_get(d, e2, "when_utc"));
+ var xn : u32 = jv_vput_str(xb[0..], 0, "when", xt[0..(xtl as usize)]);
+ var xs : [200]u8 = [0; 200];
+ const xsl : u32 = secs_text_raw(xs[0..], jv_num(d, obj_get(d, e2, "total_ms")));
+ xn = jv_vput_str(xb[0..], xn, "s", xs[0..(xsl as usize)]);
+ xn = jv_vput_val(xb[0..], xn, "load", d, obj_get(d, e2, "loadavg_1m_at_start"));
+ n = jv_fill(dst, n, SAY_EARLIER, xb[0..], xn, false);
+ n = raw(dst, n, "");
+ x = x + 1;
+ }
+ }
+ const cc : i64 = obj_get(d, r, "cross_check");
+ const cs : i64 = obj_get(d, cc, "status");
+ const cok : bool = jv_is(d, cs, "ok");
+ const ceq : bool = jv_truthy(d, obj_get(d, cc, "payload_equal"));
+ if (cok and !ceq) { n = div_open(dst, n, "br-sub", "br-bad"); } else { n = div_open(dst, n, "br-sub", ""); }
+ if (cok and ceq) {
+ var cb : [256]u8 = [0; 256];
+ const cn : u32 = jv_vput_val(cb[0..], 0, "ms", d, obj_get(d, cc, "ms"));
+ n = jv_fill(dst, n, SAY_CROSS_OK, cb[0..], cn, false);
+ } else {
+ if (cok) { n = text(dst, n, SAY_CROSS_BAD); } else { n = text(dst, n, SAY_CROSS_SKIP); }
+ }
+ n = raw(dst, n, "");
+ n = rule_html(dst, n);
+ n = barcode_html(dst, n, d, sha);
+ n = div_open(dst, n, "br-center", "br-hex br-tiny");
+ n = tc_text(dst, n, d, sha);
+ n = raw(dst, n, "");
+ }
+ n = div_open(dst, n, "br-center", "br-small");
+ n = text(dst, n, SAY_NOT_PROGRAMMED);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-center", "br-faint br-small");
+ n = text(dst, n, SAY_FOOTER);
+ n = raw(dst, n, "");
+ return raw(dst, n, "");
+}
+
+/// `x ?? ''` as a child / template-literal piece: the value's String(), nothing for null / missing.
+fn nn_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return n0; }
+ return jv_text(dst, n0, d, sp);
+}
+
+/// A pool entry for `{key: x ?? ''}`.
+fn jv_vput_nn(vb: []u8, n0: u32, key: []const u8, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return jv_vput_str(vb, n0, key, ""); }
+ return jv_vput_val(vb, n0, key, d, sp);
+}
+
+/// utc(iso) as raw bytes (not escaped) into out; the length.
+fn utc_raw(out: []u8, d: []const u8, sp: i64) -> u32 {
+ var l : u32 = 0;
+ if (!nullish(d, sp)) { l = jv_str(out, 0, d, sp); }
+ var k : u32 = 0;
+ var done : bool = false;
+ while (k < l and !done) {
+ if (out[k as usize] == 84) {
+ out[k as usize] = 32;
+ done = true;
+ }
+ k = k + 1;
+ }
+ if (l > 0 and out[(l - 1) as usize] == 90) {
+ var e : u32 = l - 1;
+ var j : u32 = e;
+ while (j > 0 and out[(j - 1) as usize] >= 48 and out[(j - 1) as usize] <= 57) { j = j - 1; }
+ if (j < e and j > 0 and out[(j - 1) as usize] == 46) { e = j - 1; }
+ l = e;
+ }
+ return l;
+}
+
+/// secs(ms) as raw bytes into out: SAY_SECONDS with (ms / 1000).toFixed(3).
+fn secs_text_raw(out: []u8, ms: f64) -> u32 {
+ var tmp : [128]u8 = [0; 128];
+ const l : u32 = js_to_fixed(tmp[0..], 0, ms / 1000.0, 3);
+ return fill_raw(out, SAY_SECONDS, tmp[0..(l as usize)]);
+}
+
+/// The template with {s} replaced by v, as raw (unescaped) bytes.
+fn fill_raw(out: []u8, t: []const u8, v: []const u8) -> u32 {
+ var n : u32 = 0;
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 1] == 115 and t[i + 2] == 125) {
+ n = jn_raw(out, n, v);
+ i = i + 3;
+ } else {
+ n = jn_put(out, n, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ if (DATA_STATE == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOADING);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ if (DATA_STATE == 2) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ERROR);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ const r : i64 = root(d);
+ n = raw(dst, n, "
");
+ var diff : [512]u8 = [0; 512];
+ const dl : u32 = spec_diff(diff[0..], 0, d, r);
+ if (dl > 0) {
+ n = raw(dst, n, "
");
+ var wb : [600]u8 = [0; 600];
+ const wn : u32 = jv_vput_str(wb[0..], 0, "what", diff[0..(dl as usize)]);
+ n = jv_fill(dst, n, SAY_SPEC_DIFF, wb[0..], wn, false);
+ n = raw(dst, n, "
");
+ }
+ n = paper_html(dst, n, d, r);
+ n = raw(dst, n, "
");
+ const cm : i64 = obj_get(d, r, "commands");
+ if (kind(d, cm) == J_ARRAY) {
+ const cc : u32 = arr_len(d, cm);
+ var c : u32 = 0;
+ while (c < cc) {
+ if (c > 0) { n = put(dst, n, 10); }
+ const el : i64 = arr_at(d, cm, c);
+ if (!nullish(d, el)) { n = jv_text(dst, n, d, el); }
+ c = c + 1;
+ }
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_VERIFY_TITLE);
+ n = raw(dst, n, "
");
+ var v : usize = 0;
+ while (v < SAY_VERIFY_STEPS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_VERIFY_STEPS[v]);
+ n = raw(dst, n, "
");
+ v = v + 1;
+ }
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call.
+/// Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ return n;
+}
From 9ec632829600595baccece4f5ab94f55694770e7 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 17:45:14 +0700
Subject: [PATCH 31/33] widgets: js_val gains jv_fill_raw, the filler without
escaping, for an SVG title (Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/js_val.t27 | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/specs/widgets/js_val.t27 b/specs/widgets/js_val.t27
index 95d896102d..72d08c5782 100644
--- a/specs/widgets/js_val.t27
+++ b/specs/widgets/js_val.t27
@@ -578,6 +578,15 @@ pub fn jv_find(vb: []const u8, vn: u32, key: []const u8) -> u32 {
/// fill(template, pool): {key} becomes the pool's value (escaped), an unknown {key} stays as written.
pub fn jv_fill(o: []u8, n0: u32, t: []const u8, vb: []const u8, vn: u32, digits_only: bool) -> u32 {
+ return jv_fill_with(o, n0, t, vb, vn, digits_only, true);
+}
+
+/// jv_fill without the escaping, for a text that is a value itself (an SVG , a pool value).
+pub fn jv_fill_raw(o: []u8, n0: u32, t: []const u8, vb: []const u8, vn: u32, digits_only: bool) -> u32 {
+ return jv_fill_with(o, n0, t, vb, vn, digits_only, false);
+}
+
+pub fn jv_fill_with(o: []u8, n0: u32, t: []const u8, vb: []const u8, vn: u32, digits_only: bool, esc: bool) -> u32 {
var n : u32 = n0;
var i : usize = 0;
while (i < t.len) {
@@ -603,13 +612,13 @@ pub fn jv_fill(o: []u8, n0: u32, t: []const u8, vb: []const u8, vn: u32, digits_
const vl : u32 = (vb[(at - 2) as usize] as u32) * 256 + (vb[(at - 1) as usize] as u32);
var v : u32 = 0;
while (v < vl) {
- n = put_escaped(o, n, vb[(at + v) as usize]);
+ if (esc) { n = put_escaped(o, n, vb[(at + v) as usize]); } else { n = put(o, n, vb[(at + v) as usize]); }
v = v + 1;
}
}
i = closed + 1;
} else {
- n = put_escaped(o, n, t[i]);
+ if (esc) { n = put_escaped(o, n, t[i]); } else { n = put(o, n, t[i]); }
i = i + 1;
}
}
From 55741ca8415045665a69a53c41b05d3293195f90 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 17:45:14 +0700
Subject: [PATCH 32/33] widgets: uart-bucket drawn natively -- 32562-byte wasm,
3288 DOM comparisons, 0 differences (Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/uart_bucket.t27 | 1141 +++++++++++++++++++++++++++++++++
1 file changed, 1141 insertions(+)
create mode 100644 specs/widgets/uart_bucket.t27
diff --git a/specs/widgets/uart_bucket.t27 b/specs/widgets/uart_bucket.t27
new file mode 100644
index 0000000000..bc169f5915
--- /dev/null
+++ b/specs/widgets/uart_bucket.t27
@@ -0,0 +1,1141 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/uart-bucket.t27 -- the bucket under the tap: answers waiting in a 512-byte UART buffer
+; Source of truth for public/widgets/uart-bucket/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, and the buffer rule is K_ constants.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT SHOWS: the Mac keeps a window of requests in flight to the AX7203 node; every answer the
+; node sends back is 19 bytes and passes through a CP2102N USB-UART bridge with a 512-byte receive
+; buffer. The node has no flow control, so while the Mac pauses reading, up to window x 19 bytes of
+; answers wait in that buffer. The reader drags the window and the bucket fills; past 512 bytes it
+; spills. Below it, the measured runs: two 2,016,000-job diagnostic arms (window 64 and window 24)
+; and two single 403,200-job runs at the edge (window 26 and window 30), then what the link carried.
+
+; WHERE THE NUMBERS COME FROM: scripts/widget-data/uart-bucket.mjs reads the record files of
+; gHashTag/trinity-fpga with git show at the commits named in DATA_SOURCES, parses every number,
+; checks the K_ constants below against the diagnostic's own spec, and writes data.json; the page
+; reads only that file. scripts/widget-data/uart-bucket-card.py draws card.png from data.json.
+
+; WHAT IT DOES NOT CLAIM: that the buffer is the cause. The diagnostic's own record says the pattern
+; fits the buffer and that nothing proves it. One USB path, one host, one evening. The model run
+; below used synthetic activation vectors; no text came out of it, and text generation ran on the Mac.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_uart_bucket;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+use widgets::js_val::jv_fill_raw;
+use widgets::js_val::jv_fill_with;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "uart-bucket";
+pub const TITLE : str = "The bucket under the tap: a 512-byte UART buffer and 19-byte answers";
+pub const DESCRIPTION : str = "Drag the number of requests in flight and watch the answers fill a CP2102N's 512-byte receive buffer, then read the measured runs on the AX7203 board.";
+pub const IMAGE_ALT : str = "The bucket under the tap card: a CP2102N receive buffer holds 512 bytes and each answer is 19 bytes, so 26 answers fit. Window 64 (1216 bytes in flight) lost 574 of 2016000 answers in 424.3 s; window 24 (456 bytes) lost 0 in 427.5 s, on the AX7203 board.";
+pub const HOOK : str = "512 bytes of buffer, 19 bytes per answer: 26 fit. Window 64 lost 574 answers in 2,016,000 jobs; window 24 lost none and took the same time.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [5]str = ["gHashTag/trinity-fpga conformance/UART_LOSS_DIAG.md at commit 5a3111806: the window-64 and window-24 diagnostic arms (2,016,000 jobs each), the 512 / 19 rule, the wire time of the requests and the caveat", "gHashTag/trinity-fpga specs/trinet/uart_loss_diag_ax7203.t27 at commit df78b3249: REQ_LEN, RESP_LEN, CP2102N_RX_BYTES, ARM_WINDOWS and JOBS_PER_PASS, checked against the K_ constants of this spec", "gHashTag/trinity-fpga conformance/TERN_TC_LAYER_RECEIPTS.md at commit e1834ee6e: the --all runs at window 24 (rerun step 1), window 26 and window 30 (rerun step 3), harness tern_tc_layer_ax7203.py", "gHashTag/trinity-fpga conformance/TERN_TC_GENERATE.md at commit b2bd5a30c: the layer run used synthetic activation vectors and generation ran on the Mac", "scripts/widget-data/uart-bucket.mjs parses those files with git show at those commits into public/widgets/uart-bucket/data.json; scripts/widget-data/uart-bucket-card.py draws card.png from it"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+; --- The rule --------------------------------------------------------------------------------
+; The buffer and the frame sizes; uart-bucket.mjs fails if they drift from the diagnostic's spec.
+pub const K_RX_BYTES : u16 = 512;
+pub const K_ANSWER_BYTES : u16 = 19;
+pub const K_REQUEST_BYTES : u16 = 24;
+pub const K_LARGEST_FIT : u16 = 26;
+; The two diagnostic arms and the two single runs at the edge.
+pub const K_ARM_WINDOWS : [2]u16 = [64, 24];
+pub const K_EDGE_WINDOWS : [2]u16 = [26, 30];
+; The slider.
+pub const K_WINDOW_MIN : u16 = 1;
+pub const K_WINDOW_MAX : u16 = 64;
+pub const K_WINDOW_START : u16 = 64;
+; One fill-and-drain cycle of the animation, in milliseconds; none when the reader asks for less motion.
+pub const K_CYCLE_MS : u16 = 3600;
+pub const K_DATA_FILE : str = "./data.json";
+
+; --- Words -----------------------------------------------------------------------------------
+; {name} is filled by tool.js from data.json or from the K_ constants; nothing else is.
+pub const SAY_LOADING : str = "Reading the measured runs...";
+pub const SAY_LOAD_FAILED : str = "The data file could not be read.";
+pub const SAY_INTRO : str = "The Mac keeps a window of requests in flight to the {board} node. Every answer coming back is {answer} bytes. While the Mac pauses reading, answers wait in the USB-UART bridge's {rx}-byte receive buffer; the node has no flow control, so nothing tells it to wait.";
+pub const SAY_TAP : str = "node answers, {answer} B each";
+pub const SAY_BUCKET : str = "CP2102N receive buffer";
+pub const SAY_BRIM : str = "{rx} B";
+pub const SAY_DRAIN : str = "the Mac reads";
+pub const SAY_SPILL : str = "lost";
+pub const SAY_WINDOW_LABEL : str = "Requests in flight (window)";
+pub const SAY_FEWER : str = "One request fewer";
+pub const SAY_MORE : str = "One request more";
+pub const SAY_MINUS : str = "-";
+pub const SAY_PLUS : str = "+";
+pub const SAY_READOUT : str = "{w} x {answer} B = {bytes} B of answers can be waiting; the buffer holds {rx} B.";
+pub const SAY_FITS : str = "Fits, {spare} B to spare, however long the Mac pauses.";
+pub const SAY_OVER : str = "Over by {over} B: if the Mac pauses long enough, answer bytes are lost.";
+pub const SAY_RULE : str = "{rx} / {answer} = {ratio}, so {fit} is the largest window that fits.";
+pub const SAY_MOTION_OFF : str = "Animation off: your system asks for reduced motion. The bucket shows the full level.";
+pub const SAY_RUNS_TITLE : str = "Measured on the {board}: {jobs} jobs per arm, one arm each";
+pub const SAY_ARM_HEAD : str = "window {w}";
+pub const SAY_ARM_ROWS : [6]str = ["bytes in flight", "jobs sent", "answers accepted", "answers lost", "loss events", "elapsed"];
+pub const SAY_SECONDS : str = "s";
+pub const SAY_SHOW : str = "Put window {w} in the bucket";
+pub const SAY_SAME_TIME : str = "The smaller window cost no time: {t64} s against {t24} s. The requests are the slow direction: {req} B x 10 bit at {baud} baud x {jobs} jobs = {wire} s on the wire.";
+pub const SAY_EDGE_TITLE : str = "Two single runs at the edge, {planned} jobs planned each";
+pub const SAY_EDGE_PASS : str = "window {w} ({bytes} B): {ok} of {planned} receipts verified, no slip";
+pub const SAY_EDGE_FAIL : str = "window {w} ({bytes} B): stopped at the first slip after {sent} jobs sent; {ok} of {planned} receipts verified";
+pub const SAY_CAVEAT : str = "What this does not show: the diagnostic's own record says the pattern fits the buffer, but nothing proves the buffer is the cause. One USB path (a Genesys hub), one host, one evening; nothing about other hosts or a hub-free path.";
+pub const SAY_CARRIED_TITLE : str = "What the link then carried, at window {w}";
+pub const SAY_CARRIED_LINE : str = "The {board} board multiplied every ternary matrix of a small language model (igla-coder, tern_tc). Every answer carried a signed receipt, checked by the Mac.";
+pub const SAY_CARRIED_ROWS : [6]str = ["ternary matrices", "rows", "ternary weights", "receipts verified", "rows bit-exact", "elapsed"];
+pub const SAY_RATE : str = "{rate} answers/s";
+pub const SAY_CARRIED_PLAIN : str = "Plainly: the inputs were synthetic test vectors and no text came out of this run. Text generation ran on the Mac. Even in later generation runs, the Mac does everything except the ternary dot products.";
+pub const SAY_SOURCES : str = "Every number on this page is parsed from these files in gHashTag/trinity-fpga:";
+pub const SAY_AT : str = "at";
+
+; --- Tests -----------------------------------------------------------------------------------
+test twenty_six_answers_fit {
+ assert K_RX_BYTES / K_ANSWER_BYTES == K_LARGEST_FIT;
+ assert K_LARGEST_FIT * K_ANSWER_BYTES <= K_RX_BYTES;
+ assert K_ANSWER_BYTES * (K_LARGEST_FIT + 1) > K_RX_BYTES;
+ assert K_LARGEST_FIT * K_ANSWER_BYTES == 494;
+}
+
+test the_two_arms_sit_either_side_of_the_buffer {
+ assert K_ARM_WINDOWS[1] * K_ANSWER_BYTES <= K_RX_BYTES;
+ assert K_RX_BYTES < K_ARM_WINDOWS[0] * K_ANSWER_BYTES;
+ assert K_ARM_WINDOWS[0] * K_ANSWER_BYTES == 1216;
+ assert K_ARM_WINDOWS[1] * K_ANSWER_BYTES == 456;
+}
+
+test the_edge_runs_bracket_the_buffer {
+ assert K_EDGE_WINDOWS[0] == K_LARGEST_FIT;
+ assert K_EDGE_WINDOWS[0] * K_ANSWER_BYTES <= K_RX_BYTES;
+ assert K_EDGE_WINDOWS[1] * K_ANSWER_BYTES > K_RX_BYTES;
+ assert K_EDGE_WINDOWS[1] * K_ANSWER_BYTES == 570;
+}
+
+test the_slider_reaches_every_measured_window {
+ assert K_WINDOW_MIN == 1;
+ assert K_WINDOW_MAX >= K_ARM_WINDOWS[0];
+ assert K_WINDOW_MIN <= K_ARM_WINDOWS[1];
+ assert K_WINDOW_START <= K_WINDOW_MAX;
+ assert K_REQUEST_BYTES > K_ANSWER_BYTES;
+}
+
+test the_page_sends_nothing {
+ assert SENDS_NOTHING == true;
+ assert READS_LOCAL_FILES == false;
+ assert K_DATA_FILE == "./data.json";
+ assert CATEGORY == "fpga";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/uart-bucket/tool.js (228 lines of hand-written JavaScript).
+; The words and numbers above are unchanged. The host protocol is race_the_bee.t27's plus:
+; w_input(i, text) the slider (input 0) now holds text
+; w_click(i) button i in document order: 0 fewer, 1 more, 2 and 3 the two arms' "put window
+; in the bucket", then one per edge run
+; w_env(reduced, hidden) prefers-reduced-motion matches / document.hidden, before the data and whenever
+; either changes; w_resync() then re-runs set() as tool.js's two listeners do
+; w_now(ms) performance.now(), before every event that can start the animation
+; w_anim() 1 while the animation wants frames (host: keep requestAnimationFrame running)
+; w_frame(now, dst) one animation frame at time `now`: the DOM changes it makes, as patch lines in
+; dst, length. A patch line is `selector@index TAB kind TAB name TAB value LF`,
+; kind a = setAttribute, s = style[name], t = textContent, c = classList.toggle
+; (value 1 or 0); the host runs it with querySelectorAll(selector)[index]
+; w_render(dst) the page's HTML, length (0 on overflow). It holds the LAST DRAWN animation state,
+; so a re-render after an event shows what tool.js's nodes would still hold.
+; The bucket is drawn by the same rules as tool.js: the level, the spill and the three drops are numbers
+; from the cycle clock ((now - start) mod K_CYCLE_MS) written with toFixed(1); the readout, verdict and
+; the SVG follow the slider. Numbers are written the JavaScript way (js_num.t27: toLocaleString for
+; fmt(), toFixed, String(x) of the slider value), values read the way String() reads them (js_val.t27).
+; Where tool.js throws while it draws (arms without the two windows, no edges, a null wire / carried /
+; sources entry) its catch shows SAY_LOAD_FAILED and starts no animation; so does this page (data_ok).
+; NOT carried over: the data file is cut at DATA_CAP bytes (the page then says it could not be read);
+; classList.toggle on happy-dom normalises a class string even when nothing changes (a trailing space of
+; "ub-arm " goes at the first set()); this page writes the normalised string, a browser keeps the space.
+; MEASURED 2026-10-19: t27c 0.5.2 gen + an 11-function Zig export wrapper + zig build-exe wasm32-freestanding
+; ReleaseSmall give a 32562-byte wasm. The original page (tool.js) and this one ran in two happy-dom
+; windows against the real 2040-byte data.json and 95 variants of it, 3288 comparisons of tag, attributes
+; (the animated y / height / class / style / x) and text, with a VIRTUAL clock and requestAnimationFrame
+; on both sides (the same timestamps, so the cycle is compared at 1360 instants across the fill, hold and
+; drain phases, from 16 ms to 20 s apart), 561 slider values (in range, fractional, blank, not a number),
+; 409 presses of fewer / more, 364 presses of the measured-run buttons, 186 changes of prefers-reduced-motion
+; and 216 of document.hidden (the animation stops and a still frame is drawn, then restarts), runs that
+; start reduced / hidden / at a late or fractional clock, data with a markup / null / number board, lost
+; counts zero / string / null, in-flight at the 512 edge, huge / negative / null numbers, swapped / extra /
+; duplicate arms, a string window, three / one / all-slipped / none-slipped edges, odd wire and carried
+; fields, no generation line, empty / odd sources, 14 files the page cannot draw (an arm missing, no edges,
+; a null edge / wire / carried / source, a root that is null or an array): 0 differences. A negative
+; control (one attribute of the native DOM changed) is caught.
+; Not compared, and why: real time (requestAnimationFrame and performance.now are virtual: happy-dom has
+; no frame clock, so smoothness and the 60 Hz cadence are not measured); CSS (happy-dom loads none, so the
+; opacity / transform the stylesheet adds is not seen); focus (range.focus() after a "put window" press is
+; not a DOM difference); real pointer or media-query events.
+
+pub const DATA_CAP : u32 = 16384;
+pub var DATA : [16384]u8 = [0; 16384];
+pub var DATA_LEN : u32 = 0;
+pub var DATA_STATE : u8 = 0;
+pub var WIN : f64 = 64.0;
+pub var TARGET : f64 = 0.0;
+pub var START : f64 = 0.0;
+pub var RAF : bool = false;
+pub var NOW : f64 = 0.0;
+pub var REDUCED : bool = false;
+pub var HIDDEN : bool = false;
+pub var NOTE : bool = false;
+pub var DRAWN : bool = false;
+pub var D_WATER_Y : f64 = 268.0;
+pub var D_OVER : bool = false;
+pub var D_SPILL : u8 = 0;
+pub var D_LVL_TXT : [48]u8 = [0; 48];
+pub var D_LVL_LEN : u32 = 0;
+pub var D_LVL_X : f64 = 0.0;
+pub var D_LVL_Y : f64 = 0.0;
+pub var D_DROP_OP : [3]u8 = [0; 3];
+pub var D_DROP_Y : [3]f64 = [0.0; 3];
+pub var D_DROP_SET : [3]bool = [false; 3];
+
+pub fn w_init(h: []const u8) -> u32 {
+ DATA_STATE = 0;
+ DATA_LEN = 0;
+ RAF = false;
+ DRAWN = false;
+ NOTE = false;
+ WIN = 64.0;
+ TARGET = 0.0;
+ D_SPILL = 0;
+ D_OVER = false;
+ D_WATER_Y = 268.0;
+ D_LVL_LEN = 0;
+ D_DROP_OP[0] = 0; D_DROP_OP[1] = 0; D_DROP_OP[2] = 0;
+ D_DROP_SET[0] = false; D_DROP_SET[1] = false; D_DROP_SET[2] = false;
+ return 0;
+}
+
+pub fn w_env(reduced: u32, hidden: u32) -> u32 {
+ REDUCED = reduced != 0;
+ HIDDEN = hidden != 0;
+ return 0;
+}
+
+pub fn w_now(ms: f64) -> u32 {
+ NOW = ms;
+ return 0;
+}
+
+pub fn w_anim() -> u32 {
+ if (RAF) { return 1; }
+ return 0;
+}
+
+fn nullish(d: []const u8, sp: i64) -> bool {
+ const k : u8 = kind(d, sp);
+ return k == J_NONE or k == J_NULL;
+}
+
+/// The arm whose window is `w` (a number equal to it), NO_SPAN when none.
+fn arm_of(d: []const u8, r: i64, w: u16) -> i64 {
+ const arms : i64 = obj_get(d, r, "arms");
+ const cnt : u32 = arr_len(d, arms);
+ var i : u32 = 0;
+ while (i < cnt) {
+ const a : i64 = arr_at(d, arms, i);
+ const v : i64 = obj_get(d, a, "window");
+ if (kind(d, v) == J_NUMBER and jv_num(d, v) == (w as f64)) { return a; }
+ i = i + 1;
+ }
+ return NO_SPAN;
+}
+
+/// Would draw() in tool.js get to its last line without a TypeError?
+fn data_ok(d: []const u8, r: i64) -> bool {
+ if (nullish(d, r)) { return false; }
+ if (nullish(d, arm_of(d, r, K_ARM_WINDOWS[0])) or nullish(d, arm_of(d, r, K_ARM_WINDOWS[1]))) { return false; }
+ const ed : i64 = obj_get(d, r, "edges");
+ if (kind(d, ed) != J_ARRAY) { return false; }
+ const ec : u32 = arr_len(d, ed);
+ if (ec == 0) { return false; }
+ var i : u32 = 0;
+ while (i < ec) {
+ if (nullish(d, arr_at(d, ed, i))) { return false; }
+ i = i + 1;
+ }
+ if (nullish(d, obj_get(d, r, "wire")) or nullish(d, obj_get(d, r, "carried"))) { return false; }
+ const so : i64 = obj_get(d, r, "sources");
+ if (kind(d, so) != J_ARRAY) { return false; }
+ const sc : u32 = arr_len(d, so);
+ i = 0;
+ while (i < sc) {
+ if (nullish(d, arr_at(d, so, i))) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+// --- the bucket's numbers -----------------------------------------------------------------------
+
+fn rxf() -> f64 {
+ return K_RX_BYTES as f64;
+}
+
+fn level_y(frac: f64) -> f64 {
+ return 268.0 - (268.0 - 104.0) * js_min(frac, 1.0);
+}
+
+/// xAt(y, +1): the right wall at height y.
+fn x_right(y: f64) -> f64 {
+ const t : f64 = (y - 104.0) / (268.0 - 104.0);
+ return 256.0 + (234.0 - 256.0) * t;
+}
+
+/// JavaScript's a % b for doubles: the sign of a, |a| mod |b| exactly.
+fn fmod(a: f64, b: f64) -> f64 {
+ if (a != a or b != b or b == 0.0 or (a - a) != 0.0) { return js_nan(0.0); }
+ var neg : bool = a < 0.0;
+ var x : f64 = a;
+ if (neg) { x = 0.0 - a; }
+ var m : f64 = b;
+ if (m < 0.0) { m = 0.0 - m; }
+ if (m > x) { return a; }
+ // subtract b * 2^k from the top down; every step is exact (x and the subtrahend are within a factor 2)
+ var scaled : f64 = m;
+ var steps : u32 = 0;
+ while (scaled * 2.0 <= x and steps < 1100) {
+ scaled = scaled * 2.0;
+ steps = steps + 1;
+ }
+ while (scaled >= m) {
+ if (x >= scaled) { x = x - scaled; }
+ scaled = scaled / 2.0;
+ }
+ if (neg) { return 0.0 - x; }
+ return x;
+}
+
+/// draw(bytes, overflowing, dropPhase) of tool.js's animator, into the last-drawn state D_*.
+fn draw_state(bytes: f64, over: bool, has_phase: bool, phase: f64) -> u32 {
+ const y : f64 = level_y(bytes / rxf());
+ DRAWN = true;
+ D_WATER_Y = y;
+ D_OVER = over;
+ if (over) { D_SPILL = 49; } else { D_SPILL = 48; }
+ var tmp : [48]u8 = [0; 48];
+ const l : u32 = js_locale_num(tmp[0..], 0, js_round(js_min(bytes, rxf())));
+ var q : u32 = 0;
+ while (q < l and q < 44) {
+ D_LVL_TXT[q as usize] = tmp[q as usize];
+ q = q + 1;
+ }
+ D_LVL_TXT[q as usize] = 32;
+ D_LVL_TXT[(q + 1) as usize] = 66;
+ D_LVL_LEN = q + 2;
+ D_LVL_X = x_right(y) + 8.0;
+ D_LVL_Y = y + 4.0;
+ var i : u32 = 0;
+ while (i < 3) {
+ if (!has_phase) {
+ D_DROP_OP[i as usize] = 48;
+ } else {
+ const p : f64 = fmod(phase + (i as f64) / 3.0, 1.0);
+ const bottom : f64 = js_max(level_y(bytes / rxf()) - 8.0, 56.0);
+ D_DROP_Y[i as usize] = 56.0 + (bottom - 56.0) * p;
+ D_DROP_SET[i as usize] = true;
+ D_DROP_OP[i as usize] = 49;
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+fn still() -> u32 {
+ return draw_state(TARGET, TARGET > rxf(), false, 0.0);
+}
+
+/// animator.set(bytes)
+fn anim_set(bytes: f64) -> u32 {
+ TARGET = bytes;
+ if (REDUCED or HIDDEN) {
+ RAF = false;
+ still();
+ return 0;
+ }
+ if (!RAF) {
+ START = NOW;
+ RAF = true;
+ }
+ return 0;
+}
+
+/// set(w) of tool.js's page.
+fn set_win(w0: f64) -> u32 {
+ const lo : f64 = K_WINDOW_MIN as f64;
+ const hi : f64 = K_WINDOW_MAX as f64;
+ WIN = js_max(lo, js_min(hi, js_round(w0)));
+ anim_set(WIN * (K_ANSWER_BYTES as f64));
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (s.len > (DATA_CAP as usize)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < s.len) {
+ DATA[k] = s[k];
+ k = k + 1;
+ }
+ DATA_LEN = s.len as u32;
+ DATA_STATE = 1;
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (!jv_valid(d)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ if (!data_ok(d, root(d))) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ NOTE = REDUCED;
+ set_win(K_WINDOW_START as f64);
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ DATA_STATE = 2;
+ return 0;
+}
+
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ if (i == 0) { set_win(jv_number_of_text(s, s.len as u32)); }
+ return 0;
+}
+
+pub fn w_resync() -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ set_win(WIN);
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ if (i == 0) { set_win(WIN - 1.0); }
+ if (i == 1) { set_win(WIN + 1.0); }
+ if (i >= 2) {
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ const r : i64 = root(d);
+ if (i == 2) { set_win(jv_num(d, obj_get(d, arm_of(d, r, K_ARM_WINDOWS[0]), "window"))); }
+ if (i == 3) { set_win(jv_num(d, obj_get(d, arm_of(d, r, K_ARM_WINDOWS[1]), "window"))); }
+ if (i >= 4) {
+ const ed : i64 = obj_get(d, r, "edges");
+ if (i - 4 < arr_len(d, ed)) { set_win(jv_num(d, obj_get(d, arr_at(d, ed, i - 4), "window"))); }
+ }
+ }
+ return 0;
+}
+
+// --- the frame ----------------------------------------------------------------------------------
+
+fn patch(dst: []u8, n0: u32, sel: []const u8, ix: u32, kind_c: u8, name: []const u8) -> u32 {
+ var n : u32 = raw(dst, n0, sel);
+ n = put(dst, n, 64);
+ n = js_str_u64(dst, n, ix as u64);
+ n = put(dst, n, 9);
+ n = put(dst, n, kind_c);
+ n = put(dst, n, 9);
+ n = raw(dst, n, name);
+ return put(dst, n, 9);
+}
+
+/// The patches that bring a page showing the previous state to the state D_*.
+fn emit_patches(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = patch(dst, n0, ".ub-water", 0, 97, "y");
+ n = js_to_fixed(dst, n, D_WATER_Y, 1);
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-water", 0, 97, "height");
+ n = js_to_fixed(dst, n, 268.0 - D_WATER_Y, 1);
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-water", 0, 99, "is-over");
+ if (D_OVER) { n = put(dst, n, 49); } else { n = put(dst, n, 48); }
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-spill", 0, 115, "opacity");
+ n = put(dst, n, D_SPILL);
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-level", 0, 116, "");
+ var q : u32 = 0;
+ while (q < D_LVL_LEN) {
+ n = put(dst, n, D_LVL_TXT[q as usize]);
+ q = q + 1;
+ }
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-level", 0, 97, "x");
+ n = js_to_fixed(dst, n, D_LVL_X, 1);
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-level", 0, 97, "y");
+ n = js_to_fixed(dst, n, D_LVL_Y, 1);
+ n = put(dst, n, 10);
+ var i : u32 = 0;
+ while (i < 3) {
+ if (D_DROP_OP[i as usize] == 49) {
+ n = patch(dst, n, ".ub-drop", i, 97, "y");
+ n = js_to_fixed(dst, n, D_DROP_Y[i as usize], 1);
+ n = put(dst, n, 10);
+ }
+ n = patch(dst, n, ".ub-drop", i, 115, "opacity");
+ n = put(dst, n, D_DROP_OP[i as usize]);
+ n = put(dst, n, 10);
+ i = i + 1;
+ }
+ return n;
+}
+
+/// One animation frame at time `now`; the patch lines, their length (0 when no frame is wanted).
+pub fn w_frame(now: f64, dst: []u8) -> u32 {
+ if (!RAF or DATA_STATE != 1) { return 0; }
+ const cyc : f64 = K_CYCLE_MS as f64;
+ const t : f64 = fmod(now - START, cyc) / cyc;
+ var bytes : f64 = 0.0;
+ var has_phase : bool = false;
+ var phase : f64 = 0.0;
+ if (t < 0.55) {
+ bytes = TARGET * (t / 0.55);
+ has_phase = true;
+ phase = fmod((t / 0.55) * 4.0, 1.0);
+ } else {
+ if (t < 0.75) {
+ bytes = TARGET;
+ } else {
+ bytes = js_min(TARGET, rxf()) * (1.0 - (t - 0.75) / 0.25);
+ }
+ }
+ draw_state(bytes, bytes > rxf(), has_phase, phase);
+ return emit_patches(dst, 0);
+}
+
+// --- the page -----------------------------------------------------------------------------------
+
+/// fmt(n): toLocaleString for a number, String(n) for anything else.
+fn fmt_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) == J_NUMBER) { return js_locale_num(dst, n0, jv_num(d, sp)); }
+ return jv_text(dst, n0, d, sp);
+}
+
+/// fmt(x) as raw bytes (for a pool value).
+fn fmt_raw(out: []u8, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) == J_NUMBER) { return js_locale_num(out, 0, jv_num(d, sp)); }
+ return jv_str(out, 0, d, sp);
+}
+
+fn fmt_f64_raw(out: []u8, v: f64) -> u32 {
+ return js_locale_num(out, 0, v);
+}
+
+fn bucket_html(dst: []u8, n0: u32, d: []const u8, r: i64, readout: []const u8, verdict: []const u8) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ n = text(dst, n, readout);
+ n = put(dst, n, 32);
+ n = text(dst, n, verdict);
+ n = raw(dst, n, "");
+ n = nn_text(dst, n, d, obj_get(d, r, "board"));
+ n = raw(dst, n, "");
+ var vb : [128]u8 = [0; 128];
+ var vn : u32 = jv_vput_f64(vb[0..], 0, "answer", K_ANSWER_BYTES as f64);
+ n = jv_fill(dst, n, SAY_TAP, vb[0..], vn, false);
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < 3) {
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_SPILL);
+ n = raw(dst, n, "");
+ var bb : [64]u8 = [0; 64];
+ const bn : u32 = jv_vput_f64(bb[0..], 0, "rx", rxf());
+ n = jv_fill(dst, n, SAY_BRIM, bb[0..], bn, false);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_DRAIN);
+ n = raw(dst, n, "");
+ if (DRAWN) {
+ var q : u32 = 0;
+ while (q < D_LVL_LEN) {
+ n = put_escaped(dst, n, D_LVL_TXT[q as usize]);
+ q = q + 1;
+ }
+ }
+ return raw(dst, n, "");
+}
+
+/// `x ?? ''` as a child.
+fn nn_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return n0; }
+ return jv_text(dst, n0, d, sp);
+}
+
+fn arm_card(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 {
+ const inflight : i64 = obj_get(d, a, "inFlight");
+ const lost : i64 = obj_get(d, a, "lost");
+ const over : bool = jv_num(d, inflight) > rxf();
+ const w : i64 = obj_get(d, a, "window");
+ const shown : bool = kind(d, w) == J_NUMBER and jv_num(d, w) == WIN;
+ var n : u32 = raw(dst, n0, "
");
+ var vb : [256]u8 = [0; 256];
+ var vn : u32 = jv_vput_val(vb[0..], 0, "w", d, w);
+ n = jv_fill(dst, n, SAY_ARM_HEAD, vb[0..], vn, false);
+ n = raw(dst, n, "
");
+ var row : usize = 0;
+ while (row < SAY_ARM_ROWS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ARM_ROWS[row]);
+ n = raw(dst, n, "
0.0) { n = raw(dst, n, "ub-lost"); }
+ n = raw(dst, n, "\">");
+ if (row == 0) {
+ n = fmt_text(dst, n, d, inflight);
+ n = raw(dst, n, " B");
+ }
+ if (row == 1) { n = fmt_text(dst, n, d, obj_get(d, a, "jobsSent")); }
+ if (row == 2) { n = fmt_text(dst, n, d, obj_get(d, a, "accepted")); }
+ if (row == 3) { n = fmt_text(dst, n, d, lost); }
+ if (row == 4) { n = fmt_text(dst, n, d, obj_get(d, a, "events")); }
+ if (row == 5) {
+ n = jv_text(dst, n, d, obj_get(d, a, "elapsedS"));
+ n = put(dst, n, 32);
+ n = text(dst, n, SAY_SECONDS);
+ }
+ n = raw(dst, n, "
");
+ row = row + 1;
+ }
+ n = raw(dst, n, "
");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ if (DATA_STATE == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOADING);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ if (DATA_STATE == 2) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOAD_FAILED);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ const r : i64 = root(d);
+ // the readout and the verdict for the window
+ const bytes : f64 = WIN * (K_ANSWER_BYTES as f64);
+ const over : bool = bytes > rxf();
+ var rd : [256]u8 = [0; 256];
+ var rb : [256]u8 = [0; 256];
+ var tmp : [64]u8 = [0; 64];
+ var rv : u32 = jv_vput_f64(rb[0..], 0, "w", WIN);
+ rv = jv_vput_f64(rb[0..], rv, "answer", K_ANSWER_BYTES as f64);
+ var tl : u32 = fmt_f64_raw(tmp[0..], bytes);
+ rv = jv_vput_str(rb[0..], rv, "bytes", tmp[0..(tl as usize)]);
+ rv = jv_vput_f64(rb[0..], rv, "rx", rxf());
+ // the filled text, unescaped, for the : the fill escapes, so fill into a scratch and unescape
+ var ro : [512]u8 = [0; 512];
+ var rl : u32 = jv_fill_raw(ro[0..], 0, SAY_READOUT, rb[0..], rv, false);
+ var vd : [512]u8 = [0; 512];
+ var vb : [256]u8 = [0; 256];
+ var vl : u32 = 0;
+ if (over) {
+ tl = fmt_f64_raw(tmp[0..], bytes - rxf());
+ const vn : u32 = jv_vput_str(vb[0..], 0, "over", tmp[0..(tl as usize)]);
+ vl = jv_fill_raw(vd[0..], 0, SAY_OVER, vb[0..], vn, false);
+ } else {
+ tl = fmt_f64_raw(tmp[0..], rxf() - bytes);
+ const vn2 : u32 = jv_vput_str(vb[0..], 0, "spare", tmp[0..(tl as usize)]);
+ vl = jv_fill_raw(vd[0..], 0, SAY_FITS, vb[0..], vn2, false);
+ }
+ n = raw(dst, n, "
");
+ var ib : [512]u8 = [0; 512];
+ var in_ : u32 = jv_vput_val(ib[0..], 0, "board", d, obj_get(d, r, "board"));
+ in_ = jv_vput_f64(ib[0..], in_, "answer", K_ANSWER_BYTES as f64);
+ in_ = jv_vput_f64(ib[0..], in_, "rx", rxf());
+ n = jv_fill(dst, n, SAY_INTRO, ib[0..], in_, false);
+ n = raw(dst, n, "
");
+ n = bucket_html(dst, n, d, r, ro[0..(rl as usize)], vd[0..(vl as usize)]);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_BUCKET);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_WINDOW_LABEL);
+ n = raw(dst, n, "
");
+ n = put_all2(dst, n, ro[0..], rl);
+ n = raw(dst, n, "
");
+ n = put_all2(dst, n, vd[0..], vl);
+ n = raw(dst, n, "
");
+ var ub : [512]u8 = [0; 512];
+ var un : u32 = jv_vput_f64(ub[0..], 0, "rx", rxf());
+ un = jv_vput_f64(ub[0..], un, "answer", K_ANSWER_BYTES as f64);
+ un = jv_vput_val(ub[0..], un, "ratio", d, obj_get(d, r, "ratio"));
+ un = jv_vput_f64(ub[0..], un, "fit", K_LARGEST_FIT as f64);
+ n = jv_fill(dst, n, SAY_RULE, ub[0..], un, false);
+ n = raw(dst, n, "
");
+ if (NOTE) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_MOTION_OFF);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ var hb : [512]u8 = [0; 512];
+ var hn : u32 = jv_vput_val(hb[0..], 0, "board", d, obj_get(d, r, "board"));
+ var jt : [64]u8 = [0; 64];
+ const jl : u32 = fmt_raw(jt[0..], d, obj_get(d, r, "jobsPerArm"));
+ hn = jv_vput_str(hb[0..], hn, "jobs", jt[0..(jl as usize)]);
+ n = jv_fill(dst, n, SAY_RUNS_TITLE, hb[0..], hn, false);
+ n = raw(dst, n, "
");
+ const a64 : i64 = arm_of(d, r, K_ARM_WINDOWS[0]);
+ const a24 : i64 = arm_of(d, r, K_ARM_WINDOWS[1]);
+ n = arm_card(dst, n, d, a64);
+ n = arm_card(dst, n, d, a24);
+ n = raw(dst, n, "
");
+ const wire : i64 = obj_get(d, r, "wire");
+ var sb : [1024]u8 = [0; 1024];
+ var sn : u32 = jv_vput_val(sb[0..], 0, "t64", d, obj_get(d, a64, "elapsedS"));
+ sn = jv_vput_val(sb[0..], sn, "t24", d, obj_get(d, a24, "elapsedS"));
+ sn = jv_vput_val(sb[0..], sn, "req", d, obj_get(d, wire, "requestBytes"));
+ var bt : [64]u8 = [0; 64];
+ var btl : u32 = fmt_raw(bt[0..], d, obj_get(d, wire, "baud"));
+ sn = jv_vput_str(sb[0..], sn, "baud", bt[0..(btl as usize)]);
+ btl = fmt_raw(bt[0..], d, obj_get(d, wire, "jobs"));
+ sn = jv_vput_str(sb[0..], sn, "jobs", bt[0..(btl as usize)]);
+ sn = jv_vput_val(sb[0..], sn, "wire", d, obj_get(d, wire, "seconds"));
+ n = jv_fill(dst, n, SAY_SAME_TIME, sb[0..], sn, false);
+ n = raw(dst, n, "
");
+ const ed : i64 = obj_get(d, r, "edges");
+ var eb : [256]u8 = [0; 256];
+ var et : [64]u8 = [0; 64];
+ const el : u32 = fmt_raw(et[0..], d, obj_get(d, arr_at(d, ed, 0), "planned"));
+ const en : u32 = jv_vput_str(eb[0..], 0, "planned", et[0..(el as usize)]);
+ n = jv_fill(dst, n, SAY_EDGE_TITLE, eb[0..], en, false);
+ n = raw(dst, n, "
");
+ const ec : u32 = arr_len(d, ed);
+ var x : u32 = 0;
+ while (x < ec) {
+ const e : i64 = arr_at(d, ed, x);
+ const slipped : bool = jv_truthy(d, obj_get(d, e, "slipped"));
+ const shown : bool = kind(d, obj_get(d, e, "window")) == J_NUMBER and jv_num(d, obj_get(d, e, "window")) == WIN;
+ n = raw(dst, n, "
");
+ var xb : [1024]u8 = [0; 1024];
+ var xn : u32 = jv_vput_val(xb[0..], 0, "w", d, obj_get(d, e, "window"));
+ var xt : [64]u8 = [0; 64];
+ var xl : u32 = fmt_raw(xt[0..], d, obj_get(d, e, "inFlight"));
+ xn = jv_vput_str(xb[0..], xn, "bytes", xt[0..(xl as usize)]);
+ xl = fmt_raw(xt[0..], d, obj_get(d, e, "receipts"));
+ xn = jv_vput_str(xb[0..], xn, "ok", xt[0..(xl as usize)]);
+ xl = fmt_raw(xt[0..], d, obj_get(d, e, "receiptsOf"));
+ xn = jv_vput_str(xb[0..], xn, "planned", xt[0..(xl as usize)]);
+ xl = fmt_raw(xt[0..], d, obj_get(d, e, "jobsSent"));
+ xn = jv_vput_str(xb[0..], xn, "sent", xt[0..(xl as usize)]);
+ if (slipped) { n = jv_fill(dst, n, SAY_EDGE_FAIL, xb[0..], xn, false); } else { n = jv_fill(dst, n, SAY_EDGE_PASS, xb[0..], xn, false); }
+ n = raw(dst, n, "
");
+ x = x + 1;
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CAVEAT);
+ n = raw(dst, n, "
");
+ const c : i64 = obj_get(d, r, "carried");
+ var cb : [256]u8 = [0; 256];
+ var cn : u32 = jv_vput_val(cb[0..], 0, "w", d, obj_get(d, c, "window"));
+ n = jv_fill(dst, n, SAY_CARRIED_TITLE, cb[0..], cn, false);
+ n = raw(dst, n, "
");
+ cn = jv_vput_val(cb[0..], 0, "board", d, obj_get(d, r, "board"));
+ n = jv_fill(dst, n, SAY_CARRIED_LINE, cb[0..], cn, false);
+ n = raw(dst, n, "
");
+ var row : usize = 0;
+ while (row < SAY_CARRIED_ROWS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CARRIED_ROWS[row]);
+ n = raw(dst, n, "
");
+ if (row == 0) { n = fmt_text(dst, n, d, obj_get(d, c, "matrices")); }
+ if (row == 1) { n = fmt_text(dst, n, d, obj_get(d, c, "modelRows")); }
+ if (row == 2) { n = fmt_text(dst, n, d, obj_get(d, c, "weights")); }
+ if (row == 3) {
+ n = fmt_text(dst, n, d, obj_get(d, c, "receipts"));
+ n = raw(dst, n, " / ");
+ n = fmt_text(dst, n, d, obj_get(d, c, "receiptsOf"));
+ }
+ if (row == 4) {
+ n = fmt_text(dst, n, d, obj_get(d, c, "rows"));
+ n = raw(dst, n, " / ");
+ n = fmt_text(dst, n, d, obj_get(d, c, "rowsOf"));
+ }
+ if (row == 5) {
+ n = jv_text(dst, n, d, obj_get(d, c, "elapsedS"));
+ n = put(dst, n, 32);
+ n = text(dst, n, SAY_SECONDS);
+ n = raw(dst, n, ", ");
+ var qb : [256]u8 = [0; 256];
+ var qt : [64]u8 = [0; 64];
+ const ql : u32 = fmt_raw(qt[0..], d, obj_get(d, c, "answersPerS"));
+ const qn : u32 = jv_vput_str(qb[0..], 0, "rate", qt[0..(ql as usize)]);
+ n = jv_fill(dst, n, SAY_RATE, qb[0..], qn, false);
+ }
+ n = raw(dst, n, "
");
+ row = row + 1;
+ }
+ n = raw(dst, n, "
");
+ if (jv_truthy(d, obj_get(d, r, "generationOnMac"))) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CARRIED_PLAIN);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_SOURCES);
+ n = raw(dst, n, "
");
+ const so : i64 = obj_get(d, r, "sources");
+ const sc : u32 = arr_len(d, so);
+ x = 0;
+ while (x < sc) {
+ const s : i64 = arr_at(d, so, x);
+ n = raw(dst, n, "
");
+ n = jv_text(dst, n, d, obj_get(d, r, "repo"));
+ n = put(dst, n, 32);
+ n = jv_text(dst, n, d, obj_get(d, s, "path"));
+ n = put(dst, n, 32);
+ n = text(dst, n, SAY_AT);
+ n = put(dst, n, 32);
+ n = jv_text(dst, n, d, obj_get(d, s, "commit"));
+ n = raw(dst, n, "
");
+ x = x + 1;
+ }
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+fn put_all2(dst: []u8, n0: u32, s: []const u8, l: u32) -> u32 {
+ var n : u32 = n0;
+ var i : u32 = 0;
+ while (i < l) {
+ n = put_escaped(dst, n, s[i as usize]);
+ i = i + 1;
+ }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call.
+/// Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ return n;
+}
From 518a1827815aa94efa3b0b8d0605cfc82520c540 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 18:22:08 +0700
Subject: [PATCH 33/33] widgets: test-touch drawn natively -- 35775-byte wasm,
V8's TimSort ported, 786923-byte data in a 1 MiB buffer, 0 differences
(Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/test_touch.t27 | 1856 ++++++++++++++++++++++++++++++++++
1 file changed, 1856 insertions(+)
create mode 100644 specs/widgets/test_touch.t27
diff --git a/specs/widgets/test_touch.t27 b/specs/widgets/test_touch.t27
new file mode 100644
index 0000000000..df83e346e9
--- /dev/null
+++ b/specs/widgets/test_touch.t27
@@ -0,0 +1,1856 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/test-touch.t27 -- did the agent touch its own tests? Every merged pull request on gHashTag/t27
+; Source of truth for public/widgets/test-touch/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, and the rule is K_ constants. The data script
+; scripts/widget-data/test-touch.mjs reads the K_ rule from this file, runs the fixture diffs below
+; through its classifier, and exits non-zero when its counts disagree with K_FIX_* or K_SNAP_*.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT SHOWS: a dated snapshot. For every pull request merged on gHashTag/t27 in the window, the
+; script reads the patch that landed on master (git diff ^1 , the whole file as context
+; for .t27 files) and gives it one badge: tests untouched, tests added, tests changed, or assert
+; removed. Clicking a pull request shows the test lines it changed and links to it on GitHub. The
+; same is done for the human lane, so the bee lane has something to be compared with.
+
+; THE LANE RULE: a pull request is the bee's when its head branch is queen- (the race-the-bee
+; rule); every other one is the human lane, which means human-steered, not hand-typed.
+
+; WHAT IS A TEST LINE: a line inside a test, invariant or bench block of a .t27 file; any line of a
+; file under a tests/, test/ or conformance/ directory; any line of a *_test.* file (and test_*.py,
+; *.test.js, *.spec.ts). Not tests: .trinity/, .claude/, docs/ and .github/ (seals, notes and CI),
+; and JSON records under a test directory that carry a "generated_by" key (reports a tool wrote).
+
+; THE HEURISTIC: removed and added test lines are paired before they are judged. A line that only
+; moved is ignored. A test whose header was rewritten under the same name is "rewritten". A removed
+; check is paired with an added check on the same subject (the left side of the comparison) in the
+; same block first, then the same file. A pair is "expect" when the expected value changed, "loosened"
+; when the comparison got weaker (== to >=, a conjunct dropped, a disjunct added), "trivial" when the
+; new check cannot fail (true, x == x, len >= 0), "tightened" for the reverse. A removed check with no
+; partner, and a whole test block deleted, is "removed". The badge is "assert removed" when any check
+; or test block was removed or any check was loosened or made trivial; "tests changed" when other
+; test lines were removed; "tests added" when test lines were only added; else "tests untouched".
+
+; KNOWN FALSE POSITIVES: an assert merged, split or rewritten with a new subject reads as removed
+; (#5731 rewrote "invariant " checks as "then" checks); two checks in one file can be paired
+; across different tests; a quantifier added to an implication reads as an expected value change;
+; JSON test vectors reformatted (Infinity to "Infinity") or with a status renamed read as changed
+; expectations; a syntax-only rewrite of a test reads as "tests changed".
+; KNOWN FALSE NEGATIVES: a CI workflow or suite expectation file that stops running a test is not a
+; test line (the page flags those pull requests separately); asserts in code outside a test block or
+; test file are counted but not judged; a constant a test reads can change outside the test; an added
+; test that cannot fail is counted as "tests added", it is not judged.
+
+; WHAT IT DOES NOT CLAIM: intent. A removed test can be right: a test of a function that was deleted,
+; a placeholder test, a test that was false. The badge says the tests got smaller or weaker; the
+; reader decides whether that was gaming. It does not claim the lanes do the same work: most bee
+; merges are machine-filed port and test tasks. Numbers are from data.json, written by the script.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_test_touch;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+use widgets::js_val::jv_fill_raw;
+use widgets::js_val::jv_fill_with;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "test-touch";
+pub const TITLE : str = "Did the agent touch its own tests? Every PR merged on t27, checked";
+pub const DESCRIPTION : str = "Every pull request merged on gHashTag/t27 in 14 days, bee lane and human lane: which added tests, changed them or removed asserts, with the exact test lines from git.";
+pub const IMAGE_ALT : str = "Test-touch card, snapshot 2026-10-04 of gHashTag/t27 over 14 days: the Queen's bees merged 367 pull requests, of which 11 removed tests or asserts, 32 changed tests, 238 added tests and 86 left them untouched. People merged 240, of which 17 removed tests or asserts. One bee PR titled Implement the 1 empty function body deleted all 26 tests in its file.";
+pub const HOOK : str = "Bees landed 367 PRs on t27 in 14 days. 11 deleted tests or asserts; one titled 'Implement the 1 empty function body' deleted all 26 tests in its file.";
+pub const CATEGORY : str = "compiler";
+pub const DATA_SOURCES : [3]str = ["gh 2.83.2, gh api graphql on gHashTag/t27: search is:pr is:merged, one day at a time, with head branch, author, merge commit and the file list GitHub reports", "git 2.52.0 on a shallow bare clone of t27 master: git diff -M ^1 , -U1000000 for .t27 files and -U0 for the rest", "node scripts/widget-data/test-touch.mjs: classifies every patch with the rule in this spec and writes public/widgets/test-touch/data.json with its UTC time"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+; --- The rule --------------------------------------------------------------------------------
+pub const K_REPO : str = "gHashTag/t27";
+pub const K_BEE_BRANCH_PREFIX : str = "queen-";
+pub const K_WINDOW_DAYS : u8 = 14;
+pub const K_DATA_FILE : str = "./data.json";
+pub const K_LANES : [2]str = ["bee", "human"];
+pub const K_TEST_DIRS : [3]str = ["tests", "test", "conformance"];
+pub const K_TEST_NAME_MARK : str = "_test.";
+pub const K_NOT_TEST_DIRS : [4]str = [".trinity", ".claude", "docs", ".github"];
+pub const K_GENERATED_KEY : str = "generated_by";
+pub const K_TEST_BLOCK_WORDS : [3]str = ["test", "invariant", "bench"];
+pub const K_TAGS : [8]str = ["removed", "trivial", "loosened", "expect", "rewritten", "tightened", "added", "line"];
+
+; --- Fixtures --------------------------------------------------------------------------------
+; Each WANT vector is [badge, class, checks added, checks removed, expect, loosened, test lines
+; added, test lines removed, code lines]. Badge: 0 untouched, 1 added, 2 changed, 3 assert removed.
+; Class: 0 code only, 1 tests only, 2 both, 3 neither.
+pub const K_FIX_COUNT : u8 = 3;
+pub const SAY_FIX_0_PATH : str = "specs/demo/clamp.t27";
+pub const SAY_FIX_0_DIFF : [7]str = ["@@ -1,3 +1,6 @@", " module clamp;", "-pub const K_MAX : u8 = 3;", "+pub const K_MAX : u8 = 4;", "+test max_is_four {", "+ assert K_MAX == 4;", "+}"];
+pub const K_FIX_0_WANT : [9]u16 = [1, 2, 1, 0, 0, 0, 3, 0, 2];
+pub const SAY_FIX_1_PATH : str = "specs/demo/width.t27";
+pub const SAY_FIX_1_DIFF : [5]str = ["@@ -1,3 +1,3 @@", " test width_ok {", "- assert width(27) == 27;", "+ assert width(27) >= 20;", " }"];
+pub const K_FIX_1_WANT : [9]u16 = [3, 1, 0, 0, 0, 1, 1, 1, 0];
+pub const SAY_FIX_2_PATH : str = "bootstrap/tests/fmt.rs";
+pub const SAY_FIX_2_DIFF : [7]str = ["@@ -3,4 +3,4 @@", " fn fmt_zero() {", "- assert_eq!(fmt(0), 0);", "- assert!(ok(1));", "+ assert!(ok(1));", "+ assert_eq!(fmt(0), 1);", " }"];
+pub const K_FIX_2_WANT : [9]u16 = [2, 1, 0, 0, 1, 0, 2, 2, 0];
+
+; --- The snapshot ----------------------------------------------------------------------------
+; [pull requests, untouched, added, changed, assert removed, code only, tests only, both, neither,
+; PRs with an expected value changed, PRs with a check loosened]. Pasted from the script's output;
+; the script exits non-zero when data.json disagrees.
+pub const K_SNAP_DATE : str = "2026-10-04";
+pub const K_SNAP_BEE : [11]u16 = [367, 86, 238, 32, 11, 77, 37, 244, 9, 10, 1];
+pub const K_SNAP_HUMAN : [11]u16 = [240, 95, 69, 59, 17, 64, 10, 135, 31, 39, 2];
+
+; --- Words -----------------------------------------------------------------------------------
+; {name} is filled from data.json by tool.js; nothing else is.
+pub const SAY_SNAPSHOT : str = "Snapshot, not live: taken {at} UTC from GitHub and git, covering every pull request merged on {repo} in the {days} days before it.";
+pub const SAY_LOADING : str = "Reading the snapshot...";
+pub const SAY_LOAD_FAILED : str = "The snapshot file could not be read.";
+pub const SAY_LANE_NAMES : [2]str = ["Bee", "Human"];
+pub const SAY_LANE_LINES : [2]str = ["head branch queen-, written by a bee the Queen dispatched", "any other branch: a person steered the session, often with an AI assistant"];
+pub const SAY_MERGED : str = "merged pull requests";
+pub const SAY_REMOVED_OF : str = "removed or weakened a test";
+pub const SAY_BADGES : [4]str = ["tests untouched", "tests added", "tests changed", "assert removed"];
+pub const SAY_BADGE_LINES : [4]str = ["no test line changed", "test lines only added", "test lines removed or rewritten, no check lost", "a check or test block removed, or a check made weaker"];
+pub const SAY_CLASSES : [4]str = ["code only", "tests only", "code and tests", "neither"];
+pub const SAY_STAT_NAMES : [5]str = ["checks added", "checks removed", "test blocks removed", "expected values changed", "checks loosened"];
+pub const SAY_VERDICT_YES : str = "Yes. {beeRemoved} of {beePrs} bee pull requests removed test blocks or asserts, or made a check weaker: {beeBlocks} test blocks deleted in all. People: {humanRemoved} of {humanPrs}.";
+pub const SAY_VERDICT_NO : str = "No bee pull request in this window removed a test block or an assert, or made a check weaker.";
+pub const SAY_VERDICT_LAST : str = "The last bee one merged {last} UTC; the {after} bee pull requests merged after it removed none.";
+pub const SAY_FILTER_LANE : str = "Lane";
+pub const SAY_FILTER_BADGE : str = "Badge";
+pub const SAY_ALL : str = "All";
+pub const SAY_SHOWING : str = "{n} of {total} pull requests";
+pub const SAY_SHOW_MORE : str = "Show more";
+pub const SAY_NO_TITLE : str = "(title not in English)";
+pub const SAY_TAGS : [8]str = ["removed", "now trivial", "loosened", "expected value", "rewritten", "tightened", "added", "test line"];
+pub const SAY_TEST_FILES : str = "{n} of {all} changed files hold test lines";
+pub const SAY_CHECKS : str = "checks +{added} / -{removed}; test blocks removed {blocks}";
+pub const SAY_NO_LINES : str = "No test line changed in this pull request.";
+pub const SAY_MORE_LINES : str = "+{n} more test lines in the pull request";
+pub const SAY_OPEN_PR : str = "Open the pull request on GitHub";
+pub const SAY_CI_GATE : str = "Also changed a CI workflow or a suite expectation file; those are not counted as test lines.";
+pub const SAY_API_ONLY : str = "GitHub lists {n} more file(s) than the merge diff holds; the badge reads the merge diff.";
+pub const SAY_EXCLUDED : str = "Left out: {notMaster} merged pull requests whose merge commit is not on master.";
+pub const SAY_RULE_TITLE : str = "How a pull request is judged, and where it is wrong";
+pub const SAY_RULE : [5]str = ["Lane: the bee's when the head branch is queen-; every other pull request is the human lane.", "Test lines: test, invariant and bench blocks in .t27 files; files under tests/, test/ or conformance/; *_test.* files. Not seals, docs, CI, or JSON reports a tool generated.", "Diff: git diff ^1 , the patch that landed on master. Lines that only moved are ignored.", "Pairing: a removed check is paired with an added check on the same subject. Then expected value changed, loosened (== to >=, a condition dropped), trivial (cannot fail), tightened, or rewritten. Unpaired: removed.", "Badge: assert removed when a check or test block was removed or a check got weaker; tests changed when other test lines went; tests added when lines were only added."];
+pub const SAY_FALSE_POS_TITLE : str = "Known false positives";
+pub const SAY_FALSE_POS : [4]str = ["An assert merged, split or rewritten with a new subject reads as removed (#5731 rewrote invariant checks as then checks).", "Two checks in one file can be paired across different tests.", "Reformatted JSON vectors (Infinity to a string) or a renamed status read as changed expected values.", "A syntax-only rewrite of a test reads as tests changed."];
+pub const SAY_FALSE_NEG_TITLE : str = "Known false negatives";
+pub const SAY_FALSE_NEG : [4]str = ["A CI workflow or suite expectation that stops running a test is not a test line; those pull requests carry a note.", "Asserts in code outside a test block are counted, not judged.", "A constant a test reads can change outside the test.", "An added test that cannot fail still counts as tests added."];
+pub const SAY_NOT_INTENT : str = "A badge is not a verdict on intent: a removed test can be right (its function was deleted, it was a placeholder, it was false). It says the tests got smaller or weaker; the lines are there to judge.";
+
+; --- Tests -----------------------------------------------------------------------------------
+test every_pull_request_gets_one_badge_and_one_class {
+ assert K_SNAP_BEE[1] + K_SNAP_BEE[2] + K_SNAP_BEE[3] + K_SNAP_BEE[4] == K_SNAP_BEE[0];
+ assert K_SNAP_BEE[5] + K_SNAP_BEE[6] + K_SNAP_BEE[7] + K_SNAP_BEE[8] == K_SNAP_BEE[0];
+ assert K_SNAP_HUMAN[1] + K_SNAP_HUMAN[2] + K_SNAP_HUMAN[3] + K_SNAP_HUMAN[4] == K_SNAP_HUMAN[0];
+ assert K_SNAP_HUMAN[5] + K_SNAP_HUMAN[6] + K_SNAP_HUMAN[7] + K_SNAP_HUMAN[8] == K_SNAP_HUMAN[0];
+ assert K_SNAP_BEE[10] <= K_SNAP_BEE[4];
+ assert K_SNAP_HUMAN[10] <= K_SNAP_HUMAN[4];
+}
+
+test the_hook_matches_the_snapshot {
+ assert K_SNAP_BEE[0] == 367;
+ assert K_SNAP_BEE[4] == 11;
+ assert K_SNAP_HUMAN[4] == 17;
+ assert K_WINDOW_DAYS == 14;
+ assert K_SNAP_DATE == "2026-10-04";
+}
+
+test the_fixtures_cover_add_loosen_and_flip {
+ assert K_FIX_COUNT == 3;
+ assert K_FIX_0_WANT[0] == 1;
+ assert K_FIX_0_WANT[2] == 1;
+ assert K_FIX_1_WANT[0] == 3;
+ assert K_FIX_1_WANT[5] == 1;
+ assert K_FIX_2_WANT[0] == 2;
+ assert K_FIX_2_WANT[4] == 1;
+ assert K_FIX_2_WANT[3] == 0;
+}
+
+test the_badges_and_tags_line_up {
+ assert SAY_BADGES[3] == "assert removed";
+ assert SAY_BADGES[0] == "tests untouched";
+ assert K_TAGS[0] == "removed";
+ assert K_TAGS[7] == "line";
+ assert K_LANES[0] == "bee";
+ assert K_BEE_BRANCH_PREFIX == "queen-";
+}
+
+test the_page_sends_nothing_and_reads_no_file {
+ assert SENDS_NOTHING == true;
+ assert READS_LOCAL_FILES == false;
+ assert K_DATA_FILE == "./data.json";
+ assert KIND == "widget-tool";
+ assert ID == "test-touch";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/test-touch/tool.js (146 lines of hand-written JavaScript).
+; The words and numbers above are unchanged. The host protocol is race_the_bee.t27's plus:
+; w_click(i) button i in document order: 0..2 the lane filter (All, Bee, Human), 3..7 the badge
+; filter (All, then the four badges from the worst), 8 "Show more"
+; w_toggle(i, open) details i (document order: the pull request rows, then the rule block) was opened
+; or closed; 1 when the page changed (the host then re-renders), else 0. A row's
+; body is built the first time it opens, as tool.js's "toggle" listener does.
+; The host must not restore open by index for this widget (it does not
+; when the widget exports w_toggle): the open state is written as the `open` attribute.
+; The 607 pull requests of data.json are NOT copied into a tree: DATA holds the file's bytes (1 MiB
+; buffer for the 786923-byte file), PRS the byte span of each pull request. The list is sorted ONCE, as
+; tool.js does, by V8's own algorithm: Array.prototype.sort is TimSort and the comparator
+; (b.badge - a.badge || (a.merged < b.merged ? 1 : -1)) never returns 0, so pull requests with the same
+; badge and merge time (the real file has three, #5821 #5675 #5595) come out in the order the algorithm
+; happens to compare them. This file ports that algorithm (array-sort.tq: runs, binary insertion,
+; galloping merges, the same argument order in every Compare) and checks it against node's sort on tie-heavy
+; random lists. Numbers and strings are written as js_num.t27 / js_val.t27 write them.
+; Where tool.js throws while it draws (prs no array, a null pull request, lanes without bee / human or a
+; lane without a vector array) its catch shows SAY_LOAD_FAILED; so does this page (data_ok). A body
+; that tool.js cannot build (a merged stamp that is no string, a line text that is no string) throws inside
+; its toggle handler, which leaves the row open and empty; this page then draws the body as empty strings.
+; NOT carried over: more than PR_CAP (1024) pull requests; a `merged` stamp compared beyond its first 32
+; bytes; sort-key `merged` values that are objects or arrays; UTF-16 order of astral characters; the last
+; merge time when a bad pull request has a non-string stamp (the default .sort() of mixed values).
+; MEMORY: DATA 1 MiB (the file is 786923 bytes), the sort keys 100 KB, the page buffer is the host's: the
+; real file renders 17472 bytes at the start, 187106 bytes with all 607 rows listed and closed, and
+; 1157198 bytes with all 607 rows open (601 bodies), so the host's output buffer is 1 MiB for the first
+; two and must be 2 MiB or more for the last. The wasm module asks for 34 pages (2.2 MB) at start; with
+; the host's 17-page buffer area and the data copied in it holds 65 pages (4.3 MB). w_data takes 64 ms in
+; node (validate, 607 spans, TimSort, filter), a closed 607-row render 24-45 ms.
+; MEASURED 2026-10-19: t27c 0.5.2 gen + a 6-function Zig export wrapper + zig build-exe wasm32-freestanding
+; ReleaseSmall give a 35775-byte wasm. The original page (tool.js) and this one ran in two happy-dom
+; windows: (1) the real 786923-byte data.json, two sessions of 70 interactions (lane and badge filters,
+; show more, rows opened and closed, the rule block): 144 comparisons of tag, attributes and text; (2) 38
+; data variants cut to 45 pull requests (no bad bee PR; a bad one with a missing / null / odd stamp; lanes
+; and badges and classes out of range or as strings; titles empty / null / markup / numbers; line lists
+; empty / odd / many files; flags; counts / author / branch / url missing; merged stamps as numbers; vectors
+; short or with strings; excluded and snapshot fields missing; 1, 0, 40, 41 and 80 pull requests) and 8
+; data files the page cannot draw (prs missing or an object, a null pull request, lanes or a vector
+; missing, a root array): 400 comparisons; (3) the SORT: 156 lists of 2 to 200 pull requests with almost
+; all badges and stamps tied (2937 tied pairs; random, ascending, descending, sawtooth, blocks), every
+; row listed, all and bee only, the order of the rows compared; (4) the t27 TimSort alone against node's
+; Array.prototype.sort with the same comparator over 10560 lists of 2 to 1024 elements (1948600 tied
+; pairs): 0 differences everywhere. A negative control (one title in the native DOM changed) is caught;
+; a 404 and a body that is not JSON end on the same line in both.
+; Not compared, and why: happy-dom's 'toggle' event is the only way a row opening reaches either page (a
+; real browser fires it on the user's click as well); CSS; a body for a pull request the original cannot
+; build; runs of more than 200 tied pull requests in the DOM (the lists of up to 1024 are compared in
+; the sort test only); the host's 'toggle' forwarding is the new host code described in the report.
+
+pub const DATA_CAP : u32 = 1048576;
+pub const PR_CAP : u32 = 1024;
+pub const PAGE : u32 = 40;
+pub var DATA : [1048576]u8 = [0; 1048576];
+pub var DATA_LEN : u32 = 0;
+pub var DATA_STATE : u8 = 0;
+pub var NPR : u32 = 0;
+pub var PRS : [1024]i64 = [0; 1024];
+pub var KB : [1024]f64 = [0.0; 1024];
+pub var KM : [1024]f64 = [0.0; 1024];
+pub var KK : [1024]u8 = [0; 1024];
+pub var KS : [32768]u8 = [0; 32768];
+pub var KL : [1024]u32 = [0; 1024];
+pub var ORDER : [1024]u32 = [0; 1024];
+pub var WORK : [1024]u32 = [0; 1024];
+pub var TMP : [1024]u32 = [0; 1024];
+pub var RUN_BASE : [96]i32 = [0; 96];
+pub var RUN_LEN : [96]i32 = [0; 96];
+pub var RUNS : i32 = 0;
+pub var MIN_GALLOP : i32 = 7;
+pub var LANE : i32 = 0 - 1;
+pub var BADGE : i32 = 0 - 1;
+pub var SHOWN : u32 = 40;
+pub var ROW_OPEN : [1024]bool = [false; 1024];
+pub var ROW_BODY : [1024]bool = [false; 1024];
+pub var RULE_OPEN : bool = false;
+pub var ROWS : [1024]u32 = [0; 1024];
+pub var NROWS : u32 = 0;
+
+pub fn w_init(h: []const u8) -> u32 {
+ DATA_STATE = 0;
+ DATA_LEN = 0;
+ NPR = 0;
+ LANE = 0 - 1;
+ BADGE = 0 - 1;
+ SHOWN = PAGE;
+ RULE_OPEN = false;
+ return 0;
+}
+
+fn nullish(d: []const u8, sp: i64) -> bool {
+ const k : u8 = kind(d, sp);
+ return k == J_NONE or k == J_NULL;
+}
+
+// --- V8's TimSort (third_party/v8/builtins/array-sort.tq), over indexes of the pull requests -------------
+
+/// Compare(a, b) of tool.js's comparator: (b.badge - a.badge) || (a.merged < b.merged ? 1 : -1), never 0.
+fn cmp(a: u32, b: u32) -> i32 {
+ const bd : f64 = KB[b as usize] - KB[a as usize];
+ if (bd == bd and bd != 0.0) {
+ if (bd < 0.0) { return 0 - 1; }
+ return 1;
+ }
+ var lt : bool = false;
+ if (KK[a as usize] == 1 and KK[b as usize] == 1) {
+ const la : u32 = KL[a as usize];
+ const lb : u32 = KL[b as usize];
+ var m : u32 = la;
+ if (lb < m) { m = lb; }
+ var i : u32 = 0;
+ var decided : bool = false;
+ while (i < m and !decided) {
+ const ca : u8 = KS[(a * 32 + i) as usize];
+ const cb : u8 = KS[(b * 32 + i) as usize];
+ if (ca != cb) {
+ lt = ca < cb;
+ decided = true;
+ }
+ i = i + 1;
+ }
+ if (!decided) { lt = la < lb; }
+ } else {
+ lt = KM[a as usize] < KM[b as usize];
+ }
+ if (lt) { return 1; }
+ return 0 - 1;
+}
+
+fn get(arr: u32, i: i32) -> u32 {
+ if (arr == 0) { return WORK[i as usize]; }
+ return TMP[i as usize];
+}
+
+fn copy_range(sa: u32, sp: i32, da: u32, dp: i32, len: i32) -> u32 {
+ if (len <= 0) { return 0; }
+ if (sa == da and sp == dp) { return 0; }
+ var k : i32 = 0;
+ if (sp < dp and sa == da) {
+ k = len - 1;
+ while (k >= 0) {
+ const v : u32 = get(sa, sp + k);
+ if (da == 0) { WORK[(dp + k) as usize] = v; } else { TMP[(dp + k) as usize] = v; }
+ k = k - 1;
+ }
+ } else {
+ while (k < len) {
+ const v2 : u32 = get(sa, sp + k);
+ if (da == 0) { WORK[(dp + k) as usize] = v2; } else { TMP[(dp + k) as usize] = v2; }
+ k = k + 1;
+ }
+ }
+ return 0;
+}
+
+fn put_w(i: i32, v: u32) -> u32 {
+ WORK[i as usize] = v;
+ return 0;
+}
+
+fn put_t(i: i32, v: u32) -> u32 {
+ TMP[i as usize] = v;
+ return 0;
+}
+
+fn gallop_left(arr: u32, key: u32, base: i32, length: i32, hint: i32) -> i32 {
+ var last_ofs : i32 = 0;
+ var offset : i32 = 1;
+ var order : i32 = cmp(get(arr, base + hint), key);
+ if (order < 0) {
+ const max_ofs : i32 = length - hint;
+ while (offset < max_ofs) {
+ order = cmp(get(arr, base + hint + offset), key);
+ if (order >= 0) { break; }
+ last_ofs = offset;
+ offset = offset * 2 + 1;
+ }
+ if (offset > max_ofs) { offset = max_ofs; }
+ last_ofs = last_ofs + hint;
+ offset = offset + hint;
+ } else {
+ const max_ofs2 : i32 = hint + 1;
+ while (offset < max_ofs2) {
+ order = cmp(get(arr, base + hint - offset), key);
+ if (order < 0) { break; }
+ last_ofs = offset;
+ offset = offset * 2 + 1;
+ }
+ if (offset > max_ofs2) { offset = max_ofs2; }
+ const tmp : i32 = last_ofs;
+ last_ofs = hint - offset;
+ offset = hint - tmp;
+ }
+ last_ofs = last_ofs + 1;
+ while (last_ofs < offset) {
+ const m : i32 = last_ofs + ((offset - last_ofs) / 2);
+ order = cmp(get(arr, base + m), key);
+ if (order < 0) { last_ofs = m + 1; } else { offset = m; }
+ }
+ return offset;
+}
+
+fn gallop_right(arr: u32, key: u32, base: i32, length: i32, hint: i32) -> i32 {
+ var last_ofs : i32 = 0;
+ var offset : i32 = 1;
+ var order : i32 = cmp(key, get(arr, base + hint));
+ if (order < 0) {
+ const max_ofs : i32 = hint + 1;
+ while (offset < max_ofs) {
+ order = cmp(key, get(arr, base + hint - offset));
+ if (order >= 0) { break; }
+ last_ofs = offset;
+ offset = offset * 2 + 1;
+ }
+ if (offset > max_ofs) { offset = max_ofs; }
+ const tmp : i32 = last_ofs;
+ last_ofs = hint - offset;
+ offset = hint - tmp;
+ } else {
+ const max_ofs2 : i32 = length - hint;
+ while (offset < max_ofs2) {
+ order = cmp(key, get(arr, base + hint + offset));
+ if (order < 0) { break; }
+ last_ofs = offset;
+ offset = offset * 2 + 1;
+ }
+ if (offset > max_ofs2) { offset = max_ofs2; }
+ last_ofs = last_ofs + hint;
+ offset = offset + hint;
+ }
+ last_ofs = last_ofs + 1;
+ while (last_ofs < offset) {
+ const m : i32 = last_ofs + ((offset - last_ofs) / 2);
+ order = cmp(key, get(arr, base + m));
+ if (order < 0) { offset = m; } else { last_ofs = m + 1; }
+ }
+ return offset;
+}
+
+fn merge_low(base_a: i32, len_a0: i32, base_b: i32, len_b0: i32) -> u32 {
+ var len_a : i32 = len_a0;
+ var len_b : i32 = len_b0;
+ copy_range(0, base_a, 1, 0, len_a);
+ var dest : i32 = base_a;
+ var ct : i32 = 0;
+ var cb : i32 = base_b;
+ put_w(dest, WORK[cb as usize]);
+ dest = dest + 1;
+ cb = cb + 1;
+ var mode : u32 = 0;
+ len_b = len_b - 1;
+ if (len_b == 0) { mode = 1; } else { if (len_a == 1) { mode = 2; } }
+ var min_gallop : i32 = MIN_GALLOP;
+ while (mode == 0) {
+ var wins_a : i32 = 0;
+ var wins_b : i32 = 0;
+ var inner : bool = true;
+ while (inner and mode == 0) {
+ const order : i32 = cmp(WORK[cb as usize], TMP[ct as usize]);
+ if (order < 0) {
+ put_w(dest, WORK[cb as usize]);
+ dest = dest + 1;
+ cb = cb + 1;
+ wins_b = wins_b + 1;
+ len_b = len_b - 1;
+ wins_a = 0;
+ if (len_b == 0) { mode = 1; } else { if (wins_b >= min_gallop) { inner = false; } }
+ } else {
+ put_w(dest, TMP[ct as usize]);
+ dest = dest + 1;
+ ct = ct + 1;
+ wins_a = wins_a + 1;
+ len_a = len_a - 1;
+ wins_b = 0;
+ if (len_a == 1) { mode = 2; } else { if (wins_a >= min_gallop) { inner = false; } }
+ }
+ }
+ if (mode != 0) { break; }
+ min_gallop = min_gallop + 1;
+ var first : bool = true;
+ while (mode == 0 and (wins_a >= 7 or wins_b >= 7 or first)) {
+ first = false;
+ min_gallop = min_gallop - 1;
+ if (min_gallop < 1) { min_gallop = 1; }
+ MIN_GALLOP = min_gallop;
+ wins_a = gallop_right(1, WORK[cb as usize], ct, len_a, 0);
+ if (wins_a > 0) {
+ copy_range(1, ct, 0, dest, wins_a);
+ dest = dest + wins_a;
+ ct = ct + wins_a;
+ len_a = len_a - wins_a;
+ if (len_a == 1) { mode = 2; }
+ if (len_a == 0) { mode = 1; }
+ }
+ if (mode == 0) {
+ put_w(dest, WORK[cb as usize]);
+ dest = dest + 1;
+ cb = cb + 1;
+ len_b = len_b - 1;
+ if (len_b == 0) { mode = 1; }
+ }
+ if (mode == 0) {
+ wins_b = gallop_left(0, TMP[ct as usize], cb, len_b, 0);
+ if (wins_b > 0) {
+ copy_range(0, cb, 0, dest, wins_b);
+ dest = dest + wins_b;
+ cb = cb + wins_b;
+ len_b = len_b - wins_b;
+ if (len_b == 0) { mode = 1; }
+ }
+ }
+ if (mode == 0) {
+ put_w(dest, TMP[ct as usize]);
+ dest = dest + 1;
+ ct = ct + 1;
+ len_a = len_a - 1;
+ if (len_a == 1) { mode = 2; }
+ }
+ }
+ if (mode == 0) {
+ min_gallop = min_gallop + 1;
+ MIN_GALLOP = min_gallop;
+ }
+ }
+ if (mode == 1) {
+ if (len_a > 0) { copy_range(1, ct, 0, dest, len_a); }
+ }
+ if (mode == 2) {
+ copy_range(0, cb, 0, dest, len_b);
+ put_w(dest + len_b, TMP[ct as usize]);
+ }
+ return 0;
+}
+
+fn merge_high(base_a: i32, len_a0: i32, base_b: i32, len_b0: i32) -> u32 {
+ var len_a : i32 = len_a0;
+ var len_b : i32 = len_b0;
+ copy_range(0, base_b, 1, 0, len_b);
+ var dest : i32 = base_b + len_b - 1;
+ var ct : i32 = len_b - 1;
+ var ca : i32 = base_a + len_a - 1;
+ put_w(dest, WORK[ca as usize]);
+ dest = dest - 1;
+ ca = ca - 1;
+ var mode : u32 = 0;
+ len_a = len_a - 1;
+ if (len_a == 0) { mode = 1; } else { if (len_b == 1) { mode = 2; } }
+ var min_gallop : i32 = MIN_GALLOP;
+ while (mode == 0) {
+ var wins_a : i32 = 0;
+ var wins_b : i32 = 0;
+ var inner : bool = true;
+ while (inner and mode == 0) {
+ const order : i32 = cmp(TMP[ct as usize], WORK[ca as usize]);
+ if (order < 0) {
+ put_w(dest, WORK[ca as usize]);
+ dest = dest - 1;
+ ca = ca - 1;
+ wins_a = wins_a + 1;
+ len_a = len_a - 1;
+ wins_b = 0;
+ if (len_a == 0) { mode = 1; } else { if (wins_a >= min_gallop) { inner = false; } }
+ } else {
+ put_w(dest, TMP[ct as usize]);
+ dest = dest - 1;
+ ct = ct - 1;
+ wins_b = wins_b + 1;
+ len_b = len_b - 1;
+ wins_a = 0;
+ if (len_b == 1) { mode = 2; } else { if (wins_b >= min_gallop) { inner = false; } }
+ }
+ }
+ if (mode != 0) { break; }
+ min_gallop = min_gallop + 1;
+ var first : bool = true;
+ while (mode == 0 and (wins_a >= 7 or wins_b >= 7 or first)) {
+ first = false;
+ min_gallop = min_gallop - 1;
+ if (min_gallop < 1) { min_gallop = 1; }
+ MIN_GALLOP = min_gallop;
+ var k : i32 = gallop_right(0, TMP[ct as usize], base_a, len_a, len_a - 1);
+ wins_a = len_a - k;
+ if (wins_a > 0) {
+ dest = dest - wins_a;
+ ca = ca - wins_a;
+ copy_range(0, ca + 1, 0, dest + 1, wins_a);
+ len_a = len_a - wins_a;
+ if (len_a == 0) { mode = 1; }
+ }
+ if (mode == 0) {
+ put_w(dest, TMP[ct as usize]);
+ dest = dest - 1;
+ ct = ct - 1;
+ len_b = len_b - 1;
+ if (len_b == 1) { mode = 2; }
+ }
+ if (mode == 0) {
+ k = gallop_left(1, WORK[ca as usize], 0, len_b, len_b - 1);
+ wins_b = len_b - k;
+ if (wins_b > 0) {
+ dest = dest - wins_b;
+ ct = ct - wins_b;
+ copy_range(1, ct + 1, 0, dest + 1, wins_b);
+ len_b = len_b - wins_b;
+ if (len_b == 1) { mode = 2; }
+ if (len_b == 0) { mode = 1; }
+ }
+ }
+ if (mode == 0) {
+ put_w(dest, WORK[ca as usize]);
+ dest = dest - 1;
+ ca = ca - 1;
+ len_a = len_a - 1;
+ if (len_a == 0) { mode = 1; }
+ }
+ }
+ if (mode == 0) {
+ min_gallop = min_gallop + 1;
+ MIN_GALLOP = min_gallop;
+ }
+ }
+ if (mode == 1) {
+ if (len_b > 0) { copy_range(1, 0, 0, dest - (len_b - 1), len_b); }
+ }
+ if (mode == 2) {
+ dest = dest - len_a;
+ ca = ca - len_a;
+ copy_range(0, ca + 1, 0, dest + 1, len_a);
+ put_w(dest, TMP[ct as usize]);
+ }
+ return 0;
+}
+
+fn merge_at(i: i32) -> u32 {
+ var base_a : i32 = RUN_BASE[i as usize];
+ var len_a : i32 = RUN_LEN[i as usize];
+ const base_b : i32 = RUN_BASE[(i + 1) as usize];
+ var len_b : i32 = RUN_LEN[(i + 1) as usize];
+ RUN_LEN[i as usize] = len_a + len_b;
+ if (i == RUNS - 3) {
+ RUN_BASE[(i + 1) as usize] = RUN_BASE[(i + 2) as usize];
+ RUN_LEN[(i + 1) as usize] = RUN_LEN[(i + 2) as usize];
+ }
+ RUNS = RUNS - 1;
+ const key_right : u32 = WORK[base_b as usize];
+ const k : i32 = gallop_right(0, key_right, base_a, len_a, 0);
+ base_a = base_a + k;
+ len_a = len_a - k;
+ if (len_a == 0) { return 0; }
+ const key_left : u32 = WORK[(base_a + len_a - 1) as usize];
+ len_b = gallop_left(0, key_left, base_b, len_b, len_b - 1);
+ if (len_b == 0) { return 0; }
+ if (len_a <= len_b) { merge_low(base_a, len_a, base_b, len_b); } else { merge_high(base_a, len_a, base_b, len_b); }
+ return 0;
+}
+
+fn run_ok(n: i32) -> bool {
+ if (n < 2) { return true; }
+ return RUN_LEN[(n - 2) as usize] > RUN_LEN[(n - 1) as usize] + RUN_LEN[n as usize];
+}
+
+fn merge_collapse() -> u32 {
+ var go : bool = true;
+ while (RUNS > 1 and go) {
+ var n : i32 = RUNS - 2;
+ if (!run_ok(n + 1) or !run_ok(n)) {
+ if (n > 0 and RUN_LEN[(n - 1) as usize] < RUN_LEN[(n + 1) as usize]) { n = n - 1; }
+ merge_at(n);
+ } else {
+ if (RUN_LEN[n as usize] <= RUN_LEN[(n + 1) as usize]) { merge_at(n); } else { go = false; }
+ }
+ }
+ return 0;
+}
+
+fn merge_force_collapse() -> u32 {
+ while (RUNS > 1) {
+ var n : i32 = RUNS - 2;
+ if (n > 0 and RUN_LEN[(n - 1) as usize] < RUN_LEN[(n + 1) as usize]) { n = n - 1; }
+ merge_at(n);
+ }
+ return 0;
+}
+
+fn count_and_make_run(low_arg: i32, high: i32) -> i32 {
+ const low : i32 = low_arg + 1;
+ if (low == high) { return 1; }
+ var run_length : i32 = 2;
+ const element_low : u32 = WORK[low as usize];
+ const element_high : u32 = WORK[(low - 1) as usize];
+ var order : i32 = cmp(element_low, element_high);
+ const desc : bool = order < 0;
+ var prev : u32 = element_low;
+ var idx : i32 = low + 1;
+ var stop : bool = false;
+ while (idx < high and !stop) {
+ const cur : u32 = WORK[idx as usize];
+ order = cmp(cur, prev);
+ if (desc) {
+ if (order >= 0) { stop = true; }
+ } else {
+ if (order < 0) { stop = true; }
+ }
+ if (!stop) {
+ prev = cur;
+ run_length = run_length + 1;
+ idx = idx + 1;
+ }
+ }
+ if (desc) {
+ var lo : i32 = low_arg;
+ var hi : i32 = low_arg + run_length - 1;
+ while (lo < hi) {
+ const t : u32 = WORK[lo as usize];
+ WORK[lo as usize] = WORK[hi as usize];
+ WORK[hi as usize] = t;
+ lo = lo + 1;
+ hi = hi - 1;
+ }
+ }
+ return run_length;
+}
+
+fn binary_insertion_sort(low: i32, start_arg: i32, high: i32) -> u32 {
+ var start : i32 = start_arg;
+ if (low == start_arg) { start = start_arg + 1; }
+ while (start < high) {
+ var left : i32 = low;
+ var right : i32 = start;
+ const pivot : u32 = WORK[right as usize];
+ while (left < right) {
+ const mid : i32 = left + ((right - left) / 2);
+ const order : i32 = cmp(pivot, WORK[mid as usize]);
+ if (order < 0) { right = mid; } else { left = mid + 1; }
+ }
+ var p : i32 = start;
+ while (p > left) {
+ WORK[p as usize] = WORK[(p - 1) as usize];
+ p = p - 1;
+ }
+ WORK[left as usize] = pivot;
+ start = start + 1;
+ }
+ return 0;
+}
+
+fn min_run_length(n0: i32) -> i32 {
+ var n : i32 = n0;
+ var r : i32 = 0;
+ while (n >= 64) {
+ r = r | (n & 1);
+ n = n / 2;
+ }
+ return n + r;
+}
+
+/// Array.prototype.sort of ORDER[0..n) with cmp, as V8 runs it.
+fn timsort(n: i32) -> u32 {
+ var i : i32 = 0;
+ while (i < n) {
+ WORK[i as usize] = i as u32;
+ i = i + 1;
+ }
+ RUNS = 0;
+ MIN_GALLOP = 7;
+ if (n >= 2) {
+ var low : i32 = 0;
+ var remaining : i32 = n;
+ const min_run : i32 = min_run_length(remaining);
+ while (remaining != 0) {
+ var cur : i32 = count_and_make_run(low, low + remaining);
+ if (cur < min_run) {
+ var forced : i32 = min_run;
+ if (remaining < forced) { forced = remaining; }
+ binary_insertion_sort(low, low + cur, low + forced);
+ cur = forced;
+ }
+ RUN_BASE[RUNS as usize] = low;
+ RUN_LEN[RUNS as usize] = cur;
+ RUNS = RUNS + 1;
+ merge_collapse();
+ low = low + cur;
+ remaining = remaining - cur;
+ }
+ merge_force_collapse();
+ }
+ i = 0;
+ while (i < n) {
+ ORDER[i as usize] = WORK[i as usize];
+ i = i + 1;
+ }
+ return 0;
+}
+
+// --- the data -------------------------------------------------------------------------------------
+
+/// d.lanes[lane].vector is an array of numbers (tool.js reads v[0..4]).
+fn lane_vec(d: []const u8, r: i64, lane: []const u8) -> i64 {
+ return obj_get(d, obj_get(d, obj_get(d, r, "lanes"), lane), "vector");
+}
+
+fn data_ok(d: []const u8, r: i64) -> bool {
+ if (kind(d, r) != J_OBJECT) { return false; }
+ if (kind(d, obj_get(d, r, "prs")) != J_ARRAY) { return false; }
+ if (kind(d, lane_vec(d, r, "bee")) != J_ARRAY or kind(d, lane_vec(d, r, "human")) != J_ARRAY) { return false; }
+ if (nullish(d, obj_get(d, obj_get(d, r, "lanes"), "bee"))) { return false; }
+ return true;
+}
+
+/// Does verdict(d) throw? stamp(last) calls .replace on the greatest stamp of the removed-assert bee pull
+/// requests (by String()); that throws when it is a truthy value that is no string.
+fn verdict_throws(d: []const u8) -> bool {
+ var have : bool = false;
+ var last : [96]u8 = [0; 96];
+ var ll : u32 = 0;
+ var bad_kind : bool = false;
+ var missing : bool = false;
+ var i : u32 = 0;
+ while (i < NPR) {
+ const sp : i64 = PRS[i as usize];
+ const bv : i64 = obj_get(d, sp, "badge");
+ if (jv_is(d, obj_get(d, sp, "lane"), "bee") and kind(d, bv) == J_NUMBER and jv_num(d, bv) == 3.0) {
+ const mv : i64 = obj_get(d, sp, "merged");
+ if (kind(d, mv) == J_NONE) {
+ missing = true;
+ } else {
+ var cand : [96]u8 = [0; 96];
+ const cl : u32 = jv_str(cand[0..], 0, d, mv);
+ if (!have or str_gt(cand[0..], cl, last[0..], ll)) {
+ have = true;
+ bad_kind = jv_truthy(d, mv) and kind(d, mv) != J_STRING;
+ var z : u32 = 0;
+ while (z < cl and z < 96) {
+ last[z as usize] = cand[z as usize];
+ z = z + 1;
+ }
+ ll = z;
+ }
+ }
+ }
+ i = i + 1;
+ }
+ if (missing) { return false; }
+ return have and bad_kind;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (s.len > (DATA_CAP as usize)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < s.len) {
+ DATA[k] = s[k];
+ k = k + 1;
+ }
+ DATA_LEN = s.len as u32;
+ DATA_STATE = 1;
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (!jv_valid(d)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ const r : i64 = root(d);
+ if (!data_ok(d, r)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ // the span of every pull request, once (arr_at from the start each time would be quadratic)
+ const arr : i64 = obj_get(d, r, "prs");
+ const hi : usize = span_hi(arr);
+ var p : usize = skip_ws(d, span_lo(arr) + 1);
+ var n : u32 = 0;
+ while (p < hi - 1 and n < PR_CAP) {
+ const e : usize = value_end(d, p);
+ if (e > hi) { break; }
+ const sp : i64 = jspan(p, e);
+ if (nullish(d, sp)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ PRS[n as usize] = sp;
+ KB[n as usize] = jv_num(d, obj_get(d, sp, "badge"));
+ const mv : i64 = obj_get(d, sp, "merged");
+ KM[n as usize] = jv_num(d, mv);
+ KK[n as usize] = 0;
+ KL[n as usize] = 0;
+ if (kind(d, mv) == J_STRING) {
+ KK[n as usize] = 1;
+ var tmp : [64]u8 = [0; 64];
+ var l : u32 = jv_str_to(tmp[0..], 0, d, mv);
+ if (l > 32) { l = 32; }
+ var q : u32 = 0;
+ while (q < l) {
+ KS[(n * 32 + q) as usize] = tmp[q as usize];
+ q = q + 1;
+ }
+ KL[n as usize] = l;
+ }
+ n = n + 1;
+ p = skip_ws(d, e);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ NPR = n;
+ if (verdict_throws(d)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ timsort(n as i32);
+ refilter(d);
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ DATA_STATE = 2;
+ return 0;
+}
+
+/// render(): the rows after the filters; every row closed and without a body again.
+fn refilter(d: []const u8) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < NPR) {
+ const pi : u32 = ORDER[i as usize];
+ const sp : i64 = PRS[pi as usize];
+ var ok_l : bool = true;
+ var ok_b : bool = true;
+ if (LANE >= 0) { ok_l = jv_is(d, obj_get(d, sp, "lane"), K_LANES[LANE as usize]); }
+ if (BADGE >= 0) {
+ const bv : i64 = obj_get(d, sp, "badge");
+ ok_b = kind(d, bv) == J_NUMBER and jv_num(d, bv) == (BADGE as f64);
+ }
+ if (ok_l and ok_b) {
+ ROWS[n as usize] = pi;
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ NROWS = n;
+ var j : u32 = 0;
+ while (j < PR_CAP) {
+ ROW_OPEN[j as usize] = false;
+ ROW_BODY[j as usize] = false;
+ j = j + 1;
+ }
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (i < 3) {
+ LANE = (i as i32) - 1;
+ SHOWN = PAGE;
+ refilter(d);
+ }
+ if (i >= 3 and i < 8) {
+ if (i == 3) { BADGE = 0 - 1; } else { BADGE = 7 - (i as i32); }
+ SHOWN = PAGE;
+ refilter(d);
+ }
+ if (i == 8) {
+ SHOWN = SHOWN + PAGE;
+ refilter(d);
+ }
+ return 0;
+}
+
+/// Would prBody(p) in tool.js get to its end? (Else its toggle handler throws and the row stays open and empty.)
+fn body_ok(d: []const u8, p: i64) -> bool {
+ const mv : i64 = obj_get(d, p, "merged");
+ if (jv_truthy(d, mv) and kind(d, mv) != J_STRING) { return false; }
+ if (nullish(d, obj_get(d, p, "counts"))) { return false; }
+ const lines : i64 = obj_get(d, p, "lines");
+ if (kind(d, lines) != J_ARRAY) { return false; }
+ const lc : u32 = arr_len(d, lines);
+ var i : u32 = 0;
+ while (i < lc) {
+ const l : i64 = arr_at(d, lines, i);
+ if (nullish(d, l)) { return false; }
+ if (kind(d, obj_get(d, l, "t")) != J_STRING) { return false; }
+ i = i + 1;
+ }
+ if (lc > 0 and nullish(d, obj_get(d, p, "files"))) { return false; }
+ return true;
+}
+
+pub fn w_toggle(i: u32, open: u32) -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ var shown_rows : u32 = NROWS;
+ if (SHOWN < shown_rows) { shown_rows = SHOWN; }
+ const o : bool = open != 0;
+ if (i < shown_rows) {
+ var changed : bool = ROW_OPEN[i as usize] != o;
+ ROW_OPEN[i as usize] = o;
+ if (o and !ROW_BODY[i as usize]) {
+ const dd : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (body_ok(dd, PRS[ROWS[i as usize] as usize])) {
+ ROW_BODY[i as usize] = true;
+ changed = true;
+ }
+ }
+ if (changed) { return 1; }
+ return 0;
+ }
+ if (i == shown_rows) {
+ if (RULE_OPEN != o) {
+ RULE_OPEN = o;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+// --- the page -------------------------------------------------------------------------------------
+
+/// stamp(iso): '' for a falsy value, else the first T as a space and a trailing :dd Z cut, as raw bytes.
+fn stamp_raw(out: []u8, d: []const u8, sp: i64) -> u32 {
+ if (!jv_truthy(d, sp)) { return 0; }
+ var l : u32 = jv_str(out, 0, d, sp);
+ var k : u32 = 0;
+ var done : bool = false;
+ while (k < l and !done) {
+ if (out[k as usize] == 84) {
+ out[k as usize] = 32;
+ done = true;
+ }
+ k = k + 1;
+ }
+ if (l >= 4 and out[(l - 1) as usize] == 90 and out[(l - 4) as usize] == 58 and out[(l - 3) as usize] >= 48 and out[(l - 3) as usize] <= 57 and out[(l - 2) as usize] >= 48 and out[(l - 2) as usize] <= 57) {
+ l = l - 4;
+ }
+ return l;
+}
+
+fn put_esc(dst: []u8, n0: u32, s: []const u8, l: u32) -> u32 {
+ var n : u32 = n0;
+ var i : u32 = 0;
+ while (i < l) {
+ n = put_escaped(dst, n, s[i as usize]);
+ i = i + 1;
+ }
+ return n;
+}
+
+fn lane_index(d: []const u8, sp: i64) -> i32 {
+ var i : usize = 0;
+ while (i < K_LANES.len) {
+ if (jv_is(d, sp, K_LANES[i])) { return i as i32; }
+ i = i + 1;
+ }
+ return 0 - 1;
+}
+
+/// laneName(lane): the word for a known lane, else the data's own text.
+fn lane_name(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ const ix : i32 = lane_index(d, sp);
+ if (ix >= 0) { return text(dst, n0, SAY_LANE_NAMES[ix as usize]); }
+ return jv_kid(dst, n0, d, sp);
+}
+
+/// The array index a value names as a property key (arr[v]): an integer number, or a canonical decimal string; -1 else.
+fn key_index(d: []const u8, sp: i64) -> i64 {
+ const k : u8 = kind(d, sp);
+ if (k == J_NUMBER) {
+ const v : f64 = jv_num(d, sp);
+ if (v >= 0.0 and v < 4294967295.0 and v == js_floor(v)) { return v as i64; }
+ return 0 - 1;
+ }
+ if (k == J_STRING) {
+ const l : u32 = str_len(d, sp);
+ if (l == 0 or l > 9) { return 0 - 1; }
+ var v2 : i64 = 0;
+ var i : u32 = 0;
+ while (i < l) {
+ const c : u8 = str_byte(d, sp, i);
+ if (c < 48 or c > 57) { return 0 - 1; }
+ if (i == 0 and c == 48 and l > 1) { return 0 - 1; }
+ v2 = v2 * 10 + ((c - 48) as i64);
+ i = i + 1;
+ }
+ return v2;
+ }
+ return 0 - 1;
+}
+
+fn badge_span(dst: []u8, n0: u32, d: []const u8, b: i64) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ const bi : i64 = key_index(d, b);
+ if (bi >= 0 and bi < (SAY_BADGES.len as i64)) { n = text(dst, n, SAY_BADGES[bi as usize]); }
+ return raw(dst, n, "");
+}
+
+fn chip_span(dst: []u8, n0: u32, d: []const u8, lane: i64) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ n = lane_name(dst, n, d, lane);
+ return raw(dst, n, "");
+}
+
+fn vnum(d: []const u8, v: i64, ix: u32) -> i64 {
+ return arr_at(d, v, ix);
+}
+
+fn lane_card(dst: []u8, n0: u32, d: []const u8, r: i64, li: u32) -> u32 {
+ const L : i64 = obj_get(d, obj_get(d, r, "lanes"), K_LANES[li as usize]);
+ const v : i64 = obj_get(d, L, "vector");
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_LANE_NAMES[li as usize]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LANE_LINES[li as usize]);
+ n = raw(dst, n, "
");
+ n = jv_text(dst, n, d, vnum(d, v, 4));
+ n = raw(dst, n, " / ");
+ n = jv_text(dst, n, d, vnum(d, v, 0));
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_REMOVED_OF);
+ n = raw(dst, n, "; ");
+ n = jv_text(dst, n, d, vnum(d, v, 0));
+ n = put(dst, n, 32);
+ n = text(dst, n, SAY_MERGED);
+ n = raw(dst, n, "
1) { n = raw(dst, n, ", "); }
+ n = text(dst, n, SAY_BADGES[(q - 1) as usize]);
+ n = put(dst, n, 32);
+ n = jv_text(dst, n, d, vnum(d, v, q));
+ q = q + 1;
+ }
+ n = raw(dst, n, "\">");
+ q = 4;
+ while (q >= 1) {
+ n = raw(dst, n, "");
+ q = q - 1;
+ }
+ n = raw(dst, n, "
");
+ q = 4;
+ while (q >= 1) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_BADGES[(q - 1) as usize]);
+ n = raw(dst, n, "");
+ n = jv_text(dst, n, d, vnum(d, v, q));
+ n = raw(dst, n, "
");
+ q = q - 1;
+ }
+ n = raw(dst, n, "
");
+ var s : usize = 0;
+ while (s < SAY_STAT_NAMES.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_STAT_NAMES[s]);
+ n = raw(dst, n, "
");
+ if (s == 0) { n = jv_text(dst, n, d, obj_get(d, L, "checksAdded")); }
+ if (s == 1) { n = jv_text(dst, n, d, obj_get(d, L, "checksRemoved")); }
+ if (s == 2) { n = jv_text(dst, n, d, obj_get(d, L, "testsRemoved")); }
+ if (s == 3) { n = jv_text(dst, n, d, obj_get(d, L, "expect")); }
+ if (s == 4) { n = jv_text(dst, n, d, obj_get(d, L, "loosened")); }
+ n = raw(dst, n, "
");
+ s = s + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+fn verdict_html(dst: []u8, n0: u32, d: []const u8, r: i64) -> u32 {
+ // bad = bee pull requests with badge === 3 ; last = the greatest of their stamps, as .sort().pop() gives it
+ var bad : u32 = 0;
+ var have_last : bool = false;
+ var last_missing : bool = false;
+ var last_null : bool = false;
+ var last : [64]u8 = [0; 64];
+ var ll : u32 = 0;
+ var i : u32 = 0;
+ while (i < NPR) {
+ const sp : i64 = PRS[i as usize];
+ const bv : i64 = obj_get(d, sp, "badge");
+ if (jv_is(d, obj_get(d, sp, "lane"), "bee") and kind(d, bv) == J_NUMBER and jv_num(d, bv) == 3.0) {
+ bad = bad + 1;
+ const mv : i64 = obj_get(d, sp, "merged");
+ if (kind(d, mv) == J_NONE) {
+ last_missing = true;
+ } else {
+ var cand : [96]u8 = [0; 96];
+ const cl : u32 = jv_str(cand[0..], 0, d, mv);
+ if (!have_last or str_gt(cand[0..], cl, last[0..], ll)) {
+ have_last = true;
+ last_null = kind(d, mv) == J_NULL;
+ var z : u32 = 0;
+ while (z < cl and z < 64) {
+ last[z as usize] = cand[z as usize];
+ z = z + 1;
+ }
+ ll = z;
+ }
+ }
+ }
+ i = i + 1;
+ }
+ var n : u32 = n0;
+ if (bad == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_VERDICT_NO);
+ return raw(dst, n, "
");
+ // after = bee pull requests whose stamp is a string greater than last
+ var after : u32 = 0;
+ if (have_last and !last_missing and !last_null) {
+ i = 0;
+ while (i < NPR) {
+ const sp2 : i64 = PRS[i as usize];
+ const mv2 : i64 = obj_get(d, sp2, "merged");
+ if (jv_is(d, obj_get(d, sp2, "lane"), "bee") and kind(d, mv2) == J_STRING) {
+ var c2 : [96]u8 = [0; 96];
+ const c2l : u32 = jv_str(c2[0..], 0, d, mv2);
+ if (str_gt(c2[0..], c2l, last[0..], ll)) { after = after + 1; }
+ }
+ i = i + 1;
+ }
+ }
+ var lb : [256]u8 = [0; 256];
+ var lt : [200]u8 = [0; 200];
+ var ltl : u32 = 0;
+ if (!last_missing and !last_null) {
+ // stamp(last): last is a string here
+ var tmp : [96]u8 = [0; 96];
+ var z2 : u32 = 0;
+ while (z2 < ll) {
+ tmp[z2 as usize] = last[z2 as usize];
+ z2 = z2 + 1;
+ }
+ var done : bool = false;
+ var k : u32 = 0;
+ while (k < ll and !done) {
+ if (tmp[k as usize] == 84) {
+ tmp[k as usize] = 32;
+ done = true;
+ }
+ k = k + 1;
+ }
+ var l2 : u32 = ll;
+ if (l2 >= 4 and tmp[(l2 - 1) as usize] == 90 and tmp[(l2 - 4) as usize] == 58 and tmp[(l2 - 3) as usize] >= 48 and tmp[(l2 - 3) as usize] <= 57 and tmp[(l2 - 2) as usize] >= 48 and tmp[(l2 - 2) as usize] <= 57) { l2 = l2 - 4; }
+ var y : u32 = 0;
+ while (y < l2) {
+ lt[y as usize] = tmp[y as usize];
+ y = y + 1;
+ }
+ ltl = l2;
+ }
+ var ln : u32 = jv_vput_str(lb[0..], 0, "last", lt[0..(ltl as usize)]);
+ ln = jv_vput_f64(lb[0..], ln, "after", after as f64);
+ n = jv_fill(dst, n, SAY_VERDICT_LAST, lb[0..], ln, false);
+ return raw(dst, n, "
");
+}
+
+/// a > b as strings (UTF-16 order for BMP text, the bytes here).
+fn str_gt(a: []const u8, al: u32, b: []const u8, bl: u32) -> bool {
+ var m : u32 = al;
+ if (bl < m) { m = bl; }
+ var i : u32 = 0;
+ while (i < m) {
+ if (a[i as usize] != b[i as usize]) { return a[i as usize] > b[i as usize]; }
+ i = i + 1;
+ }
+ return al > bl;
+}
+
+fn same_key(d: []const u8, a: i64, b: i64) -> bool {
+ const ka : u8 = kind(d, a);
+ if (ka != kind(d, b)) { return false; }
+ if (ka == J_NUMBER) {
+ const x : f64 = jv_num(d, a);
+ const y : f64 = jv_num(d, b);
+ return x == y or (x != x and y != y);
+ }
+ if (ka == J_STRING) {
+ const l : u32 = str_len(d, a);
+ if (l != str_len(d, b)) { return false; }
+ var k : u32 = 0;
+ while (k < l) {
+ if (str_byte(d, a, k) != str_byte(d, b, k)) { return false; }
+ k = k + 1;
+ }
+ return true;
+ }
+ return ka != J_OBJECT and ka != J_ARRAY and ka != J_NONE;
+}
+
+/// replace(/^\s+/, '') then the rest of the string t as escaped text.
+fn trimmed_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) != J_STRING) { return n0; }
+ var tmp : [4096]u8 = [0; 4096];
+ const l : u32 = jv_str_to(tmp[0..], 0, d, sp);
+ var lo : u32 = 0;
+ var w : u32 = 1;
+ while (lo < l and w > 0) {
+ w = jv_ws_at(tmp[0..], lo, l);
+ lo = lo + w;
+ }
+ var n : u32 = n0;
+ var q : u32 = lo;
+ while (q < l) {
+ n = put_escaped(dst, n, tmp[q as usize]);
+ q = q + 1;
+ }
+ return n;
+}
+
+fn tag_word(dst: []u8, n0: u32, d: []const u8, g: i64) -> u32 {
+ var i : usize = 0;
+ while (i < K_TAGS.len) {
+ if (jv_is(d, g, K_TAGS[i])) { return text(dst, n0, SAY_TAGS[i]); }
+ i = i + 1;
+ }
+ return jv_kid(dst, n0, d, g);
+}
+
+fn body_html(dst: []u8, n0: u32, d: []const u8, p: i64) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var sb : [1100]u8 = [0; 1100];
+ const sl : u32 = stamp_raw(sb[0..], d, obj_get(d, p, "merged"));
+ n = put_esc(dst, n, sb[0..], sl);
+ n = raw(dst, n, ", ");
+ n = jv_text(dst, n, d, obj_get(d, p, "author"));
+ n = raw(dst, n, ", ");
+ n = jv_text(dst, n, d, obj_get(d, p, "branch"));
+ n = raw(dst, n, ", ");
+ const cls : i64 = obj_get(d, p, "cls");
+ const ci : i64 = key_index(d, cls);
+ if (ci >= 0 and ci < (SAY_CLASSES.len as i64)) {
+ n = text(dst, n, SAY_CLASSES[ci as usize]);
+ } else {
+ n = raw(dst, n, "undefined");
+ }
+ n = raw(dst, n, "
");
+ var vb : [512]u8 = [0; 512];
+ var vn : u32 = jv_vput_val(vb[0..], 0, "n", d, obj_get(d, p, "testFiles"));
+ vn = jv_vput_val(vb[0..], vn, "all", d, obj_get(d, p, "allFiles"));
+ n = jv_fill(dst, n, SAY_TEST_FILES, vb[0..], vn, false);
+ n = raw(dst, n, "; ");
+ const c : i64 = obj_get(d, p, "counts");
+ vn = jv_vput_nn0(vb[0..], 0, "added", d, obj_get(d, c, "checksAdded"));
+ vn = jv_vput_nn0(vb[0..], vn, "removed", d, obj_get(d, c, "checksRemoved"));
+ vn = jv_vput_nn0(vb[0..], vn, "blocks", d, obj_get(d, c, "testsRemoved"));
+ n = jv_fill(dst, n, SAY_CHECKS, vb[0..], vn, false);
+ n = raw(dst, n, "
");
+ const lines : i64 = obj_get(d, p, "lines");
+ const lc : u32 = arr_len(d, lines);
+ if (lc > 0) {
+ // group by file in first-seen order
+ var done : [128]bool = [false; 128];
+ var a : u32 = 0;
+ while (a < lc and a < 128) {
+ if (!done[a as usize]) {
+ const la : i64 = arr_at(d, lines, a);
+ const fa : i64 = obj_get(d, la, "f");
+ n = raw(dst, n, "
");
+ const fidx : i64 = key_index(d, fa);
+ if (fidx >= 0) {
+ const fe : i64 = arr_at(d, obj_get(d, p, "files"), fidx as u32);
+ n = nn_text(dst, n, d, obj_get(d, fe, "p"));
+ }
+ n = raw(dst, n, "
");
+ var b : u32 = a;
+ while (b < lc and b < 128) {
+ if (!done[b as usize]) {
+ const lb : i64 = arr_at(d, lines, b);
+ if (same_key(d, fa, obj_get(d, lb, "f"))) {
+ done[b as usize] = true;
+ const sgn : i64 = obj_get(d, lb, "s");
+ n = raw(dst, n, "
");
+ n = tag_word(dst, n, d, obj_get(d, lb, "g"));
+ n = raw(dst, n, "");
+ n = jv_text(dst, n, d, sgn);
+ n = put(dst, n, 32);
+ n = trimmed_text(dst, n, d, obj_get(d, lb, "t"));
+ n = raw(dst, n, "");
+ if (jv_truthy(d, obj_get(d, lb, "b"))) {
+ n = raw(dst, n, "");
+ n = jv_text(dst, n, d, obj_get(d, lb, "b"));
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "
");
+ }
+ }
+ b = b + 1;
+ }
+ n = raw(dst, n, "
");
+ }
+ a = a + 1;
+ }
+ } else {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NO_LINES);
+ n = raw(dst, n, "
");
+ }
+ if (jv_truthy(d, obj_get(d, p, "more"))) {
+ n = raw(dst, n, "
");
+ var mb : [128]u8 = [0; 128];
+ const mn : u32 = jv_vput_val(mb[0..], 0, "n", d, obj_get(d, p, "more"));
+ n = jv_fill(dst, n, SAY_MORE_LINES, mb[0..], mn, false);
+ n = raw(dst, n, "
");
+ }
+ if (jv_truthy(d, obj_get(d, p, "ciGate"))) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CI_GATE);
+ n = raw(dst, n, "
");
+ }
+ if (jv_truthy(d, obj_get(d, p, "apiOnly"))) {
+ n = raw(dst, n, "
");
+ var ab : [128]u8 = [0; 128];
+ const an : u32 = jv_vput_val(ab[0..], 0, "n", d, obj_get(d, p, "apiOnly"));
+ n = jv_fill(dst, n, SAY_API_ONLY, ab[0..], an, false);
+ n = raw(dst, n, "
");
+}
+
+/// A pool entry for `{key: x ?? 0}`.
+fn jv_vput_nn0(vb: []u8, n0: u32, key: []const u8, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return jv_vput_str(vb, n0, key, "0"); }
+ return jv_vput_val(vb, n0, key, d, sp);
+}
+
+fn nn_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return n0; }
+ return jv_text(dst, n0, d, sp);
+}
+
+fn row_html(dst: []u8, n0: u32, d: []const u8, ri: u32) -> u32 {
+ const p : i64 = PRS[ROWS[ri as usize] as usize];
+ var n : u32 = raw(dst, n0, "");
+ n = badge_span(dst, n, d, obj_get(d, p, "badge"));
+ n = put(dst, n, 32);
+ n = chip_span(dst, n, d, obj_get(d, p, "lane"));
+ n = raw(dst, n, " #");
+ n = jv_text(dst, n, d, obj_get(d, p, "n"));
+ n = raw(dst, n, "");
+ const tt : i64 = obj_get(d, p, "title");
+ if (jv_truthy(d, tt)) { n = jv_text(dst, n, d, tt); } else { n = text(dst, n, SAY_NO_TITLE); }
+ n = raw(dst, n, "");
+ if (ROW_BODY[ri as usize]) { n = body_html(dst, n, d, p); }
+ return raw(dst, n, "");
+}
+
+fn filter_buttons(dst: []u8, n0: u32, which: u32) -> u32 {
+ var n : u32 = n0;
+ if (which == 0) {
+ var i : i32 = 0 - 1;
+ while (i < 2) {
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ } else {
+ var j : i32 = 0 - 1;
+ while (j < 4) {
+ var b : i32 = j;
+ if (j >= 0) { b = 3 - j; }
+ n = raw(dst, n, "");
+ j = j + 1;
+ }
+ }
+ return n;
+}
+
+fn bullets_rule(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var i : usize = 0;
+ while (i < SAY_RULE.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_RULE[i]);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+fn bullets_pos(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var i : usize = 0;
+ while (i < SAY_FALSE_POS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FALSE_POS[i]);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+fn bullets_neg(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var i : usize = 0;
+ while (i < SAY_FALSE_NEG.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FALSE_NEG[i]);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ if (DATA_STATE == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOADING);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ if (DATA_STATE == 2) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOAD_FAILED);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ const r : i64 = root(d);
+ n = raw(dst, n, "
");
+ var vb : [1200]u8 = [0; 1200];
+ var st : [1100]u8 = [0; 1100];
+ const stl : u32 = stamp_raw(st[0..], d, obj_get(d, r, "snapshotAt"));
+ var vn : u32 = jv_vput_str(vb[0..], 0, "at", st[0..(stl as usize)]);
+ vn = jv_vput_val(vb[0..], vn, "days", d, obj_get(d, r, "days"));
+ vn = jv_vput_val(vb[0..], vn, "repo", d, obj_get(d, r, "repo"));
+ n = jv_fill(dst, n, SAY_SNAPSHOT, vb[0..], vn, false);
+ n = raw(dst, n, "
");
+ n = verdict_html(dst, n, d, r);
+ n = raw(dst, n, "
");
+ n = lane_card(dst, n, d, r, 0);
+ n = lane_card(dst, n, d, r, 1);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FILTER_LANE);
+ n = raw(dst, n, "");
+ n = filter_buttons(dst, n, 0);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FILTER_BADGE);
+ n = raw(dst, n, "");
+ n = filter_buttons(dst, n, 1);
+ n = raw(dst, n, "
");
+ var cb : [128]u8 = [0; 128];
+ var cn : u32 = jv_vput_f64(cb[0..], 0, "n", NROWS as f64);
+ cn = jv_vput_f64(cb[0..], cn, "total", NPR as f64);
+ n = jv_fill(dst, n, SAY_SHOWING, cb[0..], cn, false);
+ n = raw(dst, n, "
");
+ var shown : u32 = NROWS;
+ if (SHOWN < shown) { shown = SHOWN; }
+ var i : u32 = 0;
+ while (i < shown) {
+ n = row_html(dst, n, d, i);
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_RULE_TITLE);
+ n = raw(dst, n, "");
+ n = bullets_rule(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FALSE_POS_TITLE);
+ n = raw(dst, n, "
");
+ n = bullets_pos(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FALSE_NEG_TITLE);
+ n = raw(dst, n, "
");
+ n = bullets_neg(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NOT_INTENT);
+ n = raw(dst, n, "
");
+ var eb : [128]u8 = [0; 128];
+ const ex : i64 = obj_get(d, obj_get(d, r, "excluded"), "notMaster");
+ var en : u32 = 0;
+ if (nullish(d, ex)) { en = jv_vput_str(eb[0..], 0, "notMaster", "0"); } else { en = jv_vput_val(eb[0..], 0, "notMaster", d, ex); }
+ n = jv_fill(dst, n, SAY_EXCLUDED, eb[0..], en, false);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call.
+/// Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ return n;
+}