@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.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.
Files changed (217) hide show
  1. package/CLAUDE.md +87 -0
  2. package/README.md +84 -2
  3. package/dist/dist-manifest.json +218 -0
  4. package/dist/eyes/index.js +109 -0
  5. package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
  6. package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
  7. package/dist/fat/pkg-node/package.json +12 -0
  8. package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
  9. package/dist/frqtl/compat/boot-node.js +34 -0
  10. package/dist/frqtl/compat/engine.js +127 -0
  11. package/dist/frqtl/compat/package-root.js +24 -0
  12. package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
  13. package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
  14. package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
  15. package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
  16. package/dist/frqtl/compat/renderer/Filters.js +163 -0
  17. package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
  18. package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
  19. package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
  20. package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
  21. package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
  22. package/dist/frqtl/compat/renderer/display-slot.js +96 -0
  23. package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
  24. package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
  25. package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
  26. package/dist/frqtl/compat/renderer/providers/index.js +22 -0
  27. package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
  28. package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
  29. package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
  30. package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
  31. package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
  32. package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
  33. package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
  34. package/dist/frqtl/compat/vendor.js +196 -0
  35. package/dist/frqtl/node-entry.js +17 -0
  36. package/dist/frqtl/objects/FrqtlApp.js +2922 -0
  37. package/dist/frqtl/objects/ObjectFactory.js +297 -0
  38. package/dist/frqtl/objects/RecipeContext.js +322 -0
  39. package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
  40. package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
  41. package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
  42. package/dist/frqtl/objects/instruments/capture-host.js +453 -0
  43. package/dist/frqtl/objects/instruments/capture.js +711 -0
  44. package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
  45. package/dist/frqtl/objects/kinds-entry.js +14 -0
  46. package/dist/frqtl/objects/kinds.js +190 -0
  47. package/dist/frqtl/objects/objects/Box.js +273 -0
  48. package/dist/frqtl/objects/objects/Bullet.js +155 -0
  49. package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
  50. package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
  51. package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
  52. package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
  53. package/dist/frqtl/objects/objects/LightSource.js +354 -0
  54. package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
  55. package/dist/frqtl/objects/objects/Tube.js +471 -0
  56. package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
  57. package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
  58. package/dist/frqtl/objects/objects/controlAction.js +89 -0
  59. package/dist/frqtl/objects/objects/controlBind.js +278 -0
  60. package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
  61. package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
  62. package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
  63. package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
  64. package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
  65. package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
  66. package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
  67. package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
  68. package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
  69. package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
  70. package/dist/frqtl/objects/objects/tagger.js +228 -0
  71. package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
  72. package/dist/frqtl/objects/schema/index.js +256 -0
  73. package/dist/frqtl/objects/schema/scopes.js +457 -0
  74. package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
  75. package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
  76. package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
  77. package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
  78. package/dist/frqtl/objects/setup/_worldInit.js +162 -0
  79. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
  80. package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
  81. package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
  82. package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
  83. package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
  84. package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
  85. package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
  86. package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
  87. package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
  88. package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
  89. package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
  90. package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
  91. package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
  92. package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
  93. package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
  94. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
  95. package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
  96. package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
  97. package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
  98. package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
  99. package/dist/frqtl/objects/v1-replacements.js +30 -0
  100. package/dist/frqtl/package-core.js +23 -0
  101. package/dist/frqtl/parts/CameraControls.js +643 -0
  102. package/dist/frqtl/parts/CameraPath.js +575 -0
  103. package/dist/frqtl/parts/CollisionActions.js +35 -0
  104. package/dist/frqtl/parts/CollisionTypes.js +31 -0
  105. package/dist/frqtl/parts/DetectorWall.js +496 -0
  106. package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
  107. package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
  108. package/dist/frqtl/parts/FrqtlVision.js +818 -0
  109. package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
  110. package/dist/frqtl/parts/LargeObject.js +406 -0
  111. package/dist/frqtl/parts/LightColor.js +255 -0
  112. package/dist/frqtl/parts/LiveChart.js +285 -0
  113. package/dist/frqtl/parts/LogicGate.js +720 -0
  114. package/dist/frqtl/parts/OvenBox.js +612 -0
  115. package/dist/frqtl/parts/PointSource.js +763 -0
  116. package/dist/frqtl/parts/Rectangle.js +200 -0
  117. package/dist/frqtl/parts/SlitLightSource.js +234 -0
  118. package/dist/frqtl/parts/SlitScreen.js +628 -0
  119. package/dist/frqtl/parts/SphereEmitter.js +946 -0
  120. package/dist/frqtl/parts/Trace.js +508 -0
  121. package/dist/frqtl/parts/Tunnel.js +1057 -0
  122. package/dist/frqtl/parts/Wall.js +1211 -0
  123. package/dist/frqtl/parts/index.js +33 -0
  124. package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
  125. package/dist/frqtl/scenes/absorbing-wall.js +32 -0
  126. package/dist/frqtl/scenes/atomic-model.js +88 -0
  127. package/dist/frqtl/scenes/big-bang.js +46 -0
  128. package/dist/frqtl/scenes/blackbody.js +81 -0
  129. package/dist/frqtl/scenes/circuit-sat.js +127 -0
  130. package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
  131. package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
  132. package/dist/frqtl/scenes/counting-wall.js +38 -0
  133. package/dist/frqtl/scenes/double-slit.js +31 -0
  134. package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
  135. package/dist/frqtl/scenes/dse-one-slit.js +114 -0
  136. package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
  137. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
  138. package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
  139. package/dist/frqtl/scenes/dse-two-slits.js +132 -0
  140. package/dist/frqtl/scenes/empty.js +14 -0
  141. package/dist/frqtl/scenes/laser-buildup.js +49 -0
  142. package/dist/frqtl/scenes/mirror-3d.js +56 -0
  143. package/dist/frqtl/scenes/mirror-route.js +140 -0
  144. package/dist/frqtl/scenes/mirror.js +38 -0
  145. package/dist/frqtl/scenes/omni-3d.js +27 -0
  146. package/dist/frqtl/scenes/omni-source.js +22 -0
  147. package/dist/frqtl/scenes/self-interference.js +39 -0
  148. package/dist/frqtl/scenes/smoking-gun.js +122 -0
  149. package/dist/frqtl/scenes/straight-walk.js +22 -0
  150. package/dist/frqtl/scenes/sun.js +39 -0
  151. package/dist/frqtl/scenes/two-source.js +37 -0
  152. package/dist/frqtl/scenes/wave.js +26 -0
  153. package/dist/shim/bundle/frqnb.js +112 -0
  154. package/dist/shim/bundle/index.js +121 -0
  155. package/dist/shim/bundle/indexHtml.js +107 -0
  156. package/dist/shim/bundle/manifest.js +95 -0
  157. package/dist/shim/bundle/zip.js +243 -0
  158. package/dist/shim/bus.js +67 -0
  159. package/dist/shim/controls/index.js +627 -0
  160. package/dist/shim/controls/registry.js +274 -0
  161. package/dist/shim/engine/artifact-shell.js +212 -0
  162. package/dist/shim/engine/boot-phases.js +44 -0
  163. package/dist/shim/engine/engine-stack-boot.js +59 -0
  164. package/dist/shim/engine/router.js +692 -0
  165. package/dist/shim/experiment-frame/index.js +296 -0
  166. package/dist/shim/expr/ast-schema.js +369 -0
  167. package/dist/shim/field-descriptor/index.js +326 -0
  168. package/dist/shim/frame-data/index.js +224 -0
  169. package/dist/shim/index.js +67 -0
  170. package/dist/shim/notebook/canvas-guard.js +82 -0
  171. package/dist/shim/notebook/catalog.js +35 -0
  172. package/dist/shim/notebook/format.js +736 -0
  173. package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
  174. package/dist/shim/notebook/kernel.js +672 -0
  175. package/dist/shim/notebook/knob.js +151 -0
  176. package/dist/shim/notebook/module-resolver.js +260 -0
  177. package/dist/shim/notebook/notebook-knobs.js +65 -0
  178. package/dist/shim/notebook/produced-bindings.js +67 -0
  179. package/dist/shim/notebook/realm-registry.js +121 -0
  180. package/dist/shim/notebook/realm-transport.js +73 -0
  181. package/dist/shim/notebook/setup-builder.js +510 -0
  182. package/dist/shim/notebook/store.js +159 -0
  183. package/dist/shim/notebook/vendor-importmap.js +134 -0
  184. package/dist/shim/notebook/view-builder.js +189 -0
  185. package/dist/shim/notebook/view-ctx.js +100 -0
  186. package/dist/shim/notebook/view-handle.js +202 -0
  187. package/dist/shim/notebook/warmup.js +102 -0
  188. package/dist/shim/package.json +4 -0
  189. package/dist/shim/page-params/index.js +214 -0
  190. package/dist/shim/project/identity.js +112 -0
  191. package/dist/shim/project/index.js +19 -0
  192. package/dist/shim/project/link.js +402 -0
  193. package/dist/shim/project/node-specifier-hook.mjs +35 -0
  194. package/dist/shim/project/register-node.mjs +17 -0
  195. package/dist/shim/project/specifiers.js +106 -0
  196. package/dist/shim/registry/copy-gate.js +110 -0
  197. package/dist/shim/registry/exhibits.js +455 -0
  198. package/dist/shim/registry/experiments.js +215 -0
  199. package/dist/shim/registry/index.js +19 -0
  200. package/dist/shim/registry/scene-source.js +157 -0
  201. package/dist/shim/registry/walkthrough.js +807 -0
  202. package/dist/shim/render/capture-envelope.js +279 -0
  203. package/dist/shim/render/heat-colormap.js +92 -0
  204. package/dist/shim/render/render-stages.js +855 -0
  205. package/dist/shim/render/sun-look.js +33 -0
  206. package/dist/shim/render-state/apply.js +262 -0
  207. package/dist/shim/render-state/index.js +74 -0
  208. package/dist/shim/rpc/index.js +207 -0
  209. package/dist/shim/runtime/index.js +1023 -0
  210. package/dist/shim/storage/boot.js +69 -0
  211. package/dist/shim/storage/index.js +169 -0
  212. package/dist/shim/storage/local-storage-provider.js +230 -0
  213. package/dist/shim/storage/make-namespace-store.js +132 -0
  214. package/dist/shim/storage/modules.js +249 -0
  215. package/dist/shim/storage/projects.js +150 -0
  216. package/llms.txt +12 -0
  217. package/package.json +62 -4
