Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/website/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,16 @@ npm run check:discovery # кон
коммитит свои числа. Публикация на t27.ai остаётся за `deploy-site.yml` — вручную,
по правилу «контент утверждается до выхода».

### T27F graph view (`/t27f/graph/`)

A static page, `public/t27f/graph/`, that draws the T27F graph of a `.t27` spec: one box per node,
one line per edge, labelled with kind, name, source lines and inference label. The graph comes from
`specs/t27f/spec_graph_export.t27` and the layout from `specs/t27f/spec_graph_view.t27`, both
compiled to `t27f.wasm`; `graph.js` is only the glue (loads the wasm, feeds the bytes, emits SVG).
A spec over the graph's cap (192 nodes, 254 edges) is refused with the verdict, never truncated.
`PROVENANCE.txt` in that directory records the t27 commit, the compiler, the checksums and how to
rebuild. To add a spec to the picker, copy it from t27 into `specs/` there and add an `<option>`.

### Проверки

```bash
Expand Down
46 changes: 46 additions & 0 deletions apps/website/public/t27f/graph/PROVENANCE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/t27f/graph/ -- where every byte on the page comes from
=======================================================

The T27F graph view (epic gHashTag/trinity#1648, RFC gHashTag/trinity#1649). The page draws one
box per node and one line per edge of a .t27 spec. It holds no graph, no layout and no verdict
of its own:

spec_graph_export.t27 reads the spec's bytes into the graph (exact nodes and edges only;
at most 192 nodes and 254 edges; over that it refuses, never truncates)
spec_graph_view.t27 places, sizes and colours every box and line
spec_graph.t27 the graph IR both of them share

Both run as t27f.wasm. graph.js (39 lines, comments included) is the whole of the hand-written
JavaScript: it loads the wasm, writes the spec's bytes in, loops over nodes and edges and emits
SVG. The one number it computes is how many characters of "kind name" fit in a box's first line
(font size x 0.6); the full text is in the box's tooltip.

Source
gHashTag/t27 master 290ff17df, specs/t27f/ and specs/tutorial/08_modules_and_arrays.t27
(specs/*.t27 here are byte-identical to that commit)
Compiler
t27c 0.5.2 (built from gHashTag/t27 90fc25b3b), zig 0.16.0, on the Railway lab
Built in
gHashTag/t27-mcp wasm/ (export.zig is the hand glue: byte buffers and getters, no spec
knowledge), PR gHashTag/t27-mcp#13, commit 72ecc84. wasm/BUILD there has the full recipe.

Checksums (sha256)
t27f.wasm eaa97ac361cb2b7ebadacf0c7a33f6daaabf7b547df1f00353900dfd3e7c44e1
spec_graph_view.mjs 739118fa01cf501bdb3eab925ef9404873230a82907faf5e6e44c006e206c962
generated Zig the wasm is built from, not vendored:
spec_graph_view.zig 191d55d9bb54d57017943a1903790e4661dfd237dfe507804cef3fcdfab33c19
spec_graph_export.zig 5297308f1d54c2688801995d56db402809fc794a7dbe5f28ad119c5b3593ede7
specs/spec_graph.t27 a3dbe0ec5110c48183ebea101109598e8542bb0ffdf9e061c672162d7da03fe7
specs/spec_graph_view.t27 22579bc98aecb3c4405991eef8b5060eb4151b17b200dfbae07a75d8b1e8547a
specs/spec_graph_export.t27 ef4ee24d5debc51be4a10835947c86a0211b867dd6ca97890a15198daa744728
specs/ui_plan.t27 383fdfb78aa13f2e48172a20e9863d91aa5147b63b1685be8e3a40667243a3f1

Reproduced on the Railway lab on 2026-10-18 from these vendored specs:
t27c gen-js spec_graph_view.t27 gives spec_graph_view.mjs byte for byte;
t27c gen spec_graph_view.t27 and spec_graph_export.t27 give the two .zig above;
zig build-exe export.zig -target wasm32-freestanding -fno-entry -rdynamic -O ReleaseSmall
-femit-bin=t27f.wasm gives t27f.wasm byte for byte.
(Generate from a directory laid out as specs/t27f/, so that the `use t27f::...` lines resolve.)

Not here yet: derived edges (calls inferred beyond what the exporter reads exactly). Every edge
on the page is exact, and the page says so.
39 changes: 39 additions & 0 deletions apps/website/public/t27f/graph/graph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Thin glue for /t27f/graph/. The exporter and the layout run in t27f.wasm (specs/t27f/spec_graph_export.t27
// and spec_graph_view.t27); this file moves a spec's bytes in, reads boxes and lines out and emits SVG. It
// decides no node, edge, span, position, size, colour or verdict. See PROVENANCE.txt.
import * as G from './spec_graph_view.mjs'
const $ = (id) => document.getElementById(id), hex = (c) => `#${c.toString(16).padStart(6, '0')}`
const nm = (p, v) => Object.keys(G).find((c) => c.startsWith(p) && !c.endsWith('KINDS') && G[c] === v)?.slice(p.length).toLowerCase() ?? String(v)
const R = (await WebAssembly.instantiateStreaming(fetch('./t27f.wasm'), {})).instance.exports
const dec = new TextDecoder(), enc = new TextEncoder()
const name = (i) => dec.decode(new Uint8Array(R.memory.buffer, R.gx_node_name_ptr(i), R.gx_node_name_len(i)))
const el = (tag, a, ...kids) => { const e = document.createElementNS('http://www.w3.org/2000/svg', tag); for (const k in a) e.setAttribute(k, a[k]); e.append(...kids); return e }
const dash = (d) => (d ? { 'stroke-dasharray': d } : {})

