@bongos/core 1.19.442 → 1.19.443

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 (162) hide show
  1. package/.bongos-core.json +464 -449
  2. package/config/branding.neutral.json +1 -1
  3. package/docs/adr/0218-the-apex-root-web-surface.md +55 -0
  4. package/docs/adr/README.md +1 -0
  5. package/docs/architecture.md +2 -1
  6. package/docs/copy-inventory.md +360 -360
  7. package/docs/copy-registry.json +456 -456
  8. package/docs/design/mocks/hub/mine.html +1 -1
  9. package/docs/design/mocks/hub/picker.html +1 -1
  10. package/docs/design/mocks/hub/planets.html +1 -1
  11. package/docs/design/mocks/hub/v1.html +3 -3
  12. package/docs/design/mocks/hub/v2.html +2 -2
  13. package/docs/design/mocks/hub/v3.html +5 -5
  14. package/docs/design/mocks/hub/wizard.html +1 -1
  15. package/docs/design/mocks/landing/v1.html +1 -1
  16. package/docs/design/mocks/landing/v2.html +1 -1
  17. package/docs/design/mocks/landing/v3.html +1 -1
  18. package/docs/design/mocks/landing-page-mock.html +3 -3
  19. package/docs/design/mocks/projects-hub-mock.html +1 -1
  20. package/docs/design/mocks/projects-hub-variants.html +1 -1
  21. package/docs/design/mocks/projects-page-mock.html +1 -1
  22. package/docs/file-map.md +3 -3
  23. package/docs/module-api-changelog.md +2 -0
  24. package/docs/modules-contract.md +9 -2
  25. package/docs/recipes/ui-look-before-you-ship.md +4 -4
  26. package/modules/copy-desk/tests/copy_flags.mjs +4 -4
  27. package/modules/public-landing/CLAUDE.md +15 -0
  28. package/modules/public-landing/module.json +15 -0
  29. package/modules/ui-design/kit/lib.js +2 -2
  30. package/modules/ui-design/kit/render.js +1 -1
  31. package/modules/ui-design/kit/serve.js +3 -3
  32. package/package-lock.json +2 -2
  33. package/package.json +1 -1
  34. package/scripts/gds/copy-inventory.js +3 -3
  35. package/scripts/gds/fitness-checks-identity.js +18 -1
  36. package/scripts/gds/publish-manifest.js +1 -1
  37. package/src/bongos/module-scope-map.js +9 -7
  38. package/src/bongos/serve-internal.js +13 -1
  39. package/src/module-api.js +1 -1
  40. package/src/module-loader/loader.js +34 -17
  41. package/src/module-loader/manifest-schema.js +16 -8
  42. package/src/platform-server.js +50 -18
  43. package/tests/copy_inventory.mjs +0 -0
  44. package/tests/cosmos_token_contrast.mjs +6 -6
  45. package/tests/fitness.mjs +20 -0
  46. package/tests/landing_legal_pages.mjs +2 -1
  47. package/tests/landing_page.mjs +7 -5
  48. package/tests/module-scope-map.mjs +2 -2
  49. package/tests/module_loader.mjs +4 -1
  50. package/tests/onboard.mjs +5 -5
  51. package/tests/projects_hub.mjs +22 -20
  52. package/tests/projects_hub_pre_uat.mjs +5 -3
  53. package/tests/public_landing_projects.mjs +2 -2
  54. package/tests/terms_acceptance.mjs +1 -1
  55. package/tests/ui_design_kit.mjs +3 -3
  56. package/tests/world_tokens.mjs +3 -2
  57. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_00.webp +0 -0
  58. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_01.webp +0 -0
  59. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_02.webp +0 -0
  60. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_03.webp +0 -0
  61. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_04.webp +0 -0
  62. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_05.webp +0 -0
  63. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_06.webp +0 -0
  64. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_07.webp +0 -0
  65. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_08.webp +0 -0
  66. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_09.webp +0 -0
  67. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_10.webp +0 -0
  68. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_11.webp +0 -0
  69. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_12.webp +0 -0
  70. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_13.webp +0 -0
  71. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_14.webp +0 -0
  72. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_15.webp +0 -0
  73. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_16.webp +0 -0
  74. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_17.webp +0 -0
  75. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_18.webp +0 -0
  76. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_19.webp +0 -0
  77. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_20.webp +0 -0
  78. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_21.webp +0 -0
  79. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_22.webp +0 -0
  80. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_23.webp +0 -0
  81. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_24.webp +0 -0
  82. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_25.webp +0 -0
  83. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_26.webp +0 -0
  84. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_27.webp +0 -0
  85. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_28.webp +0 -0
  86. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_29.webp +0 -0
  87. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_30.webp +0 -0
  88. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_31.webp +0 -0
  89. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_32.webp +0 -0
  90. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_33.webp +0 -0
  91. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_34.webp +0 -0
  92. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_35.webp +0 -0
  93. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_36.webp +0 -0
  94. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_37.webp +0 -0
  95. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_38.webp +0 -0
  96. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_39.webp +0 -0
  97. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_40.webp +0 -0
  98. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_41.webp +0 -0
  99. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_42.webp +0 -0
  100. /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_43.webp +0 -0
  101. /package/{public-landing → modules/public-landing/public}/assets/cosmos.css +0 -0
  102. /package/{public-landing → modules/public-landing/public}/assets/verro-medallion.png +0 -0
  103. /package/{public-landing → modules/public-landing/public}/assets/world/og-card.jpg +0 -0
  104. /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp +0 -0
  105. /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp.json +0 -0
  106. /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg +0 -0
  107. /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg.json +0 -0
  108. /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp +0 -0
  109. /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp.json +0 -0
  110. /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg +0 -0
  111. /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg.json +0 -0
  112. /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp +0 -0
  113. /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp.json +0 -0
  114. /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg +0 -0
  115. /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg.json +0 -0
  116. /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp +0 -0
  117. /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp.json +0 -0
  118. /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp +0 -0
  119. /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp.json +0 -0
  120. /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp +0 -0
  121. /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp.json +0 -0
  122. /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp +0 -0
  123. /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp.json +0 -0
  124. /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp +0 -0
  125. /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp.json +0 -0
  126. /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-pair-cut.webp +0 -0
  127. /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-pair-cut.webp.json +0 -0
  128. /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-bead-cut.webp +0 -0
  129. /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-bead-cut.webp.json +0 -0
  130. /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-blob-cut.webp +0 -0
  131. /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-blob-cut.webp.json +0 -0
  132. /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-pair-cut.webp +0 -0
  133. /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-pair-cut.webp.json +0 -0
  134. /package/{public-landing → modules/public-landing/public}/assets/world/r3-space-nebula.jpg +0 -0
  135. /package/{public-landing → modules/public-landing/public}/assets/world/r3-space-nebula.jpg.json +0 -0
  136. /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-chrome.jpg +0 -0
  137. /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-chrome.jpg.json +0 -0
  138. /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-deepfield.jpg +0 -0
  139. /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-deepfield.jpg.json +0 -0
  140. /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-dust.jpg +0 -0
  141. /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-dust.jpg.json +0 -0
  142. /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-nebula.jpg +0 -0
  143. /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-nebula.jpg.json +0 -0
  144. /package/{public-landing → modules/public-landing/public}/assets/world.css +0 -0
  145. /package/{public-landing → modules/public-landing/public}/buddha.png +0 -0
  146. /package/{public-landing → modules/public-landing/public}/contact.html +0 -0
  147. /package/{public-landing → modules/public-landing/public}/index.html +0 -0
  148. /package/{public-landing → modules/public-landing/public}/index.probes.json +0 -0
  149. /package/{public-landing → modules/public-landing/public}/index.states.json +0 -0
  150. /package/{public-landing → modules/public-landing/public}/privacy.html +0 -0
  151. /package/{public-landing → modules/public-landing/public}/projects-empty.states.json +0 -0
  152. /package/{public-landing → modules/public-landing/public}/projects-floor.states.json +0 -0
  153. /package/{public-landing → modules/public-landing/public}/projects.html +0 -0
  154. /package/{public-landing → modules/public-landing/public}/projects.probes.json +0 -0
  155. /package/{public-landing → modules/public-landing/public}/projects.states.json +0 -0
  156. /package/{public-landing → modules/public-landing/public}/terms.html +0 -0
  157. /package/{public-landing → modules/public-landing/public}/world/assets/clouds/cloud-island-large.png +0 -0
  158. /package/{public-landing → modules/public-landing/public}/world/assets/clouds/cloud-puff.png +0 -0
  159. /package/{public-landing → modules/public-landing/public}/world/assets/creatures/creature-whale-cargo.png +0 -0
  160. /package/{public-landing → modules/public-landing/public}/world/assets/creatures/creature-whale-glide.png +0 -0
  161. /package/{public-landing → modules/public-landing/public}/world/assets/plates/plate-clouds-near.png +0 -0
  162. /package/{public-landing → modules/public-landing/public}/world/assets/plates/scene-establishing.png +0 -0
