@bongos/core 1.24.66 → 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 +17 -17
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/ui-look-before-you-ship.md +1 -0
- package/modules/government/protected-surfaces.json +10 -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 +12 -0
- package/src/module-api.js +1 -1
- package/tests/government_protected_surfaces.mjs +30 -0
- 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-
|
|
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",
|
|
@@ -5757,7 +5757,7 @@
|
|
|
5757
5757
|
{
|
|
5758
5758
|
"path": "modules/government/protected-surfaces.json",
|
|
5759
5759
|
"mode": "0000644",
|
|
5760
|
-
"sha256": "
|
|
5760
|
+
"sha256": "0b54c64b1515a2208a8094e80a891e99e374a99bc26e99f70c81d592ef125cbc"
|
|
5761
5761
|
},
|
|
5762
5762
|
{
|
|
5763
5763
|
"path": "modules/government/ranks.js",
|
|
@@ -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",
|
|
@@ -18437,7 +18437,7 @@
|
|
|
18437
18437
|
{
|
|
18438
18438
|
"path": "tests/government_protected_surfaces.mjs",
|
|
18439
18439
|
"mode": "0000644",
|
|
18440
|
-
"sha256": "
|
|
18440
|
+
"sha256": "de07f6405cc18311d0e048efcbc450bb15b270387a9579b53811ae4ac506c7e0"
|
|
18441
18441
|
},
|
|
18442
18442
|
{
|
|
18443
18443
|
"path": "tests/government_rank_sync.mjs",
|
|
@@ -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",
|
|
@@ -3386,5 +3386,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3386
3386
|
landed since 1.24.64 with no explicit bump. run 42516347052. (task 1002620)
|
|
3387
3387
|
1.24.66 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3388
3388
|
landed since 1.24.65 with no explicit bump. run 42686885661. (task 1002620)
|
|
3389
|
+
1.24.67 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
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)
|
|
3389
3393
|
---------------------------------------------------------------------------
|
|
3390
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 |
|
|
@@ -104,6 +104,16 @@
|
|
|
104
104
|
{ "glob": "scripts/bongos/main-audit.js", "group": "pipeline", "why": "the post-push audit — the authoritative substrate layer (self-protect)" },
|
|
105
105
|
{ "glob": "scripts/bongos/push-main.js", "group": "pipeline", "why": "the sanctioned main-push (carries the override)" },
|
|
106
106
|
{ "glob": "scripts/bongos/run-unit-tests.js", "group": "pipeline", "why": "the unit-gate test SELECTOR — muting tests here blunts the CI gate (ADR 0058)" },
|
|
107
|
+
{ "glob": "scripts/bongos/fitness.js", "group": "pipeline", "why": "task 1004383. The architecture fitness gate CI runs on every merge, including the module-migration DDL check task 1003373 extends. scripts/gds/fitness.js is only a forwarder to it (already under scripts/gds/); the real gate sat unprotected." },
|
|
108
|
+
{ "glob": "scripts/bongos/fitness-checks-error-envelope.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
109
|
+
{ "glob": "scripts/bongos/fitness-checks-identity.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
110
|
+
{ "glob": "scripts/bongos/fitness-checks-packaging.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
111
|
+
{ "glob": "scripts/bongos/fitness-checks-route-pins.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
112
|
+
{ "glob": "scripts/bongos/fitness-checks-vocabulary.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
113
|
+
{ "glob": "scripts/bongos/fitness-checks-write-validation.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
114
|
+
{ "glob": "scripts/bongos/fitness-lib.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
115
|
+
{ "glob": "scripts/bongos/fitness-migration-contraction.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
116
|
+
{ "glob": "scripts/bongos/fitness-ratchets.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
|
|
107
117
|
{ "glob": "scripts/bongos/hard-floor.js", "group": "pipeline", "why": "task 1004859. The trust-boundary hard-floor list (self-protect). It moved here from scripts/bongos/gate-review.js when that never-run CI classifier was removed; publish-manifest.js and adopt-preflight.js read it." },
|
|
108
118
|
{ "glob": "scripts/bongos/setup.js", "group": "pipeline", "why": "/builder-setup — installs the pre-push hook" },
|
|
109
119
|
{ "glob": "scripts/bongos/install-git-hooks.js", "group": "pipeline", "why": "the hook installer (self-protect)" },
|
|
@@ -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
|
@@ -11399,5 +11399,17 @@
|
|
|
11399
11399
|
"id": "1004384",
|
|
11400
11400
|
"text": "Sessions that start on a claimed task no longer receive raw idea titles from Discord: hint titles are now cleaned and boxed off as data, and outside ideas are left out, so a planted instruction can't steer the agent. The hints"
|
|
11401
11401
|
}
|
|
11402
|
+
],
|
|
11403
|
+
"1.24.67": [
|
|
11404
|
+
{
|
|
11405
|
+
"id": "1004383",
|
|
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
|
+
}
|
|
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
|
+
}
|
|
11402
11414
|
]
|
|
11403
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');
|
|
@@ -350,6 +350,19 @@ const ADDED_SINCE_R101 = [
|
|
|
350
350
|
glob: 'modules/lifecycle/claim-eligibility.js',
|
|
351
351
|
why: "the queue axis of the claim gate: which tasks a builder's permissions let them claim at all.",
|
|
352
352
|
},
|
|
353
|
+
// task 1004383 (2026-09-29 security-goal audit). The fitness CI gate: the filing task
|
|
354
|
+
// named scripts/gds/fitness.js, which by the claim was a forwarder already protected
|
|
355
|
+
// under scripts/gds/ — the real gate and its check files sat unprotected.
|
|
356
|
+
{
|
|
357
|
+
after: 'scripts/bongos/run-unit-tests.js',
|
|
358
|
+
glob: 'scripts/bongos/fitness.js',
|
|
359
|
+
why: 'the architecture fitness gate CI runs on every merge, including the module-'
|
|
360
|
+
+ 'migration DDL check; muting a check here blunts the gate.',
|
|
361
|
+
},
|
|
362
|
+
...carve('scripts/bongos/fitness.js', ['fitness-checks-error-envelope', 'fitness-checks-identity',
|
|
363
|
+
'fitness-checks-packaging', 'fitness-checks-route-pins', 'fitness-checks-vocabulary',
|
|
364
|
+
'fitness-checks-write-validation', 'fitness-lib', 'fitness-migration-contraction', 'fitness-ratchets'],
|
|
365
|
+
'a check file of the fitness CI gate, split out beside fitness.js.'),
|
|
353
366
|
// task 1004546: the Autobongos self-edit wall. The grade of task 1004540 refused a
|
|
354
367
|
// version whose guard lived in unprotected shared files, so it moved into these two
|
|
355
368
|
// small ones and they sit beside the rest of the publish pipeline.
|
|
@@ -602,6 +615,23 @@ const AUTHZ_INVENTORY = [
|
|
|
602
615
|
'permissionAllowsClaim', 'claimEligibilityAllows', // modules/lifecycle/claim-eligibility.js
|
|
603
616
|
];
|
|
604
617
|
|
|
618
|
+
// task 1004383. The files that ACT on a grade or gate a merge — not just the grader itself.
|
|
619
|
+
// db-grade.js writes the grade and attributes its credit (registered with the lifecycle/db.js
|
|
620
|
+
// family, task 1004382); scripts/bongos/fitness.js is the CI gate, and the carve tripwire
|
|
621
|
+
// above makes its fitness-*.js check files follow it. scripts/gds/fitness.js is only a
|
|
622
|
+
// forwarder to it, already under scripts/gds/. modules/economy/credits.js is deliberately
|
|
623
|
+
// NOT here: registering it walls the whole economy module, an owner decision (task 1005184).
|
|
624
|
+
const GATE_ACTORS = ['modules/lifecycle/db-grade.js', 'scripts/bongos/fitness.js'];
|
|
625
|
+
|
|
626
|
+
test('the grade write and the fitness CI gate are protected at Metic (1004383)', () => {
|
|
627
|
+
for (const rel of GATE_ACTORS) {
|
|
628
|
+
assert.ok(fs.existsSync(path.join(ROOT, rel)), `${rel} is gone — update GATE_ACTORS to where it moved`);
|
|
629
|
+
const surface = ppc.surfaceFor(rel);
|
|
630
|
+
assert.ok(surface, `${rel} acts on a grade or gates a merge but no protected surface covers it.`);
|
|
631
|
+
assert.ok(ppc.RANK_TIER[surface.floor] >= ppc.RANK_TIER.metic, `${rel} is floored below Metic.`);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
605
635
|
// Files (repo-relative) that DEFINE `name` as a function, out of the tracked non-test .js.
|
|
606
636
|
function definitionSites(name, tracked, read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8')) {
|
|
607
637
|
const re = new RegExp(`(?:^|[^\\w$.])(?:async\\s+)?function\\s*\\*?\\s*${name}\\s*\\(`
|
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
|
+
});
|