@descix/frqtl-node 0.6.0-p1.1 → 0.6.0

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 (80) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.md +6 -4
  3. package/dist/dist-manifest.json +71 -65
  4. package/dist/eyes/index.js +5 -2
  5. package/dist/fat/pkg-node/package.json +2 -10
  6. package/dist/frqtl/compat/providers/wasm-engine.js +3 -4
  7. package/dist/frqtl/compat/renderer/providers/p5-provider.js +1 -1
  8. package/dist/frqtl/objects/FrqtlApp.js +33 -6
  9. package/dist/frqtl/objects/RecipeContext.js +284 -13
  10. package/dist/frqtl/objects/constants/WallBehaviors.js +1 -1
  11. package/dist/frqtl/objects/instruments/capture.js +1 -24
  12. package/dist/frqtl/objects/instruments/hosted-eyes.js +216 -29
  13. package/dist/frqtl/objects/objects/makeControlsForObject.js +5 -5
  14. package/dist/frqtl/objects/record/Record.js +346 -0
  15. package/dist/frqtl/objects/record/open-record.js +22 -0
  16. package/dist/frqtl/objects/schema/engine-scopes.js +2 -2
  17. package/dist/frqtl/objects/schema/scopes.js +2 -2
  18. package/dist/frqtl/objects/setup/_worldInit.js +1 -2
  19. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +1 -2
  20. package/dist/frqtl/parts/CameraControls.js +8 -0
  21. package/dist/frqtl/parts/DetectorWall.js +1 -1
  22. package/dist/frqtl/parts/FrqtlVision.js +21 -6
  23. package/dist/frqtl/parts/GraphingDetectorWall.js +7 -7
  24. package/dist/frqtl/parts/LargeObject.js +2 -2
  25. package/dist/frqtl/parts/LiveChart.js +2 -2
  26. package/dist/frqtl/parts/LogicGate.js +3 -3
  27. package/dist/frqtl/parts/OvenBox.js +1 -1
  28. package/dist/frqtl/parts/PointSource.js +1 -1
  29. package/dist/frqtl/parts/SlitLightSource.js +1 -1
  30. package/dist/frqtl/parts/SlitScreen.js +1 -1
  31. package/dist/frqtl/parts/SphereEmitter.js +12 -12
  32. package/dist/frqtl/parts/Trace.js +1 -1
  33. package/dist/frqtl/parts/Tunnel.js +11 -11
  34. package/dist/frqtl/parts/Wall.js +20 -10
  35. package/dist/frqtl/scenes/circuit-sat.js +1 -0
  36. package/dist/frqtl/scenes/dse-one-emitter.js +32 -34
  37. package/dist/frqtl/scenes/dse-two-emitters.js +33 -34
  38. package/dist/frqtl/scenes/mirror-route.js +96 -79
  39. package/dist/shim/bundle/index.js +118 -96
  40. package/dist/shim/bundle/indexHtml.js +66 -39
  41. package/dist/shim/controls/index.js +1 -1
  42. package/dist/shim/engine/artifact-shell.js +1 -1
  43. package/dist/shim/engine/router.js +3 -3
  44. package/dist/shim/host/host-view.js +228 -0
  45. package/dist/shim/host/index.js +18 -0
  46. package/dist/shim/index.js +3 -5
  47. package/dist/shim/notebook/format.js +1 -1
  48. package/dist/shim/notebook/kernel.js +1 -1
  49. package/dist/shim/notebook/setup-builder.js +2 -2
  50. package/dist/shim/notebook/store.js +2 -3
  51. package/dist/shim/package.json +1 -0
  52. package/dist/shim/page-params/index.js +2 -30
  53. package/dist/shim/project/fork.js +293 -0
  54. package/dist/shim/project/identity.js +3 -1
  55. package/dist/shim/project/index.js +16 -3
  56. package/dist/shim/project/link.js +224 -46
  57. package/dist/shim/project/manifest.js +151 -0
  58. package/dist/shim/project/parts-writer.js +68 -7
  59. package/dist/shim/project/read-text.js +43 -0
  60. package/dist/shim/project/record-format.js +345 -0
  61. package/dist/shim/project/specifiers.js +104 -15
  62. package/dist/shim/registry/exhibits.js +15 -34
  63. package/dist/shim/registry/index.js +3 -3
  64. package/dist/shim/registry/scene-roster.js +35 -0
  65. package/dist/shim/registry/scene-source.js +48 -64
  66. package/dist/shim/registry/walkthrough.js +6 -6
  67. package/dist/shim/render-state/apply.js +66 -31
  68. package/dist/shim/render-state/index.js +6 -2
  69. package/dist/shim/runtime/index.js +4 -4
  70. package/dist/shim/storage/codec-chains.js +88 -0
  71. package/dist/shim/storage/folders.js +205 -0
  72. package/dist/shim/storage/index.js +64 -8
  73. package/dist/shim/storage/make-namespace-store.js +5 -5
  74. package/llms.txt +4 -3
  75. package/package.json +19 -1
  76. package/dist/shim/bundle/frqnb.js +0 -112
  77. package/dist/shim/bundle/manifest.js +0 -95
  78. package/dist/shim/registry/experiments.js +0 -215
  79. package/dist/shim/storage/modules.js +0 -249
  80. package/dist/shim/storage/projects.js +0 -150