@@ -19,8 +19,10 @@ import { fileURLToPath } from 'node:url';
19
19
  import { createRequire } from 'node:module';
20
20
 
21
21
  const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
22
- const HUB = fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.html'), 'utf8');
23
- const COSMOS = fs.readFileSync(path.join(ROOT, 'public-landing', 'assets', 'cosmos.css'), 'utf8');
22
+ // the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
23
+ const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
24
+ const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8');
25
+ const COSMOS = fs.readFileSync(path.join(LANDING, 'assets', 'cosmos.css'), 'utf8');
24
26
  const STYLE = (HUB.match(/<style>([\s\S]*?)<\/style>/) || [, ''])[1];
25
27
  const SCRIPT = (HUB.match(/<script>([\s\S]*?)<\/script>\s*<\/body>/) || [, ''])[1];
26
28
 
@@ -71,7 +73,7 @@ test('?view= is the deep-link contract, read on boot and written with replaceSta
71
73
  assert.match(SCRIPT, /'&id=' \+ encodeURIComponent\(manageId\)/,
72
74
  'the URL writer must carry the manage id, encoded');
73
75
  // the landing page's CTAs have always pointed here — this is the bug they hit
74
- const landing = fs.readFileSync(path.join(ROOT, 'public-landing', 'index.html'), 'utf8');
76
+ const landing = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
75
77
  assert.match(landing, /href="\/projects\?view=new"/,
76
78
  'the landing CTA target must stay a view the hub knows');
77
79
  });