@@ -0,0 +1,672 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * @typedef {'idle'|'running'|'ok'|'error'|'stale'} CellStatus
5
+ */
6
+
7
+ /**
8
+ * @typedef {object} CellState
9
+ * @property {CellStatus} status — current execution status.
10
+ * @property {Object<string,number>} lastRunInputVersions — binding versions at last run.
11
+ * @property {Object<string,*>} outputs — the last committed output values.
12
+ */
13
+
14
+ /**
15
+ * @typedef {object} BindingEntry
16
+ * @property {*} value — the binding value.
17
+ * @property {number} version — increments on each new commit from any producer.
18
+ * @property {string} producerCellId — id of the cell that last wrote this binding.
19
+ */
20
+
21
+ /**
22
+ * @typedef {object} RunAllResult
23
+ * @property {number} total — total runnable cells.
24
+ * @property {number} ran — cells that were attempted.
25
+ * @property {boolean} ok — `true` if all ran successfully; `false` on first error.
26
+ * @property {string|null} failedCellId — the first cell that failed, or `null`.
27
+ * @property {string|null} error — error message of the first failure, or `null`.
28
+ * @property {Array<{cellId:string, result:RunCellResult}>} results — per-cell results.
29
+ */
30
+
31
+ /**
32
+ * NotebookKernel — per-notebook execution controller (UNIFIED_NOTEBOOK_DESIGN §3).
33
+ *
34
+ * A PER-NOTEBOOK INSTANCE (NOT a module singleton). The pane owns the kernel's
35
+ * lifetime and calls `dispose()` on notebook unload.
36
+ *
37
+ * Responsibilities:
38
+ * - Typed binding store keyed by name (`{ value, version, producerCellId }`).
39
+ * - Per-cell execution state (`idle | running | ok | error | stale`).
40
+ * - Dependency graph (declared `in`/`out`) used ONLY for staleness + run-order
41
+ * hints — it NEVER auto-runs (marks `stale` only).
42
+ *
43
+ * Execution discipline:
44
+ * - `runCell()` calls `Runtime.runCell()` DIRECTLY. The kernel registers ZERO
45
+ * `Bus.on` execution listeners. The ONLY Bus traffic it produces is ONE outbound
46
+ * `notebook:cellstate { cellId, status }` event per state transition.
47
+ *
48
+ * @example
49
+ * import { parse } from 'lib/shim/notebook/format.js';
50
+ * import { NotebookKernel } from 'lib/shim/notebook/kernel.js';
51
+ *
52
+ * const nb = parse(htmlSource);
53
+ * const kernel = new NotebookKernel(nb);
54
+ *
55
+ * // Run a single cell (FAIL-LOUD if a declared input is not yet produced):
56
+ * const result = await kernel.runCell('c-stat-01');
57
+ *
58
+ * // Run all cells in topological order:
59
+ * const summary = await kernel.runAll();
60
+ *
61
+ * // Tear down when the notebook closes:
62
+ * kernel.dispose();
63
+ */
64
+
65
+ import { Bus as DefaultBus } from '../bus.js';
66
+ import { Runtime as DefaultRuntime } from '../runtime/index.js';
67
+
68
+ 'use strict';
69
+
70
+ var STATUS = {
71
+ IDLE: 'idle',
72
+ RUNNING: 'running',
73
+ OK: 'ok',
74
+ ERROR: 'error',
75
+ STALE: 'stale'
76
+ };
77
+
78
+ export class NotebookKernel {
79
+ /**
80
+ * @param {object} notebook — parsed notebook from `format.parse()`. Must have
81
+ * `cells: [{ id, type, lane, in, out, body }]`.
82
+ * @param {object} [opts]
83
+ * @param {object} [opts.runtime] — shim Runtime (injected for testability).
84
+ * @param {object} [opts.bus] — shim Bus (injected for testability).
85
+ */
86
+ constructor(notebook, opts) {
87
+ opts = opts || {};
88
+ if (!notebook || !Array.isArray(notebook.cells)) {
89
+ throw new Error('[NotebookKernel] requires a notebook object with a cells[] array.');
90
+ }
91
+ this._runtime = opts.runtime || DefaultRuntime;
92
+ this._bus = opts.bus || DefaultBus;
93
+ // The injected runtime's contract, asserted HERE — at the boundary, once.
94
+ // `resetMathScope` is what bounds the arena (see resetScope() below); a
95
+ // runtime without it would otherwise die with a bare TypeError from inside
96
+ // resetScope(), mid-run, AFTER the binding store was already cleared. This
97
+ // is an assertion, NOT a guard around the call: skipping the reset would
98
+ // run the notebook on an unbounded arena and never say so.
99
+ var missing = ['runCell', 'resetMathScope'].filter(function (m) {
100
+ return typeof this._runtime[m] !== 'function';
101
+ }, this);
102
+ if (missing.length) {
103
+ throw new Error(
104
+ '[NotebookKernel] the injected runtime does not satisfy the kernel\'s contract — ' +
105
+ 'missing: ' + missing.join(', ') + '. Pass lib/shim/runtime/index.js\'s `Runtime` ' +
106
+ '(Runtime.runCell, Runtime.resetMathScope), or a stub that implements both.'
107
+ );
108
+ }
109
+ this._disposed = false;
110
+
111
+ // notebook cells in document order; keep an id→cell index.
112
+ this._cells = notebook.cells.slice();
113
+ this._cellById = new Map();
114
+ for (var i = 0; i < this._cells.length; i++) {
115
+ var c = this._cells[i];
116
+ if (c.id) { this._cellById.set(c.id, c); }
117
+ }
118
+
119
+ // bindings: Map<name, { value, kind?, version, producerCellId }>
120
+ this.bindings = new Map();
121
+
122
+ // cellState: Map<cellId, { status, lastRunInputVersions, outputs }>
123
+ this.cellState = new Map();
124
+ for (var j = 0; j < this._cells.length; j++) {
125
+ var cell = this._cells[j];
126
+ if (!cell.id) { continue; }
127
+ this.cellState.set(cell.id, {
128
+ status: STATUS.IDLE,
129
+ lastRunInputVersions: {},
130
+ outputs: {}
131
+ });
132
+ }
133
+
134
+ // dependency graph: producerName → Set<consumerCellId>. Derived from
135
+ // declared in/out. Used ONLY for staleness propagation + run hints.
136
+ this._consumersOfName = new Map();
137
+ for (var k = 0; k < this._cells.length; k++) {
138
+ var cc = this._cells[k];
139
+ if (!cc.id || !Array.isArray(cc.in)) { continue; }
140
+ for (var n = 0; n < cc.in.length; n++) {
141
+ var name = cc.in[n];
142
+ if (!this._consumersOfName.has(name)) {
143
+ this._consumersOfName.set(name, new Set());
144
+ }
145
+ this._consumersOfName.get(name).add(cc.id);
146
+ }
147
+ }
148
+ }
149
+
150
+ /* ── scope ──────────────────────────────────────────────────────────────
151
+ * THE SCOPE RULE (FRAQTL issue #67): a math value's scope IS this kernel's
152
+ * binding scope. On the shipped WASM dist an EGPT value is an arena handle,
153
+ * so if the arena is drained while a binding still names one, that binding is
154
+ * a dead handle and the next cell dies with `arena:stale-handle`. The two
155
+ * facts therefore move TOGETHER, here, in ONE method — they cannot drift.
156
+ * A cell is not a run: `Runtime.runCell()` deliberately does not reset. */
157
+
158
+ /**
159
+ * Begin a fresh value scope: drop every binding AND end the math backend's
160
+ * value scope (arena drain + epoch bump for `wasm-math`, a no-op for
161
+ * `js-reference`).
162
+ *
163
+ * FAIL-LOUD is preserved, not softened: after this call a handle captured
164
+ * before it — in a closure, a chart, a host-held output — is genuinely stale
165
+ * and still throws `arena:stale-handle`. That is the point of bumping the
166
+ * epoch rather than silently reusing slots.
167
+ *
168
+ * Called at the START of `runAll()` (every binding is about to be re-produced
169
+ * by the run itself) and by `dispose()` (notebook unload). A single-cell run
170
+ * does NOT call it — its output has to outlive the cell.
171
+ *
172
+ * WHAT BOUNDS THE ARENA, and the residual this costs. N consecutive Run Alls
173
+ * cost ONE run's residency, not N — each begins a fresh scope (measured:
174
+ * identical `arenaStats().len` across runs 1/2/3 for every shipped notebook;
175
+ * worst case `egpt-test-suite`, 194,370 slots), and unload drains it to 0.
176
+ * BUT repeated MANUAL single-cell runs ACCUMULATE until the next `runAll()` or
177
+ * `dispose()`: the arena has no reclaim finer than the scope, so every
178
+ * INTERMEDIATE of the re-run cell stays resident too, not just its output
179
+ * (measured on a 1000-add cell: 1,002 slots per re-run — 999 dead accumulators
180
+ * and the one live result; 20 re-runs → 21,042, then back to 1,002 at the next
181
+ * Run All). This is NOT a REPL's heap property — a Jupyter kernel retains
182
+ * bindings and collects unreferenced intermediates. Fixing it needs a two-level
183
+ * scope in the arena (a per-cell frame whose intermediates die at cell end, with
184
+ * the cell's declared outputs PROMOTED into the notebook scope) — a retain
185
+ * contract that belongs in `arena.rs` + the facade, never faked on the JS side.
186
+ *
187
+ * @returns {void}
188
+ */
189
+ resetScope() {
190
+ this.bindings.clear();
191
+ this._runtime.resetMathScope();
192
+ }
193
+
194
+ /* ── module set (B2) ─────────────────────────────────────────────────────
195
+ * { name → source } over the notebook's `type="module"` cells (the user-code
196
+ * module SET the cell-as-module resolver builds its in-memory blob closure
197
+ * from when a `js` builder cell imports a notebook-local module). The source is
198
+ * read LIVE from cell.body each call so a just-edited module cell is current
199
+ * (never a cached, stale copy). The pane syncs an open module-cell editor's
200
+ * value back into cell.body before a run (mirroring the js-cell sync), so this
201
+ * reflects author edits. */
202
+
203
+ /**
204
+ * Returns the notebook's in-memory user-code module set.
205
+ *
206
+ * Keys are the `name` attribute values of `type="module"` cells
207
+ * (e.g. `'./setups/wave.js'`); values are the current cell body source.
208
+ * Read LIVE so a just-edited module cell is always current.
209
+ *
210
+ * @returns {Object<string,string>} `{ name → source }` map.
211
+ */
212
+ moduleSet() {
213
+ var set = {};
214
+ for (var i = 0; i < this._cells.length; i++) {
215
+ var c = this._cells[i];
216
+ if (c.type === 'module' && c.name) {
217
+ set[c.name] = (c.body == null) ? '' : String(c.body);
218
+ }
219
+ }
220
+ return set;
221
+ }
222
+
223
+ /* ── state emit ─────────────────────────────────────────────────────── */
224
+
225
+ // Set a cell's status and emit the single outbound badge event. NEVER
226
+ // subscribes back. NEVER triggers a run.
227
+ _setStatus(cellId, status) {
228
+ if (this._disposed) { return; }
229
+ var st = this.cellState.get(cellId);
230
+ if (!st) { return; }
231
+ st.status = status;
232
+ this._bus.emit('notebook:cellstate', { cellId: cellId, status: status });
233
+ }
234
+
235
+ /* ── run one cell ───────────────────────────────────────────────────── */
236
+
237
+ /**
238
+ * Run a single cell by id.
239
+ *
240
+ * 1. Resolve declared inputs from the binding store. A missing input →
241
+ * FAIL-LOUD reject naming the producing cell (upstream is NOT auto-run).
242
+ * 2. Call `Runtime.runCell()` DIRECTLY (a Promise — no Bus round-trip).
243
+ * 3. On `ok`: commit each declared output into bindings (`++version`); mark
244
+ * every consumer of a just-updated name `stale` (NOT executed).
245
+ * 4. Emit `notebook:cellstate { cellId, status }` once per state transition.
246
+ *
247
+ * @param {string} cellId
248
+ * @param {object} [opts]
249
+ * @param {Function} [opts.display]
250
+ * Structured-output sink. The pane passes a DOM-aware display so the cell's
251
+ * output renders in its own region rather than the shared console pane.
252
+ * @returns {Promise<RunCellResult>}
253
+ *
254
+ * @example
255
+ * // Run cell 'c-build-01', which produces a live runner handle:
256
+ * const { ok, outputs } = await kernel.runCell('c-build-01');
257
+ * if (ok) console.log(outputs.dsRun); // the live FrqtlApp
258
+ */
259
+ runCell(cellId, opts) {
260
+ opts = opts || {};
261
+ if (this._disposed) {
262
+ return Promise.reject(new Error('[NotebookKernel] disposed; cannot run cell "' + cellId + '".'));
263
+ }
264
+ var cell = this._cellById.get(cellId);
265
+ if (!cell) {
266
+ return Promise.reject(new Error('[NotebookKernel] no cell with id "' + cellId + '".'));
267
+ }
268
+ var self = this;
269
+
270
+ // 1. Resolve inputs — FAIL-LOUD on a missing binding, naming the producer.
271
+ var inputs = {};
272
+ var declaredIn = Array.isArray(cell.in) ? cell.in : [];
273
+ var lastRunInputVersions = {};
274
+ for (var i = 0; i < declaredIn.length; i++) {
275
+ var name = declaredIn[i];
276
+ if (!self.bindings.has(name)) {
277
+ var producer = self._findProducerCellId(name);
278
+ var hint = producer
279
+ ? 'run cell ' + producer + ' first.'
280
+ : 'no cell in this notebook declares it as an output.';
281
+ var err = new Error(
282
+ '[NotebookKernel] cell "' + cellId + '" declares input "' + name +
283
+ '", not yet produced — ' + hint + ' (FAIL-LOUD; upstream is NOT auto-run.)'
284
+ );
285
+ self._setStatus(cellId, STATUS.ERROR);
286
+ var st0 = self.cellState.get(cellId);
287
+ if (st0) { st0.outputs = {}; }
288
+ return Promise.reject(err);
289
+ }
290
+ var binding = self.bindings.get(name);
291
+ inputs[name] = binding.value;
292
+ lastRunInputVersions[name] = binding.version;
293
+ }
294
+
295
+ // 2. Execute via Runtime.runCell DIRECTLY (no Bus round-trip).
296
+ self._setStatus(cellId, STATUS.RUNNING);
297
+ return self._runtime.runCell({
298
+ lane: cell.lane || 'math',
299
+ code: cell.body || '',
300
+ id: cellId,
301
+ inputs: inputs,
302
+ // B2: the notebook's user-code module set so a builder cell's
303
+ // `import './setups/x.js'` resolves to the in-memory blob closure.
304
+ modules: self.moduleSet(),
305
+ declaredOutputs: Array.isArray(cell.out) ? cell.out : [],
306
+ // Optional per-cell structured-output sink the pane passes (a DOM-aware
307
+ // display that appends nodes/lines to the cell's output region). The
308
+ // kernel stays display-agnostic — it only forwards the caller's fn.
309
+ display: opts.display || null
310
+ }).then(function (result) {
311
+ if (self._disposed) { return result; }
312
+ var st = self.cellState.get(cellId);
313
+ if (result && result.ok) {
314
+ // 3. Commit outputs (++version), mark consumers stale.
315
+ var declaredOut = Array.isArray(cell.out) ? cell.out : [];
316
+ for (var o = 0; o < declaredOut.length; o++) {
317
+ var outName = declaredOut[o];
318
+ var prev = self.bindings.get(outName);
319
+ var nextVersion = prev ? (prev.version + 1) : 1;
320
+ self.bindings.set(outName, {
321
+ value: result.outputs[outName],
322
+ version: nextVersion,
323
+ producerCellId: cellId
324
+ });
325
+ self._markConsumersStale(outName, cellId);
326
+ }
327
+ if (st) {
328
+ st.outputs = result.outputs || {};
329
+ st.lastRunInputVersions = lastRunInputVersions;
330
+ }
331
+ self._setStatus(cellId, STATUS.OK);
332
+ } else {
333
+ if (st) { st.outputs = {}; }
334
+ self._setStatus(cellId, STATUS.ERROR);
335
+ }
336
+ return result;
337
+ });
338
+ }
339
+
340
+ /**
341
+ * Commit outputs for a view cell (canvas / chart) that does NOT execute through
342
+ * `Runtime.runCell`. The pane calls this when the live runner handle is available
343
+ * (e.g. after a canvas realm boots) so downstream `js` cells can consume it.
344
+ *
345
+ * Mirrors steps 3–4 of `runCell()` (commit `++version`, mark consumers `stale`,
346
+ * set the producing cell `ok`) — one binding-commit authority (DRY).
347
+ * Adds ZERO Bus listeners; never calls Runtime.
348
+ *
349
+ * @param {string} cellId
350
+ * @param {Object<string,*>} outputsObj — `{ name: value, … }` the view cell produces.
351
+ * @returns {void}
352
+ */
353
+ commitViewOutputs(cellId, outputsObj) {
354
+ if (this._disposed) { return; }
355
+ var st = this.cellState.get(cellId);
356
+ var bag = (outputsObj && typeof outputsObj === 'object') ? outputsObj : {};
357
+ var keys = Object.keys(bag);
358
+ for (var i = 0; i < keys.length; i++) {
359
+ var name = keys[i];
360
+ var prev = this.bindings.get(name);
361
+ var nextVersion = prev ? (prev.version + 1) : 1;
362
+ this.bindings.set(name, {
363
+ value: bag[name],
364
+ version: nextVersion,
365
+ producerCellId: cellId
366
+ });
367
+ this._markConsumersStale(name, cellId);
368
+ }
369
+ if (st) { st.outputs = bag; }
370
+ this._setStatus(cellId, STATUS.OK);
371
+ }
372
+
373
+ /**
374
+ * Returns the runnable cells (`js`/`canvas`/`chart`/`view`, not `markdown`)
375
+ * in the order `runAll()` should execute them.
376
+ *
377
+ * A producer of a name runs BEFORE any consumer. Ties and un-declared cells
378
+ * keep document order. A dependency cycle → FAIL-LOUD throw (no safe order).
379
+ * NEVER auto-runs; NEVER mutates bindings.
380
+ *
381
+ * FRQTL-MAINT(runall-excludes-chart-cells) — FIXED (ws-artifact-showcase Bite
382
+ * A2, FRAQTL issue #61). The RUNNABLE map used to be `{js, canvas}` only —
383
+ * `runAll()` reported `total:N ran:N` while silently never running `chart`/
384
+ * `view` cells (tester finding 2026-07-03, `double-slit-rosetta-qft.html`),
385
+ * even though a manual per-cell Run on the same cell works (it routes through
386
+ * the pane's realm-cell runner, `opts.runner`, not `runCell`). `chart`/`view`
387
+ * cells DO declare `in`/`out` bindings today (`_resolveChartRecipe` FAIL-LOUD
388
+ * -throws on an unproduced `in`; `commitViewOutputs` commits a view cell's
389
+ * bound outputs) — so including them here is not just "run one more cell",
390
+ * it lets the SAME topological sort correctly order a chart/view cell AFTER
391
+ * whichever cell produces the binding it reads, exactly like a `js` consumer.
392
+ * The pane's `opts.runner` (lib/components/notebook-pane/index.js `runAll()`)
393
+ * already special-cased `chart` alongside `canvas` — it was only ever missing
394
+ * `view`, fixed in the same bite.
395
+ *
396
+ * @returns {Array<object>} ordered cell objects (each has `{ id, type, lane, body, in, out }`).
397
+ * @throws {Error} if a cycle is detected in the declared `in`/`out` graph.
398
+ */
399
+ runOrder() {
400
+ var RUNNABLE = { js: true, canvas: true, chart: true, view: true };
401
+ // The runnable cells in document order; markdown (and unknown types that
402
+ // never reach Runtime.runCell) are excluded — they are not "run".
403
+ var ordered = [];
404
+ for (var i = 0; i < this._cells.length; i++) {
405
+ var c = this._cells[i];
406
+ if (c.id && RUNNABLE[c.type]) { ordered.push(c); }
407
+ }
408
+
409
+ // Build edges producer→consumer over the runnable set, keyed by cell id.
410
+ // docIndex gives a stable document-order tiebreak in the ready queue.
411
+ var docIndex = new Map();
412
+ var indegree = new Map();
413
+ var edgesFrom = new Map(); // producerId → Set<consumerId>
414
+ for (var d = 0; d < ordered.length; d++) {
415
+ docIndex.set(ordered[d].id, d);
416
+ indegree.set(ordered[d].id, 0);
417
+ edgesFrom.set(ordered[d].id, new Set());
418
+ }
419
+ for (var e = 0; e < ordered.length; e++) {
420
+ var consumer = ordered[e];
421
+ var declaredIn = Array.isArray(consumer.in) ? consumer.in : [];
422
+ for (var n = 0; n < declaredIn.length; n++) {
423
+ var producerId = this._findProducerCellId(declaredIn[n]);
424
+ // Only count an edge when the producer is itself a runnable cell in
425
+ // THIS set and is not the consumer itself (a cell that declares the
426
+ // same name in+out is a self-edge — ignore it, not a real dep).
427
+ if (producerId && producerId !== consumer.id && indegree.has(producerId)) {
428
+ var fanout = edgesFrom.get(producerId);
429
+ if (!fanout.has(consumer.id)) {
430
+ fanout.add(consumer.id);
431
+ indegree.set(consumer.id, indegree.get(consumer.id) + 1);
432
+ }
433
+ }
434
+ }
435
+ }
436
+
437
+ // Kahn's algorithm, ready queue kept in document order (smallest docIndex
438
+ // first) so the output is stable and == document order with no deps.
439
+ var ready = [];
440
+ for (var r = 0; r < ordered.length; r++) {
441
+ if (indegree.get(ordered[r].id) === 0) { ready.push(ordered[r].id); }
442
+ }
443
+ var result = [];
444
+ var byId = this._cellById;
445
+ while (ready.length > 0) {
446
+ ready.sort(function (a, b) { return docIndex.get(a) - docIndex.get(b); });
447
+ var id = ready.shift();
448
+ result.push(byId.get(id));
449
+ edgesFrom.get(id).forEach(function (consumerId) {
450
+ indegree.set(consumerId, indegree.get(consumerId) - 1);
451
+ if (indegree.get(consumerId) === 0) { ready.push(consumerId); }
452
+ });
453
+ }
454
+
455
+ if (result.length !== ordered.length) {
456
+ throw new Error(
457
+ '[NotebookKernel] runAll cannot order cells: a dependency CYCLE exists ' +
458
+ 'among declared in/out bindings. (FAIL-LOUD — no safe linear run order.)'
459
+ );
460
+ }
461
+ return result;
462
+ }
463
+
464
+ /**
465
+ * Run all runnable cells in topological order (sequential — one at a time).
466
+ *
467
+ * A pure SEQUENCER over `runCell()`. Registers ZERO Bus listeners, adds ZERO
468
+ * executors. Emits one `notebook:runall { index, total, cellId, phase }` per step.
469
+ *
470
+ * FAIL-LOUD stop-on-error: the FIRST cell that fails STOPS the run;
471
+ * `runAll` resolves (does not throw) with `{ ok: false, failedCellId }`.
472
+ *
473
+ * @param {object} [opts]
474
+ * @param {Function} [opts.displayFor]
475
+ * `(cellId: string) → displayFn`. The pane supplies this so each cell's
476
+ * structured output routes to its own region.
477
+ * @param {Function} [opts.onProgress]
478
+ * `({ index, total, cellId, phase }) → void`. Progress callback in addition
479
+ * to the Bus event.
480
+ * @param {Function} [opts.runner]
481
+ * `(cell) → Promise<{ ok, error }> | null`. Pane-supplied runner for cell
482
+ * types the kernel cannot execute via `Runtime.runCell` (e.g. `canvas` cells,
483
+ * which the pane mounts as a live GraphicsPane). When it returns a thenable,
484
+ * that result is used INSTEAD of `runCell` for that cell.
485
+ * @returns {Promise<RunAllResult>}
486
+ *
487
+ * @example
488
+ * const summary = await kernel.runAll({
489
+ * displayFor: (id) => pane.displayFor(id),
490
+ * onProgress: ({ index, total, cellId }) =>
491
+ * console.log(`Running ${index}/${total}: ${cellId}`)
492
+ * });
493
+ * if (!summary.ok) {
494
+ * console.error('Failed at cell:', summary.failedCellId, summary.error);
495
+ * }
496
+ */
497
+ runAll(opts) {
498
+ opts = opts || {};
499
+ if (this._disposed) {
500
+ return Promise.reject(new Error('[NotebookKernel] disposed; cannot runAll.'));
501
+ }
502
+ var self = this;
503
+ var order;
504
+ try {
505
+ order = self.runOrder();
506
+ } catch (orderErr) {
507
+ return Promise.reject(orderErr);
508
+ }
509
+ // A fresh value scope for the run (#67). Safe by construction: runAll
510
+ // re-produces every binding a runnable cell declares, in an order that
511
+ // puts each producer before its consumers — so nothing in the cleared
512
+ // store is read before it is rewritten. A leftover from an earlier,
513
+ // partial run is exactly what must NOT silently satisfy an input.
514
+ self.resetScope();
515
+ var total = order.length;
516
+ var displayFor = (typeof opts.displayFor === 'function') ? opts.displayFor : null;
517
+ var onProgress = (typeof opts.onProgress === 'function') ? opts.onProgress : null;
518
+ var runner = (typeof opts.runner === 'function') ? opts.runner : null;
519
+ var results = [];
520
+
521
+ function emitProgress(index, cellId, phase) {
522
+ var payload = { index: index, total: total, cellId: cellId, phase: phase };
523
+ if (!self._disposed) { self._bus.emit('notebook:runall', payload); }
524
+ if (onProgress) { onProgress(payload); }
525
+ }
526
+
527
+ // Sequential reduce: await each runCell before the next (chain of .then).
528
+ var chain = Promise.resolve();
529
+ var stopped = { ok: true, failedCellId: null, error: null, ran: 0 };
530
+
531
+ order.forEach(function (cell, idx) {
532
+ chain = chain.then(function () {
533
+ if (self._disposed || !stopped.ok) { return; }
534
+ emitProgress(idx + 1, cell.id, 'running');
535
+ // Pane-supplied runner for cell types the kernel cannot execute via
536
+ // the Runtime runCell path (canvas). If it returns a thenable we use
537
+ // it; otherwise fall through to runCell. NO executor added either way.
538
+ var paneRun = runner ? runner(cell) : null;
539
+ if (paneRun && typeof paneRun.then === 'function') {
540
+ return paneRun.then(function (result) {
541
+ results.push({ cellId: cell.id, result: result });
542
+ stopped.ran += 1;
543
+ if (!result || !result.ok) {
544
+ stopped.ok = false;
545
+ stopped.failedCellId = cell.id;
546
+ stopped.error = (result && result.error)
547
+ ? result.error
548
+ : 'cell "' + cell.id + '" did not complete ok.';
549
+ emitProgress(idx + 1, cell.id, 'error');
550
+ } else {
551
+ emitProgress(idx + 1, cell.id, 'ok');
552
+ }
553
+ }, function (err) {
554
+ results.push({ cellId: cell.id, result: { ok: false, error: (err && err.message) ? err.message : String(err) } });
555
+ stopped.ran += 1;
556
+ stopped.ok = false;
557
+ stopped.failedCellId = cell.id;
558
+ stopped.error = (err && err.message) ? err.message : String(err);
559
+ emitProgress(idx + 1, cell.id, 'error');
560
+ });
561
+ }
562
+ var runOpts = displayFor ? { display: displayFor(cell.id) } : {};
563
+ return self.runCell(cell.id, runOpts).then(function (result) {
564
+ results.push({ cellId: cell.id, result: result });
565
+ stopped.ran += 1;
566
+ if (!result || !result.ok) {
567
+ // runCell resolved ok:false (e.g. a missing declared OUTPUT)
568
+ // — STOP here, FAIL-LOUD naming the cell.
569
+ stopped.ok = false;
570
+ stopped.failedCellId = cell.id;
571
+ stopped.error = (result && result.error)
572
+ ? result.error
573
+ : 'cell "' + cell.id + '" did not complete ok.';
574
+ emitProgress(idx + 1, cell.id, 'error');
575
+ } else {
576
+ emitProgress(idx + 1, cell.id, 'ok');
577
+ }
578
+ }, function (err) {
579
+ // runCell REJECTED (e.g. a missing declared INPUT, or a thrown
580
+ // body error surfaced as a reject) — STOP here, FAIL-LOUD.
581
+ results.push({ cellId: cell.id, result: { ok: false, error: (err && err.message) ? err.message : String(err) } });
582
+ stopped.ran += 1;
583
+ stopped.ok = false;
584
+ stopped.failedCellId = cell.id;
585
+ stopped.error = (err && err.message) ? err.message : String(err);
586
+ emitProgress(idx + 1, cell.id, 'error');
587
+ });
588
+ });
589
+ });
590
+
591
+ return chain.then(function () {
592
+ return {
593
+ total: total,
594
+ ran: stopped.ran,
595
+ ok: stopped.ok,
596
+ failedCellId: stopped.failedCellId,
597
+ error: stopped.error,
598
+ results: results
599
+ };
600
+ });
601
+ }
602
+
603
+ /* ── staleness (marks only — NEVER auto-runs) ───────────────────────── */
604
+
605
+ // Mark every consumer of `name` stale, except the cell that just produced it.
606
+ _markConsumersStale(name, producerCellId) {
607
+ var consumers = this._consumersOfName.get(name);
608
+ if (!consumers) { return; }
609
+ var self = this;
610
+ consumers.forEach(function (consumerId) {
611
+ if (consumerId === producerCellId) { return; }
612
+ var st = self.cellState.get(consumerId);
613
+ if (!st) { return; }
614
+ // Only a cell that has already run (ok/error) becomes stale; an idle
615
+ // cell stays idle (nothing to invalidate yet).
616
+ if (st.status === STATUS.OK || st.status === STATUS.ERROR) {
617
+ self._setStatus(consumerId, STATUS.STALE);
618
+ }
619
+ });
620
+ }
621
+
622
+ // The first cell (document order) that declares `name` as an output.
623
+ _findProducerCellId(name) {
624
+ for (var i = 0; i < this._cells.length; i++) {
625
+ var c = this._cells[i];
626
+ if (c.id && Array.isArray(c.out) && c.out.indexOf(name) !== -1) {
627
+ return c.id;
628
+ }
629
+ }
630
+ return null;
631
+ }
632
+
633
+ /* ── read accessors ─────────────────────────────────────────────────── */
634
+
635
+ /**
636
+ * @param {string} cellId
637
+ * @returns {CellState|null} current cell state, or `null` if not found.
638
+ */
639
+ getCellState(cellId) { return this.cellState.get(cellId) || null; }
640
+
641
+ /**
642
+ * @param {string} name — binding name.
643
+ * @returns {BindingEntry|null} the binding entry, or `null` if not yet produced.
644
+ */
645
+ getBinding(name) { return this.bindings.get(name) || null; }
646
+
647
+ /* ── dispose ────────────────────────────────────────────────────────── */
648
+
649
+ /**
650
+ * Tear down the kernel. The pane calls this on notebook unload.
651
+ *
652
+ * The kernel holds NO Bus listeners to remove (veto #1). `dispose()` marks
653
+ * the instance dead (in-flight `runCell` resolutions become no-ops) and clears
654
+ * all internal maps. Canvas/runner teardown is the pane's responsibility.
655
+ *
656
+ * It also ends the math value scope (`resetScope()`): the bindings that kept
657
+ * those arena values alive are going away with the notebook, so this is what
658
+ * bounds the arena across notebook loads.
659
+ *
660
+ * @returns {void}
661
+ */
662
+ dispose() {
663
+ this._disposed = true;
664
+ this.resetScope();
665
+ this.cellState.clear();
666
+ this._cellById.clear();
667
+ this._consumersOfName.clear();
668
+ this._cells = [];
669
+ }
670
+ }
671
+
672
+ export { STATUS as NOTEBOOK_CELL_STATUS };