@bongos/core 1.19.413 → 1.19.415
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 +190 -40
- package/docs/adr/0204-the-apex-is-dark-only.md +36 -0
- package/docs/adr/README.md +1 -0
- package/docs/copy-inventory.md +310 -312
- package/docs/copy-registry.json +394 -412
- package/docs/design/landing-page-direction-v2.md +2 -0
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg.json +13 -0
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg.json +13 -0
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg.json +13 -0
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg.json +13 -0
- package/docs/design/mocks/hub/planets.html +127 -3
- package/docs/design/projects-hub-direction-v2.md +14 -0
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/ui-look-before-you-ship.md +4 -1
- package/modules/platform-identity/migrations/platform_identity_013_project_template_materials.sql +31 -0
- package/modules/platform-identity/platform-identity.js +18 -5
- package/modules/platform-identity/routes/projects.js +3 -2
- package/modules/platform-identity/tests/platform-identity.mjs +23 -12
- package/modules/ui-design/kit/fixtures/featured.json +1 -1
- package/modules/ui-design/kit/lib.js +3 -1
- package/modules/ui-design/kit/render.js +5 -4
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +50 -18
- package/public-landing/assets/world/r3-frost-bead-cut.webp +0 -0
- package/public-landing/assets/world/r3-frost-bead-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-frost-blob-cut.webp +0 -0
- package/public-landing/assets/world/r3-frost-blob-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-frost-pair-cut.webp +0 -0
- package/public-landing/assets/world/r3-frost-pair-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-gold-bead-cut.webp +0 -0
- package/public-landing/assets/world/r3-gold-bead-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-gold-blob-cut.webp +0 -0
- package/public-landing/assets/world/r3-gold-blob-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-gold-pair-cut.webp +0 -0
- package/public-landing/assets/world/r3-gold-pair-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-obsidian-bead-cut.webp +0 -0
- package/public-landing/assets/world/r3-obsidian-bead-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-obsidian-blob-cut.webp +0 -0
- package/public-landing/assets/world/r3-obsidian-blob-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-obsidian-pair-cut.webp +0 -0
- package/public-landing/assets/world/r3-obsidian-pair-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-space-nebula.jpg +0 -0
- package/public-landing/assets/world/r3-space-nebula.jpg.json +13 -0
- package/public-landing/contact.html +2 -6
- package/public-landing/index.html +5 -44
- package/public-landing/index.states.json +3 -2
- package/public-landing/privacy.html +2 -6
- package/public-landing/projects.html +27 -55
- package/public-landing/projects.probes.json +0 -3
- package/public-landing/projects.states.json +4 -3
- package/public-landing/terms.html +2 -6
- package/scripts/gds/worktree.js +11 -0
- package/src/module-api.js +1 -1
- package/tests/landing_legal_pages.mjs +10 -0
- package/tests/landing_page.mjs +16 -19
- package/tests/projects_hub.mjs +69 -19
- package/tests/projects_hub_pre_uat.mjs +4 -3
- package/tests/ui_design_kit.mjs +10 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "The hub's states for modules/ui-design/kit/render.js (task 1003320): name → url + actions + expectations. `auth` picks the signed-in stub. Rendered dark
|
|
2
|
+
"_": "The hub's states for modules/ui-design/kit/render.js (task 1003320): name → url + actions + expectations. `auth` picks the signed-in stub. Rendered dark only at 1440/390/320 (the apex pins data-mode=\"dark\", task 1003336) and audited against the platform floors. The card states (task 1003301) walk the poster's ladder: the full poster, the artless two-up band, the all-null floor, the platform's own card.",
|
|
3
3
|
"page": "/projects",
|
|
4
4
|
"surface": "public-landing",
|
|
5
|
-
"
|
|
5
|
+
"modes": ["dark"],
|
|
6
|
+
"modeQuery": false,
|
|
6
7
|
"ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects", "/provisioning/"],
|
|
7
8
|
"ignoreConsole": ["/api/(gds|bongos)/me", "\\b401\\b", "Unauthorized"],
|
|
8
9
|
"ignoreControls": [".skip", ".vh"],
|
|
9
10
|
"states": {
|
|
10
11
|
"out-map": { "auth": false, "expect": { "expanded": false } },
|
|
11
|
-
"in-map": { "auth": true, "expect": { "expanded": false, "count": [["#orbField .worldOrb[data-shape][data-style]", 4]], "attr": [["#selfOrb", "data-template", "pair-chrome"], ["#orbField a.worldOrb[data-origin=\"https://amazonprimea.com\"]", "data-template", "bead-wash"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-shape", "pair"], ["#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]", "data-template", "bead-
|
|
12
|
+
"in-map": { "auth": true, "expect": { "expanded": false, "count": [["#orbField .worldOrb[data-shape][data-style]", 4]], "attr": [["#selfOrb", "data-template", "pair-chrome"], ["#orbField a.worldOrb[data-origin=\"https://amazonprimea.com\"]", "data-template", "bead-wash"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-shape", "pair"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-material", "gold"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-template", "pair-gold-wash"], ["#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]", "data-template", "bead-obsidian-plain"]] } },
|
|
12
13
|
"in-map-projects": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProjects" } },
|
|
13
14
|
"in-map-profile": { "auth": true, "actions": [["click", "#who"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProfile" } },
|
|
14
15
|
"in-map-kbd": { "auth": true, "actions": [["focus", "#projTrig"], ["press", "Enter"], ["wait", 350], ["press", "ArrowDown"], ["wait", 120]], "expect": { "expanded": true, "activeText": "Table" } },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
2
|
+
<html lang="en" data-mode="dark">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
@@ -15,11 +15,7 @@
|
|
|
15
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
16
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
17
17
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
18
|
-
<
|
|
19
|
-
/* Mode, resolved before first paint — the same contract as every landing page. */
|
|
20
|
-
(function(){try{var m=localStorage.getItem('cb-color-mode');
|
|
21
|
-
if(m==='light'||m==='dark')document.documentElement.setAttribute('data-mode',m);}catch(e){}})();
|
|
22
|
-
</script>
|
|
18
|
+
<!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
23
19
|
<style>
|
|
24
20
|
/* The reading column only. No :root block — the pack's injected theme owns the
|
|
25
21
|
tokens (landing direction C1); everything below reads them. */
|
package/scripts/gds/worktree.js
CHANGED
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
// junction BEFORE git worktree remove. Worktrees INSIDE the repo
|
|
12
12
|
// (.claude/worktrees/<name>, the project default) need no junction at all.
|
|
13
13
|
//
|
|
14
|
+
// THE OTHER SHARED-STATE TRAP — `git stash` (task 1003344). The stash stack
|
|
15
|
+
// belongs to the REPOSITORY, not the worktree: every worktree shares one stack
|
|
16
|
+
// with the main checkout and with each other, so a stash here and a pop there
|
|
17
|
+
// hands one agent another's tree and silently drops the original entry. Commit
|
|
18
|
+
// on your own branch instead — a commit is worktree-local, a stash is not. The
|
|
19
|
+
// full rule (named-stash escape hatch, clean-baseline advice, and the
|
|
20
|
+
// `git fsck --unreachable` recovery path) has ONE canonical copy, in
|
|
21
|
+
// scripts/gds/CLAUDE.md under "Gotchas / invariants", and is learning 1000215,
|
|
22
|
+
// which claim.js surfaces at claim time. Deliberately short here: two full
|
|
23
|
+
// copies of the same prose would drift apart.
|
|
24
|
+
//
|
|
14
25
|
// Usage:
|
|
15
26
|
// node scripts/gds/worktree.js add <task-id|name> [--path <dir> | --outside] [--base <ref>]
|
|
16
27
|
// node scripts/gds/worktree.js remove <name|path> [--force]
|
package/src/module-api.js
CHANGED
|
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
33
33
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
34
34
|
// the entry to that file. Look for a version's history there, not here.
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
|
-
const CORE_VERSION = '1.19.
|
|
36
|
+
const CORE_VERSION = '1.19.415'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
37
37
|
|
|
38
38
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
39
39
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -160,3 +160,13 @@ for (const p of PAGES) {
|
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
// ── dark only (task 1003336) ──────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
test('the legal pages are dark only — data-mode="dark" fixed on <html>, no mode script (task 1003336)', () => {
|
|
167
|
+
for (const p of PAGES) {
|
|
168
|
+
const html = read(p.file);
|
|
169
|
+
assert.match(html, /^<html lang="en" data-mode="dark">/m, `${p.file}: dark is fixed on <html>`);
|
|
170
|
+
assert.doesNotMatch(html, /cb-color-mode|setAttribute\('data-mode'/, `${p.file}: nothing reads or writes a mode`);
|
|
171
|
+
}
|
|
172
|
+
});
|
package/tests/landing_page.mjs
CHANGED
|
@@ -55,8 +55,6 @@ const PAGE_CSS = PAGE_STYLES.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
|
55
55
|
// The page script minus its comments — these assertions are about what the code
|
|
56
56
|
// DOES, and the comments quote the very shapes being ruled out.
|
|
57
57
|
const CODE = HTML.split('<script>').pop().replace(/\/\*[\s\S]*?\*\//g, '');
|
|
58
|
-
// The pre-paint mode script, which is the FIRST <script> in <head>.
|
|
59
|
-
const PREPAINT = HTML.split('<script>')[1].split('</script>')[0];
|
|
60
58
|
|
|
61
59
|
// ── (1) the apex conditional ───────────────────────────────────────────────
|
|
62
60
|
|
|
@@ -255,11 +253,11 @@ test('every dark-mode rule is written twice — the block and its media twin (C2
|
|
|
255
253
|
const explicit = (PAGE_CSS.match(/:root\[data-mode="dark"\]/g) || []).length;
|
|
256
254
|
const media = (PAGE_CSS.match(/:root:not\(\[data-mode="light"\]\)/g) || []).length;
|
|
257
255
|
assert.equal(explicit, media, 'a dark rule with no prefers-color-scheme twin only half-works');
|
|
258
|
-
// the shipped page writes
|
|
259
|
-
// one
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
assert.ok(explicit >=
|
|
256
|
+
// the shipped page writes two: the wide plate/geometry token block and the
|
|
257
|
+
// narrow one (the mode-toggle icon swap left with the toggle, task 1003336).
|
|
258
|
+
// The floor is the count observed at the build, so deleting a twin fails even
|
|
259
|
+
// when the pair stays balanced.
|
|
260
|
+
assert.ok(explicit >= 2, `expected at least 2 dark rules, found ${explicit}`);
|
|
263
261
|
});
|
|
264
262
|
|
|
265
263
|
test('the page links world.css root-absolute, and does not fork the hub sheet', () => {
|
|
@@ -504,16 +502,15 @@ test('the tagline is one server-stamped string, split for the composition only',
|
|
|
504
502
|
assert.match(PAGE_CSS, /h1 \.whole\{[^}]*white-space:normal/, 'the whole-tagline fallback must wrap');
|
|
505
503
|
});
|
|
506
504
|
|
|
507
|
-
test('the mode is
|
|
508
|
-
//
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
assert.match(
|
|
512
|
-
assert.
|
|
513
|
-
assert.
|
|
514
|
-
|
|
515
|
-
assert.
|
|
516
|
-
assert.match(CODE, /localStorage\.setItem\('cb-color-mode', next\)/, 'the toggle persists the choice');
|
|
505
|
+
test('the apex is dark only: data-mode="dark" is fixed on <html>, nothing reads or writes a mode (task 1003336)', () => {
|
|
506
|
+
// The owner's call on the planets review: light stays the builders' hall's.
|
|
507
|
+
// A fixed attribute beats a script — nothing to flash, nothing to persist,
|
|
508
|
+
// and the OS preference never reaches the page.
|
|
509
|
+
assert.match(HTML, /^<html lang="en" data-mode="dark">/m);
|
|
510
|
+
assert.doesNotMatch(HTML, /cb-color-mode/, 'no persisted choice is read or written');
|
|
511
|
+
assert.doesNotMatch(HTML, /URLSearchParams\(location\.search\)\.get\('mode'\)/, '?mode= no longer walks a second mode');
|
|
512
|
+
assert.doesNotMatch(HTML, /matchMedia\('\(prefers-color-scheme: dark\)'\)/, 'no OS-preference read in script');
|
|
513
|
+
assert.doesNotMatch(CODE, /setAttribute\('data-mode'/, 'nothing flips the mode');
|
|
517
514
|
});
|
|
518
515
|
|
|
519
516
|
// ── the assertions tests/platform_boot.mjs will make once the apex flips ───
|
|
@@ -530,13 +527,13 @@ test('the page satisfies the parked platform_boot assertions', () => {
|
|
|
530
527
|
// the profile band. Everything a visitor can reach is markup; the script only
|
|
531
528
|
// opens and closes. These pin the shape the grader could not see from a diff.
|
|
532
529
|
|
|
533
|
-
test('the bar is markup: the mark, the Projects trigger, the
|
|
530
|
+
test('the bar is markup: the mark, the Projects trigger, the sign-in door — and no mode glyph (tasks 1003300, 1003336)', () => {
|
|
534
531
|
const body = HTML.split('<body>')[1].split('<script>')[0];
|
|
535
532
|
assert.match(body, /<header class="topbar bar">/, 'one bar, the hub\'s class beside the page\'s');
|
|
536
533
|
assert.match(body, /<a class="barMark" href="\/" aria-label="\{\{productName\}\}">\s*<svg viewBox="0 0 26 20"/, 'the static mark is the way home, labelled from the stamped name');
|
|
537
534
|
assert.match(body, /<span class="mark-word">\{\{productName\}\}<\/span>/, 'the product name as plain type, folded away under 900');
|
|
538
535
|
assert.match(body, /<button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv"/);
|
|
539
|
-
assert.
|
|
536
|
+
assert.doesNotMatch(body, /modeBtn|id="mode"|class="moon"|class="sun"/, 'the mode glyph left the bar — the apex is dark only (task 1003336)');
|
|
540
537
|
assert.match(body, /<span class="whoSlot" id="who">\s*<a class="pill signin" href="\/api\/bongos\/auth\/web\/start\?return=\/">Sign in<\/a>\s*<\/span>/, 'the door stands in the identity slot until the session resolves');
|
|
541
538
|
// what left with the pill bar
|
|
542
539
|
assert.doesNotMatch(body, /class="pill nav-pill"|class="right-pills"|class="pill mark-pill"|data-nav="home"/, 'no nav pill, no right pills, no Home');
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import assert from 'node:assert/strict';
|
|
|
16
16
|
import fs from 'node:fs';
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { createRequire } from 'node:module';
|
|
19
20
|
|
|
20
21
|
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
21
22
|
const HUB = fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.html'), 'utf8');
|
|
@@ -297,7 +298,7 @@ test('no id is declared twice — the wizard merge had four collisions to clear'
|
|
|
297
298
|
// id makes the second node unreachable by getElementById and the first
|
|
298
299
|
// silently wins. Scan the real markup only (JS builds ids like `dom-<iid>`
|
|
299
300
|
// from fragments that would false-positive here).
|
|
300
|
-
const afterHead = HUB.slice(HUB.indexOf('
|
|
301
|
+
const afterHead = HUB.slice(HUB.indexOf('<body'));
|
|
301
302
|
const markup = afterHead.slice(0, afterHead.indexOf('<script>'));
|
|
302
303
|
assert.ok(markup.includes('id="view-new"'), 'the markup slice must span the views');
|
|
303
304
|
const ids = [...markup.matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]);
|
|
@@ -1403,31 +1404,41 @@ test('the joinability in-flight line shares the visibility section’s push pred
|
|
|
1403
1404
|
/* ── the planets and the field (task 1003319) — the map's orbs as the landing's
|
|
1404
1405
|
chrome objects in the project's accent, a per-project template with a
|
|
1405
1406
|
deterministic default walk, and the field brought to the world. ── */
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1407
|
+
const TEMPLATE_RE = /^(blob|pair|bead)-(tint|wash|chrome|(frost|gold|obsidian)-(tint|wash|plain))$/;
|
|
1408
|
+
const partsOf = (t) => { const p = t.split('-'); return p.length === 3 ? p : [p[0], 'chrome', p[1]]; };
|
|
1409
|
+
|
|
1410
|
+
test('the planet templates: shape × material × style; the walk changes all three each step; the catalog and the migration agree (tasks 1003319, 1003337)', () => {
|
|
1411
|
+
const walkList = (SCRIPT.match(/var ORB_WALK = \[([^\]]*)\]/) || [, ''])[1];
|
|
1412
|
+
const walk = [...walkList.matchAll(/'([a-z-]+)'/g)].map((m) => m[1]);
|
|
1413
|
+
assert.equal(walk.length, 12);
|
|
1414
|
+
assert.equal(new Set(walk).size, 12, 'twelve distinct walk positions');
|
|
1415
|
+
for (const t of walk) assert.match(t, TEMPLATE_RE);
|
|
1416
|
+
for (let i = 0; i < walk.length; i++) {
|
|
1417
|
+
const [s0, m0, y0] = partsOf(walk[(i + walk.length - 1) % walk.length]);
|
|
1418
|
+
const [s1, m1, y1] = partsOf(walk[i]);
|
|
1419
|
+
assert.ok(s0 !== s1 && m0 !== m1 && y0 !== y1, `walk step ${i} changes shape, material and style (wrapping)`);
|
|
1416
1420
|
}
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1421
|
+
const P = createRequire(import.meta.url)('../modules/platform-identity/platform-identity.js');
|
|
1422
|
+
assert.deepEqual(P.PROJECT_TEMPLATE_WALK, walk, 'the hub and the catalog walk the same list in the same order');
|
|
1423
|
+
// the client's full set is built from the same grammar the server builds it from
|
|
1424
|
+
assert.match(SCRIPT, /var ORB_SHAPES = \['blob', 'pair', 'bead'\], ORB_MATERIALS = \['frost', 'gold', 'obsidian'\];/);
|
|
1425
|
+
assert.match(SCRIPT, /ORB_SHAPES\.forEach\(function \(s\) \{ \['tint', 'wash', 'chrome'\]\.forEach\(function \(y\) \{ ORB_TEMPLATES\.push\(s \+ '-' \+ y\); \}\); \}\);/);
|
|
1426
|
+
assert.match(SCRIPT, /ORB_SHAPES\.forEach\(function \(s\) \{ ORB_MATERIALS\.forEach\(function \(m\) \{ \['tint', 'wash', 'plain'\]\.forEach\(function \(y\) \{ ORB_TEMPLATES\.push\(s \+ '-' \+ m \+ '-' \+ y\); \}\); \}\); \}\);/);
|
|
1427
|
+
assert.equal(P.PROJECT_TEMPLATES.length, 36);
|
|
1428
|
+
for (const t of P.PROJECT_TEMPLATES) assert.match(t, TEMPLATE_RE);
|
|
1429
|
+
for (const t of walk) assert.ok(P.PROJECT_TEMPLATES.includes(t), `the walk's ${t} is a valid name`);
|
|
1430
|
+
const mig = fs.readFileSync(path.join(ROOT, 'modules', 'platform-identity', 'migrations', 'platform_identity_013_project_template_materials.sql'), 'utf8');
|
|
1431
|
+
for (const t of P.PROJECT_TEMPLATES) assert.ok(mig.includes(`'${t}'`), `migration 013 names ${t}`);
|
|
1422
1432
|
});
|
|
1423
1433
|
|
|
1424
1434
|
test('the walk assigns the template by canonical position beside the accent; a curated template wins; the platform is the pair in chrome', () => {
|
|
1425
|
-
assert.match(SCRIPT, /function assignAccents[\s\S]*?p\._acc = ORB_ACCENTS\[i % ORB_ACCENTS\.length\];\s*p\._tpl =
|
|
1435
|
+
assert.match(SCRIPT, /function assignAccents[\s\S]*?p\._acc = ORB_ACCENTS\[i % ORB_ACCENTS\.length\];\s*p\._tpl = ORB_WALK\[i % ORB_WALK\.length\];/,
|
|
1426
1436
|
'one walk, two assignments');
|
|
1427
|
-
assert.match(SCRIPT, /function templateOf\(p\) \{\s*return ORB_TEMPLATES\.indexOf\(p\.template\) !== -1 \? p\.template : \(p\._tpl \|\|
|
|
1437
|
+
assert.match(SCRIPT, /function templateOf\(p\) \{\s*return ORB_TEMPLATES\.indexOf\(p\.template\) !== -1 \? p\.template : \(p\._tpl \|\| ORB_WALK\[0\]\);/,
|
|
1428
1438
|
'a curated name wins; an unknown name is not a choice');
|
|
1429
1439
|
assert.match(SCRIPT, /_acc: ORB_ACCENTS\[slugAccent\(inst\.slug\)\], _tpl: slugTemplate\(inst\.slug\)/, 'an owned-only entry walks by its slug');
|
|
1430
|
-
assert.match(SCRIPT, /
|
|
1440
|
+
assert.match(SCRIPT, /function slugTemplate\(slug\) \{ return ORB_WALK\[slugHash\(slug\) % ORB_WALK\.length\]; \}/, 'by the walk, not the whole set');
|
|
1441
|
+
assert.match(SCRIPT, /var tpl = templateOf\(p\), tplParts = tpl\.split\('-'\);\s*a\.setAttribute\('data-template', tpl\);\s*a\.setAttribute\('data-shape', tplParts\[0\]\);\s*a\.setAttribute\('data-material', tplParts\.length === 3 \? tplParts\[1\] : 'chrome'\);\s*a\.setAttribute\('data-style', tplParts\[tplParts\.length - 1\]\);/,
|
|
1431
1442
|
'the orb carries its template as data attributes; the sheet does the rest');
|
|
1432
1443
|
assert.match(HUB, /<a class="worldOrb" id="selfOrb" href="\/" data-shape="pair" data-style="chrome" data-template="pair-chrome"/, 'the platform is its own mark');
|
|
1433
1444
|
});
|
|
@@ -1455,6 +1466,43 @@ test("the planet is the landing's object on the landing's crop maths: the three
|
|
|
1455
1466
|
assert.match(COSMOS, /--plate-bongos-cut:url\(\/assets\/world\/r2-bongos-chrome-a-cut\.webp\);/);
|
|
1456
1467
|
});
|
|
1457
1468
|
|
|
1469
|
+
test('the materials (task 1003337): a whole object per plate, the crop recomputed from the cutout\'s alpha box; every plate ships with alpha and a sidecar', () => {
|
|
1470
|
+
// the boxes measured from the shipped cutouts (alpha >= 16 on the 2048² plate); the sheet
|
|
1471
|
+
// pads each side by 1.5% and derives zoom = 1/width, px/py = -u0/-v0 × zoom (square plates)
|
|
1472
|
+
const BBOX = { 'frost-blob': { u0:0.168, u1:0.8223, v0:0.2495, v1:0.7642 }, 'frost-pair': { u0:0.0591, u1:0.9409, v0:0.2764, v1:0.7231 }, 'frost-bead': { u0:0.1685, u1:0.8066, v0:0.1738, v1:0.8198 }, 'gold-blob': { u0:0.1274, u1:0.8584, v0:0.1035, v1:0.9019 }, 'gold-pair': { u0:0.0552, u1:0.9458, v0:0.2739, v1:0.7383 }, 'gold-bead': { u0:0.0542, u1:0.9478, v0:0.0542, v1:0.9434 }, 'obsidian-blob': { u0:0.1885, u1:0.8018, v0:0.1177, v1:0.8979 }, 'obsidian-pair': { u0:0.0249, u1:0.9707, v0:0.3096, v1:0.7158 }, 'obsidian-bead': { u0:0.1416, u1:0.854, v0:0.1431, v1:0.8535 } };
|
|
1473
|
+
const MARGIN = 0.015;
|
|
1474
|
+
const f5 = (n) => { const s = n.toFixed(5); return s.replace(/^(-?)0\./, '$1.').replace(/\.?0+$/, '') || '0'; };
|
|
1475
|
+
for (const [key, b] of Object.entries(BBOX)) {
|
|
1476
|
+
const [material, shape] = key.split('-');
|
|
1477
|
+
const U0 = Math.max(0, b.u0 - MARGIN), U1 = Math.min(1, b.u1 + MARGIN), V0 = Math.max(0, b.v0 - MARGIN), V1 = Math.min(1, b.v1 + MARGIN);
|
|
1478
|
+
const w = U1 - U0, h = V1 - V0, zoom = 1 / w;
|
|
1479
|
+
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)};}`;
|
|
1480
|
+
assert.ok(COSMOS.includes(rule), `the ${key} crop follows from its box: ${rule}`);
|
|
1481
|
+
assert.ok(COSMOS.includes(`--plate-${material}-${shape}-cut:url(/assets/world/r3-${material}-${shape}-cut.webp);`), `the ${key} plate token`);
|
|
1482
|
+
const file = path.join(ROOT, 'public-landing', 'assets', 'world', `r3-${material}-${shape}-cut.webp`);
|
|
1483
|
+
assert.ok(fs.existsSync(file), `${key}: the cutout ships`);
|
|
1484
|
+
assert.ok(fs.existsSync(file + '.json'), `${key}: with its sidecar`);
|
|
1485
|
+
const head = fs.readFileSync(file).subarray(0, 40).toString('latin1');
|
|
1486
|
+
assert.ok(head.startsWith('RIFF') && head.includes('WEBP') && /ALPH|VP8X/.test(head), `${key}: WebP with an alpha chunk`);
|
|
1487
|
+
}
|
|
1488
|
+
assert.match(COSMOS, /\.worldOrb\[data-style="plain"\]\{--tpl-ink:transparent;--tpl-blend:normal;\}/, 'plain is the material as rendered');
|
|
1489
|
+
// the material rules carry two attributes so they outrank the one-attribute chrome crops
|
|
1490
|
+
assert.ok(COSMOS.indexOf('.worldOrb[data-material="frost"][data-shape="blob"]') > COSMOS.indexOf('.worldOrb[data-shape="bead"]{'), 'the materials follow the chrome crops');
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
test('the field carries a rendered backdrop (task 1003337): one token, cover, a slow transform drift, the plate and its sidecar on disk', () => {
|
|
1494
|
+
assert.match(COSMOS, /--field-plate:url\(\/assets\/world\/r3-space-nebula\.jpg\);/, 'the pick is one token');
|
|
1495
|
+
assert.match(COSMOS, /\.field \.ground\{position:absolute;inset:-4%;background:var\(--sky-bg\) var\(--field-plate\) center\/cover no-repeat;\}/);
|
|
1496
|
+
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');
|
|
1497
|
+
assert.match(COSMOS, /@keyframes groundDrift\{from\{transform:translate3d\([^)]*\) scale\(1\.02\);\}to\{transform:translate3d\([^)]*\) scale\(1\.06\);\}\}/, 'transform only');
|
|
1498
|
+
const plate = path.join(ROOT, 'public-landing', 'assets', 'world', 'r3-space-nebula.jpg');
|
|
1499
|
+
assert.ok(fs.existsSync(plate) && fs.existsSync(plate + '.json'), 'the plate and its sidecar ship');
|
|
1500
|
+
const side = JSON.parse(fs.readFileSync(plate + '.json', 'utf8'));
|
|
1501
|
+
assert.equal(side.kind, 'backdrop');
|
|
1502
|
+
assert.match(side.prompt, /pure black/i, 'a text-free plate on black');
|
|
1503
|
+
assert.doesNotMatch(COSMOS, /\.field \.ground::after\{content:""/, 'nothing drawn over the plate');
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1458
1506
|
test('the style is a blend on a masked colour layer; the accent never paints the box; the ring and the spotlight follow the silhouette', () => {
|
|
1459
1507
|
assert.match(COSMOS, /\.worldOrb \.disc::before\{content:"";position:absolute;inset:0;\s*background-image:linear-gradient\(var\(--tpl-ink\),var\(--tpl-ink\)\),var\(--tpl-src\);/);
|
|
1460
1508
|
assert.match(COSMOS, /background-blend-mode:var\(--tpl-blend\),normal;\s*-webkit-mask-image:var\(--tpl-src\);mask-image:var\(--tpl-src\);/, 'the mask is the plate itself');
|
|
@@ -1502,9 +1550,11 @@ test('the kit proves the field path: the fixture carries a curated template and
|
|
|
1502
1550
|
const fixture = JSON.parse(fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'fixtures', 'featured.json'), 'utf8'));
|
|
1503
1551
|
const otb = fixture.projects.find((p) => p.origin === 'https://amazonprimea.com');
|
|
1504
1552
|
assert.equal(otb.template, 'bead-wash');
|
|
1553
|
+
assert.equal(fixture.projects.find((p) => p.origin === 'https://demo.cloudbongos.com').template, 'pair-gold-wash', 'a material template rides the wire (task 1003337)');
|
|
1505
1554
|
assert.ok(fixture.projects.every((p) => 'template' in p), 'the wire never drops the key');
|
|
1506
1555
|
const states = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.states.json'), 'utf8'));
|
|
1507
1556
|
const attrs = states.states['in-map'].expect.attr.map((a) => a.join(' '));
|
|
1508
1557
|
assert.ok(attrs.includes('#selfOrb data-template pair-chrome'));
|
|
1509
1558
|
assert.ok(attrs.some((a) => a.includes('amazonprimea.com') && a.endsWith('data-template bead-wash')), 'the curated template reaches the orb');
|
|
1559
|
+
assert.ok(attrs.some((a) => a.includes('demo.cloudbongos.com') && a.endsWith('data-material gold')), 'the material reaches the orb as its own attribute');
|
|
1510
1560
|
});
|
|
@@ -581,13 +581,14 @@ test('completed rail steps are a way back, the repo picker is honest ARIA, the c
|
|
|
581
581
|
assert.equal((HUB.match(/aria-controls="projectsList"/g) || []).length, 5, 'the five state filters name the list they filter');
|
|
582
582
|
});
|
|
583
583
|
|
|
584
|
-
test('control nits: orb cursor, caret under reduced motion, mode
|
|
584
|
+
test('control nits: orb cursor, caret under reduced motion, no mode switch, home label, held reduce-motion query, target sizes', () => {
|
|
585
585
|
assert.doesNotMatch(COSMOS, /\.orb\{flex:0 0 54px;[^}]*cursor:pointer/, 'the base orb is not a pointer');
|
|
586
586
|
assert.match(COSMOS, /\na\.orb\{cursor:pointer;\}/, 'only the linked orb is');
|
|
587
587
|
assert.doesNotMatch(COSMOS, /\.orb\.ghost\{[^}]*cursor:default/);
|
|
588
588
|
assert.doesNotMatch(HUB, /\.shell \.caret/, 'no caret to hide — the hub mark is static (task 1003300)');
|
|
589
|
-
assert.
|
|
590
|
-
assert.doesNotMatch(
|
|
589
|
+
assert.doesNotMatch(SCRIPT, /modeToggle|paintModeToggle|resolvedMode|cb-color-mode/, 'the mode switch left the hub — the apex is dark only (task 1003336)');
|
|
590
|
+
assert.doesNotMatch(HUB, /id="modeToggle"|class="modeBtn"/);
|
|
591
|
+
assert.match(HUB, /^<html lang="en" data-mode="dark">/m, 'dark is fixed on <html>');
|
|
591
592
|
assert.match(SCRIPT, /\$\('barMark'\)\.setAttribute\('aria-label', productName \+ ' — home'\);/, 'the mark — the only way home — is labelled from the product name');
|
|
592
593
|
assert.match(SCRIPT, /var REDUCED_MQ = window\.matchMedia \? window\.matchMedia\('\(prefers-reduced-motion: reduce\)'\) : null;/);
|
|
593
594
|
assert.match(SCRIPT, /if \(REDUCED\) stopSky\(\); else startSky\(\);/, 'a mid-session flip stops or restarts the sky');
|
package/tests/ui_design_kit.mjs
CHANGED
|
@@ -39,6 +39,12 @@ test('the shipped states files validate and sit beside the page they name', () =
|
|
|
39
39
|
assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
|
|
40
40
|
assert.equal(path.basename(rel), `${lib.pageName(json.page)}.states.json`, `${rel} is named for its page`);
|
|
41
41
|
}
|
|
42
|
+
// the apex is dark only (task 1003336): its states files name the one mode, and
|
|
43
|
+
// render.js reads it as the default so a plain `--page /` never renders light
|
|
44
|
+
for (const rel of ['public-landing/projects.states.json', 'public-landing/index.states.json']) {
|
|
45
|
+
assert.deepEqual(readJson(rel).modes, ['dark'], `${rel}: the apex renders dark only`);
|
|
46
|
+
assert.equal(readJson(rel).modeQuery, false, `${rel}: no query walks a second mode`);
|
|
47
|
+
}
|
|
42
48
|
});
|
|
43
49
|
|
|
44
50
|
test('the shipped probes files validate', () => {
|
|
@@ -60,6 +66,10 @@ test('a states file that breaks the contract is refused with the reason named',
|
|
|
60
66
|
assert.ok(bad.errors.some((e) => /states\.ok\.auth/.test(e)));
|
|
61
67
|
const stub = lib.validateStatesFile({ page: '/x', stub: { prefix: 'builders' }, states: { a: {} } });
|
|
62
68
|
assert.ok(stub.errors.some((e) => /stub\.prefix/.test(e)));
|
|
69
|
+
const modes = lib.validateStatesFile({ page: '/x', modes: ['sepia'], states: { a: {} } });
|
|
70
|
+
assert.ok(modes.errors.some((e) => e.startsWith('modes:')), 'an unknown mode is refused');
|
|
71
|
+
assert.ok(lib.validateStatesFile({ page: '/x', modes: [], states: { a: {} } }).errors.some((e) => e.startsWith('modes:')), 'an empty modes list is refused');
|
|
72
|
+
assert.deepEqual(lib.validateStatesFile({ page: '/x', modes: ['dark'], states: { a: {} } }).errors, [], 'one declared mode is the dark-only shape');
|
|
63
73
|
});
|
|
64
74
|
|
|
65
75
|
test('a probes file needs a name and an expectation per probe, and cannot continue from nothing', () => {
|