@bongos/core 1.24.67 → 1.24.68
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 +15 -15
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/ui-look-before-you-ship.md +1 -0
- package/modules/ui-design/kit/README.md +1 -1
- package/modules/ui-design/kit/lib.js +52 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/ui_design_kit.mjs +4 -1
- package/tests/ui_design_kit_fit.mjs +55 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/bongos/package-core.js",
|
|
5
|
-
"core_version": "1.24.
|
|
6
|
-
"core_contract": "1.24.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.24.68",
|
|
6
|
+
"core_contract": "1.24.68",
|
|
7
|
+
"source_commit": "11d18a24f88e4a0dc0348653d46f1436332533b7",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-11-09T20:
|
|
9
|
+
"built_at": "2026-11-09T20:26:36.856Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 638,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 4575,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "55f8f3d3ffc2f4ed3ccf4c3dfbc91607b1a72f70d465e1930d322a45ed7438dd",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -3007,7 +3007,7 @@
|
|
|
3007
3007
|
{
|
|
3008
3008
|
"path": "docs/module-api-changelog.md",
|
|
3009
3009
|
"mode": "0000644",
|
|
3010
|
-
"sha256": "
|
|
3010
|
+
"sha256": "15027e86a94259e4ea08d9d2b47234bd92e4ee8b205677ee27ef20b6579e30a2"
|
|
3011
3011
|
},
|
|
3012
3012
|
{
|
|
3013
3013
|
"path": "docs/modules-contract.md",
|
|
@@ -3262,7 +3262,7 @@
|
|
|
3262
3262
|
{
|
|
3263
3263
|
"path": "docs/recipes/ui-look-before-you-ship.md",
|
|
3264
3264
|
"mode": "0000644",
|
|
3265
|
-
"sha256": "
|
|
3265
|
+
"sha256": "c78908392eedeed9255a952f959c866cd6282acd6a746258e7761e156f2bcff0"
|
|
3266
3266
|
},
|
|
3267
3267
|
{
|
|
3268
3268
|
"path": "docs/recipes/upgrading-the-core.md",
|
|
@@ -11357,7 +11357,7 @@
|
|
|
11357
11357
|
{
|
|
11358
11358
|
"path": "modules/ui-design/kit/README.md",
|
|
11359
11359
|
"mode": "0000644",
|
|
11360
|
-
"sha256": "
|
|
11360
|
+
"sha256": "8fe3b49ba8dc29bd0f923c300978e6650fe27b7937dacef9f14b3f368babf52b"
|
|
11361
11361
|
},
|
|
11362
11362
|
{
|
|
11363
11363
|
"path": "modules/ui-design/kit/check-mock.js",
|
|
@@ -11667,7 +11667,7 @@
|
|
|
11667
11667
|
{
|
|
11668
11668
|
"path": "modules/ui-design/kit/lib.js",
|
|
11669
11669
|
"mode": "0000644",
|
|
11670
|
-
"sha256": "
|
|
11670
|
+
"sha256": "70d6c629ee019f96d10726cdc70e0faa2f1a3422a858d70ea9dd0d3cc17002f8"
|
|
11671
11671
|
},
|
|
11672
11672
|
{
|
|
11673
11673
|
"path": "modules/ui-design/kit/png.js",
|
|
@@ -11902,12 +11902,12 @@
|
|
|
11902
11902
|
{
|
|
11903
11903
|
"path": "package-lock.json",
|
|
11904
11904
|
"mode": "0000644",
|
|
11905
|
-
"sha256": "
|
|
11905
|
+
"sha256": "ae81a9706d5fb007fa6ab9d3fa6783616f3adb642fd256c5478bf30a211026f6"
|
|
11906
11906
|
},
|
|
11907
11907
|
{
|
|
11908
11908
|
"path": "package.json",
|
|
11909
11909
|
"mode": "0000644",
|
|
11910
|
-
"sha256": "
|
|
11910
|
+
"sha256": "cd129832a4eea52b6613553aa6da6fb2ce7f853bff8d1032d5adfd233e53710c"
|
|
11911
11911
|
},
|
|
11912
11912
|
{
|
|
11913
11913
|
"path": "public-docs/index.html",
|
|
@@ -11927,7 +11927,7 @@
|
|
|
11927
11927
|
{
|
|
11928
11928
|
"path": "release-notes.json",
|
|
11929
11929
|
"mode": "0000644",
|
|
11930
|
-
"sha256": "
|
|
11930
|
+
"sha256": "24f4dda6011cf1e1c255226457e1647e80821260e443a11b76e858193073b8eb"
|
|
11931
11931
|
},
|
|
11932
11932
|
{
|
|
11933
11933
|
"path": "scripts/bongos/CLAUDE.md",
|
|
@@ -15877,7 +15877,7 @@
|
|
|
15877
15877
|
{
|
|
15878
15878
|
"path": "src/module-api.js",
|
|
15879
15879
|
"mode": "0000644",
|
|
15880
|
-
"sha256": "
|
|
15880
|
+
"sha256": "c1a27021e0c321c2125b4cac14ae9441f74250c88c3b9fb8fe2d7b562d06924c"
|
|
15881
15881
|
},
|
|
15882
15882
|
{
|
|
15883
15883
|
"path": "src/module-loader/catalog.js",
|
|
@@ -22567,12 +22567,12 @@
|
|
|
22567
22567
|
{
|
|
22568
22568
|
"path": "tests/ui_design_kit.mjs",
|
|
22569
22569
|
"mode": "0000644",
|
|
22570
|
-
"sha256": "
|
|
22570
|
+
"sha256": "a5187e0955766f86c5eafe7ec57b5b7af553883a23342cea03ee29278291a807"
|
|
22571
22571
|
},
|
|
22572
22572
|
{
|
|
22573
22573
|
"path": "tests/ui_design_kit_fit.mjs",
|
|
22574
22574
|
"mode": "0000644",
|
|
22575
|
-
"sha256": "
|
|
22575
|
+
"sha256": "f72a2535bd11c2bfa2d29f01c05af1b2228de1f559551ab214b42f9b7997d78d"
|
|
22576
22576
|
},
|
|
22577
22577
|
{
|
|
22578
22578
|
"path": "tests/ui_design_skills.mjs",
|
|
@@ -3388,5 +3388,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3388
3388
|
landed since 1.24.65 with no explicit bump. run 42686885661. (task 1002620)
|
|
3389
3389
|
1.24.67 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3390
3390
|
landed since 1.24.66 with no explicit bump. run 42689728958. (task 1002620)
|
|
3391
|
+
1.24.68 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3392
|
+
landed since 1.24.67 with no explicit bump. run 42692736161. (task 1002620)
|
|
3391
3393
|
---------------------------------------------------------------------------
|
|
3392
3394
|
```
|
|
@@ -49,6 +49,7 @@ node modules/ui-design/kit/render.js --page / --style expedition # the sa
|
|
|
49
49
|
| `text-fit` | a word of text more than 1px past its box (the padding box of its nearest non-inline ancestor), or an SVG `<text>` past the smallest `<rect>` in its own `<g>` that holds its start — a scroller, `text-overflow: ellipsis` and a visually-hidden label are skipped ([ADR 0329](../adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md)) |
|
|
50
50
|
| `sibling-overlap` | two in-flow, block-level children of one parent that both carry text overlapping by more than 2px each way — absolute, fixed, sticky and negative-margin layering is deliberate and skipped |
|
|
51
51
|
| `empty-graphic` | a shown `<use>` that paints nothing (its referenced SVG failed to parse, or the id is missing) or an `<img>` that did not decode or renders 0×0 — the logo that silently vanishes; a sprite sheet's own `<symbol>`/`<defs>`, a hidden graphic and a lazy image not yet loaded are skipped (task 1005104) |
|
|
52
|
+
| `covered-content` | on-screen text a painted layer sits on top of — hit-tested at the middle of each text run with pointer-events forced on, so a `pointer-events: none` veil counts: an ancestor's positioned `::before`/`::after` (the hall's fixed `body::after` plate veil over content not lifted above it), or another element that paints at half alpha or more. A fixed or sticky bar, a masked or clipped shape, a glow that fades to transparent, a transparent stretched-link overlay, the scrim behind an open modal and a visually-hidden label are skipped (task 1005132) |
|
|
52
53
|
| `one-header-main-h1` | not exactly one *rendered* `<header>`, `<main>` and `<h1>` |
|
|
53
54
|
| `controls-24px` | a visible control under 24px on either side (`ignoreControls` for the skip link and the like) |
|
|
54
55
|
| `top-bar-80px` | a `<header>` taller than 80px |
|
|
@@ -12,7 +12,7 @@ node modules/ui-design/kit/contrast.js --pack [look] | --url <url> [--glyphs sel
|
|
|
12
12
|
|
|
13
13
|
A **look** is a branding pack from the module's style library (`modules/ui-design/styles/<name>/`, task 1003323): `STYLE=<name>` lays it over the instance's own pack through `GDS_BRANDING_FILE`, so any surface renders under any look with no page edit.
|
|
14
14
|
|
|
15
|
-
Two of the floors are about fit ([task 1004203](https://cloudbongos.com/builders#/task/1004203), [ADR 0329](../../../docs/adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md)): `text-fit` (text past its box, SVG text past its card) and `sibling-overlap` (text-bearing siblings on top of each other). `scripts/bongos/render-check.js` runs just those two over the pages a branch changed, and `/ship-check` shows them as an advisory row. A third in-page floor, `empty-graphic` ([task 1005104](https://cloudbongos.com/builders#/task/1005104)), reports a shown `<use>` or `<img>` that paints nothing — a referenced SVG that failed to parse leaves no console error and no failed request, so the logo vanished under ALL CLEAN.
|
|
15
|
+
Two of the floors are about fit ([task 1004203](https://cloudbongos.com/builders#/task/1004203), [ADR 0329](../../../docs/adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md)): `text-fit` (text past its box, SVG text past its card) and `sibling-overlap` (text-bearing siblings on top of each other). `scripts/bongos/render-check.js` runs just those two over the pages a branch changed, and `/ship-check` shows them as an advisory row. A third in-page floor, `empty-graphic` ([task 1005104](https://cloudbongos.com/builders#/task/1005104)), reports a shown `<use>` or `<img>` that paints nothing — a referenced SVG that failed to parse leaves no console error and no failed request, so the logo vanished under ALL CLEAN. A fourth, `covered-content` ([task 1005132](https://cloudbongos.com/builders#/task/1005132)), reports on-screen text a painted layer sits on top of — the hall specimen read near-black in dark under the fixed `body::after` veil while every other floor passed.
|
|
16
16
|
|
|
17
17
|
A surface describes itself in two JSON files beside it — `<page>.states.json` and `<page>.probes.json` — in the vocabularies `lib.js` defines and validates. A page served at the surface root (the hall's Home, `/builders/`) reads `index.states.json`. A file (or one state) may carry `"storage": { "session": {…}, "local": {…} }`, written into the browser before the page's own scripts run, so a first-visit question renders as a returning visitor sees it: every hall file seeds `hall-craft-asked` so the mode picker stays out of the shots ([task 1005080](https://cloudbongos.com/builders#/task/1005080)). A hall state that needs real data (a goal's detail, the Settings sections) passes against the hall-preview harness with `--base-in`/`--base-out`, one render at a time. The full loop, the file shapes and the failure table: [`docs/recipes/ui-look-before-you-ship.md`](../../../docs/recipes/ui-look-before-you-ship.md). Exit codes everywhere: 0 clean · 1 problems · 2 a broken file or usage · 3 no browser.
|
|
18
18
|
|
|
@@ -29,6 +29,7 @@ const FLOORS = Object.freeze([
|
|
|
29
29
|
'text-fit', // no text runs past its box (HTML) or its card rect (SVG)
|
|
30
30
|
'sibling-overlap', // no two in-flow, block-level, text-bearing siblings overlap
|
|
31
31
|
'empty-graphic', // no shown <use> or <img> that paints nothing (a referenced SVG that failed to parse)
|
|
32
|
+
'covered-content', // no on-screen text sits under a painted layer (a veil, a plate, an overlay)
|
|
32
33
|
'one-header-main-h1', // exactly one <header>, one <main>, one <h1>
|
|
33
34
|
'controls-24px', // every visible control clears 24px on both sides
|
|
34
35
|
'top-bar-80px', // the page's <header> is at most 80px tall
|
|
@@ -307,6 +308,56 @@ function factsInPage(spec) {
|
|
|
307
308
|
else if (r.width < 1 || r.height < 1) emptyGraphics.push(`${where(im)} → "${ref(im)}" renders ${Math.round(r.width)}x${Math.round(r.height)}`);
|
|
308
309
|
});
|
|
309
310
|
out.emptyGraphics = emptyGraphics;
|
|
311
|
+
// The covered-content floor (task 1005132): the hall specimen read near-black in dark because a fixed
|
|
312
|
+
// body::after veil painted OVER content that was not lifted above it, and every other floor passed. Hit-test the
|
|
313
|
+
// middle of each on-screen text run with pointer-events forced on (a veil is pointer-events: none, which hit
|
|
314
|
+
// testing would otherwise look straight through): a hit outside the text's own box is a layer on top of it, and
|
|
315
|
+
// it counts when that layer paints — an ancestor's positioned ::before/::after, or (with no modal open) another
|
|
316
|
+
// element or its pseudo. A transparent stretched-link overlay and the scrim behind an open dialog are skipped.
|
|
317
|
+
const alpha = (c) => { const m = String(c).match(/rgba?\(([^)]*)\)/); if (!m) return c === 'transparent' ? 0 : 1; const n = m[1].split(/[\s,/]+/).filter(Boolean); return n.length > 3 ? parseFloat(n[3]) * (/%/.test(n[3]) ? 0.01 : 1) : 1; };
|
|
318
|
+
// Opaque enough to hide text: a background colour, or a gradient every stop of which, at half alpha or more —
|
|
319
|
+
// a glow or halo that fades to transparent is not a cover. An image (url()) is not judged, and neither is a
|
|
320
|
+
// masked or clipped layer, whose box is bigger than what it paints (the cosmos orb's shaped disc).
|
|
321
|
+
const solid = (cs) => {
|
|
322
|
+
if ((cs.maskImage || cs.webkitMaskImage || 'none') !== 'none' || (cs.clipPath || 'none') !== 'none') return false;
|
|
323
|
+
const stops = /gradient\(/.test(cs.backgroundImage) ? cs.backgroundImage.match(/rgba?\([^)]*\)|transparent/g) || [] : [];
|
|
324
|
+
return +cs.opacity * Math.max(alpha(cs.backgroundColor), stops.length ? Math.min(...stops.map(alpha)) : 0) >= 0.5;
|
|
325
|
+
};
|
|
326
|
+
const paints = (cs, pseudo) => !!cs && (!pseudo || (cs.content !== 'none' && cs.content !== 'normal' && /absolute|fixed/.test(cs.position))) && cs.display !== 'none' && cs.visibility !== 'hidden' && solid(cs);
|
|
327
|
+
// A fixed or sticky bar that content scrolls under is layered on purpose.
|
|
328
|
+
const pinned = (hit, box) => { for (let a = hit; a && !a.contains(box); a = a.parentElement) if (/fixed|sticky/.test(getComputedStyle(a).position)) return true; return false; };
|
|
329
|
+
const layerOf = (hit, box) => {
|
|
330
|
+
const name = hit === document.body ? 'body' : hit === de ? 'html' : where(hit);
|
|
331
|
+
for (const ps of ['::after', '::before']) if (paints(getComputedStyle(hit, ps), true)) return name + ps;
|
|
332
|
+
return !hit.contains(box) && !pinned(hit, box) && paints(getComputedStyle(hit), false) ? name : null;
|
|
333
|
+
};
|
|
334
|
+
const modal = qa('dialog[open], [aria-modal="true"]').some(vis);
|
|
335
|
+
const coveredText = [], probed = new Set();
|
|
336
|
+
const force = document.createElement('style');
|
|
337
|
+
force.textContent = '*, *::before, *::after { pointer-events: auto !important; }';
|
|
338
|
+
de.appendChild(force);
|
|
339
|
+
try {
|
|
340
|
+
const tw = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
341
|
+
for (let n = tw.nextNode(); n && probed.size < 300; n = tw.nextNode()) {
|
|
342
|
+
const el = n.parentElement;
|
|
343
|
+
if (!n.nodeValue.trim() || !el || NO_TEXT.test(el.tagName) || el.closest('svg') || !vis(el)) continue;
|
|
344
|
+
let box = el;
|
|
345
|
+
while (box && /^(inline|contents)$/.test(getComputedStyle(box).display)) box = box.parentElement;
|
|
346
|
+
if (!box || probed.has(box) || box.clientWidth < 4 || box.clientHeight < 4) continue; // < 4: a visually-hidden label
|
|
347
|
+
const rg = document.createRange(); rg.selectNodeContents(n);
|
|
348
|
+
const r = [...rg.getClientRects()].find((x) => x.width > 0 && x.height > 0);
|
|
349
|
+
if (!r) continue;
|
|
350
|
+
const x = r.left + r.width / 2, y = r.top + r.height / 2;
|
|
351
|
+
if (x < 0 || y < 0 || x >= innerWidth || y >= innerHeight) continue;
|
|
352
|
+
probed.add(box);
|
|
353
|
+
const hit = document.elementFromPoint(x, y);
|
|
354
|
+
if (!hit || hit === el || el.contains(hit) || box.contains(hit)) continue;
|
|
355
|
+
if (modal && !hit.contains(box)) continue;
|
|
356
|
+
const layer = layerOf(hit, box);
|
|
357
|
+
if (layer) coveredText.push(`${where(box)} "${quote(n.nodeValue)}" under ${layer}`);
|
|
358
|
+
}
|
|
359
|
+
} finally { force.remove(); }
|
|
360
|
+
out.covered = coveredText;
|
|
310
361
|
return out;
|
|
311
362
|
}
|
|
312
363
|
// Pure: compare collected facts against an expectation. Returns problem strings.
|
|
@@ -345,6 +396,7 @@ function auditFloors(facts, { width, mode } = {}) {
|
|
|
345
396
|
if (facts.textFit && facts.textFit.length) p.push(`text-fit: ${some(facts.textFit)}`);
|
|
346
397
|
if (facts.overlaps && facts.overlaps.length) p.push(`sibling-overlap: ${some(facts.overlaps)}`);
|
|
347
398
|
if (facts.emptyGraphics && facts.emptyGraphics.length) p.push(`empty-graphic: ${some(facts.emptyGraphics)}`);
|
|
399
|
+
if (facts.covered && facts.covered.length) p.push(`covered-content: ${some(facts.covered)}`);
|
|
348
400
|
if (facts.headers !== 1 || facts.mains !== 1 || facts.h1s !== 1) p.push(`one-header-main-h1: header ${facts.headers} main ${facts.mains} h1 ${facts.h1s}`);
|
|
349
401
|
if (facts.smallControls && facts.smallControls.length) p.push(`controls-24px: ${facts.smallControls.join(' | ')}`);
|
|
350
402
|
if (facts.bar && facts.bar.h > 80) p.push(`top-bar-80px: header is ${facts.bar.h}px`);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.68",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.68",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.68",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -11405,5 +11405,11 @@
|
|
|
11405
11405
|
"id": "1004383",
|
|
11406
11406
|
"text": "The CI check that guards every merge is now protected, so a builder below Metic can no longer quietly switch its checks off. Protecting the credit-minting file was held back for the owner to decide, because it would lock the w"
|
|
11407
11407
|
}
|
|
11408
|
+
],
|
|
11409
|
+
"1.24.68": [
|
|
11410
|
+
{
|
|
11411
|
+
"id": "1005132",
|
|
11412
|
+
"text": "The design checker now catches text hidden under a dark overlay (the bug that made the components page unreadable in dark mode), instead of reporting it clean."
|
|
11413
|
+
}
|
|
11408
11414
|
]
|
|
11409
11415
|
}
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.68'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/ui_design_kit.mjs
CHANGED
|
@@ -27,7 +27,7 @@ const readJson = (rel) => JSON.parse(fs.readFileSync(path.join(ROOT, rel), 'utf8
|
|
|
27
27
|
|
|
28
28
|
// ── the contract ──────────────────────────────────────────────────────────────
|
|
29
29
|
test('the platform floors are a pinned list — a floor cannot vanish quietly', () => {
|
|
30
|
-
assert.deepEqual([...lib.FLOORS], ['console-errors', 'failed-requests', 'horizontal-overflow', 'text-fit', 'sibling-overlap', 'empty-graphic', 'one-header-main-h1', 'controls-24px', 'top-bar-80px', 'reduced-motion']);
|
|
30
|
+
assert.deepEqual([...lib.FLOORS], ['console-errors', 'failed-requests', 'horizontal-overflow', 'text-fit', 'sibling-overlap', 'empty-graphic', 'covered-content', 'one-header-main-h1', 'controls-24px', 'top-bar-80px', 'reduced-motion']);
|
|
31
31
|
assert.deepEqual(lib.EXIT, { CLEAN: 0, PROBLEMS: 1, USAGE: 2, NO_BROWSER: 3 });
|
|
32
32
|
assert.deepEqual(Object.keys(lib.SIZES), ['320', '390', '1024', '1280', '1440']);
|
|
33
33
|
assert.deepEqual([...lib.DEFAULT_SIZES], [1440, 390, 320], 'render.js renders these unless --sizes names 1280');
|
|
@@ -103,6 +103,9 @@ test('auditFloors names each floor a render breaks, and says nothing about a cle
|
|
|
103
103
|
// task 1005104: a logo whose referenced SVG failed to parse is a floor problem, not ALL CLEAN
|
|
104
104
|
assert.deepEqual(lib.auditFloors({ ...clean(), emptyGraphics: [] }, { width: 1440, mode: 'dark' }), []);
|
|
105
105
|
assert.deepEqual(lib.auditFloors({ ...clean(), emptyGraphics: ['a.brand > svg > use → "/logo.svg#mark" paints nothing'] }, { width: 1440, mode: 'dark' }), ['empty-graphic: a.brand > svg > use → "/logo.svg#mark" paints nothing']);
|
|
106
|
+
// task 1005132: text under a painted veil is a floor problem, not ALL CLEAN
|
|
107
|
+
assert.deepEqual(lib.auditFloors({ ...clean(), covered: [] }, { width: 1440, mode: 'dark' }), []);
|
|
108
|
+
assert.deepEqual(lib.auditFloors({ ...clean(), covered: ['main.spec-main > p.spec-lede "Each shared component" under body::after'] }, { width: 1440, mode: 'dark' }), ['covered-content: main.spec-main > p.spec-lede "Each shared component" under body::after']);
|
|
106
109
|
});
|
|
107
110
|
|
|
108
111
|
test('expectProblems compares every expectation the vocabulary allows', () => {
|
|
@@ -103,3 +103,58 @@ test('the empty-graphic floor reports a <use> or <img> that paints nothing, and
|
|
|
103
103
|
const problems = lib.auditFloors(facts, { width: 1280 });
|
|
104
104
|
assert.ok(problems.some((p) => p.startsWith('empty-graphic: ') && p.includes('#logo-gone')), problems.join('\n'));
|
|
105
105
|
});
|
|
106
|
+
|
|
107
|
+
// task 1005132: the hall specimen read near-black in dark because style.css's fixed body::after veil painted OVER
|
|
108
|
+
// content that was not lifted above it (task 1005137 fixed the page) — and the render said ALL CLEAN. The
|
|
109
|
+
// covered-content floor reports text a painted layer sits on top of; text lifted above the veil, a transparent
|
|
110
|
+
// stretched-link overlay and the scrim behind an open dialog stay silent.
|
|
111
|
+
const VEIL = `body::after { content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none; background-image: linear-gradient(rgba(0,0,0,.82), rgba(0,0,0,.93)); }`;
|
|
112
|
+
const covered = async (html) => {
|
|
113
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
114
|
+
const page = await ctx.newPage();
|
|
115
|
+
await page.setContent(html);
|
|
116
|
+
const facts = await page.evaluate(lib.factsInPage, {});
|
|
117
|
+
await ctx.close();
|
|
118
|
+
return facts;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
test('the covered-content floor reports text a painted veil sits over, and not text lifted above it', { skip: !browser && 'no system Edge or Chrome' }, async () => {
|
|
122
|
+
const facts = await covered(`<!doctype html><html><head><style>${VEIL} .lift { position: relative; z-index: 1; }</style></head><body>
|
|
123
|
+
<header class="lift"><h1>lifted</h1></header><main><p id="under">this paragraph sits under the veil</p></main></body></html>`);
|
|
124
|
+
assert.deepEqual(facts.covered.map((c) => c.split(' "')[0]), ['#under'], facts.covered.join('\n'));
|
|
125
|
+
assert.match(facts.covered[0], /^#under "this paragraph sits under the v.*" under body::after$/);
|
|
126
|
+
assert.ok(lib.auditFloors(facts, { width: 1280 }).some((p) => p.startsWith('covered-content: #under')));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('a transparent stretched link, an open dialog\'s scrim and an off-screen paragraph are not covered content', { skip: !browser && 'no system Edge or Chrome' }, async () => {
|
|
130
|
+
const facts = await covered(`<!doctype html><html><head><style>
|
|
131
|
+
.card { position: relative; width: 300px; } .card a::after { content: ""; position: absolute; inset: 0; }
|
|
132
|
+
.scrim { position: fixed; inset: 0; background: rgba(0,0,0,.6); }
|
|
133
|
+
dialog { z-index: 2; }
|
|
134
|
+
</style></head><body><header></header><main><h1>cards</h1>
|
|
135
|
+
<div class="card"><h2><a href="/x">the card's link</a></h2><p>the card's text under a transparent overlay</p></div>
|
|
136
|
+
<p>a paragraph behind the open dialog's scrim</p>
|
|
137
|
+
<p style="margin-top: 2000px">far below the fold</p>
|
|
138
|
+
<div class="scrim"></div><div role="dialog" aria-modal="true" style="position: fixed; top: 10px; left: 10px; z-index: 3; background: white">the dialog</div>
|
|
139
|
+
</main></body></html>`);
|
|
140
|
+
assert.deepEqual(facts.covered, [], facts.covered.join('\n'));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('a fixed top bar over scrolled text, a glow that fades to transparent and a visually-hidden heading are not covered content', { skip: !browser && 'no system Edge or Chrome' }, async () => {
|
|
144
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
145
|
+
const page = await ctx.newPage();
|
|
146
|
+
await page.setContent(`<!doctype html><html><head><style>
|
|
147
|
+
header { position: fixed; top: 0; left: 0; right: 0; height: 60px; background: #111; z-index: 5; }
|
|
148
|
+
.orb { position: relative; width: 120px; } .orb .disc::before { content: ""; position: absolute; inset: -40px; background-image: radial-gradient(rgba(255,160,80,.6), transparent 70%); }
|
|
149
|
+
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
150
|
+
.sky { position: absolute; inset: 0; background: #000; z-index: 2; }
|
|
151
|
+
</style></head><body><header>the bar</header><main><div class="sky"></div><h1 class="vh">the project map</h1>
|
|
152
|
+
<div class="orb" style="position: relative; z-index: 3; margin-top: 80px"><span class="disc">●</span><div class="nm">Demo world</div></div>
|
|
153
|
+
<div class="orb" style="position: relative; z-index: 3"><span class="shape" style="position: relative; display: block; height: 20px"><i style="position: absolute; left: 0; top: 0; width: 120px; height: 80px; background: #c84; -webkit-mask-image: linear-gradient(black 20px, transparent 20px); mask-image: linear-gradient(black 20px, transparent 20px)"></i></span><div class="nm">a masked shape's name</div></div>
|
|
154
|
+
<p style="position: relative; z-index: 3">a paragraph the page scrolls under the bar</p><div style="height: 2000px"></div>
|
|
155
|
+
</main></body></html>`);
|
|
156
|
+
await page.evaluate(() => window.scrollTo(0, 120));
|
|
157
|
+
const facts = await page.evaluate(lib.factsInPage, {});
|
|
158
|
+
await ctx.close();
|
|
159
|
+
assert.deepEqual(facts.covered, [], facts.covered.join('\n'));
|
|
160
|
+
});
|