@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,159 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * lib/shim/notebook/store.js — NotebookDocumentStore.
5
+ *
6
+ * Thin CRUD helpers that persist a serialized <frqtl-notebook> document on top
7
+ * of any StorageProvider (lib/shim/storage/index.js) using the "notebooks"
8
+ * namespace. Mirrors the shape of lib/shim/storage/modules.js and
9
+ * lib/shim/storage/projects.js — ONE persistent-store pattern across Studio
10
+ * projects, Studio modules, and Notebooks.
11
+ *
12
+ * ── Why this lives in lib/shim ──────────────────────────────────────────────
13
+ *
14
+ * The serialized text is owned by format.serialize (lib/shim/notebook/format.js)
15
+ * and the storage seam is owned by StorageProvider (lib/shim/storage/index.js).
16
+ * Both are host-neutral shim contracts. The notebook-pane (lib/components)
17
+ * consumes this module and does not re-implement the store helpers. Arrow is legal: lib/shim → lib/shim.
18
+ *
19
+ * ── Notebook envelope ────────────────────────────────────────────────────────
20
+ *
21
+ * {
22
+ * id: string — unique identifier (newNotebookId())
23
+ * title: string — human-readable title (mirrors notebook.title)
24
+ * html: string — the serialized <frqtl-notebook>…</frqtl-notebook>
25
+ * text produced by format.serialize(). The value
26
+ * the pane writes is the CANONICAL writer's output —
27
+ * never hand-assembled markup.
28
+ * createdAt: string — ISO-8601 creation timestamp
29
+ * updatedAt: string — ISO-8601 last-updated timestamp
30
+ * schemaVersion: number — integer version; currently 1
31
+ * }
32
+ *
33
+ * ── Namespace ──────────────────────────────────────────────────────────────
34
+ *
35
+ * All helpers use the "notebooks" namespace. Keys are notebook ids.
36
+ *
37
+ * ── The ONE persistence contract (§2.4 of the spec) ────────────────────────
38
+ *
39
+ * The web IDE drives:
40
+ * provider.put('notebooks', id, { html: format.serialize(nb), … })
41
+ *
42
+ * ── Usage ──────────────────────────────────────────────────────────────────
43
+ *
44
+ * import { newNotebookId, saveNotebook, loadNotebook,
45
+ * listNotebooks, deleteNotebook } from 'lib/shim/notebook/store.js';
46
+ * import { getProvider } from 'lib/shim/storage';
47
+ *
48
+ * const provider = getProvider();
49
+ *
50
+ * const id = newNotebookId();
51
+ * const html = serialize(notebook); // format.serialize — sole writer
52
+ * saveNotebook(provider, { id, title: 'My notebook', html,
53
+ * createdAt: now, updatedAt: now, schemaVersion: 1 });
54
+ *
55
+ * const record = loadNotebook(provider, id); // { id, title, html, … } | null
56
+ * const all = listNotebooks(provider); // [{ id, title, updatedAt }]
57
+ * deleteNotebook(provider, id);
58
+ */
59
+
60
+ 'use strict';
61
+
62
+ import { makeNamespaceStore } from '../storage/make-namespace-store.js';
63
+
64
+ /** @type {string} The storage namespace for all notebook records. */
65
+ export var NOTEBOOKS_NAMESPACE = 'notebooks';
66
+
67
+ /** @type {number} Current schema version. Increment on breaking envelope changes. */
68
+ export var NOTEBOOK_SCHEMA_VERSION = 1;
69
+
70
+ // ── Envelope validation ──────────────────────────────────────────────────────
71
+
72
+ function _validateRecord(record) {
73
+ if (!record || typeof record !== 'object') {
74
+ throw new Error('[notebook/store] record must be a non-null object');
75
+ }
76
+ if (typeof record.id !== 'string' || record.id.length === 0) {
77
+ throw new Error('[notebook/store] record.id must be a non-empty string');
78
+ }
79
+ if (typeof record.title !== 'string') {
80
+ throw new Error('[notebook/store] record.title must be a string');
81
+ }
82
+ if (typeof record.html !== 'string' || record.html.length === 0) {
83
+ throw new Error('[notebook/store] record.html must be a non-empty string; use format.serialize() to produce it');
84
+ }
85
+ if (typeof record.schemaVersion !== 'number') {
86
+ throw new Error('[notebook/store] record.schemaVersion must be a number');
87
+ }
88
+ }
89
+
90
+ // ── Summary projection ────────────────────────────────────────────────────────
91
+
92
+ function _summaryFields(record) {
93
+ return {
94
+ id: record.id,
95
+ title: record.title || '',
96
+ updatedAt: record.updatedAt || '',
97
+ };
98
+ }
99
+
100
+ // ── Store instance ────────────────────────────────────────────────────────────
101
+
102
+ var _store = makeNamespaceStore({
103
+ namespace: NOTEBOOKS_NAMESPACE,
104
+ schemaVersion: NOTEBOOK_SCHEMA_VERSION,
105
+ idPrefix: 'nb_',
106
+ validate: _validateRecord,
107
+ summaryFields: _summaryFields,
108
+ });
109
+
110
+ // ── Public API (names and signatures unchanged) ───────────────────────────────
111
+
112
+ /**
113
+ * Generate a new unique notebook id.
114
+ * @returns {string} e.g. "nb_lxyz0abc1234"
115
+ */
116
+ export var newNotebookId = _store.newId;
117
+
118
+ /**
119
+ * Save (create or update) a notebook record.
120
+ *
121
+ * The `html` field MUST be the output of `format.serialize()` — the single
122
+ * canonical writer. This function does NOT call serialize itself; callers are
123
+ * responsible for producing the text first.
124
+ *
125
+ * `updatedAt` is NOT automatically set — callers must set it before passing.
126
+ *
127
+ * @param {import('../storage/index.js').StorageProvider} provider
128
+ * @param {object} record
129
+ * @throws {Error} on validation failure or provider write failure.
130
+ */
131
+ export var saveNotebook = _store.save;
132
+
133
+ /**
134
+ * Load a notebook record by id.
135
+ * @param {import('../storage/index.js').StorageProvider} provider
136
+ * @param {string} id
137
+ * @returns {object|null} The Notebook envelope, or null if not found.
138
+ */
139
+ export var loadNotebook = _store.load;
140
+
141
+ /**
142
+ * List all notebooks, returning lightweight summary objects.
143
+ *
144
+ * Returns an array of { id, title, updatedAt } objects suitable for a notebook
145
+ * picker. Records with a missing or malformed id are silently skipped.
146
+ * Sorted by updatedAt descending.
147
+ *
148
+ * @param {import('../storage/index.js').StorageProvider} provider
149
+ * @returns {Array<{id: string, title: string, updatedAt: string}>}
150
+ */
151
+ export var listNotebooks = _store.list;
152
+
153
+ /**
154
+ * Delete a notebook record by id.
155
+ * No-op if the record does not exist.
156
+ * @param {import('../storage/index.js').StorageProvider} provider
157
+ * @param {string} id
158
+ */
159
+ export var deleteNotebook = _store['delete'];
@@ -0,0 +1,134 @@
1
+ // Copyright 2023-2026 by Essam Abadir
2
+ // lib/shim/notebook/vendor-importmap.js — the ONE pure import-map generator.
3
+ //
4
+ // WS-NOTEBOOK-VENDOR Phase 2a (§2a.3). Maps the committed vendor manifest
5
+ // (lib/frqtl/assets/vendor/manifest.json) → a browser `<script type="importmap">`
6
+ // payload `{ imports: { <spec>: <url> } }`, so a realm cell's builder/view module
7
+ // can `import * as d3 from 'd3'` and the realm's native module loader resolves the
8
+ // bare specifier to a PLACED /vendor artifact.
9
+ //
10
+ // PURE: no DOM, no Runtime, no engine import. Node-testable.
11
+ //
12
+ // PATH AUTHORITY (the canonical-stack DRY rule): `vendorBase` is supplied by the
13
+ // caller from `router.vendorBase` (the single path authority — DEV_CORE_PROFILE /
14
+ // DIST_PROFILE `__vendorBase`). This module NEVER hardcodes a `/vendor` literal; it
15
+ // only joins the caller-provided base with each manifest entry.
16
+ //
17
+ // FAIL-LOUD (the slit-objects-hardcoded-list bug class): a malformed manifest (no
18
+ // `.libs`, or a lib without `.entry`) THROWS — never silently drops a lib.
19
+
20
+ import { buildFrqtlSetupsImportMap } from './frqtl-setups-importmap.js';
21
+ import { importMapFor } from '../project/specifiers.js';
22
+
23
+ /**
24
+ * @typedef {object} VendorManifest
25
+ * @property {number} [version]
26
+ * @property {Object<string, { entry: string }>} libs
27
+ * bare-specifier → { entry: path RELATIVE TO THE VENDOR ROOT }.
28
+ */
29
+
30
+ /**
31
+ * Build a browser import-map from the vendor manifest.
32
+ *
33
+ * @param {VendorManifest} manifest parsed manifest.json (`{ libs: { spec: { entry } } }`).
34
+ * @param {string} vendorBase router.vendorBase, e.g. `'/vendor'` (no trailing slash needed).
35
+ * @returns {{ imports: Object<string, string> }}
36
+ * `{ imports: { <spec>: <vendorBase>/<entry> } }`.
37
+ * @throws {Error} FAIL-LOUD on a malformed manifest or a missing vendorBase.
38
+ *
39
+ * @example
40
+ * buildImportMap({ libs: { d3: { entry: 'd3/dist/d3.min.js' } } }, '/vendor')
41
+ * // → { imports: { d3: '/vendor/d3/dist/d3.min.js' } }
42
+ */
43
+ export function buildImportMap(manifest, vendorBase) {
44
+ if (!manifest || typeof manifest !== 'object' || !manifest.libs || typeof manifest.libs !== 'object') {
45
+ throw new Error('[vendor-importmap] malformed manifest: missing `.libs` object (FAIL-LOUD).');
46
+ }
47
+ if (typeof vendorBase !== 'string' || vendorBase.length === 0) {
48
+ throw new Error('[vendor-importmap] vendorBase is required (read it from router.vendorBase) (FAIL-LOUD).');
49
+ }
50
+ // Normalise the base: strip a single trailing slash so the join is unambiguous.
51
+ var base = vendorBase.replace(/\/+$/, '');
52
+ var imports = {};
53
+ var specs = Object.keys(manifest.libs);
54
+ for (var i = 0; i < specs.length; i++) {
55
+ var spec = specs[i];
56
+ var rec = manifest.libs[spec];
57
+ if (!rec || typeof rec.entry !== 'string' || rec.entry.length === 0) {
58
+ throw new Error('[vendor-importmap] manifest lib "' + spec + '" has no `.entry` (FAIL-LOUD — a lib is never silently dropped).');
59
+ }
60
+ // entry is relative to the vendor root; strip any leading slash so the join is clean.
61
+ var entry = rec.entry.replace(/^\/+/, '');
62
+ imports[spec] = base + '/' + entry;
63
+ }
64
+ return { imports: imports };
65
+ }
66
+
67
+ /**
68
+ * The lib-identity root, as the document sees it, for a router: where `frqtl/parts/index.js` is
69
+ * placed, minus that path (every profile places the game layer at its lib/-relative path).
70
+ *
71
+ * @param {{resolve:Function}} router
72
+ * @returns {string} ending in '/'
73
+ * @throws {Error} FAIL-LOUD when the profile does not place the parts at their lib/-relative path.
74
+ */
75
+ export function libRootOf(router) {
76
+ var parts = router.resolve('parts/index.js');
77
+ var tail = 'frqtl/parts/index.js';
78
+ if (parts.slice(-tail.length) !== tail) {
79
+ throw new Error('[vendor-importmap] the router places parts/index.js at "' + parts + '", not at its ' +
80
+ 'lib/-relative placement (…/' + tail + ') — the document\'s lib root cannot be located (FAIL-LOUD).');
81
+ }
82
+ var root = parts.slice(0, -tail.length);
83
+ return root === '' ? './' : root;
84
+ }
85
+
86
+ /**
87
+ * The REALM import map — the ONE merged `{imports}` every host injects into a document:
88
+ * the vendor libs (`buildImportMap(manifest, router.vendorBase)`), the canonical
89
+ * setup-factory bare specifiers (`buildFrqtlSetupsImportMap(router)`) and the sketch's
90
+ * `@descix/frqtl-web/...` specifiers (lib/shim/project/specifiers.js importMapFor, at this
91
+ * router's lib root). A document may carry only ONE import map, so the fragments always travel
92
+ * together, resolved through the SAME router (never a vendor base from one profile and module
93
+ * URLs from another).
94
+ *
95
+ * @param {VendorManifest} manifest
96
+ * @param {{resolve:Function, vendorBase:string}} router an engineRouter() instance.
97
+ * @returns {{ imports: Object<string, string> }}
98
+ * @throws {Error} FAIL-LOUD on a malformed manifest or a missing router.
99
+ */
100
+ export function buildRealmImportMap(manifest, router) {
101
+ if (!router || typeof router.resolve !== 'function') {
102
+ throw new Error('[vendor-importmap] buildRealmImportMap(manifest, router) requires an engineRouter() instance (FAIL-LOUD).');
103
+ }
104
+ var vendorMap = buildImportMap(manifest, router.vendorBase);
105
+ var setupsMap = buildFrqtlSetupsImportMap(router);
106
+ var webMap = importMapFor(libRootOf(router));
107
+ return { imports: Object.assign({}, vendorMap.imports, setupsMap.imports, webMap.imports) };
108
+ }
109
+
110
+ /**
111
+ * Splice the realm import map into an HTML document as
112
+ * `<script type="importmap" data-frqtl-vendor-importmap>`, immediately BEFORE the first
113
+ * `<script type="module"` (a map added after module loading starts is ignored), else
114
+ * before `</head>`, else prepended. Idempotent: a document already carrying the
115
+ * `data-frqtl-vendor-importmap` tag is returned unchanged.
116
+ *
117
+ * The ONE splice for every host: the dev serve-time splice
118
+ * (lib/server/realm-html.js) and the static build-time bake (apps/ide/vite.config.js).
119
+ *
120
+ * @param {string} html
121
+ * @param {VendorManifest} manifest
122
+ * @param {{resolve:Function, vendorBase:string}} router the host's engineRouter() instance.
123
+ * @returns {string}
124
+ */
125
+ export function spliceRealmImportMap(html, manifest, router) {
126
+ if (/data-frqtl-vendor-importmap/.test(html)) { return html; } // idempotent
127
+ var body = JSON.stringify(buildRealmImportMap(manifest, router));
128
+ var tag = '<script type="importmap" data-frqtl-vendor-importmap>' + body + '</script>\n ';
129
+ var moduleIdx = html.search(/<script\s+type=["']module["']/i);
130
+ if (moduleIdx !== -1) { return html.slice(0, moduleIdx) + tag + html.slice(moduleIdx); }
131
+ var headIdx = html.search(/<\/head>/i);
132
+ if (headIdx !== -1) { return html.slice(0, headIdx) + ' ' + tag + html.slice(headIdx); }
133
+ return tag + html;
134
+ }
@@ -0,0 +1,189 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * view-builder — the SOLE authority that synthesizes the canonical VIEW-ENTRY source
5
+ * from a `view` cell's declarative configuration (WS-NOTEBOOK-VENDOR-VIEW Phase 2b).
6
+ *
7
+ * Named ESM export. PURE module — NO DOM / engine / Runtime / Bus import. It is
8
+ * host-neutral and node-testable: hosts (the notebook-pane in-app) import this one
9
+ * helper and NONE inlines the template (the §router single-path-authority rule applied to the view-entry
10
+ * template). It is the SIBLING of `setup-builder.js` for the generic `view` cell,
11
+ * and it REUSES setup-builder's export-resolution + module-resolution helpers (DRY —
12
+ * one single-export-else-FAIL-LOUD authority, one in-memory-module-set resolver).
13
+ *
14
+ * ── What it does ─────────────────────────────────────────────────────────────
15
+ *
16
+ * Given a view declaration — `module="<module-name>#<exportName>"` — and the
17
+ * notebook's user-code module SET (`{ name → source }`), it returns the canonical
18
+ * VIEW-ENTRY SOURCE STRING + the realm-internal output name. The entry imports the
19
+ * user render module by name and RETURNS its render export under the realm-internal
20
+ * output name:
21
+ *
22
+ * import { <export> } from '<module>';
23
+ * return { <outName>: <export> };
24
+ *
25
+ * `Runtime.runCell` runs THIS entry IN THE REALM (the SAME executor + module-compile
26
+ * path the declared canvas uses). The entry's `import { <export> } from '<module>'`
27
+ * resolves against the notebook's in-memory module closure; the USER module's OWN
28
+ * `import * as d3 from 'd3'` bare-specifier imports resolve via the realm's injected
29
+ * 2a import-map (bare specifiers pass through the blob-module rewrite verbatim — see
30
+ * module-resolver `rewriteRelativeToSiblingBlobs`, "bare → host loader"). The render
31
+ * export is returned as a LIVE realm-internal ref (NOT a kernel binding, NOT crossed
32
+ * over the data plane) — exactly as the declared-canvas synthesis returns a live
33
+ * `runner` ref. The HOST then invokes `renderExport(ctx)` with the view `ctx`
34
+ * (mount + frozen handle + binding seam) it assembles realm-side (see view-ctx.js).
35
+ *
36
+ * ── single-export resolution (FAIL-LOUD, never guess) ─────────────────────────
37
+ *
38
+ * `#export` may be omitted ONLY when the named module has EXACTLY ONE export. Zero or
39
+ * multiple exports → FAIL-LOUD with a precise message (never a guess). This reuses
40
+ * setup-builder's `moduleExportNames` + the same resolution discipline (C5 mirror).
41
+ *
42
+ * ── the import resolves against the in-memory module closure ──────────────────
43
+ *
44
+ * The emitted `import` uses the DECLARED module name (e.g. `'./views/d3-bars.js'`),
45
+ * resolved against the notebook's `moduleSet()` via the existing in-memory blob
46
+ * closure. An unknown/missing module → FAIL-LOUD HERE so the caller never ships an
47
+ * entry that 404s the relative import downstream.
48
+ */
49
+
50
+ 'use strict';
51
+
52
+ import { canonKey } from './module-resolver.js';
53
+ import { moduleExportNames } from './setup-builder.js';
54
+
55
+ /* ── parse "<module-name>#<exportName>" ──────────────────────────────────────── */
56
+
57
+ /**
58
+ * Split the view module declaration into `{ moduleName, exportName }`. The
59
+ * `exportName` is the substring after the FIRST '#'; absent → ''. The `moduleName`
60
+ * is verbatim (kept with its relative './…' prefix so the emitted import matches the
61
+ * module cell name the closure is keyed by).
62
+ *
63
+ * @param {string} moduleDecl — `"<module-name>#<exportName>"` (exportName optional).
64
+ * @returns {{ moduleName: string, exportName: string }}
65
+ * @throws {Error} FAIL-LOUD on an empty declaration or a declaration with no module name.
66
+ */
67
+ export function parseViewDeclaration(moduleDecl) {
68
+ if (typeof moduleDecl !== 'string' || !moduleDecl.trim()) {
69
+ throw new Error(
70
+ '[view-builder] a `view` cell requires a `module="<module-name>#<exportName>"` ' +
71
+ 'attribute (got: ' + JSON.stringify(moduleDecl) + '). (FAIL-LOUD.)'
72
+ );
73
+ }
74
+ var s = moduleDecl.trim();
75
+ var hash = s.indexOf('#');
76
+ var moduleName = (hash === -1) ? s : s.slice(0, hash);
77
+ var exportName = (hash === -1) ? '' : s.slice(hash + 1);
78
+ moduleName = moduleName.trim();
79
+ exportName = exportName.trim();
80
+ if (!moduleName) {
81
+ throw new Error(
82
+ '[view-builder] `module` declaration "' + moduleDecl + '" has no module name ' +
83
+ 'before the "#". Expected "<module-name>#<exportName>". (FAIL-LOUD.)'
84
+ );
85
+ }
86
+ return { moduleName: moduleName, exportName: exportName };
87
+ }
88
+
89
+ /* ── resolve the module source from the module set ───────────────────────────── */
90
+
91
+ // Look the declared module up in the module set, matching by canonical key so
92
+ // './views/x.js' and 'views/x.js' resolve the same. FAIL-LOUD on absence.
93
+ function _resolveModuleSource(moduleName, modules) {
94
+ var set = modules || {};
95
+ if (Object.prototype.hasOwnProperty.call(set, moduleName)) { return set[moduleName]; }
96
+ var wantKey = canonKey(moduleName);
97
+ var keys = Object.keys(set);
98
+ for (var i = 0; i < keys.length; i++) {
99
+ if (canonKey(keys[i]) === wantKey) { return set[keys[i]]; }
100
+ }
101
+ throw new Error(
102
+ '[view-builder] declared view module "' + moduleName + '" is not a ' +
103
+ '`type="module"` cell in this notebook (known modules: [' + keys.join(', ') +
104
+ ']). (FAIL-LOUD — the `view` cell references an editable in-notebook module ' +
105
+ 'cell, never an opaque by-name load.)'
106
+ );
107
+ }
108
+
109
+ /* ── resolve the export name (single-export-else-FAIL-LOUD) ───────────────────── */
110
+
111
+ // Return the export name to import: the explicit `exportName` when given (verified
112
+ // present); otherwise the module's SOLE export. Zero or multiple exports with no
113
+ // explicit `#export` → FAIL-LOUD (never guess). Mirrors setup-builder's C5 rule.
114
+ function _resolveExportName(moduleName, exportName, source) {
115
+ var exports = moduleExportNames(source);
116
+ if (exportName) {
117
+ if (exports.indexOf(exportName) === -1) {
118
+ throw new Error(
119
+ '[view-builder] declared view export "' + exportName + '" is not exported ' +
120
+ 'by module "' + moduleName + '" (its exports: [' + exports.join(', ') +
121
+ ']). (FAIL-LOUD.)'
122
+ );
123
+ }
124
+ return exportName;
125
+ }
126
+ if (exports.length === 1) { return exports[0]; }
127
+ if (exports.length === 0) {
128
+ throw new Error(
129
+ '[view-builder] view module "' + moduleName + '" has NO named export — ' +
130
+ 'declare `module="' + moduleName + '#<exportName>"` once the module exports a ' +
131
+ 'render function. (FAIL-LOUD — never guess.)'
132
+ );
133
+ }
134
+ throw new Error(
135
+ '[view-builder] view module "' + moduleName + '" has MULTIPLE exports [' +
136
+ exports.join(', ') + '] — the `#export` part of `module` is required to pick one ' +
137
+ '(e.g. module="' + moduleName + '#' + exports[0] + '"). (FAIL-LOUD — never guess.)'
138
+ );
139
+ }
140
+
141
+ /* ── the synthesis (SOLE authority) ──────────────────────────────────────────── */
142
+
143
+ /**
144
+ * synthesizeViewEntrySource — turn a `view` cell declaration into the canonical
145
+ * VIEW-ENTRY source string + the realm-internal output name.
146
+ *
147
+ * @param {object} decl
148
+ * @param {string} decl.module — `"<module-name>#<exportName>"` (exportName optional).
149
+ * @param {Object<string,string>} decl.modules — the notebook module set `{ name → source }`,
150
+ * used to resolve a single export + verify the module exists.
151
+ * @param {string} [decl.outName] — realm-internal output name; default `'_view'`.
152
+ * @returns {{ entryCode: string, outName: string, moduleName: string, exportName: string }}
153
+ * @throws {Error} FAIL-LOUD on a missing module / unknown export / zero-or-multiple
154
+ * exports with no `#export`.
155
+ */
156
+ export function synthesizeViewEntrySource(decl) {
157
+ decl = decl || {};
158
+ var parsed = parseViewDeclaration(decl.module);
159
+ var source = _resolveModuleSource(parsed.moduleName, decl.modules);
160
+ var exportName = _resolveExportName(parsed.moduleName, parsed.exportName, source);
161
+
162
+ // The realm-internal output name is emitted as an UNQUOTED object key in the
163
+ // synthesized `return { <outName>: <export> }`. Callers pass `cell.id + '_view'`,
164
+ // and notebook cell ids contain hyphens (e.g. `c-d3-view`) — a bare hyphen in a
165
+ // key is a SyntaxError. Sanitize to a valid JS identifier (it is realm-internal,
166
+ // never user-referenced, so the exact spelling does not matter — only that it is a
167
+ // legal key and that the value we RETURN as `outName` matches the key the template
168
+ // emits, so the caller's `result.outputs[outName]` lookup hits). Mirrors
169
+ // setup-builder's outName sanitization (the hyphen bug class).
170
+ var rawOutName = decl.outName || '_view';
171
+ var outName = String(rawOutName).replace(/[^A-Za-z0-9_$]/g, '_');
172
+ if (/^[0-9]/.test(outName)) { outName = '_' + outName; }
173
+
174
+ // The canonical VIEW-ENTRY template. The synthesized entry:
175
+ // - imports the editable in-notebook view module's render export by name,
176
+ // - returns it as a realm-internal LIVE ref (the host invokes it with the ctx).
177
+ // The USER module's OWN bare-specifier imports (e.g. `import * as d3 from 'd3'`)
178
+ // resolve via the realm's injected 2a import-map — they are not rewritten here.
179
+ var entryCode =
180
+ "import { " + exportName + " } from '" + parsed.moduleName + "';\n" +
181
+ "return { " + outName + ": " + exportName + " };\n";
182
+
183
+ return {
184
+ entryCode: entryCode,
185
+ outName: outName,
186
+ moduleName: parsed.moduleName,
187
+ exportName: exportName
188
+ };
189
+ }
@@ -0,0 +1,100 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * view-ctx — the SOLE authority that assembles the `view` cell CONTEXT object handed
5
+ * to a user render module's export (WS-NOTEBOOK-VENDOR-VIEW Phase 2b).
6
+ *
7
+ * Named ESM export. PURE module — NO DOM / engine / Runtime / Bus import. It is a
8
+ * THIN convenience WRAPPER over the EXISTING frozen ViewHandle + the realm→kernel
9
+ * binding seam; it introduces NO new handle shape (the design's hard constraint). It
10
+ * is host-neutral + node-testable: a test passes a FAKE handle + a plain object as
11
+ * the `mount` + fake binding callbacks and asserts the ctx verbs delegate correctly
12
+ * and that `commit` enforces serializability (VETO-CLASS #1) — WITHOUT a DOM.
13
+ *
14
+ * The user render function is invoked as `render(ctx)` where `ctx` is:
15
+ *
16
+ * {
17
+ * mount, // DOM element inside the realm host (the view's render target)
18
+ * handle, // the frozen ViewHandle for THIS realm (on/call/start/stop/step/dispose)
19
+ * on, // convenience = handle.on (SAME-CELL local tick stream)
20
+ * call, // convenience = handle.call
21
+ * getBinding, // (name) → the kernel binding value for an `in` name (data IN)
22
+ * commit, // (name, value) → commit a `produces` value (data OUT, serializable-asserted)
23
+ * imports, // optional: pre-resolved blessed modules (e.g. { makeChartEnv } for liveChart)
24
+ * }
25
+ *
26
+ * `mount`/`handle`/`on`/`call` are LIVE refs (the same realm trust zone the canvas
27
+ * builder runs in). `getBinding`/`commit` are the kernel binding seam: `getBinding`
28
+ * reads from a host-supplied resolver (the host pre-resolves `in` bindings before
29
+ * invoking render); `commit` routes a serializable value through a host-supplied
30
+ * commit callback (in-app: kernel.commitViewOutputs),
31
+ * asserting serializability HERE (FAIL-LOUD on a non-serializable commit — the same
32
+ * gate the frozen handle's data plane enforces).
33
+ */
34
+
35
+ 'use strict';
36
+
37
+ import { assertSerializable } from './view-handle.js';
38
+
39
+ /**
40
+ * makeViewCtx(opts) → ctx
41
+ *
42
+ * @param {object} opts
43
+ * @param {*} opts.mount — the view's DOM render target (a live element; opaque here).
44
+ * @param {object} opts.handle — the frozen ViewHandle for the booted realm (REQUIRED;
45
+ * must expose `on` + `call`). The ctx NEVER reshapes it — it is passed through and
46
+ * its `on`/`call` are surfaced as convenience refs.
47
+ * @param {(name: string) => *} [opts.getBinding] — resolve an `in` binding value by
48
+ * name. Default: a resolver over `opts.bindings` (a plain `{ name → value }` map);
49
+ * absent name → `undefined` (the host validated `in` presence before invoking).
50
+ * @param {Object<string,*>} [opts.bindings] — pre-resolved `in` bindings (used by the
51
+ * default getBinding when no explicit resolver is supplied).
52
+ * @param {(name: string, value: *) => void} [opts.commit] — host commit callback
53
+ * (in-app: kernel.commitViewOutputs). When absent,
54
+ * `commit` FAILS LOUD (a view that declares `produces` requires a commit sink).
55
+ * @param {Object<string,*>} [opts.imports] — pre-resolved blessed modules.
56
+ * @returns {object} the view ctx.
57
+ * @throws {Error} FAIL-LOUD if `handle` is missing `on`/`call`.
58
+ */
59
+ export function makeViewCtx(opts) {
60
+ opts = opts || {};
61
+ var mount = opts.mount;
62
+ var handle = opts.handle;
63
+ if (!handle || typeof handle.on !== 'function' || typeof handle.call !== 'function') {
64
+ throw new Error('[view-ctx] requires a frozen ViewHandle with on()/call(). (FAIL-LOUD.)');
65
+ }
66
+
67
+ var bindings = opts.bindings || {};
68
+ var getBinding = (typeof opts.getBinding === 'function')
69
+ ? opts.getBinding
70
+ : function (name) {
71
+ return Object.prototype.hasOwnProperty.call(bindings, name) ? bindings[name] : undefined;
72
+ };
73
+
74
+ var commitSink = (typeof opts.commit === 'function') ? opts.commit : null;
75
+ function commit(name, value) {
76
+ if (typeof name !== 'string' || !name) {
77
+ throw new Error('[view-ctx] commit: name must be a non-empty string. (FAIL-LOUD.)');
78
+ }
79
+ if (!commitSink) {
80
+ throw new Error('[view-ctx] commit("' + name + '"): no commit sink wired for this ' +
81
+ 'view (declare a `produces` binding + supply a host commit callback). (FAIL-LOUD.)');
82
+ }
83
+ // VETO-CLASS #1: a committed value MUST be structured-clone-serializable (it
84
+ // crosses into the kernel binding store, which a marshalling host may carry).
85
+ assertSerializable(value, 'view commit("' + name + '") value');
86
+ commitSink(name, value);
87
+ }
88
+
89
+ return {
90
+ mount: mount,
91
+ handle: handle,
92
+ // convenience refs over the frozen handle — bind so a destructured `on`/`call`
93
+ // keeps the handle as `this` is irrelevant (the handle methods are closures).
94
+ on: function (event, cb) { return handle.on(event, cb); },
95
+ call: function (fn, args) { return handle.call(fn, args); },
96
+ getBinding: getBinding,
97
+ commit: commit,
98
+ imports: opts.imports || {}
99
+ };
100
+ }