@bongos/core 1.19.403 → 1.19.404
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 +14 -14
- package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +2 -0
- package/docs/copy-inventory.md +79 -82
- package/docs/copy-registry.json +95 -122
- package/docs/design/landing-page-direction-v2.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/index.html +72 -237
- package/src/module-api.js +3 -1
- package/tests/landing_page.mjs +86 -28
package/tests/landing_page.mjs
CHANGED
|
@@ -19,10 +19,13 @@
|
|
|
19
19
|
// is an accessibility constraint nobody sees fail; it holds through a
|
|
20
20
|
// cross-FILE dependency (world.css carries ONE universal switch rather than
|
|
21
21
|
// every page repeating it), which is the right shape but an invisible one;
|
|
22
|
-
// * the live-data contract —
|
|
23
|
-
// and then refreshed from
|
|
24
|
-
// the payload; the no-JS state, the XSS discipline and the
|
|
25
|
-
// the
|
|
22
|
+
// * the live-data contract — the ledger line, meter and rows are baked real
|
|
23
|
+
// and then refreshed from three public feeds, with third-party strings in
|
|
24
|
+
// the payload; the no-JS state, the XSS discipline and the definition of
|
|
25
|
+
// the ledger line are all invisible to a passing render;
|
|
26
|
+
// * the owner's review of the hero (task 1003333) — no figure or leader line
|
|
27
|
+
// on the landing, the doors below the blob's centre, the big plates on
|
|
28
|
+
// their own slow drifts: a regression here looks like a design choice.
|
|
26
29
|
//
|
|
27
30
|
// Deliberately NOT asserted here: that GET / returns the page (tests/platform_boot.mjs
|
|
28
31
|
// owns that), and nothing about geometry or colour — the world's token layer is
|
|
@@ -61,9 +64,9 @@ test('the apex-only sentinels are balanced and each one wraps content', () => {
|
|
|
61
64
|
const starts = HTML.match(/<!--apex-only:start-->/g) || [];
|
|
62
65
|
const ends = HTML.match(/<!--apex-only:end-->/g) || [];
|
|
63
66
|
assert.equal(starts.length, ends.length, 'every apex-only:start needs its end');
|
|
64
|
-
// four sections plus
|
|
65
|
-
// numbers as its no-JS state, and a child must show an empty
|
|
66
|
-
// platform's
|
|
67
|
+
// four sections plus the baked ledger line, meter and rows (task 1003298: the
|
|
68
|
+
// page bakes real numbers as its no-JS state, and a child must show an empty
|
|
69
|
+
// ledger, not the platform's, until its own feed answers)
|
|
67
70
|
assert.ok(starts.length >= 4, 'the direction marks at least four apex-only regions');
|
|
68
71
|
// no start after an unclosed start — a nested pair would make the non-greedy
|
|
69
72
|
// strip close on the wrong sentinel and leak the platform's claims.
|
|
@@ -95,8 +98,8 @@ test('a child instance is served none of the platform-only claims', () => {
|
|
|
95
98
|
test("a child instance is served none of the platform's NUMBERS either", () => {
|
|
96
99
|
// The v3 composition puts real ledger values in the markup as the no-JS state.
|
|
97
100
|
// A claim is obvious when it leaks; a number is not — a fresh child instance
|
|
98
|
-
// would silently advertise the hub's
|
|
99
|
-
//
|
|
101
|
+
// would silently advertise the hub's V1 progress line and its six shipped
|
|
102
|
+
// rows until its own feed answered. Both are apex-only for exactly that reason.
|
|
100
103
|
// the MARKUP only: the page script names every one of these hooks in a
|
|
101
104
|
// querySelector, so scanning the whole file would match its own selectors.
|
|
102
105
|
const child = stripApexOnly(HTML, false)
|
|
@@ -107,7 +110,6 @@ test("a child instance is served none of the platform's NUMBERS either", () => {
|
|
|
107
110
|
'a thousands-separated figure survived the strip',
|
|
108
111
|
);
|
|
109
112
|
for (const [what, re, expected] of [
|
|
110
|
-
['the three hero figures', /data-fig="[a-z]+"[^>]*>([\s\S]*?)<\/span>/g, 3],
|
|
111
113
|
['the ledger line', /data-ledger="line"[^>]*>([\s\S]*?)<\/p>/g, 1],
|
|
112
114
|
['the ledger meter', /data-ledger="meter"[^>]*>([\s\S]*?)<\/div>/g, 1],
|
|
113
115
|
]) {
|
|
@@ -134,7 +136,6 @@ test('a child instance keeps the whole portable spine', () => {
|
|
|
134
136
|
'id="who"', // the identity slot
|
|
135
137
|
'The six most recent, straight from the ledger.', // the ledger's own sub-line
|
|
136
138
|
// the live hooks: a child's OWN feeds fill these, so the shells must survive
|
|
137
|
-
'data-fig="shipped"', 'data-fig="value"', 'data-fig="builders"',
|
|
138
139
|
'data-ledger="line"', 'data-ledger="meter"', 'data-rows',
|
|
139
140
|
]) {
|
|
140
141
|
assert.ok(child.includes(portable), `a child instance lost "${portable}"`);
|
|
@@ -314,8 +315,9 @@ test('nothing on the page marks an animation !important and escapes that switch'
|
|
|
314
315
|
|
|
315
316
|
test('the signature animations are plain declarations, so the switch reaches them', () => {
|
|
316
317
|
for (const name of [
|
|
317
|
-
'breathe', 'sheen',
|
|
318
|
-
'
|
|
318
|
+
'breathe', 'sheen', // the breath
|
|
319
|
+
'drift-form', 'drift-orb', // the drift (task 1003333)
|
|
320
|
+
'arrive-form', 'arrive-orb', 'arrive-mark', // the authored moment
|
|
319
321
|
]) {
|
|
320
322
|
assert.match(PAGE_CSS, new RegExp(`@keyframes\\s+${name}\\s*\\{`), `@keyframes ${name} is gone`);
|
|
321
323
|
assert.match(PAGE_CSS, new RegExp(`animation:[^;}]*\\b${name}\\b`), `nothing runs ${name}`);
|
|
@@ -334,22 +336,83 @@ test('the breath stays slow and the arrival stays brief', () => {
|
|
|
334
336
|
};
|
|
335
337
|
assert.ok(period('breathe', 's') >= 30, 'the blob breathes at 32s; under 30s it reads as a pulse');
|
|
336
338
|
assert.ok(period('sheen', 's') >= 30, 'the hue drift is 40s; under 30s the iridescence shimmers');
|
|
337
|
-
|
|
338
|
-
|
|
339
|
+
// the lava lamp (task 1003333): two long loops, and two DIFFERENT ones, so the
|
|
340
|
+
// plates never phase-lock into one animation
|
|
341
|
+
const form = period('drift-form', 's');
|
|
342
|
+
const orb = period('drift-orb', 's');
|
|
343
|
+
assert.ok(form >= 30, 'the sweeping form drifts over 74s; under 30s it paddles');
|
|
344
|
+
assert.ok(orb >= 30, 'the blob drifts over 58s; under 30s it paddles');
|
|
345
|
+
assert.notEqual(form, orb, 'equal periods would make the two plates read as one animation');
|
|
346
|
+
for (const arrive of ['arrive-form', 'arrive-orb', 'arrive-mark']) {
|
|
339
347
|
assert.ok(period(arrive, 'ms') <= 800, `${arrive} holds the first paint too long`);
|
|
340
348
|
}
|
|
341
349
|
});
|
|
342
350
|
|
|
351
|
+
// ── the owner's review of the hero (task 1003333) ──────────────────────────
|
|
352
|
+
// Three corrections that a passing render cannot distinguish from taste: the
|
|
353
|
+
// figures with their leader lines were "random lines" on the landing (they live
|
|
354
|
+
// on the /projects poster card now), the doors sat too high and central, and
|
|
355
|
+
// the small bead's orbit was not the motion asked for — the big plates drift.
|
|
356
|
+
|
|
357
|
+
test('no figure, leader line or bead renders on the landing hero at any width (task 1003333)', () => {
|
|
358
|
+
const body = HTML.split('<body>')[1].split('<script>')[0];
|
|
359
|
+
assert.doesNotMatch(body, /class="callout|class="leader|data-fig=|callouts-row/, 'a hero callout came back');
|
|
360
|
+
assert.doesNotMatch(body, /class="plate bead"/, 'the orbiting bead came back');
|
|
361
|
+
// the narrow breakpoint has no three-up row to fall back to either
|
|
362
|
+
assert.doesNotMatch(PAGE_CSS, /\.callout|\.callouts-row|\.leader\b|\.fig\b|\.cap\b|\.bead\b/, 'callout or bead CSS survived');
|
|
363
|
+
assert.doesNotMatch(PAGE_CSS, /--c[123]-[xy]|--leader-len|--orbit-r|--bead-(w|cx|cy)/, 'a callout or bead token survived');
|
|
364
|
+
assert.doesNotMatch(PAGE_CSS, /@keyframes\s+(orbit|arrive-bead)\b/, 'the orbit keyframes survived');
|
|
365
|
+
// and the script no longer reads the two feeds that fed only those figures
|
|
366
|
+
assert.doesNotMatch(CODE, /setFig|data-fig|\/public\/cost-summary|\/public\/leaderboard/);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('the doors hang below the blob\'s centre in both modes (task 1003333)', () => {
|
|
370
|
+
// the door pair is a stage token; the blob's centre is --orb-y + half its height. In dark the
|
|
371
|
+
// blob spans stage 24% to ~100% (centre ~62%); in light 32% to ~99% (centre ~66%). The doors
|
|
372
|
+
// used to sit at 53% / 60% — above centre and "too high and central" to the owner.
|
|
373
|
+
const cy = (block) => {
|
|
374
|
+
const m = block.match(/--doors-cy:calc\(var\(--vh\) \* (\d+)\)/);
|
|
375
|
+
assert.ok(m, 'the doors\' vertical token is no longer a stage percentage — update this test');
|
|
376
|
+
return Number(m[1]);
|
|
377
|
+
};
|
|
378
|
+
const light = PAGE_CSS.match(/\nbody\{([\s\S]*?)\n\}/);
|
|
379
|
+
// the wide blocks carry the stage percentage; the under-900 twins zero the token
|
|
380
|
+
const dark = [...PAGE_CSS.matchAll(/:root(?:\[data-mode="dark"\]|:not\(\[data-mode="light"\]\)) body\{([\s\S]*?)\n\s*\}/g)]
|
|
381
|
+
.filter((m) => /--doors-cy:calc\(/.test(m[1]));
|
|
382
|
+
assert.ok(light, 'the light token block is findable');
|
|
383
|
+
assert.equal(dark.length, 2, 'the dark token block and its prefers-color-scheme twin');
|
|
384
|
+
assert.ok(cy(light[1]) >= 70, 'light: the doors sit in the blob\'s lower half');
|
|
385
|
+
for (const d of dark) assert.ok(cy(d[1]) >= 68, 'dark: the doors sit in the blob\'s lower half');
|
|
386
|
+
assert.equal(cy(dark[0][1]), cy(dark[1][1]), 'the dark twins agree');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test('the big plates drift on transform alone, and the still object never does (task 1003333)', () => {
|
|
390
|
+
// transform is the one property the arrival (translate), the breath (scale) and the sheen
|
|
391
|
+
// (filter) do not use, so the drift composes with them instead of overriding one
|
|
392
|
+
for (const name of ['drift-form', 'drift-orb']) {
|
|
393
|
+
const kf = PAGE_CSS.match(new RegExp(`@keyframes\\s+${name}\\s*\\{([\\s\\S]*?)\\}\\s*\\}`));
|
|
394
|
+
assert.ok(kf, `@keyframes ${name} is gone`);
|
|
395
|
+
assert.doesNotMatch(kf[1], /translate:|scale:|filter:|opacity:|left:|top:/, `${name} must ride the transform property only`);
|
|
396
|
+
assert.match(kf[1], /0%\{transform:translate\(0,0\) rotate\(0deg\)\}/, `${name}'s rest frame is the graded composition`);
|
|
397
|
+
assert.match(kf[1], /100%\{transform:translate\(0,0\) rotate\(0deg\)/, `${name} must close its loop where it opened`);
|
|
398
|
+
}
|
|
399
|
+
const form = PAGE_CSS.match(/\n\.form\{[^}]*\}/);
|
|
400
|
+
const orb = PAGE_CSS.match(/\n\.orb\{[^}]*\}/);
|
|
401
|
+
const mark = PAGE_CSS.match(/\n\.mark\{[^}]*\}/);
|
|
402
|
+
assert.match(form[0], /animation:[^}]*\bdrift-form\b/, 'the sweeping form drifts');
|
|
403
|
+
assert.match(orb[0], /animation:[^}]*\bdrift-orb\b/, 'the big blob drifts');
|
|
404
|
+
assert.doesNotMatch(mark[0], /drift/, 'the bongo pair is the still object the moving ground is read against');
|
|
405
|
+
assert.doesNotMatch(PAGE_CSS, /\.doors\{[^}]*animation/, 'the doors never move with a plate');
|
|
406
|
+
});
|
|
407
|
+
|
|
343
408
|
// ── the live-data contract ─────────────────────────────────────────────────
|
|
344
409
|
// Five public feeds fill the page after it has already rendered a real, baked
|
|
345
410
|
// state. Everything here is invisible to a passing render: a feed renamed, an
|
|
346
411
|
// innerHTML crept in with third-party titles in it, a figure quietly redefined.
|
|
347
412
|
|
|
348
|
-
test('the script reads the
|
|
413
|
+
test('the script reads the three public feeds the head comment documents', () => {
|
|
349
414
|
for (const feed of [
|
|
350
|
-
'/public/progress', // the
|
|
351
|
-
'/public/cost-summary', // the value figure
|
|
352
|
-
'/public/leaderboard', // the builders figure
|
|
415
|
+
'/public/progress', // the ledger line + meter
|
|
353
416
|
'/public/recent-shipped?limit=6', // the ledger rows
|
|
354
417
|
'/projects/featured', // living proof
|
|
355
418
|
]) {
|
|
@@ -381,17 +444,12 @@ test('a live task title is normalised to the front door\'s own punctuation', ()
|
|
|
381
444
|
assert.ok(has('/,\\s*,/g'), 'the comma a dash leaves beside one collapses');
|
|
382
445
|
});
|
|
383
446
|
|
|
384
|
-
test('
|
|
447
|
+
test('the ledger line is defined the way the head comment says it is', () => {
|
|
385
448
|
// A figure whose definition drifts still renders a plausible number, so the
|
|
386
|
-
// page would lie without ever looking wrong.
|
|
387
|
-
|
|
388
|
-
assert.match(CODE, /setFig\('shipped'/);
|
|
389
|
-
assert.match(CODE, /data\.total_value_credits/, 'value = total_value_credits from the cost summary');
|
|
390
|
-
assert.match(CODE, /setFig\('value'/);
|
|
391
|
-
assert.match(CODE, /Number\(r\.total_credits\) > 0/, 'builders = leaderboard entries HOLDING credits');
|
|
392
|
-
assert.match(CODE, /setFig\('builders'/);
|
|
393
|
-
// the ledger line + meter come off the first `building` row
|
|
449
|
+
// page would lie without ever looking wrong. The ledger line + meter come off
|
|
450
|
+
// the first `building` row (else the first row).
|
|
394
451
|
assert.match(CODE, /r\.status === 'building'/);
|
|
452
|
+
assert.match(CODE, /if \(!live\) live = rows\[0\];/);
|
|
395
453
|
assert.match(CODE, /' shipped on '/);
|
|
396
454
|
// numbers are grouped, so the live value matches the baked one's shape
|
|
397
455
|
assert.match(CODE, /toLocaleString\('en-US'\)/);
|