@0xbuff/throughline 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/COPYRIGHT.md +15 -0
- package/LICENSE +201 -0
- package/README.md +191 -2
- package/dist/describe-ZC7v_r7z.js +138 -0
- package/dist/describe-ZC7v_r7z.js.map +1 -0
- package/dist/examples.d.ts +17 -0
- package/dist/examples.js +70 -0
- package/dist/examples.js.map +1 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +1134 -0
- package/dist/index.js.map +1 -0
- package/dist/model-Dwy20ibD.d.ts +97 -0
- package/dist/react/index.d.ts +22 -0
- package/dist/react/index.js +810 -0
- package/dist/react/index.js.map +1 -0
- package/docs/architecture.md +93 -0
- package/docs/authoring.md +54 -0
- package/docs/catalog.md +96 -0
- package/docs/grammar.md +97 -0
- package/package.json +94 -3
- package/src/styles/throughline.css +77 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["PAD"],"sources":["../../src/layout/edges.ts","../../src/layout/ranks.ts","../../src/layout/value.ts","../../src/layout.ts","../../src/react/caption.tsx","../../src/seek.ts","../../src/react/controls.tsx","../../src/react/inspector.tsx","../../src/react/node-card.tsx","../../src/react/scene.tsx","../../src/react/use-player.ts","../../src/sonify.ts","../../src/react/use-sound.ts","../../src/react/player.tsx"],"sourcesContent":["import type { Relation } from \"../model.js\";\nimport type { Box, Edge, Pt } from \"./types.js\";\n\nfunction bypass(a: Box, b: Box, blockers: Box[], horizontal: boolean): Edge {\n\tif (horizontal) {\n\t\tconst y =\n\t\t\tMath.max(a.y + a.h, b.y + b.h, ...blockers.map((box) => box.y + box.h)) +\n\t\t\t16;\n\t\tconst from: Pt = [a.x + a.w / 2, a.y + a.h];\n\t\tconst to: Pt = [b.x + b.w / 2, b.y + b.h];\n\t\treturn {\n\t\t\tfrom,\n\t\t\tto,\n\t\t\td: `M${from[0]} ${from[1]}V${y}H${to[0]}V${to[1]}`,\n\t\t\tlabel: [(from[0] + to[0]) / 2, y],\n\t\t\tangle: -Math.PI / 2,\n\t\t};\n\t}\n\tconst x = Math.min(a.x, b.x, ...blockers.map((box) => box.x)) - 10;\n\tconst from: Pt = [a.x, a.y + a.h / 2];\n\tconst to: Pt = [b.x, b.y + b.h / 2];\n\treturn {\n\t\tfrom,\n\t\tto,\n\t\td: `M${from[0]} ${from[1]}H${x}V${to[1]}H${to[0]}`,\n\t\tlabel: [x, (from[1] + to[1]) / 2],\n\t\tangle: 0,\n\t};\n}\n\n/** Attach to the facing sides, preserving direction in horizontal and stacked scenes. */\nexport function routeEdges(\n\trelations: Relation[],\n\tnodes: Record<string, Box>,\n): Record<string, Edge> {\n\tconst edges: Record<string, Edge> = {};\n\tfor (const relation of relations) {\n\t\tconst a = nodes[relation.from];\n\t\tconst b = nodes[relation.to];\n\t\tif (!a || !b) continue;\n\t\tconst dx = b.x + b.w / 2 - a.x - a.w / 2;\n\t\tconst dy = b.y + b.h / 2 - a.y - a.h / 2;\n\t\tconst horizontal = Math.abs(dx) > Math.abs(dy);\n\t\tconst blockers = Object.values(nodes).filter(\n\t\t\t(box) =>\n\t\t\t\tbox !== a &&\n\t\t\t\tbox !== b &&\n\t\t\t\t(horizontal\n\t\t\t\t\t? box.x > Math.min(a.x, b.x) &&\n\t\t\t\t\t\tbox.x < Math.max(a.x, b.x) &&\n\t\t\t\t\t\tbox.y < Math.max(a.y + a.h, b.y + b.h) &&\n\t\t\t\t\t\tbox.y + box.h > Math.min(a.y, b.y)\n\t\t\t\t\t: box.y > Math.min(a.y, b.y) &&\n\t\t\t\t\t\tbox.y < Math.max(a.y, b.y) &&\n\t\t\t\t\t\tbox.x < Math.max(a.x + a.w, b.x + b.w) &&\n\t\t\t\t\t\tbox.x + box.w > Math.min(a.x, b.x)),\n\t\t);\n\t\tif (blockers.length) {\n\t\t\tedges[relation.id] = bypass(a, b, blockers, horizontal);\n\t\t\tcontinue;\n\t\t}\n\t\tlet from: Pt, to: Pt, c1: Pt, c2: Pt;\n\t\tif (relation.from === relation.to) {\n\t\t\tfrom = [a.x + a.w * 0.65, a.y + a.h];\n\t\t\tto = [a.x + a.w * 0.35, a.y + a.h];\n\t\t\tc1 = [from[0] + 12, from[1] + 24];\n\t\t\tc2 = [to[0] - 12, to[1] + 24];\n\t\t} else if (Math.abs(dx) > Math.abs(dy)) {\n\t\t\tconst direction = Math.sign(dx);\n\t\t\tfrom = [direction > 0 ? a.x + a.w : a.x, a.y + a.h / 2];\n\t\t\tto = [direction > 0 ? b.x : b.x + b.w, b.y + b.h / 2];\n\t\t\tconst bend = Math.max(14, Math.abs(to[0] - from[0]) / 2);\n\t\t\tc1 = [from[0] + direction * bend, from[1]];\n\t\t\tc2 = [to[0] - direction * bend, to[1]];\n\t\t} else {\n\t\t\tconst direction = Math.sign(dy) || 1;\n\t\t\tfrom = [a.x + a.w / 2, direction > 0 ? a.y + a.h : a.y];\n\t\t\tto = [b.x + b.w / 2, direction > 0 ? b.y : b.y + b.h];\n\t\t\tconst bend = Math.max(14, Math.abs(to[1] - from[1]) / 2);\n\t\t\tc1 = [from[0], from[1] + direction * bend];\n\t\t\tc2 = [to[0], to[1] - direction * bend];\n\t\t}\n\t\tedges[relation.id] = {\n\t\t\td: `M${from[0]} ${from[1]}C${c1[0]} ${c1[1]} ${c2[0]} ${c2[1]} ${to[0]} ${to[1]}`,\n\t\t\tfrom,\n\t\t\tto,\n\t\t\tlabel: [\n\t\t\t\t(from[0] + 3 * c1[0] + 3 * c2[0] + to[0]) / 8,\n\t\t\t\t(from[1] + 3 * c1[1] + 3 * c2[1] + to[1]) / 8,\n\t\t\t],\n\t\t\tangle: Math.atan2(to[1] - c2[1], to[0] - c2[0]),\n\t\t};\n\t}\n\treturn edges;\n}\n","import type { Entity, Relation } from \"../model.js\";\n\nexport function assignRanks(\n\tentities: Entity[],\n\trelations: Relation[],\n): Map<string, number> {\n\tconst out = new Map<string, string[]>(\n\t\tentities.map((entity) => [entity.id, []]),\n\t);\n\tconst indegree = new Map<string, number>(\n\t\tentities.map((entity) => [entity.id, 0]),\n\t);\n\tconst connected = new Set<string>();\n\tfor (const relation of relations) {\n\t\tout.get(relation.from)?.push(relation.to);\n\t\tindegree.set(relation.to, indegree.get(relation.to)! + 1);\n\t\tconnected.add(relation.from);\n\t\tconnected.add(relation.to);\n\t}\n\tconst colour = new Map<string, 1 | 2>();\n\tconst back = new Set<string>();\n\tconst visit = (id: string) => {\n\t\tcolour.set(id, 1);\n\t\tfor (const to of out.get(id)!) {\n\t\t\tconst state = colour.get(to);\n\t\t\tif (state === 1) back.add(`${id}>${to}`);\n\t\t\telse if (!state) visit(to);\n\t\t}\n\t\tcolour.set(id, 2);\n\t};\n\tconst order = [\n\t\t...entities.filter((entity) => indegree.get(entity.id) === 0),\n\t\t...entities,\n\t];\n\tfor (const entity of order) if (!colour.has(entity.id)) visit(entity.id);\n\n\tconst preds = new Map<string, string[]>(\n\t\tentities.map((entity) => [entity.id, []]),\n\t);\n\tfor (const relation of relations)\n\t\tif (\n\t\t\trelation.from !== relation.to &&\n\t\t\t!back.has(`${relation.from}>${relation.to}`)\n\t\t)\n\t\t\tpreds.get(relation.to)?.push(relation.from);\n\tconst ranks = new Map<string, number>();\n\tconst rank = (id: string): number => {\n\t\tconst known = ranks.get(id);\n\t\tif (known !== undefined) return known;\n\t\tranks.set(id, 0);\n\t\tconst value = Math.max(-1, ...(preds.get(id) ?? []).map(rank)) + 1;\n\t\tranks.set(id, value);\n\t\treturn value;\n\t};\n\tfor (const entity of entities) if (connected.has(entity.id)) rank(entity.id);\n\tconst perLane = new Map<string, number>();\n\tfor (const entity of entities) {\n\t\tif (connected.has(entity.id)) continue;\n\t\tconst lane = entity.lane ?? \"State\";\n\t\tconst next = perLane.get(lane) ?? 0;\n\t\tperLane.set(lane, next + 1);\n\t\tranks.set(entity.id, next);\n\t}\n\treturn ranks;\n}\n","import type { Entity, Value } from \"../model.js\";\nimport type { Path } from \"../path.js\";\nimport { effectiveView, format } from \"./measure.js\";\n\nexport type ValueCell = {\n\tpath: Path;\n\ttext: string;\n\tlabel?: string;\n\tx: number;\n\ty: number;\n\tw: number;\n\th: number;\n};\nexport type ValueLayout = {\n\tw: number;\n\th: number;\n\tcells: ValueCell[];\n\tnote?: string;\n};\n\nconst PAD = 10;\nconst TOP = 32;\nconst gap = 4;\nconst limit = (n: number, min: number, max: number): number =>\n\tMath.max(min, Math.min(max, n));\n\n/** Keep the focused position in a bounded window without hiding truncation. */\nfunction windowStart(length: number, count: number, focus: number): number {\n\treturn limit(focus - count + 1, 0, Math.max(0, length - count));\n}\n\nfunction focusIndex(key: Path[number] | undefined, fallback = 0): number {\n\treturn typeof key === \"number\" && Number.isFinite(key) ? key : fallback;\n}\n\nfunction grid(\n\titems: { text: string; path: Path; label?: string }[],\n\tcolumns: number,\n\tcellW: number,\n\tnote?: string,\n): ValueLayout {\n\tconst labeled = items.some((item) => item.label !== undefined);\n\tconst stride = labeled ? 46 : 32;\n\treturn {\n\t\tw: columns * (cellW + gap) - gap + PAD * 2,\n\t\th:\n\t\t\tTOP +\n\t\t\tMath.max(1, Math.ceil(items.length / columns)) * stride +\n\t\t\t(note ? 24 : 6),\n\t\tnote,\n\t\tcells: items.map((item, i) => ({\n\t\t\t...item,\n\t\t\tx: PAD + (i % columns) * (cellW + gap),\n\t\t\ty: TOP + Math.floor(i / columns) * stride,\n\t\t\tw: cellW,\n\t\t\th: 28,\n\t\t})),\n\t};\n}\n\n/** Shared by measurement and SVG rendering: the two cannot disagree on clipping. */\nexport function presentValue(\n\tentity: Entity,\n\tmaxWidth: number,\n\tfocus: Path[] = [],\n): ValueLayout {\n\tconst { value } = entity;\n\tconst view = effectiveView(entity.view, value);\n\tconst width = Math.max(80, maxWidth);\n\tlet result: ValueLayout;\n\tif (view === \"matrix\") {\n\t\tconst rows = value as Value[][];\n\t\tconst colCount = rows.reduce(\n\t\t\t(maximum, row) => Math.max(maximum, row.length),\n\t\t\t0,\n\t\t);\n\t\tconst cols = Math.max(\n\t\t\t1,\n\t\t\tMath.min(6, colCount, Math.floor((width - 16) / 38)),\n\t\t);\n\t\tconst firstRow = windowStart(rows.length, 6, focusIndex(focus[0]?.[0]));\n\t\tconst firstCol = windowStart(colCount, cols, focusIndex(focus[0]?.[1]));\n\t\tconst visibleRows = rows.slice(firstRow, firstRow + 6);\n\t\tconst items = visibleRows.flatMap((row, r) =>\n\t\t\tArray.from({ length: cols }, (_, c) => ({\n\t\t\t\ttext: row[firstCol + c] === undefined ? \"·\" : format(row[firstCol + c]),\n\t\t\t\tpath: [firstRow + r, firstCol + c],\n\t\t\t})),\n\t\t);\n\t\tconst note =\n\t\t\trows.length > 6 || colCount > cols\n\t\t\t\t? `Rows ${firstRow + 1}–${firstRow + visibleRows.length}/${rows.length} · cols ${firstCol + 1}–${firstCol + cols}/${colCount}`\n\t\t\t\t: undefined;\n\t\tresult = grid(\n\t\t\titems,\n\t\t\tcols,\n\t\t\tMath.min(56, (width - 20 - (cols - 1) * gap) / cols),\n\t\t\tnote,\n\t\t);\n\t} else if (view === \"array\" || view === \"stack\" || view === \"bits\") {\n\t\tconst bits =\n\t\t\tview === \"bits\"\n\t\t\t\t? (value as number)\n\t\t\t\t\t\t.toString(2)\n\t\t\t\t\t\t.padStart(\n\t\t\t\t\t\t\tMath.max(\n\t\t\t\t\t\t\t\t8,\n\t\t\t\t\t\t\t\tMath.ceil((value as number).toString(2).length / 4) * 4,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\"0\",\n\t\t\t\t\t\t)\n\t\t\t\t: \"\";\n\t\tconst items: Value[] = bits ? [...bits] : (value as Value[]);\n\t\tconst count = view === \"stack\" ? 6 : view === \"bits\" ? items.length : 12;\n\t\tconst focused = focusIndex(\n\t\t\tfocus[0]?.[0],\n\t\t\tview === \"stack\" ? items.length - 1 : 0,\n\t\t);\n\t\tconst start = windowStart(items.length, count, focused);\n\t\tconst shown = items.slice(start, start + count);\n\t\tconst cellW =\n\t\t\tview === \"bits\"\n\t\t\t\t? 28\n\t\t\t\t: Math.min(\n\t\t\t\t\t\t86,\n\t\t\t\t\t\tMath.max(\n\t\t\t\t\t\t\t34,\n\t\t\t\t\t\t\t...shown.map((item) => format(item).length * 7.5 + 14),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\tconst cols =\n\t\t\tview === \"stack\"\n\t\t\t\t? 1\n\t\t\t\t: Math.max(\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\tshown.length || 1,\n\t\t\t\t\t\t\tMath.floor((width - 16) / (cellW + gap)),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\tconst note =\n\t\t\titems.length > count\n\t\t\t\t? `Items ${start + 1}–${start + shown.length} of ${items.length}`\n\t\t\t\t: undefined;\n\t\tresult = grid(\n\t\t\tshown.map((item, i) => ({\n\t\t\t\ttext: format(item),\n\t\t\t\tpath: [bits ? bits.length - 1 - i : start + i],\n\t\t\t\tlabel: String(bits ? bits.length - 1 - i : start + i),\n\t\t\t})),\n\t\t\tcols,\n\t\t\tMath.min(cellW, width - 20),\n\t\t\tnote,\n\t\t);\n\t} else if (view === \"record\") {\n\t\tconst entries = Object.entries(value as Record<string, Value>);\n\t\tconst focused = entries.findIndex(([key]) => key === focus[0]?.[0]);\n\t\tconst start = windowStart(entries.length, 6, focused);\n\t\tconst shown = entries.slice(start, start + 6);\n\t\tconst note =\n\t\t\tentries.length > 6\n\t\t\t\t? `Fields ${start + 1}–${start + shown.length} of ${entries.length}`\n\t\t\t\t: undefined;\n\t\tconst cellW = Math.min(\n\t\t\twidth - 20,\n\t\t\tMath.max(\n\t\t\t\t100,\n\t\t\t\t...shown.map(\n\t\t\t\t\t([key, item]) => (key.length + format(item).length + 2) * 7.5 + 16,\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\tresult = grid(\n\t\t\tshown.map(([key, item]) => ({\n\t\t\t\ttext: `${key}: ${format(item)}`,\n\t\t\t\tpath: [key],\n\t\t\t})),\n\t\t\t1,\n\t\t\tcellW,\n\t\t\tnote,\n\t\t);\n\t} else {\n\t\tconst text = format(value);\n\t\tresult = grid(\n\t\t\t[{ text, path: [] }],\n\t\t\t1,\n\t\t\tMath.min(width - 20, Math.max(60, text.length * 8 + 16)),\n\t\t);\n\t}\n\tresult.w = Math.min(\n\t\twidth,\n\t\tMath.max(result.w, Math.min(240, entity.label.length * 7 + 20)),\n\t);\n\tif (!result.cells.length)\n\t\tresult.cells.push({\n\t\t\tpath: [],\n\t\t\ttext: \"empty\",\n\t\t\tx: PAD,\n\t\t\ty: TOP,\n\t\t\tw: result.w - 20,\n\t\t\th: 28,\n\t\t});\n\treturn result;\n}\n","import { routeEdges } from \"./layout/edges.js\";\nimport { assignRanks } from \"./layout/ranks.js\";\nimport type { Box, Layout } from \"./layout/types.js\";\nimport { presentValue } from \"./layout/value.js\";\nimport type { Entity, GroupLayout, Relation, Trace } from \"./model.js\";\nimport { parseTarget } from \"./path.js\";\n\nexport { effectiveView, format } from \"./layout/measure.js\";\nexport type { Box, Edge, Layout, Pt } from \"./layout/types.js\";\n\nconst PAD = 12;\nconst GAP = 28;\nconst laneOf = (entity: Entity): string => entity.lane ?? \"State\";\n\ntype Group = Layout[\"groups\"][number] & { nodes: Record<string, Box> };\n\nfunction arrangeGroup(\n\tname: string,\n\tmembers: Entity[],\n\trelations: Relation[],\n\tsizes: Map<string, Box>,\n\tarrangement: GroupLayout,\n\tmaxWidth: number,\n): Group {\n\tconst ids = new Set(members.map((entity) => entity.id));\n\tconst ranks = assignRanks(\n\t\tmembers,\n\t\trelations.filter(\n\t\t\t(relation) => ids.has(relation.from) && ids.has(relation.to),\n\t\t),\n\t);\n\tconst rows: Entity[][] = [];\n\tfor (const member of members) {\n\t\tconst rank =\n\t\t\tarrangement === \"tree\"\n\t\t\t\t? (ranks.get(member.id) ?? 0)\n\t\t\t\t: arrangement === \"column\"\n\t\t\t\t\t? rows.length\n\t\t\t\t\t: 0;\n\t\trows[rank] ??= [];\n\t\trows[rank].push(member);\n\t}\n\tconst nodes: Record<string, Box> = {};\n\tlet y = 32,\n\t\tw = Math.min(maxWidth, Math.max(100, name.length * 7 + 24));\n\tfor (const row of rows) {\n\t\tif (!row) continue;\n\t\tlet x = PAD,\n\t\t\trowHeight = 0;\n\t\tfor (const entity of row) {\n\t\t\tconst size = sizes.get(entity.id)!;\n\t\t\tif (x > PAD && x + size.w + PAD > maxWidth) {\n\t\t\t\ty += rowHeight + GAP;\n\t\t\t\tx = PAD;\n\t\t\t\trowHeight = 0;\n\t\t\t}\n\t\t\tnodes[entity.id] = { ...size, x, y };\n\t\t\trowHeight = Math.max(rowHeight, size.h);\n\t\t\tw = Math.max(w, x + size.w + PAD);\n\t\t\tx += size.w + GAP;\n\t\t}\n\t\ty += rowHeight + GAP;\n\t}\n\t// Center each tree layer after its widest layer has set the panel width.\n\tif (arrangement === \"tree\") {\n\t\tconst levels = new Set(Object.values(nodes).map((box) => box.y));\n\t\tfor (const level of levels) {\n\t\t\tconst boxes = Object.values(nodes).filter((box) => box.y === level);\n\t\t\tconst right = Math.max(...boxes.map((box) => box.x + box.w));\n\t\t\tconst offset = (w - PAD - right) / 2;\n\t\t\tfor (const box of boxes) box.x += offset;\n\t\t}\n\t}\n\treturn { name, x: 0, y: 0, w, h: Math.max(64, y), nodes };\n}\n\n/** History-sized boxes and responsive group packing; no per-step layout or shrinking text. */\nexport function composeTrace(trace: Trace, availableWidth = 720): Layout {\n\tconst width = Math.max(\n\t\t160,\n\t\tNumber.isFinite(availableWidth) ? Math.floor(availableWidth) : 720,\n\t);\n\tconst entities = trace.frames[0]?.state.entities ?? [];\n\tconst relations = trace.frames[0]?.state.relations ?? [];\n\tconst maxWidth = width - PAD * 2;\n\tconst nodeWidth = Math.min(260, maxWidth - PAD * 2);\n\tconst sizes = new Map<string, Box>();\n\tfor (const frame of trace.frames) {\n\t\tconst targets = frame.state.active.map(parseTarget);\n\t\tfor (const entity of frame.state.entities) {\n\t\t\tconst focus = targets\n\t\t\t\t.filter((target) => target?.id === entity.id)\n\t\t\t\t.map((target) => target!.path);\n\t\t\tconst value = presentValue(entity, nodeWidth, focus);\n\t\t\tconst old = sizes.get(entity.id);\n\t\t\tsizes.set(entity.id, {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\tw: Math.max(old?.w ?? 0, value.w),\n\t\t\t\th: Math.max(old?.h ?? 0, value.h),\n\t\t\t});\n\t\t}\n\t}\n\tconst names = [...new Set(entities.map(laneOf))];\n\tconst groups = names.map((name) =>\n\t\tarrangeGroup(\n\t\t\tname,\n\t\t\tentities.filter((entity) => laneOf(entity) === name),\n\t\t\trelations,\n\t\t\tsizes,\n\t\t\ttrace.groups?.find((group) => group.name === name)?.layout ?? \"row\",\n\t\t\tmaxWidth,\n\t\t),\n\t);\n\tconst nodes: Record<string, Box> = {};\n\tlet x = PAD,\n\t\ty = PAD,\n\t\trowHeight = 0;\n\tfor (const group of groups) {\n\t\tif (x > PAD && x + group.w + PAD > width) {\n\t\t\tx = PAD;\n\t\t\ty += rowHeight + GAP;\n\t\t\trowHeight = 0;\n\t\t}\n\t\tgroup.x = x;\n\t\tgroup.y = y;\n\t\tfor (const [id, box] of Object.entries(group.nodes))\n\t\t\tnodes[id] = { ...box, x: box.x + x, y: box.y + y };\n\t\tx += group.w + GAP;\n\t\trowHeight = Math.max(rowHeight, group.h);\n\t}\n\tfor (const rowY of new Set(groups.map((group) => group.y))) {\n\t\tconst row = groups.filter((group) => group.y === rowY);\n\t\tconst right = Math.max(...row.map((group) => group.x + group.w));\n\t\tconst offset = Math.max(0, (width - PAD - right) / 2);\n\t\tfor (const group of row) {\n\t\t\tgroup.x += offset;\n\t\t\tfor (const id of Object.keys(group.nodes)) nodes[id].x += offset;\n\t\t}\n\t}\n\tconst height = Math.max(100, y + rowHeight + PAD);\n\treturn {\n\t\twidth,\n\t\theight,\n\t\tviewBox: `0 0 ${width} ${height}`,\n\t\tgroups: groups.map(({ nodes: _, ...group }) => group),\n\t\tnodes,\n\t\tedges: routeEdges(relations, nodes),\n\t};\n}\n","import type * as React from \"react\";\nimport { format } from \"../layout/measure.js\";\nimport type { Frame } from \"../model.js\";\n\ninterface PlayerCaptionProps {\n\tframe: Frame;\n\tid: string;\n}\nexport function PlayerCaption({\n\tframe,\n\tid,\n}: PlayerCaptionProps): React.JSX.Element {\n\treturn (\n\t\t<div className=\"thl-caption\" id={id}>\n\t\t\t<span className=\"thl-step\">\n\t\t\t\t{String(frame.index + 1).padStart(2, \"0\")}\n\t\t\t</span>\n\t\t\t<div>\n\t\t\t\t<p>\n\t\t\t\t\t<strong>{frame.label}</strong>\n\t\t\t\t\t{frame.state.notes.length > 0 && (\n\t\t\t\t\t\t<span>{frame.state.notes.join(\" \")}</span>\n\t\t\t\t\t)}\n\t\t\t\t</p>\n\t\t\t\t{frame.state.transfers.length > 0 && (\n\t\t\t\t\t<ul className=\"thl-transfers\" aria-label=\"Transfers\">\n\t\t\t\t\t\t{frame.state.transfers.map((transfer, index) => {\n\t\t\t\t\t\t\tconst link = frame.state.relations.find(\n\t\t\t\t\t\t\t\t(relation) => relation.id === transfer.relation,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: transfers are stateless entries; identical sends may repeat within one frame.\n\t\t\t\t\t\t\t\t<li key={`${transfer.relation}:${index}`}>\n\t\t\t\t\t\t\t\t\t{link ? `${link.from} → ${link.to}` : transfer.relation}\n\t\t\t\t\t\t\t\t\t{transfer.value !== undefined && (\n\t\t\t\t\t\t\t\t\t\t<code>{format(transfer.value)}</code>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</ul>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * Player seek model: pure state transitions, no DOM, no timers. The React\n * binding (usePlayerInTimeline) drives these from an interval; tests drive\n * them directly.\n */\n\nexport const SPEEDS = [0.5, 1, 2] as const;\nexport type Speed = (typeof SPEEDS)[number];\n\nexport type SeekState = {\n\t/** Current frame index, 0..max. */\n\tstep: number;\n\tmax: number;\n\tplaying: boolean;\n\tspeed: Speed;\n};\n\nexport type SeekAction =\n\t| { type: \"reset\"; max: number }\n\t| { type: \"tick\" }\n\t| { type: \"toggle\" }\n\t| { type: \"pause\" }\n\t| { type: \"step\"; to: number }\n\t| { type: \"speed\"; to: Speed };\n\nexport const initialSeek = (max: number): SeekState => ({\n\tstep: 0,\n\tmax,\n\tplaying: false,\n\tspeed: 1,\n});\n\nconst clamp = (n: number, max: number) => Math.max(0, Math.min(max, n));\n\nexport function seekReducer(state: SeekState, action: SeekAction): SeekState {\n\tswitch (action.type) {\n\t\tcase \"reset\":\n\t\t\treturn initialSeek(action.max);\n\t\tcase \"tick\":\n\t\t\tif (!state.playing) return state;\n\t\t\tif (state.step >= state.max) return { ...state, playing: false };\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tstep: state.step + 1,\n\t\t\t\tplaying: state.step + 1 < state.max,\n\t\t\t};\n\t\tcase \"toggle\":\n\t\t\tif (state.playing) return { ...state, playing: false };\n\t\t\tif (state.step >= state.max) return { ...state, step: 0, playing: true };\n\t\t\treturn { ...state, playing: true };\n\t\tcase \"pause\":\n\t\t\treturn { ...state, playing: false };\n\t\tcase \"step\":\n\t\t\treturn { ...state, step: clamp(action.to, state.max), playing: false };\n\t\tcase \"speed\":\n\t\t\treturn { ...state, speed: action.to };\n\t}\n}\n\n/** Milliseconds per frame at the given speed; `base` is the 1x cadence. */\nexport const stepDelayMs = (base: number, speed: Speed): number =>\n\tMath.round(base / speed);\n","import type * as React from \"react\";\nimport {\n\ttype SeekAction,\n\ttype SeekState,\n\tSPEEDS,\n\ttype Speed,\n} from \"../seek.js\";\n\ninterface PlayerControlsProps {\n\tstate: SeekState;\n\tdispatch: React.Dispatch<SeekAction>;\n\tsound?: { enabled: boolean; toggle: () => void };\n}\nexport function PlayerControls({\n\tstate,\n\tdispatch,\n\tsound,\n}: PlayerControlsProps): React.JSX.Element {\n\treturn (\n\t\t<div className=\"thl-controls\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\taria-label=\"Step back\"\n\t\t\t\tdisabled={state.step === 0}\n\t\t\t\tonClick={() => dispatch({ type: \"step\", to: state.step - 1 })}\n\t\t\t>\n\t\t\t\t←\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"thl-play\"\n\t\t\t\taria-label={state.playing ? \"Pause\" : \"Play\"}\n\t\t\t\tdisabled={state.max < 1}\n\t\t\t\tonClick={() => dispatch({ type: \"toggle\" })}\n\t\t\t>\n\t\t\t\t{state.playing\n\t\t\t\t\t? \"Pause\"\n\t\t\t\t\t: state.step === state.max && state.max > 0\n\t\t\t\t\t\t? \"Replay\"\n\t\t\t\t\t\t: \"Play\"}\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\taria-label=\"Step forward\"\n\t\t\t\tdisabled={state.step === state.max}\n\t\t\t\tonClick={() => dispatch({ type: \"step\", to: state.step + 1 })}\n\t\t\t>\n\t\t\t\t→\n\t\t\t</button>\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\tclassName=\"thl-timeline\"\n\t\t\t\taria-label=\"Timeline\"\n\t\t\t\taria-valuetext={`Step ${state.step + 1} of ${state.max + 1}`}\n\t\t\t\tmin={0}\n\t\t\t\tmax={state.max}\n\t\t\t\tvalue={state.step}\n\t\t\t\tdisabled={state.max < 1}\n\t\t\t\tonChange={(event) =>\n\t\t\t\t\tdispatch({ type: \"step\", to: Number(event.currentTarget.value) })\n\t\t\t\t}\n\t\t\t/>\n\t\t\t<span className=\"thl-count\">\n\t\t\t\t{state.step + 1} / {state.max + 1}\n\t\t\t</span>\n\t\t\t<select\n\t\t\t\taria-label=\"Playback speed\"\n\t\t\t\tvalue={state.speed}\n\t\t\t\tonChange={(event) =>\n\t\t\t\t\tdispatch({\n\t\t\t\t\t\ttype: \"speed\",\n\t\t\t\t\t\tto: Number(event.currentTarget.value) as Speed,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t{SPEEDS.map((speed) => (\n\t\t\t\t\t<option key={speed} value={speed}>\n\t\t\t\t\t\t{speed}×\n\t\t\t\t\t</option>\n\t\t\t\t))}\n\t\t\t</select>\n\t\t\t{sound && (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\taria-label=\"Sonify trace\"\n\t\t\t\t\taria-pressed={sound.enabled}\n\t\t\t\t\tonClick={sound.toggle}\n\t\t\t\t>\n\t\t\t\t\tSound\n\t\t\t\t</button>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n","import type * as React from \"react\";\nimport type { Frame } from \"../model.js\";\nimport { visibleScene } from \"../scene.js\";\n\ninterface StateInspectorProps {\n\tframe: Frame;\n}\nexport function StateInspector({\n\tframe,\n}: StateInspectorProps): React.JSX.Element {\n\treturn (\n\t\t<details className=\"thl-inspector\">\n\t\t\t<summary>Inspect state</summary>\n\t\t\t<dl>\n\t\t\t\t{visibleScene(frame.state).entities.map((entity) => (\n\t\t\t\t\t<div key={entity.id}>\n\t\t\t\t\t\t<dt>\n\t\t\t\t\t\t\t{entity.label} <span>({entity.id})</span>\n\t\t\t\t\t\t</dt>\n\t\t\t\t\t\t<dd>\n\t\t\t\t\t\t\t<pre>{JSON.stringify(entity.value)}</pre>\n\t\t\t\t\t\t</dd>\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</dl>\n\t\t</details>\n\t);\n}\n","import { cn } from \"cn\";\nimport type * as React from \"react\";\nimport { effectiveView, format } from \"../layout/measure.js\";\nimport type { Box } from \"../layout/types.js\";\nimport { presentValue } from \"../layout/value.js\";\nimport type { Entity, Value } from \"../model.js\";\nimport { getAt, overlaps, type Path } from \"../path.js\";\n\nexport const truncate = (text: string, length: number): string =>\n\ttext.length > length ? `${text.slice(0, Math.max(1, length - 1))}…` : text;\n\ninterface NodeCardProps {\n\tentity: Entity;\n\tprevious?: Value;\n\tbox: Box;\n\tfocus: Path[];\n\tchanged: Path[];\n}\n\nexport function NodeCard({\n\tentity,\n\tprevious,\n\tbox,\n\tfocus,\n\tchanged,\n}: NodeCardProps): React.JSX.Element {\n\tconst value = presentValue(entity, box.w, focus);\n\treturn (\n\t\t<g transform={`translate(${box.x},${box.y})`} data-node={entity.id}>\n\t\t\t<title>{`${entity.label}: ${format(entity.value)}${focus.length ? \" (focus)\" : \"\"}`}</title>\n\t\t\t<rect\n\t\t\t\twidth={box.w}\n\t\t\t\theight={box.h}\n\t\t\t\trx={8}\n\t\t\t\tclassName={cn(\"thl-node\", { \"thl-focused\": focus.length > 0 })}\n\t\t\t/>\n\t\t\t<text x={10} y={19} className=\"thl-node-label\">\n\t\t\t\t{truncate(entity.label, Math.floor((box.w - 20) / 7))}\n\t\t\t</text>\n\t\t\t{value.cells.map((cell) => {\n\t\t\t\tconst isChanged =\n\t\t\t\t\tchanged.some((path) => overlaps(path, cell.path)) &&\n\t\t\t\t\t(effectiveView(entity.view, entity.value) === \"bits\"\n\t\t\t\t\t\t? typeof previous !== \"number\" ||\n\t\t\t\t\t\t\tString(Math.floor(previous / 2 ** Number(cell.path[0])) % 2) !==\n\t\t\t\t\t\t\t\tcell.text\n\t\t\t\t\t\t: JSON.stringify(getAt(previous, cell.path)) !==\n\t\t\t\t\t\t\tJSON.stringify(getAt(entity.value, cell.path)));\n\t\t\t\tconst isFocused = focus.some((path) => overlaps(path, cell.path));\n\t\t\t\treturn (\n\t\t\t\t\t<g\n\t\t\t\t\t\tkey={JSON.stringify(cell.path)}\n\t\t\t\t\t\ttransform={`translate(${cell.x},${cell.y})`}\n\t\t\t\t\t\tclassName={cn(\"thl-cell\", {\n\t\t\t\t\t\t\t\"thl-changed\": isChanged,\n\t\t\t\t\t\t\t\"thl-focused\": isFocused,\n\t\t\t\t\t\t})}\n\t\t\t\t\t>\n\t\t\t\t\t\t<title>{cell.text}</title>\n\t\t\t\t\t\t<rect width={cell.w} height={cell.h} rx={4} />\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx={cell.w / 2}\n\t\t\t\t\t\t\ty={cell.h / 2}\n\t\t\t\t\t\t\tdominantBaseline=\"central\"\n\t\t\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{truncate(cell.text, Math.floor((cell.w - 10) / 7.8))}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t\t{cell.label !== undefined && (\n\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\tx={cell.w / 2}\n\t\t\t\t\t\t\t\ty={cell.h + 13}\n\t\t\t\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\t\t\t\tclassName=\"thl-index\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{cell.label}\n\t\t\t\t\t\t\t</text>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</g>\n\t\t\t\t);\n\t\t\t})}\n\t\t\t{value.note && (\n\t\t\t\t<text x={10} y={box.h - 10} className=\"thl-window\">\n\t\t\t\t\t{truncate(value.note, Math.floor((box.w - 20) / 6.2))}\n\t\t\t\t\t<title>{value.note}</title>\n\t\t\t\t</text>\n\t\t\t)}\n\t\t</g>\n\t);\n}\n","import { cn } from \"cn\";\nimport type * as React from \"react\";\nimport type { Edge, Layout } from \"../layout/types.js\";\nimport type { Frame, Relation } from \"../model.js\";\nimport { parseTarget } from \"../path.js\";\nimport { visibleScene } from \"../scene.js\";\nimport { NodeCard, truncate } from \"./node-card.js\";\n\ninterface SceneLinkProps {\n\trelation: Relation;\n\tedge: Edge;\n\tframe: Frame;\n}\nfunction SceneLink({\n\trelation,\n\tedge,\n\tframe,\n}: SceneLinkProps): React.JSX.Element {\n\tconst transfer = frame.state.transfers.find(\n\t\t(item) => item.relation === relation.id,\n\t);\n\tconst label = relation.label;\n\treturn (\n\t\t<g className={cn(\"thl-link\", { \"thl-transferred\": !!transfer })}>\n\t\t\t<title>{`${relation.from} → ${relation.to}${relation.label ? `: ${relation.label}` : \"\"}`}</title>\n\t\t\t<path d={edge.d} className=\"thl-edge\" />\n\t\t\t{transfer && (\n\t\t\t\t<path\n\t\t\t\t\tkey={frame.index}\n\t\t\t\t\td={edge.d}\n\t\t\t\t\tpathLength={1}\n\t\t\t\t\tclassName=\"thl-flow\"\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<polygon\n\t\t\t\tpoints=\"0,0 -7,-3.5 -7,3.5\"\n\t\t\t\ttransform={`translate(${edge.to[0]},${edge.to[1]}) rotate(${(edge.angle * 180) / Math.PI})`}\n\t\t\t/>\n\t\t\t{label && (\n\t\t\t\t<text\n\t\t\t\t\tx={edge.label[0]}\n\t\t\t\t\ty={edge.label[1] - 8}\n\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\tclassName=\"thl-edge-label\"\n\t\t\t\t>\n\t\t\t\t\t{truncate(label, 20)}\n\t\t\t\t\t<title>{label}</title>\n\t\t\t\t</text>\n\t\t\t)}\n\t\t</g>\n\t);\n}\n\ninterface TraceSceneProps {\n\tframe: Frame;\n\tprevious?: Frame;\n\tlayout: Layout;\n}\nexport function TraceScene({\n\tframe,\n\tprevious,\n\tlayout,\n}: TraceSceneProps): React.JSX.Element {\n\tconst scene = visibleScene(frame.state);\n\tconst before = new Map(\n\t\tprevious?.state.entities.map((entity) => [entity.id, entity.value]),\n\t);\n\tconst targets = frame.state.active.map(parseTarget);\n\tconst changes = frame.state.changed.map(parseTarget);\n\treturn (\n\t\t<>\n\t\t\t{layout.groups\n\t\t\t\t.filter((group) =>\n\t\t\t\t\tscene.entities.some(\n\t\t\t\t\t\t(entity) => (entity.lane ?? \"State\") === group.name,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\t.map((group) => (\n\t\t\t\t\t<g key={group.name}>\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\tx={group.x}\n\t\t\t\t\t\t\ty={group.y}\n\t\t\t\t\t\t\twidth={group.w}\n\t\t\t\t\t\t\theight={group.h}\n\t\t\t\t\t\t\trx={12}\n\t\t\t\t\t\t\tclassName=\"thl-group\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<text x={group.x + 12} y={group.y + 20} className=\"thl-group-label\">\n\t\t\t\t\t\t\t{truncate(group.name, Math.floor((group.w - 24) / 7))}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t</g>\n\t\t\t\t))}\n\t\t\t{scene.relations.map((relation) => (\n\t\t\t\t<SceneLink\n\t\t\t\t\tkey={relation.id}\n\t\t\t\t\trelation={relation}\n\t\t\t\t\tedge={layout.edges[relation.id]}\n\t\t\t\t\tframe={frame}\n\t\t\t\t/>\n\t\t\t))}\n\t\t\t{scene.entities.map((entity) => (\n\t\t\t\t<NodeCard\n\t\t\t\t\tkey={entity.id}\n\t\t\t\t\tentity={entity}\n\t\t\t\t\tprevious={before.get(entity.id)}\n\t\t\t\t\tbox={layout.nodes[entity.id]}\n\t\t\t\t\tfocus={targets.flatMap((target) =>\n\t\t\t\t\t\ttarget?.id === entity.id ? [target.path] : [],\n\t\t\t\t\t)}\n\t\t\t\t\tchanged={changes.flatMap((target) =>\n\t\t\t\t\t\ttarget?.id === entity.id ? [target.path] : [],\n\t\t\t\t\t)}\n\t\t\t\t/>\n\t\t\t))}\n\t\t\t{!scene.entities.length && (\n\t\t\t\t<text\n\t\t\t\t\tx={layout.width / 2}\n\t\t\t\t\ty={50}\n\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\tclassName=\"thl-empty\"\n\t\t\t\t>\n\t\t\t\t\tNo visible state at this step.\n\t\t\t\t</text>\n\t\t\t)}\n\t\t</>\n\t);\n}\n","import type * as React from \"react\";\nimport { useEffect, useReducer, useState } from \"react\";\nimport type { Trace } from \"../model.js\";\nimport {\n\tinitialSeek,\n\ttype SeekAction,\n\ttype SeekState,\n\tseekReducer,\n\tstepDelayMs,\n} from \"../seek.js\";\n\nexport function usePlayer(trace: Trace): {\n\tstate: SeekState;\n\tdispatch: React.Dispatch<SeekAction>;\n\tonKeyDown: React.KeyboardEventHandler;\n} {\n\tconst [identity, setIdentity] = useState(trace);\n\tconst [state, dispatch] = useReducer(\n\t\tseekReducer,\n\t\tMath.max(0, trace.frames.length - 1),\n\t\tinitialSeek,\n\t);\n\t// Reset during render, before any child can read a stale frame index.\n\tif (identity !== trace) {\n\t\tsetIdentity(trace);\n\t\tdispatch({ type: \"reset\", max: Math.max(0, trace.frames.length - 1) });\n\t}\n\tuseEffect(() => {\n\t\tif (!state.playing) return;\n\t\tconst timer = setInterval(\n\t\t\t() => dispatch({ type: \"tick\" }),\n\t\t\tstepDelayMs(1500, state.speed),\n\t\t);\n\t\treturn () => clearInterval(timer);\n\t}, [state.playing, state.speed]);\n\tconst onKeyDown: React.KeyboardEventHandler = (event) => {\n\t\tif (event.target !== event.currentTarget) return;\n\t\tif ([\"ArrowRight\", \"ArrowLeft\", \"Home\", \"End\"].includes(event.key)) {\n\t\t\tevent.preventDefault();\n\t\t\tconst to =\n\t\t\t\tevent.key === \"Home\"\n\t\t\t\t\t? 0\n\t\t\t\t\t: event.key === \"End\"\n\t\t\t\t\t\t? state.max\n\t\t\t\t\t\t: state.step + (event.key === \"ArrowRight\" ? 1 : -1);\n\t\t\tdispatch({ type: \"step\", to });\n\t\t} else if (event.key === \" \" && state.max > 0) {\n\t\t\tevent.preventDefault();\n\t\t\tdispatch({ type: \"toggle\" });\n\t\t}\n\t};\n\treturn { state, dispatch, onKeyDown };\n}\n\nexport function useSceneWidth(\n\tref: React.RefObject<HTMLDivElement | null>,\n): number {\n\tconst [width, setWidth] = useState(720);\n\tuseEffect(() => {\n\t\tconst element = ref.current;\n\t\tif (!element) return;\n\t\tconst observer = new ResizeObserver(([entry]) => {\n\t\t\tif (entry.contentRect.width > 0)\n\t\t\t\tsetWidth(Math.floor(entry.contentRect.width));\n\t\t});\n\t\tobserver.observe(element);\n\t\treturn () => observer.disconnect();\n\t}, [ref]);\n\treturn width;\n}\n","/** Optional stage feedback. Visual focus is not evidence of cost or contention. */\nexport const SONIFY_MIN_HZ = 261.63;\nexport const SONIFY_MAX_HZ = 523.25;\nexport const SONIFY_STANDARD_MS = 180;\nexport type SonificationEvent = {\n\tstep: number;\n\ttotal: number;\n\tfrequency: number;\n\tdurationMs: number;\n};\nexport type SonificationHandler = (event: SonificationEvent) => void;\nexport type SonificationOptions = {\n\tenabled?: boolean;\n\tonTone?: SonificationHandler;\n};\n\nexport function frequencyForStep(step: number, total: number): number {\n\tif (\n\t\t!Number.isFinite(step) ||\n\t\t!Number.isFinite(total) ||\n\t\ttotal <= 1 ||\n\t\tstep <= 0\n\t)\n\t\treturn SONIFY_MIN_HZ;\n\tif (step >= total - 1) return SONIFY_MAX_HZ;\n\treturn (\n\t\tMath.round(\n\t\t\tSONIFY_MIN_HZ *\n\t\t\t\t(SONIFY_MAX_HZ / SONIFY_MIN_HZ) ** (step / (total - 1)) *\n\t\t\t\t100,\n\t\t) / 100\n\t);\n}\n\nexport function sonificationEvent(\n\tstep: number,\n\ttotal: number,\n): SonificationEvent {\n\treturn {\n\t\tstep,\n\t\ttotal,\n\t\tfrequency: frequencyForStep(step, total),\n\t\tdurationMs: SONIFY_STANDARD_MS,\n\t};\n}\n","import { useEffect, useRef } from \"react\";\nimport { type SonificationOptions, sonificationEvent } from \"../sonify.js\";\n\n/** One short voice at a time; seeking or disabling sound stops the previous voice. */\nexport function useSound(\n\tstep: number,\n\ttotal: number,\n\tenabled: boolean,\n\toptions?: SonificationOptions,\n): void {\n\tconst context = useRef<AudioContext | null>(null);\n\tconst handler = options?.onTone;\n\tuseEffect(\n\t\t() => () => {\n\t\t\tconst audio = context.current;\n\t\t\tcontext.current = null;\n\t\t\tvoid audio?.close().catch(() => {});\n\t\t},\n\t\t[],\n\t);\n\tuseEffect(() => {\n\t\tif (!enabled || total === 0) return;\n\t\tconst event = sonificationEvent(step, total);\n\t\ttry {\n\t\t\tif (handler) {\n\t\t\t\thandler(event);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (typeof AudioContext === \"undefined\") return;\n\t\t\tcontext.current ??= new AudioContext();\n\t\t\tconst audio = context.current;\n\t\t\tvoid audio.resume().catch(() => {});\n\t\t\tconst oscillator = audio.createOscillator();\n\t\t\tconst gain = audio.createGain();\n\t\t\tconst now = audio.currentTime;\n\t\t\toscillator.frequency.value = event.frequency;\n\t\t\tgain.gain.setValueAtTime(0.0001, now);\n\t\t\tgain.gain.exponentialRampToValueAtTime(0.1, now + 0.02);\n\t\t\tgain.gain.exponentialRampToValueAtTime(\n\t\t\t\t0.0001,\n\t\t\t\tnow + event.durationMs / 1000,\n\t\t\t);\n\t\t\toscillator.connect(gain);\n\t\t\tgain.connect(audio.destination);\n\t\t\toscillator.onended = () => {\n\t\t\t\toscillator.disconnect();\n\t\t\t\tgain.disconnect();\n\t\t\t};\n\t\t\toscillator.start();\n\t\t\toscillator.stop(now + event.durationMs / 1000 + 0.02);\n\t\t\treturn () => {\n\t\t\t\toscillator.stop();\n\t\t\t};\n\t\t} catch {\n\t\t\t// Audio is optional; unavailable hardware or a consumer callback cannot stop playback.\n\t\t}\n\t}, [step, total, enabled, handler]);\n}\n","import { cn } from \"cn\";\nimport type * as React from \"react\";\nimport { useId, useMemo, useRef, useState } from \"react\";\nimport { describeFrame } from \"../describe.js\";\nimport { composeTrace } from \"../layout.js\";\nimport type { Trace } from \"../model.js\";\nimport type { SonificationOptions } from \"../sonify.js\";\nimport { PlayerCaption } from \"./caption.js\";\nimport { PlayerControls } from \"./controls.js\";\nimport { StateInspector } from \"./inspector.js\";\nimport { TraceScene } from \"./scene.js\";\nimport { usePlayer, useSceneWidth } from \"./use-player.js\";\nimport { useSound } from \"./use-sound.js\";\n\nexport interface ThroughlinePlayerProps {\n\ttrace: Trace;\n\tclassName?: string;\n\ttheme?: \"light\" | \"dark\" | \"auto\";\n\t/** Omit to remove audio controls entirely. */\n\tsonification?: SonificationOptions;\n}\n\nexport function ThroughlinePlayer({\n\ttrace,\n\tclassName,\n\ttheme = \"auto\",\n\tsonification,\n}: ThroughlinePlayerProps): React.JSX.Element {\n\tconst ref = useRef<HTMLDivElement>(null);\n\tconst width = useSceneWidth(ref);\n\tconst { state, dispatch, onKeyDown } = usePlayer(trace);\n\tconst [sound, setSound] = useState(sonification?.enabled ?? false);\n\tconst layout = useMemo(() => composeTrace(trace, width), [trace, width]);\n\tconst captionId = useId();\n\tconst frame = trace.frames[state.step] ?? trace.frames[0];\n\tuseSound(\n\t\tstate.step,\n\t\ttrace.frames.length,\n\t\t!!sonification && sound,\n\t\tsonification,\n\t);\n\tconst summary = frame\n\t\t? describeFrame(frame, trace.frames.length)\n\t\t: \"No steps to display.\";\n\treturn (\n\t\t// biome-ignore lint/a11y/useSemanticElements: one focusable group owns scene stepping without changing native control keys.\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName={cn(\"thl\", className)}\n\t\t\tdata-theme={theme}\n\t\t\trole=\"group\"\n\t\t\taria-label=\"Visualization player\"\n\t\t\t// biome-ignore lint/a11y/noNoninteractiveTabindex: the group exposes documented Arrow/Home/End/Space shortcuts.\n\t\t\ttabIndex={0}\n\t\t\tonKeyDown={onKeyDown}\n\t\t>\n\t\t\t<p role=\"status\" className=\"thl-sr-only\">\n\t\t\t\t{summary}\n\t\t\t</p>\n\t\t\t{frame ? (\n\t\t\t\t<>\n\t\t\t\t\t<svg\n\t\t\t\t\t\twidth=\"100%\"\n\t\t\t\t\t\theight={layout.height}\n\t\t\t\t\t\tviewBox={layout.viewBox}\n\t\t\t\t\t\trole=\"img\"\n\t\t\t\t\t\taria-label={summary}\n\t\t\t\t\t\taria-describedby={captionId}\n\t\t\t\t\t\tclassName=\"thl-scene\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<TraceScene\n\t\t\t\t\t\t\tframe={frame}\n\t\t\t\t\t\t\tprevious={trace.frames[state.step - 1]}\n\t\t\t\t\t\t\tlayout={layout}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</svg>\n\t\t\t\t\t<PlayerCaption frame={frame} id={captionId} />\n\t\t\t\t\t<PlayerControls\n\t\t\t\t\t\tstate={state}\n\t\t\t\t\t\tdispatch={dispatch}\n\t\t\t\t\t\tsound={\n\t\t\t\t\t\t\tsonification\n\t\t\t\t\t\t\t\t? { enabled: sound, toggle: () => setSound(!sound) }\n\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t\t<StateInspector frame={frame} />\n\t\t\t\t</>\n\t\t\t) : (\n\t\t\t\t<p className=\"thl-empty\">No steps to display.</p>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Throughline architecture
|
|
2
|
+
|
|
3
|
+
Throughline separates **authored programs**, **compiled snapshots**
|
|
4
|
+
and **optional presentation**. This allows the compiler to run in Node
|
|
5
|
+
build pipelines or other offline ESM environments without React or a DOM.
|
|
6
|
+
|
|
7
|
+
## Data flow
|
|
8
|
+
|
|
9
|
+
1. **Source:** `parseThl` reads one `thl <domain>` block (optionally
|
|
10
|
+
surrounded by Markdown backtick/tilde fences). It preserves source
|
|
11
|
+
line positions for diagnostics; parsing an entire Markdown document
|
|
12
|
+
belongs to the host application.
|
|
13
|
+
2. **Domain:** `compileAst` dispatches `flow` to the authored-program
|
|
14
|
+
parser. The four computed adapters parse small parameter languages
|
|
15
|
+
and build the same `Program` through a deterministic tracer.
|
|
16
|
+
3. **Validation:** `validateProgram` checks version, entity and relation
|
|
17
|
+
identifiers, event shapes, finite JSON-like values, targets,
|
|
18
|
+
overlapping writes, visibility and limits. Errors are surfaced
|
|
19
|
+
as `ProgramError` with diagnostics.
|
|
20
|
+
4. **Compilation:** `compileProgram` emits a version-1 `Trace` with frame
|
|
21
|
+
zero (the initial state) followed by an immutable snapshot for each
|
|
22
|
+
authored beat. Each frame records active/changed targets,
|
|
23
|
+
transfers, notes and any source information.
|
|
24
|
+
5. **Presentation:** `ThroughlinePlayer` derives a deterministic SVG
|
|
25
|
+
scene from these frames and lays out nodes/links for the available
|
|
26
|
+
width. Controls seek among precompiled frames instead of re-running
|
|
27
|
+
authoring code. Accessible captions and the inspector expose state
|
|
28
|
+
beyond the visual scene.
|
|
29
|
+
|
|
30
|
+
## Public module boundaries
|
|
31
|
+
|
|
32
|
+
| Module | Responsibility | React required? |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `@0xbuff/throughline` | Parsing, validation, compilation, tracer and types | No |
|
|
35
|
+
| `@0xbuff/throughline/react` | Accessible player and optional sonification | Yes, React 19 peer |
|
|
36
|
+
| `@0xbuff/throughline/examples` | Public sample inputs | No |
|
|
37
|
+
| `@0xbuff/throughline/styles/throughline.css` | Namespaced player CSS | No |
|
|
38
|
+
|
|
39
|
+
The root compiler does not fetch remote data or use runtime application
|
|
40
|
+
services. External consumers import only the declared public exports;
|
|
41
|
+
deep imports are deliberately unsupported.
|
|
42
|
+
|
|
43
|
+
## Trace format and compatibility
|
|
44
|
+
|
|
45
|
+
`Program.version` and `Trace.version` are both `1`, not the npm package
|
|
46
|
+
version. Each authored step yields one frame after the initial frame,
|
|
47
|
+
with state values, visibility and relevant event annotations.
|
|
48
|
+
|
|
49
|
+
A package upgrade that changes syntax, model types, trace meaning,
|
|
50
|
+
diagnostics, controls or CSS selectors is potentially breaking even
|
|
51
|
+
when the trace-schema version stays constant. Maintain fixtures,
|
|
52
|
+
regression tests and release notes for such changes.
|
|
53
|
+
|
|
54
|
+
## Work and validation limits
|
|
55
|
+
|
|
56
|
+
- Maximum source: **262,144 UTF-16 code units**.
|
|
57
|
+
- Program: **200 entities**, **1,000 relations**, **2,000 steps**,
|
|
58
|
+
**10,000 events**.
|
|
59
|
+
- Program payload budget: **1,000,000 deterministic work units**.
|
|
60
|
+
- Expanded trace budget: **8,000,000 deterministic work units**.
|
|
61
|
+
- Reject unsupported or cyclic/non-JSON input values and conflicting
|
|
62
|
+
overlapping writes with explicit diagnostics.
|
|
63
|
+
|
|
64
|
+
These are work/payload constraints, not exact memory or CPU
|
|
65
|
+
measurements. The four computed adapters apply their own domain limits;
|
|
66
|
+
see [the catalog](catalog.md).
|
|
67
|
+
|
|
68
|
+
## Playback and accessibility
|
|
69
|
+
|
|
70
|
+
The player uses SVG to render the active scene. It keeps entity identity
|
|
71
|
+
stable across changes, reserves layout space for future values,
|
|
72
|
+
supports responsive groups and bounded views of large values, and
|
|
73
|
+
provides a textual inspector. Keyboard navigation, native inputs,
|
|
74
|
+
live status, reduced-motion preferences and SSR/hydration behavior
|
|
75
|
+
are covered by browser and packed-consumer tests.
|
|
76
|
+
|
|
77
|
+
Sonification is explicitly opted into by the caller and is not
|
|
78
|
+
triggered merely by importing the package. Neither playback nor
|
|
79
|
+
compilation records grades or authenticates learners.
|
|
80
|
+
|
|
81
|
+
## Build and trust boundaries
|
|
82
|
+
|
|
83
|
+
`tsdown` produces ESM, declarations and JavaScript source maps for
|
|
84
|
+
the three JavaScript exports. The stylesheet is shipped separately.
|
|
85
|
+
The source package uses one Node 24/pnpm CI job to run compiler and
|
|
86
|
+
browser tests, inspect third-party dependency license files and
|
|
87
|
+
install a packed tarball in independent Node, TypeScript, SSR and
|
|
88
|
+
Vite/React consumers.
|
|
89
|
+
|
|
90
|
+
These checks are necessary but do not establish copyright ownership,
|
|
91
|
+
absence of all secrets, or public-release permission. The private
|
|
92
|
+
source history must be separately cleared before any public-source
|
|
93
|
+
cutover.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Throughline adapter authoring guide
|
|
2
|
+
|
|
3
|
+
How to add a computed domain. Copy `binary.ts` (simplest) or
|
|
4
|
+
`bindings.ts` (dynamic topology on static edges); keep the adapter
|
|
5
|
+
~100 lines plus tests.
|
|
6
|
+
|
|
7
|
+
## Recipe
|
|
8
|
+
|
|
9
|
+
1. **Syntax.** Define 2–5 lines of params in the fence body, e.g.
|
|
10
|
+
`a = 1011`. Iterate them with `sourceLines(ast)` (blank lines and `#`
|
|
11
|
+
comments skipped without shifting the original fence-relative numbers).
|
|
12
|
+
2. **Parse: `parseX(ast: ThlBlock): Params`.** Validate everything and throw
|
|
13
|
+
`ProgramError([{ message, line }])` — these fail the build with the
|
|
14
|
+
same rendering as grammar errors. Parse never computes.
|
|
15
|
+
3. **Build: `buildX(params: Params): Program`.** Run the ordinary
|
|
16
|
+
algorithm and narrate it with `tracer()`: `node`/`link` declarations
|
|
17
|
+
first, then one `step(label, body)` per micro-step using
|
|
18
|
+
`set`/`focus`/`send`/`note`. Use `group(name, "row" | "column" | "tree")`
|
|
19
|
+
to arrange a set of nodes. `tracer().get()` reads live state
|
|
20
|
+
between steps. Validate by compiling: `compileProgram` enforces
|
|
21
|
+
unknown targets, conflicting writes, and value shapes.
|
|
22
|
+
4. **Register.** Add the name to `DomainName` (`domains/types.ts`),
|
|
23
|
+
`SUPPORTED_DOMAINS`, and the `COMPUTED` dispatch table
|
|
24
|
+
(`domains/registry.ts`), all re-exported from the package index.
|
|
25
|
+
5. **Document.** Add a catalog entry: syntax, params, exactly which
|
|
26
|
+
micro-steps it emits, a minimal example, and its limits.
|
|
27
|
+
|
|
28
|
+
## Staging rules
|
|
29
|
+
|
|
30
|
+
The two staging rules live in `grammar.md` (one idea per beat;
|
|
31
|
+
`send` does not mutate) and bind computed adapters too. Two
|
|
32
|
+
adapter-specific additions:
|
|
33
|
+
- **Stable identities, changing visibility.** Predeclare nodes and links;
|
|
34
|
+
future objects and inactive references start with `hidden: true`.
|
|
35
|
+
Use `show("node", id)` / `hide("node", id)` and their `"link"` equivalents
|
|
36
|
+
to describe the current structure. Rebinding hides the previous reference
|
|
37
|
+
and shows its replacement in one step. A transfer cannot use a hidden link
|
|
38
|
+
or endpoint. Reserving layout space does not require showing future objects.
|
|
39
|
+
- **One source line = one beat** where the domain has source lines
|
|
40
|
+
(`bindings`); otherwise one computation step = one beat (`binary`
|
|
41
|
+
bits, `matrix` cells, `callstack` pushes/pops).
|
|
42
|
+
|
|
43
|
+
## Tests (same change, no exceptions)
|
|
44
|
+
|
|
45
|
+
- Parse: happy path plus every error branch (missing/duplicate/bad
|
|
46
|
+
operand, out-of-range) asserting the `/Line N/` message.
|
|
47
|
+
- Build: beat labels in order, final entity values, at least one
|
|
48
|
+
transfer assertion, and one failure/edge case (zero carry, rebind,
|
|
49
|
+
1×1 grid, depth 1).
|
|
50
|
+
- Registry: one routing test per domain in `__tests__/registry.test.ts`.
|
|
51
|
+
- From this standalone package root, run `pnpm test:run` and
|
|
52
|
+
`pnpm typecheck`, plus browser tests for interactive changes.
|
|
53
|
+
Avoid `describe.only`, `test.only`, and skipped tests; current CI
|
|
54
|
+
does not automatically reject them.
|
package/docs/catalog.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Throughline adapter catalog
|
|
2
|
+
|
|
3
|
+
Computed domains turn terse params into micro-step Programs. Each adapter
|
|
4
|
+
is `parse(ast) → params`, `build(params) → Program` (via `tracer()`),
|
|
5
|
+
routed by the fence domain in `compile`. Hand-authored traces stay on
|
|
6
|
+
`flow` (`grammar.md`); adapters exist only where humans should not
|
|
7
|
+
hand-write the steps.
|
|
8
|
+
|
|
9
|
+
## flow (hand-authored)
|
|
10
|
+
|
|
11
|
+
Core grammar: `node`, `link`, `step`, `set`, `focus`,
|
|
12
|
+
`send`, `note`, `show`, `hide`. The fallback for every domain without a computed
|
|
13
|
+
adapter — workflows, protocols, pipelines, git/CI, threat models.
|
|
14
|
+
See `grammar.md`. No limits beyond the engine caps (200 entities,
|
|
15
|
+
1000 relations, 2000 steps, 10000 events).
|
|
16
|
+
|
|
17
|
+
## bindings — variable slots, primitives vs references
|
|
18
|
+
|
|
19
|
+
```thl bindings
|
|
20
|
+
let a = 5
|
|
21
|
+
let b = a
|
|
22
|
+
b = 9
|
|
23
|
+
const list = [1, 2]
|
|
24
|
+
const alias = list
|
|
25
|
+
alias.push(3)
|
|
26
|
+
alias = [7]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Statements: `let`/`const <name> = <json|name>`, `<name> = <json|name>`,
|
|
30
|
+
`<name>.push(<json>, …)`. One source line = one beat.
|
|
31
|
+
- Emits: a `Stack` lane (one slot per variable, `null` until declared)
|
|
32
|
+
plus a `Heap` lane (`o1`, `o2`, … per array/object literal). Copying a
|
|
33
|
+
reference sends a packet along a `var → heap` edge; pushing focuses
|
|
34
|
+
every variable sharing the object so the sync is visible.
|
|
35
|
+
- Limits: `const` is not enforced; no expressions, indexing, or methods
|
|
36
|
+
besides `push`; push targets arrays only. Future variables and heap objects
|
|
37
|
+
start hidden. Only current reference edges are visible; rebinding hides
|
|
38
|
+
the previous edge while retaining the heap object for other aliases.
|
|
39
|
+
|
|
40
|
+
## binary — ripple-carry addition
|
|
41
|
+
|
|
42
|
+
```thl binary
|
|
43
|
+
a = 1011
|
|
44
|
+
b = 0110
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- Params: `a = <bits>`, `b = <bits>` (order free). Operands are 1–16
|
|
48
|
+
binary digits.
|
|
49
|
+
- Emits: `A`/`B` as `bits` nodes, a `Sum` row (MSB-first with a leading
|
|
50
|
+
carry-out slot, `·` placeholders), a `Carry` number. One beat per bit,
|
|
51
|
+
LSB first (`bit 0` …), then a `Result` beat: carries ripple visibly
|
|
52
|
+
because each beat sets its sum cell and the carry together.
|
|
53
|
+
- Limits: 16 bits; unsigned only. Final result is always width n+1
|
|
54
|
+
(leading zero when there is no carry-out).
|
|
55
|
+
|
|
56
|
+
## matrix — matrix multiplication
|
|
57
|
+
|
|
58
|
+
```thl matrix
|
|
59
|
+
A = [[1,2],[3,4]]
|
|
60
|
+
B = [[5,6],[7,8]]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- Params: `A = <grid>`, `B = <grid>` (order free). Grids are 1–4 rows of
|
|
64
|
+
1–4 finite numbers, all rows equal length, inner dimensions matching.
|
|
65
|
+
- Emits: `A`, `B`, `C` as `matrix` nodes (`C` starts as `·`), a `Terms`
|
|
66
|
+
work row, and `row`/`col` read edges. One beat per output cell in
|
|
67
|
+
row-major order: focuses the A row and B column cells, writes
|
|
68
|
+
`C[i][j]`, lands the pairwise products in `Terms`, and sends both
|
|
69
|
+
reads as packets. Notes carry the full `C[i][j] = t0 + t1 = total`
|
|
70
|
+
expansion.
|
|
71
|
+
- Limits: 4×4. No transpose/inverse/determinant — `flow` covers the rest.
|
|
72
|
+
|
|
73
|
+
## callstack — factorial recursion
|
|
74
|
+
|
|
75
|
+
```thl callstack
|
|
76
|
+
fact 4
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- Params: one line, `fact <n>`, depth 1–6.
|
|
80
|
+
- Emits: `slot1…slotN` frame slots (`Call stack` lane, bottom-up) plus a
|
|
81
|
+
`Returns` row. Push beats run `fact(n)` down to `fact(1)` (`calls`
|
|
82
|
+
packets travel caller → callee); unwind beats pop each frame to `null`,
|
|
83
|
+
record its value, and send a `↩` packet; a final `done` beat lands the
|
|
84
|
+
total. Notes narrate the blocked-on / got-back arithmetic.
|
|
85
|
+
- Limits: factorial only, depth ≤ 6 (13 beats max). General calls,
|
|
86
|
+
branches, and prediction are future adapters.
|
|
87
|
+
|
|
88
|
+
## Planned (tier 2, lesson-on-demand)
|
|
89
|
+
|
|
90
|
+
Each is one `parse` + one `build()` on `tracer()`, ~100 lines with
|
|
91
|
+
tests — transcription of the `visualizer/` reference computations, never
|
|
92
|
+
bespoke SVG: `vectors` (dot-product geometry, SEF6), `gradient`
|
|
93
|
+
(descent + finite-difference compare, SEF6/AI3), `search` (binary
|
|
94
|
+
search, SEF3), `sort` (bubble/insertion, SEF3), `cache`/`race`/`tcp`
|
|
95
|
+
(SYS1–SYS4 simulators). Workflows never need adapters: `flow` + the
|
|
96
|
+
grammar covers them.
|
package/docs/grammar.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Throughline grammar reference — `thl flow`
|
|
2
|
+
|
|
3
|
+
Source of truth for hand-authored traces. AI lesson authors write this;
|
|
4
|
+
`compile` parses and validates it into snapshots. A host can call this during
|
|
5
|
+
its build or at runtime.
|
|
6
|
+
Parameterized domains (`bindings`, `binary`, `matrix`, `callstack`) are
|
|
7
|
+
documented in `catalog.md`; they compile to this same grammar.
|
|
8
|
+
|
|
9
|
+
## Fence
|
|
10
|
+
|
|
11
|
+
```thl flow
|
|
12
|
+
node input "Vector" in "Memory" as array = [1,2,3]
|
|
13
|
+
step "Load"
|
|
14
|
+
focus input
|
|
15
|
+
note "Vector in memory"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- Info string names the domain: ```` ```thl flow ````.
|
|
19
|
+
- Everything between the fences is the program. Surrounding prose carries
|
|
20
|
+
the concept; the fence carries only state and beats.
|
|
21
|
+
- Blank lines and `#` comment lines are ignored.
|
|
22
|
+
|
|
23
|
+
## Statements
|
|
24
|
+
|
|
25
|
+
| Statement | Syntax | Notes |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `group` | `group "<name>" as <row|column|tree>` | Arranges nodes sharing this group name; omitted groups use `row`. |
|
|
28
|
+
| `node` | `node <id> "<label>" [in "<lane>"] [as <view>] [hidden] [= <json>]` | Declares an entity. Missing value defaults to `null`. |
|
|
29
|
+
| `link` | `link <id> <from> -> <to> ["<label>"] [hidden]` | Declares a directed edge between two node ids. |
|
|
30
|
+
| `step` | `step "<label>"` | Opens a beat. Every `set`/`focus`/`send`/`note` after it belongs to it until the next `step`. |
|
|
31
|
+
| `set` | `set <target> = <json>` | Writes a JSON value into an entity or a position inside it. |
|
|
32
|
+
| `focus` | `focus [<target>, ...]` | Highlights entities/positions for this beat. Comma- or space-separated; may be empty. |
|
|
33
|
+
| `send` | `send <relation> [= <json>]` | Highlights a directed transfer along a link; payload appears in the caption. Value optional. |
|
|
34
|
+
| `note` | `note "<text>"` | Caption for this beat. |
|
|
35
|
+
| `show` / `hide` | `show node <id>` / `hide link <id>` | Changes persistent visibility; either verb accepts `node` or `link`. |
|
|
36
|
+
|
|
37
|
+
Groups, nodes, and links must come before the first `step`. Events before any
|
|
38
|
+
`step` fail the build.
|
|
39
|
+
|
|
40
|
+
## Targets and values
|
|
41
|
+
|
|
42
|
+
- Target: an entity id, optionally followed by positions: `arr`, `arr[3]`,
|
|
43
|
+
`grid[1][2]`, `cfg.mode`. Writes to impossible paths (indexing a
|
|
44
|
+
non-array, `.key` on a non-record, out-of-range index) fail the build
|
|
45
|
+
at compile time. `focus` targets only need a known entity id — a
|
|
46
|
+
position that never resolves simply never highlights. Arrays may grow
|
|
47
|
+
by exactly one (`arr[len]` appends); records may gain keys.
|
|
48
|
+
- Value: finite JSON. Quote strings (`"emit"`); arrays and objects are
|
|
49
|
+
supported. `NaN`/`Infinity` are rejected.
|
|
50
|
+
- Views (per node, `as <view>`): `auto`, `text`, `number`, `array`,
|
|
51
|
+
`stack`, `matrix`, `bits`, `record`. Unknown views fail the build.
|
|
52
|
+
- Lanes (per node, `in "<lane>"`): free-form grouping string rendered as
|
|
53
|
+
a group header (e.g. `"Memory"`, `"Output"`).
|
|
54
|
+
|
|
55
|
+
## Frames
|
|
56
|
+
|
|
57
|
+
Compilation emits frame 0 (`Initial state`, entity initial values) plus
|
|
58
|
+
one frame per `step`. Seeking is a snapshot lookup, never recompute.
|
|
59
|
+
|
|
60
|
+
- `set` updates the entity value for this and later frames; the beat's
|
|
61
|
+
`changed` list carries the raw target strings (e.g. `input[1]`).
|
|
62
|
+
- `focus` and `set` targets together form the beat's `active` list.
|
|
63
|
+
- `send` adds a transfer for this beat only.
|
|
64
|
+
- `note` appends to the beat's caption lines.
|
|
65
|
+
- `show` / `hide` changes visibility for this and later frames. Hidden nodes
|
|
66
|
+
retain their values and identities; links with hidden endpoints are omitted.
|
|
67
|
+
Declare future objects with `hidden`, then show them when they appear.
|
|
68
|
+
Conflicting visibility writes and transfers along hidden links or endpoints
|
|
69
|
+
fail compilation. Changes within a step are applied together.
|
|
70
|
+
|
|
71
|
+
## Two staging rules (learned the hard way)
|
|
72
|
+
|
|
73
|
+
1. **One idea per beat.** Square *then* add; carry *then* land. Fusing
|
|
74
|
+
two state changes into one step fuses their highlights and captions,
|
|
75
|
+
and a same-value write (e.g. `1² = 1` over `1`) shows no change highlight — only
|
|
76
|
+
the focus ring. Split beats stay legible.
|
|
77
|
+
2. **`send` does not mutate.** The edge highlights the transfer, but the
|
|
78
|
+
destination keeps its old value until a `set` writes it. To show state
|
|
79
|
+
sync, pair them across beats (`send feed = 4` on the Add beat, or a
|
|
80
|
+
`Carry` beat with only `send` followed by a `Land` beat with `set`).
|
|
81
|
+
|
|
82
|
+
## Limits
|
|
83
|
+
|
|
84
|
+
200 entities, 1000 relations, 2000 steps, 10000 events per program.
|
|
85
|
+
Overlapping writes to the same target (or its parent/child path) inside
|
|
86
|
+
one step fail: split into separate steps.
|
|
87
|
+
|
|
88
|
+
## Errors (line-aware, fail the build)
|
|
89
|
+
|
|
90
|
+
Line 2: Unknown view "sparkle". Use auto, text, number, array, stack, matrix, bits, record.
|
|
91
|
+
Line 4: Invalid target "a..x". Use an id, optionally followed by [index] or .key.
|
|
92
|
+
Line 5: Conflicting writes to "a[1]" in one step; split into separate steps.
|
|
93
|
+
|
|
94
|
+
`ProgramError` carries diagnostics with source lines where available.
|
|
95
|
+
Compiling a block throws on invalid syntax or state. A host build must invoke
|
|
96
|
+
the compiler to reject invalid content before publication; the Academy also
|
|
97
|
+
has a render-time error panel for malformed content.
|
package/package.json
CHANGED
|
@@ -1,6 +1,97 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xbuff/throughline",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Readable state over time, authored in Markdown. A deterministic trace compiler with an optional React player.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=24"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/0xbuffdev/throughline.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/0xbuffdev/throughline/issues"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./react": {
|
|
24
|
+
"types": "./dist/react/index.d.ts",
|
|
25
|
+
"default": "./dist/react/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./examples": {
|
|
28
|
+
"types": "./dist/examples.d.ts",
|
|
29
|
+
"default": "./dist/examples.js"
|
|
30
|
+
},
|
|
31
|
+
"./styles/throughline.css": "./src/styles/throughline.css"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src/styles",
|
|
36
|
+
"docs",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE",
|
|
39
|
+
"COPYRIGHT.md",
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
],
|
|
42
|
+
"sideEffects": [
|
|
43
|
+
"**/*.css"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"cn": "^0.4.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": "^19.2.7",
|
|
50
|
+
"react-dom": "^19.2.7"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"react": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"react-dom": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@playwright/test": "^1.61.0",
|
|
62
|
+
"@types/node": "^24.0.0",
|
|
63
|
+
"@types/react": "^19.2.14",
|
|
64
|
+
"@types/react-dom": "^19.2.3",
|
|
65
|
+
"@vitest/browser-playwright": "^4.1.11",
|
|
66
|
+
"react": "^19.2.7",
|
|
67
|
+
"react-dom": "^19.2.7",
|
|
68
|
+
"tsdown": "^0.23.5",
|
|
69
|
+
"typescript": "^5.9.3",
|
|
70
|
+
"vite": "^8.0.3",
|
|
71
|
+
"vitest": "^4.1.8",
|
|
72
|
+
"vitest-browser-react": "^2.3.0"
|
|
73
|
+
},
|
|
74
|
+
"publishConfig": {
|
|
75
|
+
"access": "public"
|
|
76
|
+
},
|
|
77
|
+
"homepage": "https://github.com/0xbuffdev/throughline#readme",
|
|
78
|
+
"keywords": [
|
|
79
|
+
"state-traces",
|
|
80
|
+
"visualization",
|
|
81
|
+
"markdown",
|
|
82
|
+
"typescript",
|
|
83
|
+
"react",
|
|
84
|
+
"developer-tools",
|
|
85
|
+
"education"
|
|
86
|
+
],
|
|
87
|
+
"scripts": {
|
|
88
|
+
"build": "tsdown && node scripts/verify-dist.mjs",
|
|
89
|
+
"typecheck": "tsc --noEmit",
|
|
90
|
+
"test:run": "vitest run",
|
|
91
|
+
"test:browser:run": "vitest run --config vitest.browser.config.ts",
|
|
92
|
+
"audit:pack": "node scripts/audit-pack.mjs",
|
|
93
|
+
"audit:source": "node scripts/audit-source.mjs",
|
|
94
|
+
"audit:browser": "node scripts/audit-browser.mjs",
|
|
95
|
+
"audit:licenses": "node scripts/audit-licenses.mjs"
|
|
96
|
+
}
|
|
6
97
|
}
|