async function show(path, file) {
const out = $('graph'), status = $('status'), body = enc.encode(await (await fetch(`./specs/${file}`)).text()), p = enc.encode(path)
out.replaceChildren(); status.className = ''
if (!R.gx_set_len(body.length) || !R.gx_set_path_len(p.length)) return (status.textContent = `${path}: too large for the exporter's buffers; nothing drawn.`, status.className = 'refused')
new Uint8Array(R.memory.buffer).set(body, R.gx_src_ptr()); new Uint8Array(R.memory.buffer).set(p, R.gx_path_ptr())
const v = R.gx_load()
if (v !== G.GRAPH_OK) { // refused, never truncated
const why = v === G.ERR_TOO_MANY_NODES ? `${R.gx_node_count()} nodes found, at most ${G.MAX_NODES}` : v === G.ERR_TOO_MANY_EDGES ? `${R.gx_edge_count()} edges found, at most ${G.MAX_EDGES}` : `verdict ${v}`
return (status.textContent = `${path}: refused, ${nm('ERR_', v).replace(/_/g, ' ')} (${why}). No graph is drawn and none is truncated.`, status.className = 'refused')
}
if (R.gx_view_verdict() !== G.VIEW_OK) return (status.textContent = `${path}: the view refuses it, verdict ${R.gx_view_verdict()}.`, status.className = 'refused')
const W = R.gx_view_width(), H = R.gx_view_height(), svg = el('svg', { viewBox: `0 0 ${W} ${H}`, width: W, height: H, 'font-family': 'system-ui,sans-serif' }, el('rect', { width: W, height: H, fill: hex(G.VIEW_BACKDROP) }))
for (let j = 0; j < R.gx_edge_count(); j++)
svg.append(el('line', { x1: R.gx_view_edge_x1(j), y1: R.gx_view_edge_y1(j), x2: R.gx_view_edge_x2(j), y2: R.gx_view_edge_y2(j), stroke: hex(R.gx_view_edge_stroke(j)), 'stroke-width': 1.5, ...dash(R.gx_view_edge_dash(j)) },
el('title', {}, `${nm('EDGE_', R.gx_edge_kind(j))}: ${name(R.gx_edge_from(j))} -> ${name(R.gx_edge_to(j))}, line ${R.gx_edge_line(j)}`)))
for (let i = 0; i < R.gx_node_count(); i++) {
const x = R.gx_view_node_x(i), y = R.gx_view_node_y(i), w = R.gx_view_node_w(i), a = R.gx_line_start(i), b = R.gx_line_end(i), px = G.VIEW_FONT_PX
const l1 = `${nm('NODE_', R.gx_node_kind(i))} ${name(i)}`, l2 = `${nm('LABEL_', R.gx_node_label(i))} - L${a}${b > a ? `-${b}` : ''}`, fit = Math.floor((w - 16) / (px * 0.6))
const t = (dy, s) => el('text', { x: x + 8, y: y + dy, 'font-size': px, fill: hex(G.VIEW_TEXT) }, s)
svg.append(el('rect', { x, y, width: w, height: R.gx_view_node_h(i), rx: 6, fill: hex(R.gx_view_node_fill(i)), stroke: hex(R.gx_view_node_stroke(i)), 'stroke-width': 1.5, ...dash(R.gx_view_node_dash(i)) }),
t(px + 2, l1.length > fit ? `${l1.slice(0, fit - 1)}...` : l1), t(2 * px + 5, l2), el('title', {}, `${l1} - ${l2}`))
}
out.append(svg)
status.textContent = `${path}: ${R.gx_line_count()} lines, ${R.gx_node_count()} nodes, ${R.gx_edge_count()} edges, ${W}x${H}. Exact only: no derived or suggested edges.`
}
const pick = $('spec'), go = () => show(pick.selectedOptions[0].dataset.path, pick.value)
pick.addEventListener('change', go); go()
33 changes: 33 additions & 0 deletions apps/website/public/t27f/graph/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>T27F graph view of a .t27 spec &middot; Trinity S&sup3;AI</title>
<meta name="description" content="The T27F graph view: a box per node and a line per edge of a .t27 spec, placed and coloured by specs/t27f/spec_graph_view.t27, running as WebAssembly in your browser.">
<link rel="canonical" href="https://t27.ai/t27f/graph/">
<link rel="icon" href="../../favicon.svg" type="image/svg+xml">
<style>body{margin:0;font:15px/1.5 system-ui,sans-serif;background:#fff;color:#1a1a1a}main{padding:1rem 1.25rem}h1{font-size:1.3rem;margin:0 0 .25rem}p{max-width:62rem;margin:.25rem 0 .75rem}select{font:inherit;padding:.2rem}#status{margin:.75rem 0;font-weight:600}#status.refused{color:#a02020}#graph{overflow:auto;border:1px solid #ccc}a{color:#2459a8}</style>
</head>
<body>
<main>
<h1>T27F graph view</h1>
<p>One box per node, one line per edge of a <code>.t27</code> spec. The graph is read from the spec's bytes by <code>spec_graph_export.t27</code>; the placement, size and colour of every box come from <code>spec_graph_view.t27</code>. Both run here as WebAssembly. A box shows its kind, name, inference label (exact, derived, suggested, unknown) and source lines; dashed outlines mark labels weaker than exact. A spec over the graph's cap is refused, never drawn truncated. See <a href="./PROVENANCE.txt">PROVENANCE.txt</a> for the exact sources, compiler and checksums.</p>
<label>Spec <select id="spec">
<option value="08_modules_and_arrays.t27" data-path="specs/tutorial/08_modules_and_arrays.t27">tutorial 08: modules and arrays</option>
<option value="quest.t27" data-path="specs/t27f/quest.t27">t27f: quest</option>
<option value="reel_gf16_nmse.t27" data-path="specs/t27f/reel_gf16_nmse.t27">t27f: reel_gf16_nmse</option>
<option value="ui_plan.t27" data-path="specs/t27f/ui_plan.t27">t27f: ui_plan (251 of 254 edges)</option>
<option value="reel_t27_basics.t27" data-path="specs/t27f/reel_t27_basics.t27">t27f: reel_t27_basics</option>
<option value="topic_reel.t27" data-path="specs/t27f/topic_reel.t27">t27f: topic_reel</option>
<option value="spec_graph.t27" data-path="specs/t27f/spec_graph.t27">t27f: spec_graph (over the cap: refused)</option>
<option value="spec_graph_view.t27" data-path="specs/t27f/spec_graph_view.t27">t27f: spec_graph_view (over the cap: refused)</option>
<option value="spec_graph_export.t27" data-path="specs/t27f/spec_graph_export.t27">t27f: spec_graph_export (over the cap: refused)</option>
</select></label>
<div id="status" role="status">Loading the T27F wasm&hellip;</div>
<div id="graph"></div>
<noscript><p>This page runs WebAssembly and needs JavaScript.</p></noscript>
</main>
<script type="module" src="./graph.js"></script>
</body>
</html>
Loading
Loading