@bongos/core 1.19.413 → 1.19.414
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 +39 -34
- 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/projects-hub-direction-v2.md +4 -0
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/ui-look-before-you-ship.md +4 -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 +3 -17
- 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 +4 -41
- package/public-landing/projects.probes.json +0 -3
- package/public-landing/projects.states.json +3 -2
- 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 +1 -1
- package/tests/projects_hub_pre_uat.mjs +4 -3
- package/tests/ui_design_kit.mjs +10 -0
|
@@ -60,6 +60,8 @@ What the pick settles: the settled constraint (dark-first with a real, tested li
|
|
|
60
60
|
|
|
61
61
|
**2026-08-28, the second note (task 1003334).** Seeing the before/after of task 1003333, the owner found the chrome bongo pair sitting on the top-right sweeping form did not fit ("the random bongos on the blobs background"). Offered three treatments (remove it from the hero; keep it off the blob on plain black or paper; phones only), the owner picked **remove it from the hero**. The pair is no longer a hero object: `.mark`, its arrival and its tokens leave; it stays as the closing figure, the second project orb and the bar's mark. The sweeping form stays as a bare backdrop. Under 900 the masthead is the big blob alone, with the words keeping the 56vw the pair used to hold above them.
|
|
62
62
|
|
|
63
|
+
**2026-08-28, the third note — dark only (task 1003336, [ADR 0204](../adr/<redacted>.md)).** Reviewing the planets (task 1003319) with both grounds in front of them, the owner took option 4 of §5 after all, widened to the whole apex: "only black or only white is a bit bland, frankly I am leaning towards only dark mode for this page and keep the dark/light mode to be solely for the builder hall … we could invest into a cool space themed backdrop for the blobs." Built: `data-mode="dark"` fixed on `<html>` of every apex page, the pre-paint script and the persisted `cb-color-mode` gone, the moon/sun glyph and its wiring out of both bars, the kit rendering dark only. What the 2026-08-27 pick settled about light — the cutouts on paper under a contact shadow, the toggle staying on the landing — no longer describes the apex; the cutouts remain the closing figure and the planets' masks. The light tier of `world.css` stays until its own removal. The space backdrop is the next task (1003337).
|
|
64
|
+
|
|
63
65
|
## 7. Shot list: real imagery still owed
|
|
64
66
|
|
|
65
67
|
No version left a placeholder slot: every image on the three pages is a real plate (the round-2 chrome set, the single blob, the two bongo pairs, the two new blob cutouts) or the real Verro Labs medallion, and the proof is the ledger rendered as page content, so no hall or hub capture is used. What the build will still owe:
|
|
@@ -170,3 +170,7 @@ Three mastheads, rendered dark and light at 1440 / 390 / 320 (ALL CLEAN) and mea
|
|
|
170
170
|
**What was built.** In `cosmos.css` the orb's disc is a masked colour layer over a cutout (`.disc::before`: the plate as both background and mask, `background-blend-mode` by style), sized `--d` by the object's own aspect; `[data-shape]` carries the three crops, `[data-style]` the three blends; the relationship ring is a circle behind the object (a mask clips a box-shadow away), the search spotlight an accent drop-shadow on the disc's filter after the contact shadow (dark's `--orb-shadow` is `opacity(1)`, a no-op, so the two compose). The starfield, its 3.2s twinkle, the 74px gravity grid and `--star-display` are gone; the labels, the badge, the hint and the hover card moved onto the world's type (hairline pill, label face, no accent on a fact, no shadow). In `projects.html` `ORB_TEMPLATES` walks beside `ORB_ACCENTS` in `assignAccents` (consecutive positions change shape and style; nine against five repeat every forty-five projects), `templateOf()` prefers a curated name, an owned-only entry walks by its slug, the orb carries `data-template` / `data-shape` / `data-style`, the physics measures the disc's own height, and the slot widths grew a quarter (a cutout's silhouette reads smaller than a filled disc of the same box). The platform's own orb is the pair in chrome. On the catalog side migration `<redacted>` adds the nullable `template` column with its nine-value CHECK, `upsertProject` carries it COALESCE-preserved, `POST /projects` validates it against `PROJECT_TEMPLATES`, and the public `/projects/featured` projection returns it. The kit's fixture gives Example a curated `bead-wash` and the `in-map` state reads it back off the orb.
|
|
171
171
|
|
|
172
172
|
**Not done, on purpose:** no owner-facing picker — a template is set through the curation route today, the way `featured` is; whether a project's owner chooses their own planet from the manage view is the owner's call. The manage view's small `.orb` spheres keep their old look (the scope named the sky's `a.worldOrb` only).
|
|
173
|
+
|
|
174
|
+
## 12. Dark only (task 1003336)
|
|
175
|
+
|
|
176
|
+
**Date:** 2026-08-28 (decided), 2026-08-29 (built) · the owner's call on the planets review (§11): the apex renders dark only and the light/dark switch is the builders' hall's alone — [ADR 0204](../adr/<redacted>.md). On `/projects`: `data-mode="dark"` is fixed on `<html>`, the pre-paint script is gone, the `#modeToggle` glyph and its `resolvedMode` / `paintModeToggle` wiring leave the bar (Projects, then the chip), the `.modeBtn` rules and the icon-swap twins leave `cosmos.css`, the mode-toggle probe leaves `projects.probes.json`, and `projects.states.json` declares `"modes": ["dark"]` so the kit renders the hub's twelve states at three widths in one mode (48 shots, ALL CLEAN; probes 31/31). The light tier of `cosmos.css` stays until its own removal. Why first: the space backdrop for the field (task 1003337) is a black-ground plate; one mode is what lets the field commit to it.
|
|
@@ -1263,5 +1263,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1263
1263
|
landed since 1.19.411 with no explicit bump. run 33259357678. (task 1002620)
|
|
1264
1264
|
1.19.413 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1265
1265
|
landed since 1.19.412 with no explicit bump. run 33259477562. (task 1002620)
|
|
1266
|
+
1.19.414 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1267
|
+
landed since 1.19.413 with no explicit bump. run 33260596712. (task 1002620)
|
|
1266
1268
|
---------------------------------------------------------------------------
|
|
1267
1269
|
```
|
|
@@ -56,7 +56,8 @@ Then a state's own `expect` (a band open, a dialog present, a field's value, a c
|
|
|
56
56
|
{
|
|
57
57
|
"page": "/projects",
|
|
58
58
|
"surface": "public-landing",
|
|
59
|
-
"
|
|
59
|
+
"modes": ["dark"],
|
|
60
|
+
"modeQuery": false,
|
|
60
61
|
"ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects"],
|
|
61
62
|
"ignoreConsole": ["\\b401\\b"],
|
|
62
63
|
"ignoreControls": [".skip", ".vh"],
|
|
@@ -69,6 +70,8 @@ Then a state's own `expect` (a band open, a dialog present, a field's value, a c
|
|
|
69
70
|
}
|
|
70
71
|
```
|
|
71
72
|
|
|
73
|
+
`modes` names the modes the page renders (default both). A page that pins one mode — the apex pins `data-mode="dark"` on `<html>` since task 1003336 — declares it here so a plain `--page` render never walks a mode the page cannot show; `--modes` on the command line still overrides. `modeQuery: false` says no query walks a mode.
|
|
74
|
+
|
|
72
75
|
Actions: `click`, `focus`, `press`, `fill`, `wait`, `clickAt x y`, `clickIfVisible`, `dispatchClick`, `check`, `back`, `forward`. Expectations: `expanded`, `dialog`, `visible[]`, `hidden[]`, `open`, `closed`, `activeId`, `activeText`, `expandedIds[]`, `param{}`, `attr[[sel,name,value]]`, `text[[sel,value]]`, `href[[sel,value]]`, `count[[sel,n]]`, `value[[sel,v]]`, `hasClass[[sel,class,bool]]`, `checked`, `noOverflow`, `top[[sel,px]]` (an element's viewport top — assert it closed, open a band, assert it again: the proof an overlay did not push the page). The kit validates the file before it opens a browser and names every problem.
|
|
73
76
|
|
|
74
77
|
The worked examples: `public-landing/projects.states.json` (the hub's thirteen), `public-landing/index.states.json` (the landing's four), `modules/hall-ui/public/ranks.states.json` (a second surface under its own prefix — it renders **red**, deliberately: the hall predates the 24px floor and Ranks has no `<h1>`, and the states file declares the shell's un-fixtured feeds rather than hiding the findings; idea 1000863 carries them).
|
|
@@ -235,7 +235,8 @@ function reducedMotionProblems({ running = [], frames = 0 } = {}) {
|
|
|
235
235
|
|
|
236
236
|
// ── the states file ───────────────────────────────────────────────────────────
|
|
237
237
|
// { "page": "/projects", "surface"?: "public-landing", "stub"?: { prefix?, child? },
|
|
238
|
-
// "
|
|
238
|
+
// "modes"?: ["dark" | "light", …] (the modes the page renders; default both — a page that pins
|
|
239
|
+
// one mode declares it here), "modeQuery"?: "mode" | false, "ignoreRequests"?: [regex strings], "ignoreConsole"?: [...],
|
|
239
240
|
// "ignoreControls"?: [selectors], "states": { name: { url?, auth?, actions?, expect? } },
|
|
240
241
|
// "reducedMotion"?: { auth?, url?, actions? } }
|
|
241
242
|
function validateStatesFile(json) {
|
|
@@ -245,6 +246,7 @@ function validateStatesFile(json) {
|
|
|
245
246
|
if (json.surface !== undefined && typeof json.surface !== 'string') errors.push('surface: a string (a checkout dir or a module key)');
|
|
246
247
|
errors.push(...validateStub(json.stub, 'stub'));
|
|
247
248
|
if (json.modeQuery !== undefined && json.modeQuery !== false && typeof json.modeQuery !== 'string') errors.push('modeQuery: the query param the page reads for its colour mode (default "mode"), or false');
|
|
249
|
+
if (json.modes !== undefined && !(Array.isArray(json.modes) && json.modes.length && json.modes.every((m) => MODES.includes(m)))) errors.push('modes: a non-empty array of "dark" | "light" — the modes the page renders (default both); a page that pins one mode declares it here');
|
|
248
250
|
for (const k of ['ignoreRequests', 'ignoreConsole', 'ignoreControls']) if (json[k] !== undefined && !(Array.isArray(json[k]) && json[k].every((s) => typeof s === 'string'))) errors.push(`${k}: an array of strings`);
|
|
249
251
|
const states = json.states;
|
|
250
252
|
if (!states || typeof states !== 'object' || Array.isArray(states) || !Object.keys(states).length) errors.push('states: required — a non-empty object of name → { url?, auth?, actions?, expect? }');
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// render.js — render a page's states and audit every render against the platform
|
|
4
4
|
// floors (task 1003320, ADR 0197). The states live in a JSON file BESIDE the
|
|
5
5
|
// surface (<dir>/<page>.states.json: name → url + actions + expectations); each
|
|
6
|
-
// is rendered
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// is rendered at 1440x900 / 390x844 / 320x700 in the modes the states file names
|
|
7
|
+
// (`"modes"`; both by default — the apex pins dark, task 1003336; `--modes` overrides)
|
|
8
|
+
// through two stubs this file starts itself (signed out, signed in), then a
|
|
9
|
+
// reduced-motion pass per mode. Writes the shots + a JSON report; exits non-zero on any problem.
|
|
9
10
|
//
|
|
10
11
|
// node modules/ui-design/kit/render.js --page /projects (public-landing/projects.states.json)
|
|
11
12
|
// node modules/ui-design/kit/render.js --page /builders/ranks --surface hall-ui
|
|
@@ -65,7 +66,7 @@ async function main() {
|
|
|
65
66
|
const args = process.argv.slice(2);
|
|
66
67
|
if (lib.hasFlag(args, '--help')) usage('');
|
|
67
68
|
const { file, json, surface } = locate(args);
|
|
68
|
-
const modes = lib.listArg(args, '--modes', [...lib.MODES]).filter((m) => lib.MODES.includes(m));
|
|
69
|
+
const modes = lib.listArg(args, '--modes', json.modes || [...lib.MODES]).filter((m) => lib.MODES.includes(m));
|
|
69
70
|
const sizes = lib.listArg(args, '--sizes', Object.keys(lib.SIZES)).map(Number).filter((w) => lib.SIZES[w]);
|
|
70
71
|
const only = lib.listArg(args, '--only', null);
|
|
71
72
|
const names = Object.keys(json.states).filter((n) => !only || only.includes(n));
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.414",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.414",
|
|
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.19.
|
|
3
|
+
"version": "1.19.414",
|
|
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",
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
· dark mode — a true-black ground with the card lifted just off it, white
|
|
17
17
|
ink, and the accent pair flipping direction so the link tone goes BRIGHTER
|
|
18
18
|
|
|
19
|
-
Mode resolution:
|
|
20
|
-
|
|
19
|
+
Mode resolution: the apex pages pin data-mode="dark" on <html> (task 1003336,
|
|
20
|
+
ADR 0204) — dark is their only mode. The light tier below stays until its
|
|
21
|
+
own removal (the light-pair contrast tests still measure it).
|
|
21
22
|
|
|
22
23
|
TOKEN TIERS:
|
|
23
24
|
· CONTRACT tier — names the branding pack overrides (themeMarkup()
|
|
@@ -585,19 +586,6 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
585
586
|
.who{gap:9px;}
|
|
586
587
|
.who img{width:26px;height:26px;border-radius:var(--radius-pill);object-fit:cover;flex:none;
|
|
587
588
|
border:1px solid var(--rule);}
|
|
588
|
-
.modeBtn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;
|
|
589
|
-
cursor:pointer;background:none;border:0;border-radius:var(--radius-pill);color:var(--ink-soft);
|
|
590
|
-
transition:color .2s cubic-bezier(.2,.7,.3,1);}
|
|
591
|
-
.modeBtn:hover{color:var(--ink);}
|
|
592
|
-
.modeBtn svg{width:20px;height:20px;}
|
|
593
|
-
/* the glyph shows the mode you would GET: the moon in light, the sun in dark */
|
|
594
|
-
.modeBtn .sun{display:none;}
|
|
595
|
-
:root[data-mode="dark"] .modeBtn .sun{display:block;}
|
|
596
|
-
:root[data-mode="dark"] .modeBtn .moon{display:none;}
|
|
597
|
-
@media (prefers-color-scheme:dark){
|
|
598
|
-
:root:not([data-mode="light"]) .modeBtn .sun{display:block;}
|
|
599
|
-
:root:not([data-mode="light"]) .modeBtn .moon{display:none;}
|
|
600
|
-
}
|
|
601
589
|
/* the one filled shape on a bar with no pills: the landing's sign-in door */
|
|
602
590
|
.signinDoor{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 26px;
|
|
603
591
|
border-radius:var(--radius-pill);background:color-mix(in srgb,var(--ink) 92%,var(--bg));color:var(--bg);
|
|
@@ -685,8 +673,6 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
685
673
|
.qWrap{flex:1 1 90px;max-width:480px;gap:10px;}
|
|
686
674
|
.trig{font-size:13.5px;}
|
|
687
675
|
.who>span{display:none;}
|
|
688
|
-
.modeBtn{width:40px;height:40px;}
|
|
689
|
-
.modeBtn svg{width:18px;height:18px;}
|
|
690
676
|
.signinDoor{padding:0 16px;font-size:13.5px;}
|
|
691
677
|
.bandIn{padding:0 12px 16px;}
|
|
692
678
|
.bandRow{grid-template-columns:1fr;gap:0;}
|
|
@@ -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">
|
|
@@ -17,11 +17,7 @@
|
|
|
17
17
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
18
18
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
19
19
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
20
|
-
<
|
|
21
|
-
/* Mode, resolved before first paint — the same contract as every landing page. */
|
|
22
|
-
(function(){try{var m=localStorage.getItem('cb-color-mode');
|
|
23
|
-
if(m==='light'||m==='dark')document.documentElement.setAttribute('data-mode',m);}catch(e){}})();
|
|
24
|
-
</script>
|
|
20
|
+
<!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
25
21
|
<style>
|
|
26
22
|
/* The reading column only. No :root block — the pack's injected theme owns the
|
|
27
23
|
tokens (landing direction C1); everything below reads them. */
|
|
@@ -1,13 +1,11 @@
|
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
(function(){try{var q=new URLSearchParams(location.search).get('mode');var m=(q==='light'||q==='dark')?q:localStorage.getItem('cb-color-mode');if(m==='light'||m==='dark')document.documentElement.setAttribute('data-mode',m);}catch(e){}})();
|
|
10
|
-
</script>
|
|
5
|
+
<!-- The apex is dark only (task 1003336, ADR 0204): data-mode="dark" is fixed on
|
|
6
|
+
<html> above — no pre-paint script, no stored choice, no OS preference, no
|
|
7
|
+
?mode=. Light is the builders' hall's. The light tier of world.css stays
|
|
8
|
+
until its own removal. -->
|
|
11
9
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
10
|
<!-- ── S0 · HEAD METADATA — SERVER-STAMPED ──────────────────────────────────
|
|
13
11
|
The {{…}} tokens below are replaced by landingPage() (src/platform-server.js)
|
|
@@ -168,11 +166,6 @@ main:focus{outline:none;box-shadow:none}
|
|
|
168
166
|
.who{gap:9px}
|
|
169
167
|
.who img{width:26px;height:26px;border-radius:var(--radius-pill);object-fit:cover;flex:none;border:1px solid var(--rule)}
|
|
170
168
|
.who-name{max-width:14ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
171
|
-
.modeBtn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;cursor:pointer;
|
|
172
|
-
background:none;border:0;border-radius:var(--radius-pill);color:var(--ink-soft);transition:color .2s cubic-bezier(.2,.7,.3,1)}
|
|
173
|
-
.modeBtn:hover{color:var(--ink)}
|
|
174
|
-
.modeBtn svg{width:20px;height:20px}
|
|
175
|
-
.sun{display:none}
|
|
176
169
|
/* the sign-in door: the one filled shape on a bar with no pills — inverted ink, lowercase */
|
|
177
170
|
.pill{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 22px;
|
|
178
171
|
border-radius:var(--radius-pill);font-family:inherit;font-size:15px;color:var(--ink);cursor:pointer;
|
|
@@ -180,10 +173,6 @@ main:focus{outline:none;box-shadow:none}
|
|
|
180
173
|
.signin{background:color-mix(in srgb,var(--ink) 92%,var(--bg));color:var(--bg);border:0;
|
|
181
174
|
text-transform:lowercase;font-weight:500;padding:0 26px}
|
|
182
175
|
.signin:hover{background:var(--ink);color:var(--bg)}
|
|
183
|
-
:root[data-mode="dark"] .sun{display:block}
|
|
184
|
-
:root[data-mode="dark"] .moon{display:none}
|
|
185
|
-
@media (prefers-color-scheme:dark){:root:not([data-mode="light"]) .sun{display:block}
|
|
186
|
-
:root:not([data-mode="light"]) .moon{display:none}}
|
|
187
176
|
|
|
188
177
|
/* the menu bands (.menuBand — .band is this page's content sections): not floating cards. Full width on the card ground, closed by one hairline, a LAYER
|
|
189
178
|
over the page under the bar — the page does not reflow (owner, 2026-08-28; task 1003332). Cells in a
|
|
@@ -491,8 +480,6 @@ body{
|
|
|
491
480
|
.mark-word{display:none}
|
|
492
481
|
.trig{font-size:13.5px}
|
|
493
482
|
.who-name{display:none}
|
|
494
|
-
.modeBtn{width:40px;height:40px}
|
|
495
|
-
.modeBtn svg{width:18px;height:18px}
|
|
496
483
|
.pill{min-height:42px;font-size:13.5px}
|
|
497
484
|
.signin{padding:0 16px}
|
|
498
485
|
.bandIn{padding:0 12px 16px}
|
|
@@ -683,17 +670,6 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
683
670
|
</div>
|
|
684
671
|
<div class="barRight">
|
|
685
672
|
<button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
|
|
686
|
-
<button class="modeBtn" id="mode" type="button" aria-label="Toggle light or dark">
|
|
687
|
-
<svg class="moon" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
|
|
688
|
-
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
|
689
|
-
<path d="M16.2 12.4A6.8 6.8 0 0 1 7.6 3.8a6.8 6.8 0 1 0 8.6 8.6z"/>
|
|
690
|
-
</svg>
|
|
691
|
-
<svg class="sun" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
|
|
692
|
-
stroke-width="1.4" stroke-linecap="round">
|
|
693
|
-
<circle cx="10" cy="10" r="3.9"/>
|
|
694
|
-
<path d="M10 1.6v2.1M10 16.3v2.1M1.6 10h2.1M16.3 10h2.1M4.1 4.1l1.5 1.5M14.4 14.4l1.5 1.5M15.9 4.1l-1.5 1.5M5.6 14.4l-1.5 1.5"/>
|
|
695
|
-
</svg>
|
|
696
|
-
</button>
|
|
697
673
|
<!-- the identity slot: the sign-in door signed out; the chip (the profile trigger) once
|
|
698
674
|
the session resolves — swapped client-side, never a wrong-state flash -->
|
|
699
675
|
<span class="whoSlot" id="who">
|
|
@@ -933,24 +909,9 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
933
909
|
|
|
934
910
|
<script>
|
|
935
911
|
(function () {
|
|
936
|
-
var root = document.documentElement;
|
|
937
912
|
var API = '/api/bongos';
|
|
938
913
|
var GDS = '/api/gds';
|
|
939
914
|
|
|
940
|
-
/* ── the light/dark switch. The pre-paint script in <head> already applied the
|
|
941
|
-
stored choice; this only flips it. The icon swap is CSS. ── */
|
|
942
|
-
function isDark() {
|
|
943
|
-
var m = root.getAttribute('data-mode');
|
|
944
|
-
if (m === 'dark') return true;
|
|
945
|
-
if (m === 'light') return false;
|
|
946
|
-
return matchMedia('(prefers-color-scheme: dark)').matches;
|
|
947
|
-
}
|
|
948
|
-
document.getElementById('mode').addEventListener('click', function () {
|
|
949
|
-
var next = isDark() ? 'light' : 'dark';
|
|
950
|
-
root.setAttribute('data-mode', next);
|
|
951
|
-
try { localStorage.setItem('cb-color-mode', next); } catch (e) {}
|
|
952
|
-
});
|
|
953
|
-
|
|
954
915
|
/* ── the tagline. It is ONE server-stamped string, but the composition wants it
|
|
955
916
|
on two lines, so the two halves are baked as separate spans. A host whose
|
|
956
917
|
tagline is not those two halves gets it back as one wrapping line rather
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "The landing's states for modules/ui-design/kit/render.js (task 1003320): the bar and its two bands, signed out and in.",
|
|
2
|
+
"_": "The landing's states for modules/ui-design/kit/render.js (task 1003320): the bar and its two bands, signed out and in. Dark only (task 1003336): the apex pins data-mode=\"dark\", so `modes` names the one mode and there is no query to walk.",
|
|
3
3
|
"page": "/",
|
|
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"],
|
|
@@ -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. */
|
|
@@ -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">
|
|
@@ -20,11 +20,9 @@
|
|
|
20
20
|
re-injects the branding :root AFTER stylesheets, so the pack stays
|
|
21
21
|
authoritative. Design contract: docs/design/projects-surfaces-redesign-direction.md -->
|
|
22
22
|
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')document.documentElement.setAttribute('data-mode',_m);}catch(e){}
|
|
27
|
-
</script>
|
|
23
|
+
<!-- The apex is dark only (task 1003336, ADR 0204): data-mode="dark" is fixed on
|
|
24
|
+
<html> above — no pre-paint script, no stored choice, no OS preference. Light
|
|
25
|
+
is the builders' hall's. cosmos.css keeps its light tier until its own removal. -->
|
|
28
26
|
<style>
|
|
29
27
|
/* Page-local layout only. /projects is the project HUB (task 1002743): one
|
|
30
28
|
surface holding the map, your projects, and creating a new one. Every
|
|
@@ -381,19 +379,6 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
381
379
|
</div>
|
|
382
380
|
<div class="barRight">
|
|
383
381
|
<button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
|
|
384
|
-
<!-- the glyph shows the mode you would GET: the moon in light, the sun in
|
|
385
|
-
dark, swapped by cosmos.css on data-mode; the script labels it -->
|
|
386
|
-
<button type="button" class="modeBtn" id="modeToggle" aria-label="Switch light or dark mode">
|
|
387
|
-
<svg class="moon" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
|
|
388
|
-
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
|
389
|
-
<path d="M16.2 12.4A6.8 6.8 0 0 1 7.6 3.8a6.8 6.8 0 1 0 8.6 8.6z"/>
|
|
390
|
-
</svg>
|
|
391
|
-
<svg class="sun" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
|
|
392
|
-
stroke-width="1.4" stroke-linecap="round">
|
|
393
|
-
<circle cx="10" cy="10" r="3.9"/>
|
|
394
|
-
<path d="M10 1.6v2.1M10 16.3v2.1M1.6 10h2.1M16.3 10h2.1M4.1 4.1l1.5 1.5M14.4 14.4l1.5 1.5M15.9 4.1l-1.5 1.5M5.6 14.4l-1.5 1.5"/>
|
|
395
|
-
</svg>
|
|
396
|
-
</button>
|
|
397
382
|
<!-- the identity chip IS the profile trigger; renderWho fills it. Signed
|
|
398
383
|
out it does not exist and the sign-in door takes its slot. -->
|
|
399
384
|
<button class="trig who" type="button" id="who" data-menu="profile" aria-expanded="false" aria-controls="menuProfile" hidden></button>
|
|
@@ -1098,28 +1083,6 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1098
1083
|
|
|
1099
1084
|
function $(id) { return document.getElementById(id); }
|
|
1100
1085
|
|
|
1101
|
-
/* ── light/dark: the sky is constant, the mode governs the cards. No saved
|
|
1102
|
-
choice = follow the OS; the toggle stores an explicit one. ── */
|
|
1103
|
-
function resolvedMode() {
|
|
1104
|
-
var m = document.documentElement.getAttribute('data-mode');
|
|
1105
|
-
if (m === 'light' || m === 'dark') return m;
|
|
1106
|
-
return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
|
|
1107
|
-
}
|
|
1108
|
-
/* the glyph shows the mode you would GET (the moon in light, the sun in
|
|
1109
|
-
dark — cosmos.css swaps them on data-mode), so the label must say the same.
|
|
1110
|
-
No aria-pressed — this swaps a mode, it is not a two-state toggle button. */
|
|
1111
|
-
function paintModeToggle() {
|
|
1112
|
-
var dark = resolvedMode() === 'dark';
|
|
1113
|
-
var el = $('modeToggle');
|
|
1114
|
-
el.setAttribute('aria-label', dark ? 'Switch to light mode' : 'Switch to dark mode');
|
|
1115
|
-
}
|
|
1116
|
-
$('modeToggle').addEventListener('click', function () {
|
|
1117
|
-
var next = resolvedMode() === 'dark' ? 'light' : 'dark';
|
|
1118
|
-
document.documentElement.setAttribute('data-mode', next);
|
|
1119
|
-
try { localStorage.setItem('cb-color-mode', next); } catch (e) {}
|
|
1120
|
-
paintModeToggle();
|
|
1121
|
-
});
|
|
1122
|
-
paintModeToggle();
|
|
1123
1086
|
function esc(s) {
|
|
1124
1087
|
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
1125
1088
|
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
@@ -82,9 +82,6 @@
|
|
|
82
82
|
{ "name": "a solo Shift tap focuses the search on the map", "continue": true,
|
|
83
83
|
"steps": [["clickAt", 700, 600], ["press", "Shift"], ["wait", 100]],
|
|
84
84
|
"expect": { "activeId": "mapQ" } },
|
|
85
|
-
{ "name": "the mode toggle: the label says what the press does, and the press does it", "continue": true,
|
|
86
|
-
"steps": [["clickAt", 700, 600], ["click", "#modeToggle"], ["wait", 100]],
|
|
87
|
-
"expect": { "attr": [["#modeToggle", "aria-label", "Switch to light mode"], ["html", "data-mode", "dark"]] } },
|
|
88
85
|
{ "name": "the Table deep link (the landing's band item) lands on the table, Table current (task 1003332)", "auth": true, "width": 1440, "url": "/projects?view=map&sub=table",
|
|
89
86
|
"steps": [["wait", 300]],
|
|
90
87
|
"expect": { "visible": ["#sub-table-body", ".tblMast"], "hidden": ["#sub-sky-body"], "text": [["[aria-current=\"page\"]", "Table"], [".tblTitle", "every project"]] } },
|
|
@@ -1,8 +1,9 @@
|
|
|
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"],
|
|
@@ -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.414'; // 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
|
@@ -297,7 +297,7 @@ test('no id is declared twice — the wizard merge had four collisions to clear'
|
|
|
297
297
|
// id makes the second node unreachable by getElementById and the first
|
|
298
298
|
// silently wins. Scan the real markup only (JS builds ids like `dom-<iid>`
|
|
299
299
|
// from fragments that would false-positive here).
|
|
300
|
-
const afterHead = HUB.slice(HUB.indexOf('
|
|
300
|
+
const afterHead = HUB.slice(HUB.indexOf('<body'));
|
|
301
301
|
const markup = afterHead.slice(0, afterHead.indexOf('<script>'));
|
|
302
302
|
assert.ok(markup.includes('id="view-new"'), 'the markup slice must span the views');
|
|
303
303
|
const ids = [...markup.matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]);
|
|
@@ -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', () => {
|