@@ -255,8 +257,8 @@ test('the gate is retired — no route, no files, no links into it', () => {
255
257
  // the FILES, not the directory: git cannot track an empty dir, so a stale
256
258
  // working tree may keep the folder while the commit is clean
257
259
  for (const f of ['index.html', 'space.js', 'space.css']) {
258
- assert.equal(fs.existsSync(path.join(ROOT, 'public-landing', 'gate', f)), false,
259
- `public-landing/gate/${f} must be gone: express.static would keep serving it`);
260
+ assert.equal(fs.existsSync(path.join(LANDING, 'gate', f)), false,
261
+ `the landing surface’s gate/${f} must be gone: express.static would keep serving it`);
260
262
  }
261
263
  const server = fs.readFileSync(path.join(ROOT, 'src', 'platform-server.js'), 'utf8');
262
264
  assert.doesNotMatch(server, /res\.redirect\(302, '\/gate\/'\)/,
@@ -272,7 +274,7 @@ test('/create is absorbed — the wizard lives here and the route only redirects
272
274
  // are real markup inside the new view (C7), create.html is deleted — the
273
275
  // gate precedent above: express.static would keep serving the raw file —
274
276
  // and the name handoff became an in-page step advance.
275
- assert.equal(fs.existsSync(path.join(ROOT, 'public-landing', 'create.html')), false,
277
+ assert.equal(fs.existsSync(path.join(LANDING, 'create.html')), false,
276
278
  'create.html must be gone: express.static would keep serving it raw and unbranded');
277
279
  assert.doesNotMatch(SCRIPT, /location\.assign\('\/create'/,
278
280
  'naming a project advances the wizard in place — no navigation');
@@ -449,8 +451,8 @@ test('the count includes the platform when it is shown', () => {
449
451
  and the hub's. ── */
450
452
 
451
453
  test('neither the landing hero nor the hub types (tasks 1003298, 1003300)', () => {
452
- const LANDING = fs.readFileSync(path.join(ROOT, 'public-landing', 'index.html'), 'utf8');
453
- assert.doesNotMatch(LANDING, /id="typed"|setTimeout\(type\b/, 'the landing hero does not type');
454
+ const LANDING_HTML = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
455
+ assert.doesNotMatch(LANDING_HTML, /id="typed"|setTimeout\(type\b/, 'the landing hero does not type');
454
456
  assert.doesNotMatch(SCRIPT, /function typeMark\(\)|HOLD_HOME|setTimeout\(type\b/, 'the hub has no typing loop');
455
457
  });
456
458
 
@@ -1569,9 +1571,9 @@ test('the walk assigns the template by canonical position beside the accent; a c
1569
1571
  });
1570
1572
 
1571
1573
  test("the planet is the landing's object on the landing's crop maths: the three rectangles are byte-equal across the two sheets", () => {
1572
- const LANDING = fs.readFileSync(path.join(ROOT, 'public-landing', 'index.html'), 'utf8');
1574
+ const LANDING_HTML = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
1573
1575
  const landingCrop = (cls) => {
1574
- const m = LANDING.match(new RegExp(`\\.${cls}\\{[^}]*--bead-ar:([^;]+);--bead-zoom:([^;]+);--bead-px:([^;]+);--bead-py:([^;}]+)`));
1576
+ const m = LANDING_HTML.match(new RegExp(`\\.${cls}\\{[^}]*--bead-ar:([^;]+);--bead-zoom:([^;]+);--bead-px:([^;]+);--bead-py:([^;}]+)`));
1575
1577
  assert.ok(m, `the landing declares .${cls}`);
1576
1578
  return m.slice(1, 5).map((s) => s.trim());
1577
1579
  };
@@ -1604,7 +1606,7 @@ test('the materials (task 1003337): a whole object per plate, the crop recompute
1604
1606
  const rule = `.worldOrb[data-material="${material}"][data-shape="${shape}"]{--tpl-src:var(--plate-${material}-${shape}-cut);--tpl-ar:${f5(w / h)};--tpl-zoom:${f5(zoom)};--tpl-px:${f5(-U0 * zoom)};--tpl-py:${f5(-V0 * zoom)};}`;
1605
1607
  assert.ok(COSMOS.includes(rule), `the ${key} crop follows from its box: ${rule}`);
1606
1608
  assert.ok(COSMOS.includes(`--plate-${material}-${shape}-cut:url(/assets/world/r3-${material}-${shape}-cut.webp);`), `the ${key} plate token`);
1607
- const file = path.join(ROOT, 'public-landing', 'assets', 'world', `r3-${material}-${shape}-cut.webp`);
1609
+ const file = path.join(LANDING, 'assets', 'world', `r3-${material}-${shape}-cut.webp`);
1608
1610
  assert.ok(fs.existsSync(file), `${key}: the cutout ships`);
1609
1611
  assert.ok(fs.existsSync(file + '.json'), `${key}: with its sidecar`);
1610
1612
  const head = fs.readFileSync(file).subarray(0, 40).toString('latin1');
@@ -1620,7 +1622,7 @@ test('the field carries a rendered backdrop (task 1003337): one token, cover, a
1620
1622
  assert.match(COSMOS, /\.field \.ground\{position:absolute;inset:-4%;background:var\(--sky-bg\) var\(--field-plate\) center\/cover no-repeat;\}/);
1621
1623
  assert.match(COSMOS, /@media \(prefers-reduced-motion:no-preference\)\{\s*\.field \.ground\{animation:groundDrift 90s ease-in-out infinite alternate;\}/, 'the drift only runs where motion is welcome');
1622
1624
  assert.match(COSMOS, /@keyframes groundDrift\{from\{transform:translate3d\([^)]*\) scale\(1\.02\);\}to\{transform:translate3d\([^)]*\) scale\(1\.06\);\}\}/, 'transform only');
1623
- const plate = path.join(ROOT, 'public-landing', 'assets', 'world', 'r3-space-nebula.jpg');
1625
+ const plate = path.join(LANDING, 'assets', 'world', 'r3-space-nebula.jpg');
1624
1626
  assert.ok(fs.existsSync(plate) && fs.existsSync(plate + '.json'), 'the plate and its sidecar ship');
1625
1627
  const side = JSON.parse(fs.readFileSync(plate + '.json', 'utf8'));
1626
1628
  assert.equal(side.kind, 'backdrop');
@@ -1680,7 +1682,7 @@ test('the kit proves the field path: the fixture carries a curated template and
1680
1682
  assert.equal(demo.planet_template, 'pair-obsidian-tint', 'the owner\'s choice rides the same wire (task 1003347)');
1681
1683
  assert.equal(demo.planet_accent, 'sea', 'a token NAME, never a colour');
1682
1684
  assert.ok(fixture.projects.every((p) => 'template' in p && 'planet_template' in p && 'planet_accent' in p), 'the wire never drops a key');
1683
- const states = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.states.json'), 'utf8'));
1685
+ const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8'));
1684
1686
  const attrs = states.states['in-map'].expect.attr.map((a) => a.join(' '));
1685
1687
  assert.ok(attrs.includes('#selfOrb data-template pair-chrome'));
1686
1688
  assert.ok(attrs.some((a) => a.includes('amazonprimea.com') && a.endsWith('data-template bead-wash')), 'the curated template reaches the orb');
@@ -1756,7 +1758,7 @@ test('the picker wears the world by scope: the preview left and four bands right
1756
1758
  });
1757
1759
 
1758
1760
  test('the kit proves the picker: a state renders it on the owner\'s feed-carried choice, three probes save / hand back / fold, and both fixtures carry the choice (task 1003347)', () => {
1759
- const states = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.states.json'), 'utf8'));
1761
+ const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8'));
1760
1762
  const st = states.states['in-manage-planet'];
1761
1763
  assert.ok(st && st.auth && st.url === '/projects?view=manage&id=101', 'the signed-in owner on Demo');
1762
1764
  assert.ok(st.expect.visible.includes('#mPlanetPick') && st.expect.visible.includes('#mPick [data-pick-reset]'));
@@ -1770,7 +1772,7 @@ test('the kit proves the picker: a state renders it on the owner\'s feed-carried
1770
1772
  const harbor = inst.instances.find((i) => i.slug === 'harbor');
1771
1773
  assert.equal(harbor.planet_template, 'blob-frost-tint');
1772
1774
  assert.equal(harbor.planet_accent, 'sun-lit');
1773
- const probes = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.probes.json'), 'utf8'));
1775
+ const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8'));
1774
1776
  const names = probes.probes.map((p) => p.name);
1775
1777
  assert.ok(names.some((n) => n.startsWith('manage flows: its planet (task 1003347)')), 'the save probe');
1776
1778
  assert.ok(names.some((n) => n.includes('handed back to the platform')), 'the reset probe');
@@ -1884,7 +1886,7 @@ test("the card paints the owner's plate before the project's own picture, and a
1884
1886
 
1885
1887
  test('every plate the picker offers ships as a real file with its provenance sidecar', () => {
1886
1888
  for (const k of ['nebula', 'dust', 'deepfield', 'chrome']) {
1887
- const file = path.join(ROOT, 'public-landing', 'assets', 'world', `r4-card-${k}.jpg`);
1889
+ const file = path.join(LANDING, 'assets', 'world', `r4-card-${k}.jpg`);
1888
1890
  assert.ok(fs.existsSync(file), `r4-card-${k}.jpg must ship — the picker offers it`);
1889
1891
  const side = JSON.parse(fs.readFileSync(`${file}.json`, 'utf8'));
1890
1892
  assert.equal(side.task, 1003352, 'the sidecar names the task that made it');
@@ -1902,9 +1904,9 @@ test("its card is the planet section's twin: its own heading, the same channel,
1902
1904
  assert.match(SCRIPT, /manageInst\.card_backdrop = \(data\.planet \|\| \{\}\)\.card_backdrop \|\| null;/,
1903
1905
  'the reply is a receipt — the page repaints from the row, never from words in the reply');
1904
1906
  assert.match(SCRIPT, /renderPick\(inst\);\r?\n\s*renderCardPick\(inst\);/, 'both sections fill from the same manage read');
1905
- const states = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.states.json'), 'utf8'));
1907
+ const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8'));
1906
1908
  assert.ok(states.states['in-manage-card'], 'the section has a rendered state');
1907
- const probes = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.probes.json'), 'utf8'));
1909
+ const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8'));
1908
1910
  const names = probes.probes.map((p) => p.name);
1909
1911
  assert.ok(names.some((n) => n.startsWith('manage flows: its card (task 1003352)')), 'the save probe');
1910
1912
  assert.ok(names.some((n) => n.includes("its card — handing it back")), 'the hand-back probe');
@@ -1960,8 +1962,8 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
1960
1962
  assert.match(SCRIPT, /target_ref: state\.owner \+ '\/' \+ state\.repo,/, 'the repo ref is unchanged');
1961
1963
  assert.match(SCRIPT, /onboard_mode: state\.mode === 'adopt' \? 'adopt' : 'greenfield'/, 'the onboard mode is unchanged');
1962
1964
  // the kit walks every step and files against the stub
1963
- const states = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.states.json'), 'utf8')).states;
1965
+ const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8')).states;
1964
1966
  for (const s of ['type', 'about', 'home', 'address', 'review', 'done']) assert.ok(states['in-new-' + s], `the ${s} step has a kit state`);
1965
- const probes = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.probes.json'), 'utf8')).probes;
1967
+ const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
1966
1968
  assert.ok(probes.some((p) => /the wizard driver \(task 1003346\)/.test(p.name) && p.steps.some((st) => st[1] === '#doCreate')), 'the wizard driver files a project end to end');
1967
1969
  });
@@ -11,11 +11,13 @@ import path from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
12
 
13
13
  const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
14
+ // the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
15
+ const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
14
16
  // LF-normalised: a Windows checkout (core.autocrlf) hands back CRLF, and the
15
17
  // anchored regexes below reason about line ends
16
- const HUB = fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
17
- const COSMOS = fs.readFileSync(path.join(ROOT, 'public-landing', 'assets', 'cosmos.css'), 'utf8').replace(/\r\n/g, '\n');
18
- const INDEX = fs.readFileSync(path.join(ROOT, 'public-landing', 'index.html'), 'utf8').replace(/\r\n/g, '\n');
18
+ const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
19
+ const COSMOS = fs.readFileSync(path.join(LANDING, 'assets', 'cosmos.css'), 'utf8').replace(/\r\n/g, '\n');
20
+ const INDEX = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8').replace(/\r\n/g, '\n');
19
21
  const SCRIPT = HUB.slice(HUB.lastIndexOf('<script>'), HUB.lastIndexOf('</script>'));
20
22
 
21
23
  // a function's text runs from its declaration to the next declaration in the
@@ -470,7 +470,7 @@ test('the stamp is derived from CONTENT, so it changes only when the file does',
470
470
  const cryptoMod = await import('node:crypto');
471
471
  const url = await import('node:url');
472
472
  const root = pathMod.dirname(url.fileURLToPath(import.meta.url));
473
- const css = fsMod.readFileSync(pathMod.join(root, '..', 'public-landing', 'assets', 'cosmos.css'));
473
+ const css = fsMod.readFileSync(pathMod.join(root, '..', 'modules', 'public-landing', 'public', 'assets', 'cosmos.css'));
474
474
  const want = cryptoMod.createHash('sha256').update(css).digest('hex').slice(0, 8);
475
475
  const res = await request('/projects');
476
476
  assert.ok(res.body.includes(`cosmos.css?v=${want}`),
@@ -488,7 +488,7 @@ test('every landing surface is stamped, not just the one that broke', async () =
488
488
  });
489
489
 
490
490
  test('routes and API paths are never mistaken for assets', async () => {
491
- // stamping resolves the URL against public-landing/ — a route names no file,
491
+ // stamping resolves the URL against the surface dir — a route names no file,
492
492
  // so it must come back untouched rather than acquiring a meaningless ?v=
493
493
  const res = await request('/projects');
494
494
  assert.match(res.body, /href="\/"/, "'/' is a route, not an asset");
@@ -94,7 +94,7 @@ async function ta(name, fn) {
94
94
 
95
95
  console.log('the page (/terms carries the consent the ADR deferred to it):');
96
96
 
97
- const termsHtml = fs.readFileSync(path.join(repo, 'public-landing', 'terms.html'), 'utf8');
97
+ const termsHtml = fs.readFileSync(path.join(repo, 'modules', 'public-landing', 'public', 'terms.html'), 'utf8');
98
98
  const { stripApexOnly } = require('../src/platform-server.js');
99
99
 
100
100
  t('the terms page states what crosses between projects', () => {
@@ -33,7 +33,7 @@ test('the platform floors are a pinned list — a floor cannot vanish quietly',
33
33
  });
34
34
 
35
35
  test('the shipped states files validate and sit beside the page they name', () => {
36
- for (const rel of ['public-landing/projects.states.json', 'public-landing/projects-empty.states.json', 'public-landing/index.states.json', 'modules/hall-ui/public/ranks.states.json']) {
36
+ for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/projects-empty.states.json', 'modules/public-landing/public/index.states.json', 'modules/hall-ui/public/ranks.states.json']) {
37
37
  const json = readJson(rel);
38
38
  const v = lib.validateStatesFile(json);
39
39
  assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
@@ -42,14 +42,14 @@ test('the shipped states files validate and sit beside the page they name', () =
42
42
  }
43
43
  // the apex is dark only (task 1003336): its states files name the one mode, and
44
44
  // render.js reads it as the default so a plain `--page /` never renders light
45
- for (const rel of ['public-landing/projects.states.json', 'public-landing/index.states.json']) {
45
+ for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/index.states.json']) {
46
46
  assert.deepEqual(readJson(rel).modes, ['dark'], `${rel}: the apex renders dark only`);
47
47
  assert.equal(readJson(rel).modeQuery, false, `${rel}: no query walks a second mode`);
48
48
  }
49
49
  });
50
50
 
51
51
  test('the shipped probes files validate', () => {
52
- for (const rel of ['public-landing/projects.probes.json', 'public-landing/index.probes.json']) {
52
+ for (const rel of ['modules/public-landing/public/projects.probes.json', 'modules/public-landing/public/index.probes.json']) {
53
53
  const v = lib.validateProbesFile(readJson(rel));
54
54
  assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
55
55
  }
@@ -34,7 +34,8 @@ import path from 'node:path';
34
34
  import { fileURLToPath } from 'node:url';
35
35
 
36
36
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
37
- const LANDING = path.join(ROOT, 'public-landing');
37
+ // the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
38
+ const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
38
39
  const CSS = path.join(LANDING, 'assets', 'world.css');
39
40
  const src = fs.readFileSync(CSS, 'utf8');
40
41
  const PAGE = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
@@ -239,7 +240,7 @@ test('every plate is root-absolute and the file is there', () => {
239
240
  for (const u of urls) {
240
241
  if (!u.startsWith('/')) { bad.push(`${u} is not root-absolute`); continue; }
241
242
  const abs = path.join(LANDING, u.replace(/^\//, ''));
242
- if (!fs.existsSync(abs)) bad.push(`${u} does not exist under public-landing/`);
243
+ if (!fs.existsSync(abs)) bad.push(`${u} does not exist under the landing surface`);
243
244
  }
244
245
  assert.deepEqual(bad, [], `\n ${bad.join('\n ')}\n`);
245
246
  // the pairs the world promises: a black-ground plate for dark, its alpha cutout