@bongos/core 1.19.441 → 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.
- package/.bongos-core.json +496 -441
- package/clients/bongos-client/index.d.ts +1 -2
- package/config/branding.neutral.json +1 -1
- package/docs/adr/0218-the-apex-root-web-surface.md +55 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +3 -20
- package/docs/api-reference.md +1 -1
- package/docs/architecture.md +2 -1
- package/docs/copy-inventory.md +363 -359
- package/docs/copy-registry.json +608 -561
- package/docs/design/mocks/hub/mine.html +1 -1
- package/docs/design/mocks/hub/picker.html +1 -1
- package/docs/design/mocks/hub/planets.html +1 -1
- package/docs/design/mocks/hub/v1.html +3 -3
- package/docs/design/mocks/hub/v2.html +2 -2
- package/docs/design/mocks/hub/v3.html +5 -5
- package/docs/design/mocks/hub/wizard.html +1 -1
- package/docs/design/mocks/landing/v1.html +1 -1
- package/docs/design/mocks/landing/v2.html +1 -1
- package/docs/design/mocks/landing/v3.html +1 -1
- package/docs/design/mocks/landing-page-mock.html +3 -3
- package/docs/design/mocks/projects-hub-mock.html +1 -1
- package/docs/design/mocks/projects-hub-variants.html +1 -1
- package/docs/design/mocks/projects-page-mock.html +1 -1
- package/docs/design/projects-hub-direction-v2.md +23 -0
- package/docs/file-map.md +3 -3
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +9 -2
- package/docs/recipes/ui-look-before-you-ship.md +4 -4
- package/modules/copy-desk/tests/copy_flags.mjs +4 -4
- package/modules/platform-identity/routes/projects.js +2 -1
- package/modules/provisioning/provisioning.js +11 -2
- package/modules/provisioning/routes/provisioning.js +10 -5
- package/modules/public-landing/CLAUDE.md +15 -0
- package/modules/public-landing/module.json +15 -0
- package/{public-landing → modules/public-landing/public}/assets/cosmos.css +8 -0
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +14 -0
- package/modules/public-landing/public/assets/world/r4-card-deepfield.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-deepfield.jpg.json +14 -0
- package/modules/public-landing/public/assets/world/r4-card-dust.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-dust.jpg.json +14 -0
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +14 -0
- package/{public-landing → modules/public-landing/public}/projects-floor.states.json +28 -1
- package/{public-landing → modules/public-landing/public}/projects.html +101 -1
- package/{public-landing → modules/public-landing/public}/projects.probes.json +6 -0
- package/{public-landing → modules/public-landing/public}/projects.states.json +1 -0
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -1
- package/modules/ui-design/kit/lib.js +2 -2
- package/modules/ui-design/kit/render.js +1 -1
- package/modules/ui-design/kit/serve.js +32 -7
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/copy-inventory.js +3 -3
- package/scripts/gds/fitness-checks-identity.js +18 -1
- package/scripts/gds/publish-manifest.js +1 -1
- package/src/bongos/module-scope-map.js +9 -7
- package/src/bongos/serve-internal.js +13 -1
- package/src/module-api.js +1 -1
- package/src/module-loader/loader.js +34 -17
- package/src/module-loader/manifest-schema.js +16 -8
- package/src/platform-server.js +50 -18
- package/tests/copy_inventory.mjs +0 -0
- package/tests/cosmos_token_contrast.mjs +6 -6
- package/tests/fitness.mjs +20 -0
- package/tests/landing_legal_pages.mjs +2 -1
- package/tests/landing_page.mjs +7 -5
- package/tests/module-scope-map.mjs +2 -2
- package/tests/module_loader.mjs +4 -1
- package/tests/onboard.mjs +5 -5
- package/tests/projects_hub.mjs +105 -20
- package/tests/projects_hub_pre_uat.mjs +5 -3
- package/tests/provisioning_settings.mjs +49 -4
- package/tests/public_landing_projects.mjs +2 -2
- package/tests/terms_acceptance.mjs +1 -1
- package/tests/ui_design_kit.mjs +3 -3
- package/tests/world_tokens.mjs +3 -2
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_00.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_01.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_02.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_03.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_04.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_05.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_06.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_07.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_08.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_09.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_10.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_11.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_12.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_13.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_14.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_15.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_16.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_17.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_18.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_19.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_20.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_21.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_22.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_23.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_24.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_25.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_26.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_27.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_28.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_29.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_30.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_31.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_32.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_33.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_34.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_35.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_36.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_37.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_38.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_39.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_40.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_41.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_42.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_43.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/verro-medallion.png +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/og-card.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-space-nebula.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-space-nebula.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world.css +0 -0
- /package/{public-landing → modules/public-landing/public}/buddha.png +0 -0
- /package/{public-landing → modules/public-landing/public}/contact.html +0 -0
- /package/{public-landing → modules/public-landing/public}/index.html +0 -0
- /package/{public-landing → modules/public-landing/public}/index.probes.json +0 -0
- /package/{public-landing → modules/public-landing/public}/index.states.json +0 -0
- /package/{public-landing → modules/public-landing/public}/privacy.html +0 -0
- /package/{public-landing → modules/public-landing/public}/projects-empty.states.json +0 -0
- /package/{public-landing → modules/public-landing/public}/terms.html +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/clouds/cloud-island-large.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/clouds/cloud-puff.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/creatures/creature-whale-cargo.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/creatures/creature-whale-glide.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/plates/plate-clouds-near.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/plates/scene-establishing.png +0 -0
package/tests/projects_hub.mjs
CHANGED
|
@@ -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
|
-
|
|
23
|
-
const
|
|
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(
|
|
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(
|
|
259
|
-
`
|
|
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(
|
|
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
|
|
453
|
-
assert.doesNotMatch(
|
|
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
|
|
|
@@ -558,7 +560,7 @@ test('the ladder: each region hides on null and the other figure stands alone',
|
|
|
558
560
|
assert.match(art, /classList\.toggle\('has-art', !!url\)/, 'art switches the whole layout');
|
|
559
561
|
assert.match(SCRIPT, /\$\('cardArt'\)\.addEventListener\('error', function \(\) \{ cardArt\(''\); \}\);/,
|
|
560
562
|
'a broken image demotes to the no-art layout, never a broken glyph');
|
|
561
|
-
assert.match(SCRIPT, /
|
|
563
|
+
assert.match(SCRIPT, /safeHttpUrl\(p\.art_url\)\)\);/, 'the art URL is scheme-checked client-side too (behind the owner\'s plate since task 1003352)');
|
|
562
564
|
});
|
|
563
565
|
|
|
564
566
|
test('exactly one hall-URL derivation: the DOM for the hub, the feed for a project, safeHttpUrl for both, and only where the hall is elsewhere', () => {
|
|
@@ -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
|
|
1574
|
+
const LANDING_HTML = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
|
|
1573
1575
|
const landingCrop = (cls) => {
|
|
1574
|
-
const m =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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');
|
|
@@ -1782,10 +1784,10 @@ test('the kit proves the picker: a state renders it on the owner\'s feed-carried
|
|
|
1782
1784
|
assert.match(stub, /planet: PLANETS\.get\(String\(id\)\) \|\| NO_PLANET/, 'and answers it on the settings read and the receipt');
|
|
1783
1785
|
});
|
|
1784
1786
|
|
|
1785
|
-
test('the feed carries the owner\'s choice through a seam: platform-identity resolves provisioning.projectPlanets optionally and spreads
|
|
1787
|
+
test('the feed carries the owner\'s choice through a seam: platform-identity resolves provisioning.projectPlanets optionally and spreads every platform-drawn key on every project (tasks 1003347, 1003352)', () => {
|
|
1786
1788
|
const route = fs.readFileSync(path.join(ROOT, 'modules', 'platform-identity', 'routes', 'projects.js'), 'utf8');
|
|
1787
1789
|
assert.match(route, /api\.resolveOptional\('provisioning\.projectPlanets', async \(\) => \(\{\}\)\)\(projects\.map\(\(p\) => hostOfOrigin\(p\.origin\)\)\)/, 'optional — an instance without provisioning has no choices to carry');
|
|
1788
|
-
assert.match(route, /return \{ planet_template: c\.planet_template \|\| null, planet_accent: c\.planet_accent \|\| null \};/, 'the wire always carries
|
|
1790
|
+
assert.match(route, /return \{ planet_template: c\.planet_template \|\| null, planet_accent: c\.planet_accent \|\| null,\r?\n\s*card_backdrop: c\.card_backdrop \|\| null \};/, 'the wire always carries every key — null = nothing chosen');
|
|
1789
1791
|
assert.match(route, /\.\.\.planetOf\(p\),/, 'spread beside the stats');
|
|
1790
1792
|
assert.doesNotMatch(route, /require\(['"][^'"]*provisioning/, 'never an import across modules');
|
|
1791
1793
|
const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, 'modules', 'provisioning', 'module.json'), 'utf8'));
|
|
@@ -1827,6 +1829,89 @@ test('the sort left the column head for a control the keyboard and a screen read
|
|
|
1827
1829
|
assert.match(COSMOS, /\.tblSort button:focus-visible\{outline:2px solid var\(--accent\);outline-offset:2px;\}/, 'keyboard focus on the sort is visible');
|
|
1828
1830
|
});
|
|
1829
1831
|
|
|
1832
|
+
/* ── the card's backdrop (task 1003352): the owner's plate, beside the planet ── */
|
|
1833
|
+
|
|
1834
|
+
test('the kit stub refuses what the real route refuses, and its copied vocabulary cannot rot', async () => {
|
|
1835
|
+
// A kit module may not import core, so serve.js copies the closed lists. The
|
|
1836
|
+
// copy is what matters: it stayed permissive through a whole probe run while
|
|
1837
|
+
// the real PATCH would have 400'd on card_backdrop (task 1003352, grade round
|
|
1838
|
+
// 2), so the stub now validates — and this pins the copy to its source.
|
|
1839
|
+
const stub = fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'serve.js'), 'utf8');
|
|
1840
|
+
assert.match(stub, /function settingsRejection\(body\)/, 'the stub validates a settings body');
|
|
1841
|
+
assert.match(stub, /if \(bad\) return json\(res, 400, \{ error: \{ code: 'validation_failed'/, 'and answers the real code');
|
|
1842
|
+
const provisioning = createRequire(import.meta.url)('../modules/provisioning/provisioning.js');
|
|
1843
|
+
const block = (stub.match(/const STUB_VOCAB = \{([\s\S]*?)\n\};/) || [, ''])[1];
|
|
1844
|
+
assert.ok(block, 'STUB_VOCAB must keep its extractable shape');
|
|
1845
|
+
const real = { ...provisioning.SETTINGS_VOCAB, ...provisioning.PLANET_VOCAB };
|
|
1846
|
+
for (const [key, values] of Object.entries(real)) {
|
|
1847
|
+
assert.match(block, new RegExp(`\\b${key}:`), `the stub must know ${key}`);
|
|
1848
|
+
if (key === 'planet_template') continue; // the template set is long: shape is checked, not membership
|
|
1849
|
+
for (const v of values) assert.match(block, new RegExp(`'${v}'`), `the stub's ${key} is missing ${v}`);
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1853
|
+
test('the card backdrop is the same KIND of key as the planet: a closed platform-drawn list on the same channel, pushing nothing', () => {
|
|
1854
|
+
const prov = fs.readFileSync(path.join(ROOT, 'modules', 'provisioning', 'provisioning.js'), 'utf8');
|
|
1855
|
+
assert.match(prov, /const CARD_BACKDROPS = Object\.freeze\(\['nebula', 'dust', 'deepfield', 'chrome'\]\);/, 'a closed list, frozen');
|
|
1856
|
+
// riding PLANET_VOCAB is the whole point: planetChoice() loops it, the settings
|
|
1857
|
+
// route builds its validator and its settingKeys from it, and publicInstance
|
|
1858
|
+
// spreads it — so the key reaches the row, the read and the wire with no new branch
|
|
1859
|
+
assert.match(prov, /planet_template: PLANET_TEMPLATES, planet_accent: PLANET_ACCENTS, card_backdrop: CARD_BACKDROPS,/, 'it rides the platform-drawn vocab');
|
|
1860
|
+
// and the route's SCHEMA is that vocab, not a copy of it: a hand-written list
|
|
1861
|
+
// is what let card_backdrop reach settingKeys while strict validation still
|
|
1862
|
+
// 400'd every real save (grade round 2)
|
|
1863
|
+
const settingsRoute = fs.readFileSync(path.join(ROOT, 'modules', 'provisioning', 'routes', 'provisioning.js'), 'utf8');
|
|
1864
|
+
assert.match(settingsRoute, /const vocabSchema = \(vocab\) => Object\.fromEntries\(/, 'the schema is derived');
|
|
1865
|
+
assert.match(settingsRoute, /\.\.\.vocabSchema\(provisioning\.SETTINGS_VOCAB\),\r?\n\s*\.\.\.vocabSchema\(provisioning\.PLANET_VOCAB\),/, 'from both vocabularies');
|
|
1866
|
+
assert.doesNotMatch(settingsRoute, /planet_template: \{ type: 'string', enum: provisioning\.PLANET_VOCAB/, 'no hand-written key survives to drift');
|
|
1867
|
+
assert.match(prov, /if \(Object\.values\(choice\)\.some\(Boolean\)\) out\[r\.host\] = choice;/, 'a host that chose ONLY a backdrop still reaches the feed');
|
|
1868
|
+
const route = fs.readFileSync(path.join(ROOT, 'modules', 'platform-identity', 'routes', 'projects.js'), 'utf8');
|
|
1869
|
+
assert.match(route, /card_backdrop: c\.card_backdrop \|\| null/, 'the wire always carries the key — null = nothing chosen');
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
test("the card paints the owner's plate before the project's own picture, and a key the platform does not draw paints nothing", () => {
|
|
1873
|
+
assert.match(SCRIPT, /cardArt\(p\.isSelf \? '' : \(backdropPlate\(p\.card_backdrop\) \|\| safeHttpUrl\(p\.art_url\)\)\);/,
|
|
1874
|
+
"the plate wins; the project's own art is the fallback, which is all the card read before");
|
|
1875
|
+
const fn = (SCRIPT.match(/function backdropPlate\(key\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
1876
|
+
assert.ok(fn, 'backdropPlate() must exist');
|
|
1877
|
+
assert.match(fn, /CARD_PLATES\[key\] \? '\/assets\/world\/' \+ CARD_PLATES\[key\] : ''/,
|
|
1878
|
+
'an unknown key — a row written before a plate was retired — must never become a broken src');
|
|
1879
|
+
const plates = (SCRIPT.match(/var CARD_PLATES = \{([^}]*)\}/) || [, ''])[1];
|
|
1880
|
+
const offered = (SCRIPT.match(/var CARD_BACKDROPS = ([\s\S]*?);\r?\n/) || [, ''])[1];
|
|
1881
|
+
for (const k of ['nebula', 'dust', 'deepfield', 'chrome']) {
|
|
1882
|
+
assert.match(plates, new RegExp(`\\b${k}\\b`), `${k} has a plate`);
|
|
1883
|
+
assert.match(offered, new RegExp(`'${k}'`), `${k} is offered in the picker`);
|
|
1884
|
+
}
|
|
1885
|
+
});
|
|
1886
|
+
|
|
1887
|
+
test('every plate the picker offers ships as a real file with its provenance sidecar', () => {
|
|
1888
|
+
for (const k of ['nebula', 'dust', 'deepfield', 'chrome']) {
|
|
1889
|
+
const file = path.join(LANDING, 'assets', 'world', `r4-card-${k}.jpg`);
|
|
1890
|
+
assert.ok(fs.existsSync(file), `r4-card-${k}.jpg must ship — the picker offers it`);
|
|
1891
|
+
const side = JSON.parse(fs.readFileSync(`${file}.json`, 'utf8'));
|
|
1892
|
+
assert.equal(side.task, 1003352, 'the sidecar names the task that made it');
|
|
1893
|
+
assert.ok(side.model && side.prompt, 'and the model and the prompt, so it can be remade');
|
|
1894
|
+
assert.equal(side.aspect_ratio, '5:2', "a card band, not a field ground — the reason this round existed");
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
test("its card is the planet section's twin: its own heading, the same channel, no restart", () => {
|
|
1899
|
+
// beside the planet rather than inside it, so each heading stays true: one is
|
|
1900
|
+
// how the project is drawn everywhere, the other is one surface's backdrop
|
|
1901
|
+
assert.match(HUB, /<div class="card mSec" id="mCard">\s*<h2>Its card<\/h2>/, 'its own section');
|
|
1902
|
+
assert.ok(HUB.indexOf('id="mCard"') > HUB.indexOf('id="mPlanetPick"'), 'and it stands after the planet, where the owner asked for it');
|
|
1903
|
+
assert.match(SCRIPT, /body: JSON\.stringify\(\{ card_backdrop: mCard \|\| null \}\)/, "the save is the settings PATCH; '' hands the pick back as null");
|
|
1904
|
+
assert.match(SCRIPT, /manageInst\.card_backdrop = \(data\.planet \|\| \{\}\)\.card_backdrop \|\| null;/,
|
|
1905
|
+
'the reply is a receipt — the page repaints from the row, never from words in the reply');
|
|
1906
|
+
assert.match(SCRIPT, /renderPick\(inst\);\r?\n\s*renderCardPick\(inst\);/, 'both sections fill from the same manage read');
|
|
1907
|
+
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8'));
|
|
1908
|
+
assert.ok(states.states['in-manage-card'], 'the section has a rendered state');
|
|
1909
|
+
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8'));
|
|
1910
|
+
const names = probes.probes.map((p) => p.name);
|
|
1911
|
+
assert.ok(names.some((n) => n.startsWith('manage flows: its card (task 1003352)')), 'the save probe');
|
|
1912
|
+
assert.ok(names.some((n) => n.includes("its card — handing it back")), 'the hand-back probe');
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1830
1915
|
/* ── the create wizard in the world (task 1003346; direction record §15, the pick: v1 the
|
|
1831
1916
|
rail above, the bongo pair kept). Two sheets carry it: cosmos.css draws the FRAME by scope
|
|
1832
1917
|
(#view-new), projects.html's own wizard block draws the wizard's structures. ── */
|
|
@@ -1877,8 +1962,8 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
|
|
|
1877
1962
|
assert.match(SCRIPT, /target_ref: state\.owner \+ '\/' \+ state\.repo,/, 'the repo ref is unchanged');
|
|
1878
1963
|
assert.match(SCRIPT, /onboard_mode: state\.mode === 'adopt' \? 'adopt' : 'greenfield'/, 'the onboard mode is unchanged');
|
|
1879
1964
|
// the kit walks every step and files against the stub
|
|
1880
|
-
const states = JSON.parse(fs.readFileSync(path.join(
|
|
1965
|
+
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8')).states;
|
|
1881
1966
|
for (const s of ['type', 'about', 'home', 'address', 'review', 'done']) assert.ok(states['in-new-' + s], `the ${s} step has a kit state`);
|
|
1882
|
-
const probes = JSON.parse(fs.readFileSync(path.join(
|
|
1967
|
+
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
|
|
1883
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');
|
|
1884
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(
|
|
17
|
-
const COSMOS = fs.readFileSync(path.join(
|
|
18
|
-
const INDEX = fs.readFileSync(path.join(
|
|
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
|
|
@@ -166,10 +166,13 @@ await ta('owner reads DEFAULTS (public / apply) on a row with empty settings —
|
|
|
166
166
|
assert.equal(r.status, 200, r.raw);
|
|
167
167
|
assert.deepEqual(r.body.settings, { platform_visibility: 'public', joinability: 'apply' });
|
|
168
168
|
// the planet rides beside the settings (task 1003347): no default — null = the platform's own pick
|
|
169
|
-
assert.deepEqual(r.body.planet, { planet_template: null, planet_accent: null });
|
|
169
|
+
assert.deepEqual(r.body.planet, { planet_template: null, planet_accent: null, card_backdrop: null });
|
|
170
170
|
assert.deepEqual(r.body.options, {
|
|
171
171
|
platform_visibility: ['public', 'gated'], joinability: ['open', 'apply', 'invite_only'],
|
|
172
172
|
planet_template: [...provisioning.PLANET_TEMPLATES], planet_accent: ['sea', 'terracotta', 'gold-soft', 'sun-lit', 'accent'],
|
|
173
|
+
// the card's backdrop (task 1003352) is the same kind of key: platform-drawn,
|
|
174
|
+
// closed list, no default, no push — so it rides the same vocab
|
|
175
|
+
card_backdrop: ['nebula', 'dust', 'deepfield', 'chrome'],
|
|
173
176
|
});
|
|
174
177
|
});
|
|
175
178
|
|
|
@@ -177,7 +180,7 @@ await ta('a stored planet reads back; a name that left the vocabulary reads null
|
|
|
177
180
|
reset({ inst: { owner_builder_id: 7, slug: 'spike', status: 'active', settings: { planet_template: 'bead-obsidian-tint', planet_accent: 'purple' } } });
|
|
178
181
|
const r = await get('/provisioning/instances/18/settings');
|
|
179
182
|
assert.equal(r.status, 200, r.raw);
|
|
180
|
-
assert.deepEqual(r.body.planet, { planet_template: 'bead-obsidian-tint', planet_accent: null });
|
|
183
|
+
assert.deepEqual(r.body.planet, { planet_template: 'bead-obsidian-tint', planet_accent: null, card_backdrop: null });
|
|
181
184
|
assert.deepEqual(r.body.settings, { platform_visibility: 'public', joinability: 'apply' }, 'the planet is not a setting: the policy read is untouched');
|
|
182
185
|
});
|
|
183
186
|
|
|
@@ -268,7 +271,7 @@ await ta('the owner picks a planet: saved, echoed as `planet`, audited — and N
|
|
|
268
271
|
reset();
|
|
269
272
|
const r = await patch('/provisioning/instances/18/settings', { planet_template: 'bead-obsidian-tint', planet_accent: 'sea' });
|
|
270
273
|
assert.equal(r.status, 200, r.raw);
|
|
271
|
-
assert.deepEqual(r.body.planet, { planet_template: 'bead-obsidian-tint', planet_accent: 'sea' });
|
|
274
|
+
assert.deepEqual(r.body.planet, { planet_template: 'bead-obsidian-tint', planet_accent: 'sea', card_backdrop: null });
|
|
272
275
|
assert.deepEqual(r.body.saved, { platform_visibility: 'public', joinability: 'apply' }, 'the policy half is untouched');
|
|
273
276
|
assert.deepEqual(updateCalls, [{ id: 18, patch: { planet_template: 'bead-obsidian-tint', planet_accent: 'sea' } }]);
|
|
274
277
|
assert.equal(r.body.push.queued, false);
|
|
@@ -283,7 +286,7 @@ await ta('null hands the pick back to the platform (stored as null, read back as
|
|
|
283
286
|
reset({ inst: { owner_builder_id: 7, slug: 'spike', status: 'active', hosting_shape: 'co-tenant', settings: { planet_template: 'bead-obsidian-tint', planet_accent: 'sea' } } });
|
|
284
287
|
const r = await patch('/provisioning/instances/18/settings', { planet_template: null, planet_accent: null });
|
|
285
288
|
assert.equal(r.status, 200, r.raw);
|
|
286
|
-
assert.deepEqual(r.body.planet, { planet_template: null, planet_accent: null });
|
|
289
|
+
assert.deepEqual(r.body.planet, { planet_template: null, planet_accent: null, card_backdrop: null });
|
|
287
290
|
assert.deepEqual(updateCalls, [{ id: 18, patch: { planet_template: null, planet_accent: null } }]);
|
|
288
291
|
assert.equal(enqueueCalls, 0);
|
|
289
292
|
});
|
|
@@ -316,6 +319,48 @@ await ta('a policy change and a planet change in one save: the push queues for t
|
|
|
316
319
|
assert.equal(enqueueCalls, 1);
|
|
317
320
|
});
|
|
318
321
|
|
|
322
|
+
await ta('the owner picks a card backdrop: the REAL router accepts it, echoes it, and still queues no push (task 1003352)', async () => {
|
|
323
|
+
// the regression this exists for: card_backdrop was added to PLANET_VOCAB (so
|
|
324
|
+
// settingKeys copied it) while the route's validator was a hand-written list
|
|
325
|
+
// that did not declare it — and strict mode 400s an undeclared field, so every
|
|
326
|
+
// real save from the manage page failed while the kit's probes stayed green
|
|
327
|
+
reset();
|
|
328
|
+
const r = await patch('/provisioning/instances/18/settings', { card_backdrop: 'nebula' });
|
|
329
|
+
assert.equal(r.status, 200, r.raw);
|
|
330
|
+
assert.deepEqual(r.body.planet, { planet_template: null, planet_accent: null, card_backdrop: 'nebula' });
|
|
331
|
+
assert.deepEqual(updateCalls, [{ id: 18, patch: { card_backdrop: 'nebula' } }]);
|
|
332
|
+
assert.equal(r.body.push.queued, false, 'the card is the platform\'s to draw — nothing is pushed');
|
|
333
|
+
assert.equal(r.body.push.restart, false);
|
|
334
|
+
assert.equal(enqueueCalls, 0, 'no settings-apply intent for a backdrop change');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await ta('null hands the card backdrop back to the platform, like the planet', async () => {
|
|
338
|
+
reset({ inst: { owner_builder_id: 7, slug: 'spike', status: 'active', settings: { card_backdrop: 'chrome' } } });
|
|
339
|
+
const r = await patch('/provisioning/instances/18/settings', { card_backdrop: null });
|
|
340
|
+
assert.equal(r.status, 200, r.raw);
|
|
341
|
+
assert.deepEqual(r.body.planet, { planet_template: null, planet_accent: null, card_backdrop: null });
|
|
342
|
+
assert.deepEqual(updateCalls, [{ id: 18, patch: { card_backdrop: null } }]);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
await ta('an off-vocabulary card backdrop → 400 validation_failed (a closed list, not a free string)', async () => {
|
|
346
|
+
reset();
|
|
347
|
+
const r = await patch('/provisioning/instances/18/settings', { card_backdrop: 'sunset' });
|
|
348
|
+
assert.equal(r.status, 400, r.raw);
|
|
349
|
+
assert.equal(r.body.error.code, 'validation_failed');
|
|
350
|
+
assert.equal(updateCalls.length, 0);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
await ta('the route\'s schema IS its vocabularies — every declared key is accepted, so one can never drift out', async () => {
|
|
354
|
+
// the guard against this whole class: a key added to SETTINGS_VOCAB or
|
|
355
|
+
// PLANET_VOCAB must be accepted by the validator without anyone remembering
|
|
356
|
+
const every = { ...provisioning.SETTINGS_VOCAB, ...provisioning.PLANET_VOCAB };
|
|
357
|
+
for (const [key, values] of Object.entries(every)) {
|
|
358
|
+
reset();
|
|
359
|
+
const r = await patch('/provisioning/instances/18/settings', { [key]: values[0] });
|
|
360
|
+
assert.equal(r.status, 200, `${key}: ${r.raw}`);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
319
364
|
await ta('an off-vocabulary planet → 400 validation_failed (a colour is a token NAME, never a hex)', async () => {
|
|
320
365
|
reset();
|
|
321
366
|
for (const body of [{ planet_accent: '#fb9569' }, { planet_accent: 'purple' }, { planet_template: 'cube-gold-wash' }]) {
|
|
@@ -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
|
|
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', () => {
|
package/tests/ui_design_kit.mjs
CHANGED
|
@@ -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
|
}
|
package/tests/world_tokens.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp
RENAMED
|
File without changes
|
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg.json
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp
RENAMED
|
File without changes
|
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg.json
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp
RENAMED
|
File without changes
|
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg.json
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp.json
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp.json
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp.json
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp.json
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp
RENAMED
|
File without changes
|
/package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp.json
RENAMED
|
File without changes
|