@@ -164,7 +164,7 @@ export function renderArtifactShellHtml(opts) {
164
164
  return (
165
165
  '<!DOCTYPE html>\n' +
166
166
  '<!-- Copyright 2023-2026 by Essam Abadir -->\n' +
167
- '<!-- FRQTL — claude.ai Artifact host shell (ws-artifact-showcase Bite A2).\n' +
167
+ '<!-- FRQTL — claude.ai Artifact host shell.\n' +
168
168
  ' Generated by lib/shim/engine/artifact-shell.js — DO NOT hand-edit.\n' +
169
169
  ' Paste this ENTIRE file into a claude.ai chat as an Artifact (type: HTML). -->\n' +
170
170
  '<html lang="en">\n' +
@@ -148,7 +148,7 @@ var DIST_PROFILE = buildDistProfile('../..');
148
148
  // lib/shim/engine/test/router.test.mjs asserts them against the two
149
149
  // package.json#version fields so a forgotten bump fails `make verify`, not a
150
150
  // silent stale-CDN-URL bug.
151
- var CDN_SDK_VERSION = '0.6.0-p1.1'; // sdk/frqtl-web/package.json#version (the ONE version owner) — bound by router.test.mjs
151
+ var CDN_SDK_VERSION = '0.6.0'; // sdk/frqtl-web/package.json#version (the ONE version owner) — bound by router.test.mjs
152
152
  var CDN_MATH_SDK_VERSION = '0.3.2'; // sdk/egpt-math-sdk/package.json#version — bound by router.test.mjs; bumped with each npm publish
153
153
 
154
154
  /**
@@ -382,7 +382,7 @@ function documentSiteBuild() {
382
382
  }
383
383
 
384
384
  // The static IDE bundle's entry document, beside its front page. The bundle ships THIS name and no
385
- // index.html: a site's index.html is the publisher's front page (host-view.js::HOST_FRONT_PAGE),
385
+ // index.html: a site's index.html is the publisher's front page (lib/shim/host/host-view.js::HOST_FRONT_PAGE),
386
386
  // which the IDE brand links home to.
387
387
  var STATIC_APP_PAGE = 'app.html';
388
388
 
@@ -507,7 +507,7 @@ var _assertProfileParity = (function () {
507
507
  * // CDN mode (static hosting — the artifact host shell has no server, no page anchor):
508
508
  * const cdnRouter = engineRouter({ mode: 'cdn', engine: 'wasm' });
509
509
  * console.log(cdnRouter.resolve('compat/engine.js'));
510
- * // 'https://cdn.jsdelivr.net/npm/@descix/frqtl-web@0.6.0-p1.1/dist/frqtl/compat/engine.js'
510
+ * // 'https://cdn.jsdelivr.net/npm/@descix/frqtl-web@0.6.0/dist/frqtl/compat/engine.js'
511
511
  *
512
512
  * // CDN mode, self-hosted: a copy of dist/ served from the consumer's own origin:
513
513
  * const selfHosted = engineRouter({ mode: 'cdn', engine: 'wasm', base: 'https://example.org/vendor/frqtl-web/dist' });
@@ -0,0 +1,228 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * host-view.js — the ONE declarer of the DeSciX host view for an app DOCUMENT.
5
+ *
6
+ * A DeSciX shell hosts the app beside its chat (SplitView, its default) unless the app asks for
7
+ * another view. This module asks — once, through the app-side SDK (`DeSciXAppSDK.js`, vendored
8
+ * by the publisher beside the page) — and RECORDS on `document.documentElement.dataset.descixView`
9
+ * which host state it met. The mode is a parameter; the policy lives at the call site.
10
+ *
11
+ * Every host state is a row of HOST_VIEW_OUTCOMES. No row throws and no row writes to
12
+ * console.error: the page's job is physics and layout is the host's, so a missing host is a
13
+ * truthful record, never a crash and never a cry-wolf console line. Whether an absence is
14
+ * LEGITIMATE is decided by a gate at the plane that knows (FRAQTL's static URL gate, egpt-frqtl's
15
+ * refresh-site refusal), not here.
16
+ *
17
+ * Two SDK rules shape the order of operations:
18
+ * - The SDK's `view` getter console.errors on a standalone page AND on a shell that does not
19
+ * publish `view`. So `view` is read only after `ready()` resolved `mode:'shell'` AND its
20
+ * `members` list names `view`.
21
+ * - `view.set()` on a host that renders a bare iframe validates and returns the mode while
22
+ * nothing moves. `view.available()` is the only instrument that tells the two apart, so it
23
+ * is consulted here, before `set`, and nowhere else.
24
+ *
25
+ * A caller that names a declared second choice (`fallback`) is answered off the shell's PUBLISHED list,
26
+ * `view.MODES`, read before any `set`: the asked mode when listed, else the fallback when listed (recorded as
27
+ * the `fallback` row, the refusal and the mode set in the detail), else nothing (`unoffered`). A `set` is never
28
+ * retried and never decided by its error: a shell that lists a mode and then refuses it is a contract violation.
29
+ */
30
+
31
+ /**
32
+ * The app-side SDK file this declarer consumes. It is PUBLISHER-provided: vendored beside the
33
+ * page by the site that publishes the static bundle, never shipped in the bundle.
34
+ */
35
+ export const HOST_SDK_SCRIPT = 'DeSciXAppSDK.js';
36
+
37
+ /**
38
+ * The attribute that declares a file host-provided, on the element that consumes it, in the
39
+ * bytes the browser executes. Both the bundle gate (the file must be ABSENT from the bundle) and
40
+ * the publisher (the file must be PRESENT beside the page) read the set from this attribute.
41
+ */
42
+ export const HOST_PROVIDED_ATTR = 'data-frqtl-host-provided';
43
+
44
+ /**
45
+ * The publisher's front page, beside the IDE entry (router.js::STATIC_APP_PAGE). PUBLISHER-provided
46
+ * like HOST_SDK_SCRIPT: the static build declares it once as `<link rel="home" href=HOST_FRONT_PAGE
47
+ * HOST_PROVIDED_ATTR>` (apps/ide/vite.config.js), the bundle gate asserts the bundle does not ship
48
+ * it, and lib/features/ide/home-link.js::homeHrefOf reads the declaration back.
49
+ */
50
+ export const HOST_FRONT_PAGE = 'index.html';
51
+
52
+ /** The closed table of host states. `setCalls` is how many times `view.set` runs in that row. */
53
+ export const HOST_VIEW_OUTCOMES = Object.freeze([
54
+ { id: 'pending', setCalls: 0,
55
+ means: 'awaiting DeSciX.ready(); a ready() that never settles stays here' },
56
+ { id: 'requested', setCalls: 1,
57
+ means: 'shell listening: view.available() true and view.set(mode) returned mode; recorded as requested:<mode>' },
58
+ { id: 'fallback', setCalls: 1,
59
+ means: 'shell listening; view.MODES lacks the asked mode and offers the caller\'s declared fallback: view.set(fallback) returned it; recorded as fallback:<asked mode>, the refusal and the mode set in dataset.descixViewDetail' },
60
+ { id: 'unoffered', setCalls: 0,
61
+ means: 'shell listening; view.MODES offers neither the asked mode nor the declared fallback; recorded as unoffered:<asked mode>, the list in dataset.descixViewDetail' },
62
+ { id: 'not-listening', setCalls: 0,
63
+ means: 'shell publishes view but view.available() is false (bare-iframe host); the host owns the layout' },
64
+ { id: 'view-unpublished', setCalls: 0,
65
+ means: 'shell resolved, but its members do not include view' },
66
+ { id: 'standalone', setCalls: 0,
67
+ means: 'ready() resolved standalone: this page is the top window, no shell above it' },
68
+ { id: 'embedded-no-bridge', setCalls: 0,
69
+ means: 'this page is framed and no ancestor carries a readable bus (the SDK rejects ready() here today)' },
70
+ { id: 'sdk-absent', setCalls: 0,
71
+ means: 'window.DeSciX is undefined: the publisher-provided DeSciXAppSDK.js is not beside this page' },
72
+ { id: 'contract-violated', setCalls: null,
73
+ means: 'the SDK or host broke its published shape; the edge is named in dataset.descixViewDetail (CONTRACT_VIOLATIONS)' },
74
+ ].map(Object.freeze));
75
+
76
+ /** The closed edges of `contract-violated`, each with its own `view.set` call count. */
77
+ export const CONTRACT_VIOLATIONS = Object.freeze({
78
+ 'no-ready': Object.freeze({ setCalls: 0, means: 'window.DeSciX has no ready() function' }),
79
+ 'ready-rejected': Object.freeze({ setCalls: 0, means: 'ready() rejected while the SDK reports a mode other than embedded-no-bridge' }),
80
+ 'ready-unknown-mode': Object.freeze({ setCalls: 0, means: 'ready() resolved a mode that is not shell, standalone or embedded-no-bridge' }),
81
+ 'ready-no-members': Object.freeze({ setCalls: 0, means: 'ready() resolved shell without a members array' }),
82
+ 'view-listed-absent': Object.freeze({ setCalls: 0, means: 'members names view but the view getter returned nothing' }),
83
+ 'view-no-available': Object.freeze({ setCalls: 0, means: 'view has no available() function' }),
84
+ 'view-no-set': Object.freeze({ setCalls: 0, means: 'view has no set() function' }),
85
+ 'available-threw': Object.freeze({ setCalls: 0, means: 'view.available() threw' }),
86
+ 'view-no-modes': Object.freeze({ setCalls: 0, means: 'a fallback was given but view.MODES is not published' }),
87
+ 'set-threw': Object.freeze({ setCalls: 1, means: 'view.set(mode) threw (or view.set(fallback), the mode view.MODES offered)' }),
88
+ 'set-returned-other': Object.freeze({ setCalls: 1, means: 'view.set(mode) returned something other than the mode it was asked for' }),
89
+ });
90
+
91
+ const ROW_IDS = new Set(HOST_VIEW_OUTCOMES.map((r) => r.id));
92
+
93
+ function messageOf(e) {
94
+ return (e && e.message) ? String(e.message) : String(e);
95
+ }
96
+
97
+ /**
98
+ * Write a row to the document record. Refuses (throws) a row or edge the tables do not list:
99
+ * that is a defect in THIS module, never a host state.
100
+ */
101
+ function record(root, row, { suffix = '', detail } = {}) {
102
+ if (!ROW_IDS.has(row)) { throw new Error(`host-view: '${row}' is not a HOST_VIEW_OUTCOMES row`); }
103
+ const value = row + suffix;
104
+ if (root) {
105
+ root.dataset.descixView = value;
106
+ if (detail === undefined) { delete root.dataset.descixViewDetail; } else { root.dataset.descixViewDetail = detail; }
107
+ }
108
+ return detail === undefined ? { row, record: value } : { row, record: value, detail };
109
+ }
110
+
111
+ function violated(root, edge, specifics) {
112
+ if (!Object.prototype.hasOwnProperty.call(CONTRACT_VIOLATIONS, edge)) {
113
+ throw new Error(`host-view: '${edge}' is not a CONTRACT_VIOLATIONS edge`);
114
+ }
115
+ return record(root, 'contract-violated', { detail: `${edge}: ${specifics}` });
116
+ }
117
+
118
+ /** The SDK's own live mode, read without touching any member getter. */
119
+ function liveMode(sdk) {
120
+ try { return sdk.mode; } catch (e) { return undefined; }
121
+ }
122
+
123
+ /**
124
+ * Ask the host for `mode`, once, and record which host state was met.
125
+ *
126
+ * @param {object} args
127
+ * @param {object|undefined} args.sdk the app-side SDK object (`window.DeSciX`), or undefined
128
+ * @param {string} args.mode the view to ask for: a mode the shell's `view.MODES` offers
129
+ * @param {string} [args.fallback] the caller's declared second choice, asked only when `view.MODES` does not
130
+ * offer `mode` (a non-empty string other than `mode`)
131
+ * @param {{dataset: object}} [args.root] where the record is written; defaults to
132
+ * `document.documentElement` when a document exists
133
+ * @returns {Promise<{row: string, record: string, detail?: string}>}
134
+ */
135
+ export async function declareHostView({ sdk, mode, fallback, root } = {}) {
136
+ if (typeof mode !== 'string' || mode === '') {
137
+ throw new TypeError(`host-view: declareHostView needs a view mode string (got ${JSON.stringify(mode)})`);
138
+ }
139
+ if (fallback !== undefined && (typeof fallback !== 'string' || fallback === '' || fallback === mode)) {
140
+ throw new TypeError(`host-view: a fallback is a non-empty view mode string other than mode ${JSON.stringify(mode)} (got ${JSON.stringify(fallback)})`);
141
+ }
142
+ const target = root || (typeof document !== 'undefined' ? document.documentElement : null);
143
+
144
+ if (sdk === undefined || sdk === null) { return record(target, 'sdk-absent'); }
145
+ if (typeof sdk.ready !== 'function') {
146
+ return violated(target, 'no-ready', `window.DeSciX is a ${typeof sdk} without ready()`);
147
+ }
148
+
149
+ record(target, 'pending');
150
+ let ready;
151
+ try {
152
+ ready = await sdk.ready();
153
+ } catch (e) {
154
+ const now = liveMode(sdk);
155
+ if (now === 'embedded-no-bridge') {
156
+ return record(target, 'embedded-no-bridge', { detail: `ready() rejected: ${messageOf(e)}` });
157
+ }
158
+ return violated(target, 'ready-rejected', `SDK mode ${JSON.stringify(now)}; ${messageOf(e)}`);
159
+ }
160
+
161
+ const resolved = ready && ready.mode;
162
+ if (resolved === 'standalone') { return record(target, 'standalone'); }
163
+ if (resolved === 'embedded-no-bridge') { return record(target, 'embedded-no-bridge'); }
164
+ if (resolved !== 'shell') {
165
+ return violated(target, 'ready-unknown-mode', `ready() resolved mode ${JSON.stringify(resolved)}`);
166
+ }
167
+ if (!Array.isArray(ready.members)) {
168
+ return violated(target, 'ready-no-members', 'ready() resolved shell without a members array');
169
+ }
170
+ if (!ready.members.includes('view')) {
171
+ return record(target, 'view-unpublished', { detail: `members: ${ready.members.join(',')}` });
172
+ }
173
+
174
+ const view = sdk.view;
175
+ if (!view) { return violated(target, 'view-listed-absent', 'members names view; the getter returned ' + String(view)); }
176
+ if (typeof view.available !== 'function') { return violated(target, 'view-no-available', 'view.available is ' + typeof view.available); }
177
+ if (typeof view.set !== 'function') { return violated(target, 'view-no-set', 'view.set is ' + typeof view.set); }
178
+
179
+ let listening;
180
+ try {
181
+ listening = view.available();
182
+ } catch (e) {
183
+ return violated(target, 'available-threw', messageOf(e));
184
+ }
185
+ if (!listening) { return record(target, 'not-listening'); }
186
+
187
+ // With a declared fallback the published list decides which ONE mode is asked; without one, the asked mode is.
188
+ let ask = mode;
189
+ if (fallback !== undefined) {
190
+ const modes = view.MODES;
191
+ if (!modes || typeof modes !== 'object') {
192
+ return violated(target, 'view-no-modes', 'fallback given but view.MODES is not published (got ' + String(modes) + ')');
193
+ }
194
+ const offered = Object.values(modes);
195
+ const list = '[' + offered.join(', ') + ']';
196
+ if (!offered.includes(mode)) {
197
+ if (!offered.includes(fallback)) {
198
+ return record(target, 'unoffered', { suffix: ':' + mode,
199
+ detail: `view.MODES ${list} offers neither "${mode}" nor the fallback "${fallback}"` });
200
+ }
201
+ ask = fallback;
202
+ }
203
+ if (ask === fallback) {
204
+ let gotFallback;
205
+ try {
206
+ gotFallback = view.set(fallback);
207
+ } catch (e) {
208
+ return violated(target, 'set-threw', messageOf(e));
209
+ }
210
+ if (gotFallback !== fallback) {
211
+ return violated(target, 'set-returned-other', `asked ${fallback}, set returned ${JSON.stringify(gotFallback)}`);
212
+ }
213
+ return record(target, 'fallback', { suffix: ':' + mode,
214
+ detail: `refused: "${mode}" is not in view.MODES ${list}; set: ${fallback}` });
215
+ }
216
+ }
217
+
218
+ let got;
219
+ try {
220
+ got = view.set(ask);
221
+ } catch (e) {
222
+ return violated(target, 'set-threw', messageOf(e));
223
+ }
224
+ if (got !== ask) {
225
+ return violated(target, 'set-returned-other', `asked ${ask}, set returned ${JSON.stringify(got)}`);
226
+ }
227
+ return record(target, 'requested', { suffix: ':' + ask });
228
+ }
@@ -0,0 +1,18 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * index.js — the published door to the host-view declarer: `@descix/frqtl-web/host-view` and
5
+ * `@descix/frqtl-node/host-view`. Exactly the names below, re-exported from THE declarer (host-view.js); its
6
+ * static-build vocabulary (HOST_PROVIDED_ATTR, HOST_FRONT_PAGE) stays FRAQTL's own.
7
+ *
8
+ * HOST_VIEW_EXPORTS the published names (the parity gate holds both packages' ./host-view to this table)
9
+ *
10
+ * A consuming app calls `declareHostView({ sdk: window.DeSciX, mode, fallback? })` once, under its own policy, and
11
+ * reads the outcome off `document.documentElement.dataset.descixView` (a HOST_VIEW_OUTCOMES row). HOST_SDK_SCRIPT is
12
+ * the publisher-provided SDK file the app vendors beside its page.
13
+ */
14
+
15
+ /** The names `./host-view` publishes. */
16
+ export const HOST_VIEW_EXPORTS = Object.freeze(['declareHostView', 'HOST_VIEW_OUTCOMES', 'CONTRACT_VIOLATIONS', 'HOST_SDK_SCRIPT']);
17
+
18
+ export { declareHostView, HOST_VIEW_OUTCOMES, CONTRACT_VIOLATIONS, HOST_SDK_SCRIPT } from './host-view.js';
@@ -7,7 +7,7 @@
7
7
  * FAIL-LOUD load error — no window.Common* ambient globals.
8
8
  *
9
9
  * Usage (under vite with alias 'lib' → repo-root/lib):
10
- * import { Bus, Runtime, EXPERIMENTS, engineRouter } from 'lib/shim';
10
+ * import { Bus, Runtime, sceneGroups, engineRouter } from 'lib/shim';
11
11
  *
12
12
  * Usage (direct relative path):
13
13
  * import { Bus } from '../../lib/shim/index.js';
@@ -15,14 +15,12 @@
15
15
 
16
16
  export { Bus } from './bus.js';
17
17
  export { Runtime } from './runtime/index.js';
18
- export { EXPERIMENTS, EXPERIMENT_TIERS, experimentById, experimentIs3D,
19
- isPublicExperiment, publicExperiments,
20
- internalExperiments } from './registry/experiments.js';
18
+ export { sceneGroups, SHOWCASE_GROUP } from './registry/scene-roster.js';
21
19
  export { WALKTHROUGH, walkthroughIs3D, walkthroughModes, walkthroughGroup,
22
20
  walkthroughById,
23
21
  FIRST_CONTACT_WALKTHROUGH_ID,
24
22
  defaultWalkthroughModeId, effectiveRenderState } from './registry/walkthrough.js';
25
- export { loadSceneSource, scenePath, EMPTY_SCENE } from './registry/scene-source.js';
23
+ export { projectOf, scenePath, EMPTY_SCENE } from './registry/scene-source.js';
26
24
  // The host-side experiment-iframe guard (the false-200 trap). Exported from the
27
25
  // barrel so every host that frames an experiment page (lib/features/ide's GYM)
28
26
  // asserts against the SAME contract.
@@ -99,7 +99,7 @@ export const PROSE_CELL_TYPES = Object.freeze(['markdown']);
99
99
  * e.g. `setupDoubleSlit`). Absent → `'factory'`, so every existing notebook is
100
100
  * byte-identical.
101
101
  * - `'recipe'`: the module's DEFAULT export is a `buildExperiment(exp)` RECIPE (a single
102
- * RecipeContext arg — the sketch form codegen / `buildFrqnb` / the scene files
102
+ * RecipeContext arg — the sketch form the scene files
103
103
  * write). The shim builds a
104
104
  * `frqtl.RecipeContext` and runs `exp.build(buildExperiment)` (the canonical
105
105
  * co-init call + `addObject()` recipe path), NOT the factory signature.
@@ -148,7 +148,7 @@ export class NotebookKernel {
148
148
  }
149
149
 
150
150
  /* ── scope ──────────────────────────────────────────────────────────────
151
- * THE SCOPE RULE (FRAQTL issue #67): a math value's scope IS this kernel's
151
+ * THE SCOPE RULE: a math value's scope IS this kernel's
152
152
  * binding scope. On the shipped WASM dist an EGPT value is an arena handle,
153
153
  * so if the arena is drained while a binding still names one, that binding is
154
154
  * a dead handle and the next cell dies with `arena:stale-handle`. The two
@@ -288,7 +288,7 @@ export function parseSeed(seed) {
288
288
  * canvas boots the same scene as every other surface.
289
289
  *
290
290
  * shape='recipe': the module's DEFAULT export is a `buildExperiment(exp)` RECIPE — a SINGLE
291
- * RecipeContext arg (the sketch form codegen and the scene files write). Template:
291
+ * RecipeContext arg (the sketch form the scene files and the parts writer write). Template:
292
292
  * const buildExperiment = (await import('<module>')).default; // FAIL-LOUD if not a function
293
293
  * const exp = frqtl.RecipeContext({ seed, deferCanvas: true }); // SIZELESS
294
294
  * exp.build(buildExperiment); // runs its co-init call + addObject() — NO start
@@ -298,7 +298,7 @@ export function parseSeed(seed) {
298
298
  * recipe takes NO knob bag — a non-empty `opts` on a recipe canvas FAILS LOUD
299
299
  * (rather than silently dropping it).
300
300
  *
301
- * WHY the template checks `buildExperiment` but not `exp` (FRAQTL #73): the two values
301
+ * WHY the template checks `buildExperiment` but not `exp`: the two values
302
302
  * have different OWNERS. `buildExperiment` is read off the module namespace by this template
303
303
  * itself, so nothing but this template can vouch for it — hence the emitted FAIL-LOUD check.
304
304
  * `exp` comes from `frqtl.RecipeContext(...)`, a caps.frqtl member: its owner
@@ -5,9 +5,8 @@
5
5
  *
6
6
  * Thin CRUD helpers that persist a serialized <frqtl-notebook> document on top
7
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.
8
+ * namespace, through the shared namespace-CRUD factory
9
+ * (lib/shim/storage/make-namespace-store.js).
11
10
  *
12
11
  * ── Why this lives in lib/shim ──────────────────────────────────────────────
13
12
  *
@@ -1,4 +1,5 @@
1
1
  {
2
2
  "private": true,
3
+ "license": "SEE LICENSE IN LICENSE.md",
3
4
  "type": "module"
4
5
  }
@@ -64,8 +64,7 @@
64
64
  var KEYS = Object.freeze({
65
65
  SEED: 'seed',
66
66
  RENDERER: 'renderer',
67
- ENGINE: 'engine',
68
- STEPPING: 'stepping'
67
+ ENGINE: 'engine'
69
68
  });
70
69
 
71
70
  /**
@@ -85,7 +84,7 @@ var KEYS = Object.freeze({
85
84
  var PAGE_PARAMS = Object.freeze([
86
85
  Object.freeze({
87
86
  key: KEYS.SEED,
88
- reader: 'lib/frqtl/experiments/_shared/fx-experiment.js',
87
+ reader: 'lib/frqtl/experiments/empty/index.html',
89
88
  forward: true
90
89
  }),
91
90
  Object.freeze({
@@ -93,33 +92,6 @@ var PAGE_PARAMS = Object.freeze([
93
92
  reader: 'lib/frqtl/experiments/_shared/boot.js',
94
93
  forward: true
95
94
  }),
96
- Object.freeze({
97
- key: KEYS.STEPPING,
98
- reader: 'lib/frqtl/experiments/single-source-interference/index.html',
99
- forward: true
100
- // Circulation §2: `stepping`
101
- // selects the WAVE-WALK STEPPING SAMPLER for one universe —
102
- // 'fixed' (deterministic error-accumulator walk, the engine's serde default)
103
- // vs 'average' (the historic per-axis Bernoulli walk). It is a PER-UNIVERSE
104
- // PHYSICS option, so it is read at the PAGE plane and ferried RAW into that
105
- // page's own `universe.init(..., { stepping })` options — deliberately NOT by
106
- // the boot module, which selects the BACKEND (`?engine=`, forward:false). Putting
107
- // a physics knob in the backend selection would conflate the two axes.
108
- //
109
- // forward:true — unlike `?engine=`, no host owns a competing stepping authority,
110
- // and a host embedding this page for an A/B (the GYM Compare shape) forwarding
111
- // its own `?stepping=` is exactly the intended use (the `?renderer=`/`?seed=`
112
- // precedent). The value is NEVER validated or defaulted page-side: an absent
113
- // param means the key is omitted from the init bag so the ENGINE's default
114
- // (the Rust `Stepping` serde enum, `Fixed`) is the ONE authority for it, and a
115
- // garbage value rides through to `universe.init()`, which throws naming both
116
- // legal samplers (one validator, zero second interpreters).
117
- //
118
- // SCALING (pre-registered, not built): a PAGE_PARAMS entry names ONE reader.
119
- // When a SECOND page consumes `?stepping=`, HOIST the read into one shared
120
- // reader module and repoint this entry — never a second entry for the same
121
- // key, never a copy-pasted inline read.
122
- }),
123
95
  Object.freeze({
124
96
  key: KEYS.ENGINE,
125
97
  reader: 'lib/frqtl/experiments/_shared/boot.js',