@bongos/core 1.24.54 → 1.24.56
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 +65 -35
- package/docs/copy-inventory.md +16 -16
- package/docs/copy-registry.json +17 -17
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +18 -18
- package/modules/hall-ui/public/approval-queue.css +9 -9
- package/modules/hall-ui/public/atlas.css +5 -1
- package/modules/hall-ui/public/atlas.js +14 -11
- package/modules/hall-ui/public/city-draw.js +14 -14
- package/modules/hall-ui/public/sky-draw.js +24 -6
- package/modules/hall-ui/public/sky-interaction.js +1 -1
- package/modules/hall-ui/public/sky-panel.js +1 -1
- package/modules/hall-ui/public/studio.css +22 -22
- package/modules/hall-ui/public/thinking.css +46 -48
- package/modules/hall-ui/public/tweak-editor.css +18 -18
- package/modules/hall-ui/public/welcome-boot.css +5 -8
- package/modules/hall-ui/public/welcome-boot.js +11 -15
- package/modules/ui-design/public/brand.css +168 -0
- package/modules/ui-design/public/brand.html +162 -0
- package/modules/ui-design/public/brand.states.json +35 -0
- package/modules/ui-design/scripts/gen-design-reference.js +354 -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/src/platform-server.js +18 -0
- package/tests/design_reference.mjs +175 -0
- package/tests/hall_catalog_world.mjs +7 -2
- package/tests/hall_city.mjs +5 -4
- package/tests/hall_private_palettes.mjs +148 -0
- package/tests/hall_sky.mjs +29 -32
- package/tests/hall_street.mjs +3 -2
- package/tests/helpers.mjs +25 -1
- package/tests/welcome_boot.mjs +1 -1
- package/tests/welcome_boot_browser.mjs +1 -1
package/tests/hall_city.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import path from 'node:path';
|
|
|
25
25
|
import vm from 'node:vm';
|
|
26
26
|
import { createRequire } from 'node:module';
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
|
+
import { cityTokens } from './helpers.mjs';
|
|
28
29
|
|
|
29
30
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
30
31
|
const PUB = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -66,13 +67,13 @@ test('the nine civic recipes draw exactly the prototype paths, in both modes', (
|
|
|
66
67
|
const a = []; const b = [];
|
|
67
68
|
p.isoInstitution(a, pk, kind, x, y, { fixes: false });
|
|
68
69
|
City.isoInstitution(b, ck, kind, x, y, { fixes: false });
|
|
69
|
-
assert.deepEqual(b, a, `${mode} ${kind}`);
|
|
70
|
+
assert.deepEqual(b, cityTokens(a), `${mode} ${kind}`);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
test('the iso sub-palette is the prototype theme().iso, both modes', () => {
|
|
75
|
-
for (const mode of ['dark', 'light']) assert.deepEqual(City.isoTheme(mode), new Proto({ mode }).theme(mode).iso, mode);
|
|
76
|
+
for (const mode of ['dark', 'light']) assert.deepEqual(City.isoTheme(mode), cityTokens(new Proto({ mode }).theme(mode).iso), mode);
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
test('every work kind the prototype draws is drawn the same way', () => {
|
|
@@ -83,7 +84,7 @@ test('every work kind the prototype draws is drawn the same way', () => {
|
|
|
83
84
|
const a = []; const b = [];
|
|
84
85
|
p.isoWork(a, pk, 6, 2, kind, 58);
|
|
85
86
|
City.isoWork(b, ck, 6, 2, kind, 58);
|
|
86
|
-
assert.deepEqual(b, a, kind);
|
|
87
|
+
assert.deepEqual(b, cityTokens(a), kind);
|
|
87
88
|
}
|
|
88
89
|
});
|
|
89
90
|
|
|
@@ -95,7 +96,7 @@ test('the ground, ring road, lanes and plaza are the prototype home view, path f
|
|
|
95
96
|
const home = p.homeView(T, p.matters(), false);
|
|
96
97
|
const model = City.cityModel({ docket: docket([matter()]), goals: [], rollups: [], mode: 'light' });
|
|
97
98
|
// The prototype's flat list is ground, 4 roads, lanes, plaza, then its 4 plots and their grid.
|
|
98
|
-
assert.deepEqual(model.flat.slice(0, 7), home.flat.slice(0, 7));
|
|
99
|
+
assert.deepEqual(model.flat.slice(0, 7), cityTokens(home.flat.slice(0, 7)));
|
|
99
100
|
});
|
|
100
101
|
|
|
101
102
|
test('with four goals the districts sit on the prototype\'s four plots, in its order', () => {
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// tests/hall_private_palettes.mjs — the hall's private-palette rooms mix the shared tokens only
|
|
2
|
+
// (task 1005087, goal 1000138 "One Cloud Bongos design system").
|
|
3
|
+
//
|
|
4
|
+
// The UI audit found Studio, Thinking (the sky), the Tweak editor, City, Approvals, Atlas and the
|
|
5
|
+
// welcome screen each re-typing the salmon and the ink, their own font stacks, and off-the-shelf
|
|
6
|
+
// green / amber / red — so a re-branded instance saw those rooms in Cloud Bongos's colours. This
|
|
7
|
+
// scans every sheet and script of those rooms (comments stripped) and fails on:
|
|
8
|
+
// * a hex, an rgb()/rgba() or a bare [r, g, b] triple equal to one of the pack's colours, the
|
|
9
|
+
// hall's dark-tier inks, the accent's light tint, or an off-the-shelf status green/amber/red;
|
|
10
|
+
// * a font family typed in by name instead of the pack's stacks;
|
|
11
|
+
// * an overshooting (bounce) easing;
|
|
12
|
+
// * the welcome screen's beacon or star twinkle coming back.
|
|
13
|
+
// Black and white (with or without alpha) stay legal: they are scrims, halos and the glass rooms'
|
|
14
|
+
// inks. Each guard was watched go red on the pre-task sheets.
|
|
15
|
+
import { test, after } from 'node:test';
|
|
16
|
+
import assert from 'node:assert/strict';
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
21
|
+
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const kit = require('../modules/ui-design/kit/lib.js');
|
|
24
|
+
|
|
25
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
|
+
const PUB = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
27
|
+
const ROOMS = /^(studio|thinking|sky-|tweak-editor|city|approval-queue|atlas|welcome-boot)/;
|
|
28
|
+
const FILES = fs.readdirSync(PUB).filter((f) => ROOMS.test(f) && /\.(css|js|html)$/.test(f));
|
|
29
|
+
|
|
30
|
+
const strip = (s) => s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/<!--[\s\S]*?-->/g, '').replace(/(^|[^:'"`\\])\/\/[^\n]*/g, '$1');
|
|
31
|
+
|
|
32
|
+
const PACK = Object.values(JSON.parse(fs.readFileSync(path.join(ROOT, 'config', 'branding.neutral.json'), 'utf8')).theme.ui.colors);
|
|
33
|
+
const BRAND = new Set([...PACK,
|
|
34
|
+
// the hall's dark-tier inks and grounds, and the accent's light tint the glass rooms used for links
|
|
35
|
+
'#101012', '#08080a', '#c0c0c0', '#a8a8a8', '#fcbfa4', '#96410f',
|
|
36
|
+
// off-the-shelf status green / amber / red (Tailwind's, and the rooms' own copies of them)
|
|
37
|
+
'#10b981', '#22c55e', '#4ade80', '#16a34a', '#15803d', '#166534', '#1f7a44',
|
|
38
|
+
'#f59e0b', '#fbbf24', '#facc15', '#d97706', '#b45309', '#92400e', '#8f6a0c',
|
|
39
|
+
'#ef4444', '#dc2626', '#b91c1c', '#991b1b', '#f87171', '#bb1f1f',
|
|
40
|
+
].map((h) => h.toLowerCase()));
|
|
41
|
+
const tripleOf = (h) => [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16)).join(',');
|
|
42
|
+
const TRIPLES = new Set([...BRAND].map(tripleOf));
|
|
43
|
+
|
|
44
|
+
function brandLiterals(src) {
|
|
45
|
+
const s = strip(src);
|
|
46
|
+
const hits = [];
|
|
47
|
+
for (const m of s.matchAll(/#[0-9a-f]{6}\b/gi)) if (BRAND.has(m[0].toLowerCase())) hits.push(m[0]);
|
|
48
|
+
for (const m of s.matchAll(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/gi)) if (TRIPLES.has(`${+m[1]},${+m[2]},${+m[3]}`)) hits.push(m[0]);
|
|
49
|
+
for (const m of s.matchAll(/\[\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\]/g)) if (TRIPLES.has(`${+m[1]},${+m[2]},${+m[3]}`)) hits.push(m[0]);
|
|
50
|
+
for (const m of s.matchAll(/--[a-z-]+-rgb\s*:\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/gi)) if (TRIPLES.has(`${+m[1]},${+m[2]},${+m[3]}`)) hits.push(m[0]);
|
|
51
|
+
return hits;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test('the rooms are all here (the scan is not vacuous)', () => {
|
|
55
|
+
for (const f of ['studio.css', 'thinking.css', 'sky-draw.js', 'tweak-editor.css', 'city.css', 'city-draw.js', 'approval-queue.css', 'atlas.css', 'atlas.js', 'welcome-boot.css', 'welcome-boot.js']) {
|
|
56
|
+
assert.ok(FILES.includes(f), `${f} is scanned`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('no hex, rgb or [r, g, b] literal for a brand colour or an off-the-shelf status colour remains', () => {
|
|
61
|
+
const found = {};
|
|
62
|
+
for (const f of FILES) { const h = brandLiterals(fs.readFileSync(path.join(PUB, f), 'utf8')); if (h.length) found[f] = [...new Set(h)]; }
|
|
63
|
+
assert.deepEqual(found, {}, 'mix the shared tokens (the pack\'s, the status vocabulary) instead');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('the scan catches each shape of brand literal, and spares black, white and comments', () => {
|
|
67
|
+
assert.deepEqual(brandLiterals('a { color: #FB9569; }'), ['#FB9569']);
|
|
68
|
+
assert.deepEqual(brandLiterals('a { color: rgba(23, 24, 26, .4); }'), ['rgba(23, 24, 26']);
|
|
69
|
+
assert.deepEqual(brandLiterals('const ACC = [251, 149, 105];'), ['[251, 149, 105]']);
|
|
70
|
+
assert.deepEqual(brandLiterals('--sky-accent-rgb: 251,149,105;'), ['--sky-accent-rgb: 251,149,105']);
|
|
71
|
+
assert.deepEqual(brandLiterals("dot: '#f59e0b'"), ['#f59e0b']);
|
|
72
|
+
assert.deepEqual(brandLiterals('/* was #fb9569 */ a { color: rgba(0, 0, 0, .5); background: #ffffff; }'), []);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('no room types a font family by name: the pack\'s stacks only', () => {
|
|
76
|
+
const hits = [];
|
|
77
|
+
for (const f of FILES.filter((n) => /\.(css|js)$/.test(n))) {
|
|
78
|
+
const s = strip(fs.readFileSync(path.join(PUB, f), 'utf8'));
|
|
79
|
+
if (/Manrope|JetBrains Mono|Segoe UI|Helvetica|Arial\b/.test(s)) hits.push(f);
|
|
80
|
+
}
|
|
81
|
+
assert.deepEqual(hits, [], 'use var(--font-body) / var(--font-display) / var(--font-mono)');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('no bounce: no easing in the rooms overshoots (use --ds-ease / --ds-ease-arrive)', () => {
|
|
85
|
+
const hits = [];
|
|
86
|
+
for (const f of FILES) {
|
|
87
|
+
const s = strip(fs.readFileSync(path.join(PUB, f), 'utf8'));
|
|
88
|
+
for (const m of s.matchAll(/cubic-bezier\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)/g)) {
|
|
89
|
+
if (+m[2] > 1 || +m[4] > 1 || +m[2] < 0 || +m[4] < 0) hits.push(`${f}: ${m[0]}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
assert.deepEqual(hits, []);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('the welcome screen keeps no beacon and no twinkle', () => {
|
|
96
|
+
const css = strip(fs.readFileSync(path.join(PUB, 'welcome-boot.css'), 'utf8'));
|
|
97
|
+
const js = strip(fs.readFileSync(path.join(PUB, 'welcome-boot.js'), 'utf8'));
|
|
98
|
+
assert.doesNotMatch(css, /wb-beacon|wb-twinkle/, 'no beacon blink, no star twinkle in the sheet');
|
|
99
|
+
assert.doesNotMatch(js, /wb-beacon/, 'the script draws no beacon');
|
|
100
|
+
assert.doesNotMatch(js, /\.wb-star[^]*animation-duration/, 'no star carries a twinkle timing');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ── the done-when, in a browser: a re-branded look renders the rooms in its own colours ──
|
|
104
|
+
// The ui-design kit's stub serves the hall under a look from the style library (grove: a
|
|
105
|
+
// sand accent), and every computed colour on each room is checked for the neutral pack's
|
|
106
|
+
// salmon. Skips when the machine has no system Edge or Chrome, as ui_design_kit_fit does.
|
|
107
|
+
let browser = null;
|
|
108
|
+
try { browser = await kit.launchBrowser(); } catch (e) { if (!(e instanceof kit.NoBrowserError)) throw e; }
|
|
109
|
+
after(() => browser && browser.close());
|
|
110
|
+
|
|
111
|
+
test('under the grove look, Thinking, the Tweak editor, Atlas, City and Studio show no salmon in either mode', { skip: !browser && 'no system Edge or Chrome', timeout: 180000 }, async () => {
|
|
112
|
+
const stub = await kit.startStub({ surface: 'hall-ui', prefix: '/builders/', style: 'grove', auth: true });
|
|
113
|
+
try {
|
|
114
|
+
for (const mode of ['dark', 'light']) {
|
|
115
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 860 }, colorScheme: mode });
|
|
116
|
+
await ctx.addInitScript(() => { try { sessionStorage.setItem('hall-craft-asked', '1'); } catch (e) { /* private mode */ } });
|
|
117
|
+
for (const room of ['thinking', 'tweak-editor', 'atlas', 'city', 'studio']) {
|
|
118
|
+
const page = await ctx.newPage();
|
|
119
|
+
await page.goto(`${stub.base}/builders/${room}`, { waitUntil: 'load' });
|
|
120
|
+
await page.waitForTimeout(800);
|
|
121
|
+
const r = await page.evaluate(() => {
|
|
122
|
+
const SALMON = /\b251,\s*149,\s*105\b/;
|
|
123
|
+
const hits = [];
|
|
124
|
+
for (const el of document.querySelectorAll('*')) {
|
|
125
|
+
const cs = getComputedStyle(el);
|
|
126
|
+
for (const k of ['color', 'backgroundColor', 'borderTopColor', 'fill', 'stroke', 'outlineColor', 'boxShadow', 'backgroundImage']) {
|
|
127
|
+
if (SALMON.test(cs[k] || '')) hits.push(`${el.tagName.toLowerCase()} ${k}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { accent: getComputedStyle(document.documentElement).getPropertyValue('--accent').trim().toLowerCase(), hits };
|
|
131
|
+
});
|
|
132
|
+
await page.close();
|
|
133
|
+
assert.notEqual(r.accent, '#fb9569', `${mode} ${room}: the look's accent is applied`);
|
|
134
|
+
assert.deepEqual(r.hits, [], `${mode} ${room}: no salmon left`);
|
|
135
|
+
}
|
|
136
|
+
await ctx.close();
|
|
137
|
+
}
|
|
138
|
+
} finally { stub.stop(); }
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('the sky reads its colours off the stage as tokens, not channels', () => {
|
|
142
|
+
const css = strip(fs.readFileSync(path.join(PUB, 'thinking.css'), 'utf8'));
|
|
143
|
+
assert.doesNotMatch(css, /--sky-[a-z]+-rgb/, 'no rgb channel tokens');
|
|
144
|
+
assert.match(css, /--void:\s*var\(--bg\)/, 'the void is the hall\'s ground');
|
|
145
|
+
assert.match(css, /--accent:\s*var\(--accent-ink\)/, 'the accent is the pack\'s (darkened on paper by the hall\'s own rule)');
|
|
146
|
+
const draw = fs.readFileSync(path.join(PUB, 'sky-draw.js'), 'utf8');
|
|
147
|
+
for (const tok of ['--ink', '--void', '--sky-cold', '--accent', '--chart']) assert.match(draw, new RegExp(`fill\\('${tok}'`), `sky-draw reads ${tok}`);
|
|
148
|
+
});
|
package/tests/hall_sky.mjs
CHANGED
|
@@ -559,65 +559,62 @@ const skyBlocks = () => {
|
|
|
559
559
|
return { dark: dark && dark[1], light: light && light[1], twin: twin && twin[1] };
|
|
560
560
|
};
|
|
561
561
|
|
|
562
|
+
// What the stage's computed style answers in each theme: the two blocks above,
|
|
563
|
+
// read the way the browser would.
|
|
564
|
+
const stageStyles = (body) => Object.fromEntries([...body.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)].map((m) => [m[1], m[2].trim()]));
|
|
565
|
+
|
|
562
566
|
await test('LIGHT IS WRITTEN TWICE AND THE TWO ARE ONE — the data-theme block and its first-paint twin declare the same lines', () => {
|
|
563
567
|
const { dark, light, twin } = skyBlocks();
|
|
564
|
-
assert.ok(dark, 'the
|
|
568
|
+
assert.ok(dark, 'the stage token block is found');
|
|
565
569
|
assert.ok(light, 'the html[data-theme="light"] block is found');
|
|
566
570
|
assert.ok(twin, 'the prefers-color-scheme: light twin is found');
|
|
567
571
|
same(declLines(twin), declLines(light), 'the twin is the data-theme block, line for line');
|
|
568
|
-
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
await test('light re-declares every colour the dark set declares, and every channel is three bytes', () => {
|
|
572
|
-
const { dark, light } = skyBlocks();
|
|
573
|
-
const type = new Set(['--label', '--mono', '--face']); // the faces are not a theme
|
|
574
|
-
const want = [...declNames(dark)].filter((n) => !type.has(n));
|
|
575
|
-
assert.ok(want.length >= 15, `the dark set names its colours (${want.length})`);
|
|
576
|
-
const have = declNames(light);
|
|
577
|
-
same(want.filter((n) => !have.has(n)), [], 'no dark colour is left for light to inherit');
|
|
578
|
-
for (const [name, body] of [['dark', dark], ['light', light]]) {
|
|
579
|
-
const chans = [...body.matchAll(/(--sky-[\w-]+-rgb)\s*:\s*([^;]+);/g)];
|
|
580
|
-
assert.ok(chans.length >= 5, `${name} declares the canvas's channels`);
|
|
581
|
-
for (const [, n, v] of chans) {
|
|
582
|
-
const parts = v.split(',').map((x) => Number(x.trim()));
|
|
583
|
-
assert.ok(parts.length === 3 && parts.every((x) => Number.isInteger(x) && x >= 0 && x <= 255), `${name} ${n} is r,g,b (got "${v}")`);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
572
|
+
same([...declNames(light)].sort(), ['--sky-drop', '--sky-temp', 'color-scheme'], 'and it carries only what the hall\'s own tier cannot: the card shadow and the canvas warmth');
|
|
586
573
|
});
|
|
587
574
|
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
|
|
575
|
+
// task 1005087: the sky's colours are the HALL's tokens, which already flip with the theme and
|
|
576
|
+
// follow the pack, so a re-branded instance's sky wears its colours. No literal, no rgb channel.
|
|
577
|
+
await test('the stage mixes the hall\'s tokens only: every colour is a var() or a color-mix of them', () => {
|
|
578
|
+
const { dark } = skyBlocks();
|
|
579
|
+
const type = new Set(['--label', '--mono', '--face', '--sky-temp', '--sky-drop']);
|
|
580
|
+
const colours = Object.entries(stageStyles(dark)).filter(([n]) => !type.has(n));
|
|
581
|
+
assert.ok(colours.length >= 10, `the stage names its colours (${colours.length})`);
|
|
582
|
+
for (const [n, v] of colours) assert.match(v, /^(var\(--[\w-]+\)|color-mix\(in srgb, var\(--[\w-]+\) [\d.]+%, (transparent|var\(--[\w-]+\))\))$/, `${n}: ${v}`);
|
|
583
|
+
assert.doesNotMatch(SKY_CSS.replace(/\/\*[\s\S]*?\*\//g, ''), /--sky-[\w-]+-rgb/, 'no rgb channel is left');
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// The stage's computed colours in each theme, as the browser answers the probe sky-draw.js reads.
|
|
587
|
+
const DARK = { '--ink': 'rgb(255, 255, 255)', '--void': 'rgb(0, 0, 0)', '--sky-cold': 'color(srgb 0.08 0.08 0.08)', '--accent': 'rgb(251, 149, 105)', '--chart': 'rgb(251, 191, 36)', '--sky-temp': '1' };
|
|
588
|
+
const LIGHT = { '--ink': 'rgb(23, 24, 26)', '--void': 'rgb(237, 236, 238)', '--sky-cold': 'color(srgb 0.88 0.88 0.89)', '--accent': 'rgb(163, 72, 26)', '--chart': 'rgb(180, 83, 9)', '--sky-temp': '.42' };
|
|
591
589
|
|
|
592
590
|
await test('THE CANVAS FOLLOWS THE TOGGLE — a theme flip re-reads the stage and repaints, with no reload', async () => {
|
|
593
|
-
const {
|
|
594
|
-
const styles = stageStyles(dark);
|
|
591
|
+
const styles = { ...DARK };
|
|
595
592
|
const p = await boot({ mine: mine(FIVE()), sky: sky([well()]), versions: versions(), styles });
|
|
596
593
|
const SKY = p.SKY(); const acc = SKY.ACC;
|
|
597
594
|
assert.ok(p.themeObservers() >= 1, 'the page watches html[data-theme]');
|
|
598
595
|
p.frame();
|
|
599
|
-
same(SKY.INK, [255, 255, 255], 'dark: the ink is the
|
|
596
|
+
same(SKY.INK, [255, 255, 255], 'dark: the ink is the hall\'s white');
|
|
600
597
|
same(SKY.VOID, [0, 0, 0], 'dark: the void is black');
|
|
601
598
|
same(SKY.tone([255, 238, 222]), [255, 238, 222], 'dark: a thought keeps its temperature');
|
|
602
599
|
|
|
603
|
-
Object.assign(styles,
|
|
600
|
+
Object.assign(styles, LIGHT);
|
|
604
601
|
p.S().dirty = false;
|
|
605
602
|
p.setTheme('light');
|
|
606
603
|
assert.equal(!!p.S().dirty, true, 'the flip marks the sky for a redraw');
|
|
607
604
|
p.frame();
|
|
608
|
-
same(SKY.INK,
|
|
609
|
-
same(SKY.VOID,
|
|
610
|
-
same(SKY.ACC,
|
|
605
|
+
same(SKY.INK, [23, 24, 26], 'light: the ink is the stage\'s');
|
|
606
|
+
same(SKY.VOID, [237, 236, 238], 'light: the void is paper');
|
|
607
|
+
same(SKY.ACC, [163, 72, 26], 'light: the accent darkens');
|
|
611
608
|
assert.equal(SKY.ACC === acc, true, 'refilled in place, so SKY.ACC is still the object every file holds');
|
|
612
|
-
|
|
613
|
-
same(SKY.tone([255, 238, 222]), [255, 238, 222].map((v) => Math.round(v * k)), 'light: a near-white thought darkens to read on paper');
|
|
609
|
+
same(SKY.tone([255, 238, 222]), [255, 238, 222].map((v) => Math.round(v * 0.42)), 'light: a near-white thought darkens to read on paper');
|
|
614
610
|
assert.equal(SKY.ink(0.5), `rgba(${SKY.INK.join(',')},0.5)`, 'ink() paints in the new ink');
|
|
615
611
|
|
|
616
|
-
Object.assign(styles,
|
|
612
|
+
Object.assign(styles, DARK);
|
|
617
613
|
p.setTheme('dark');
|
|
618
614
|
p.frame();
|
|
619
615
|
same(SKY.INK, [255, 255, 255], 'and back: the white returns');
|
|
620
616
|
assert.equal(SKY.ink(0.5), 'rgba(255,255,255,0.5)', 'ink() forgot the light strings');
|
|
621
617
|
});
|
|
622
618
|
|
|
619
|
+
|
|
623
620
|
summary();
|
package/tests/hall_street.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import path from 'node:path';
|
|
|
26
26
|
import vm from 'node:vm';
|
|
27
27
|
import { createRequire } from 'node:module';
|
|
28
28
|
import { fileURLToPath } from 'node:url';
|
|
29
|
+
import { cityTokens } from './helpers.mjs';
|
|
29
30
|
|
|
30
31
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
31
32
|
const PUB = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -83,7 +84,7 @@ test('the street palette is the prototype theme() (less the iso half city-draw.j
|
|
|
83
84
|
const t = new Proto({ mode }).theme(mode);
|
|
84
85
|
delete t.iso;
|
|
85
86
|
assert.deepEqual(S.theme(mode), t, mode);
|
|
86
|
-
assert.deepEqual(City.isoTheme(mode), new Proto({ mode }).theme(mode).iso, 'and the iso half is still city-draw.js\'s');
|
|
87
|
+
assert.deepEqual(City.isoTheme(mode), cityTokens(new Proto({ mode }).theme(mode).iso), 'and the iso half is still city-draw.js\'s');
|
|
87
88
|
}
|
|
88
89
|
});
|
|
89
90
|
|
|
@@ -123,7 +124,7 @@ test('a task not started is the prototype\'s workItem "ready" slab, and the more
|
|
|
123
124
|
const a = []; const b = [];
|
|
124
125
|
p.workItem(a, T, 'engineer', 'ready', 660, 19, 0);
|
|
125
126
|
S.work(b, S.theme('dark'), 'slab', 660, 0, 19, 6, false);
|
|
126
|
-
assert.deepEqual(b, a, 'the slab, at a base of 0 as workItem draws it');
|
|
127
|
+
assert.deepEqual(b, cityTokens(a), 'the slab, at a base of 0 as workItem draws it');
|
|
127
128
|
const c = []; const d = [];
|
|
128
129
|
p.workItem(c, T, 'engineer', 'backlog', 660, 40, 0);
|
|
129
130
|
S.work(d, S.theme('dark'), 'stakes', 660, 0, 40, 0, false);
|
package/tests/helpers.mjs
CHANGED
|
@@ -980,7 +980,12 @@ export function bootSkyPage({
|
|
|
980
980
|
setTimeout: windowObj.setTimeout, clearTimeout: windowObj.clearTimeout,
|
|
981
981
|
MutationObserver, console, Event, URLSearchParams, Math, Number, Date, JSON, Map, Set, Promise, Object, Array, String, Infinity, NaN,
|
|
982
982
|
};
|
|
983
|
-
if (styles) sandbox.getComputedStyle = (el) => ({
|
|
983
|
+
if (styles) sandbox.getComputedStyle = (el) => ({
|
|
984
|
+
getPropertyValue: (name) => (el && String(el.className || '').split(/\s+/).includes('sky-stage') && name in styles ? styles[name] : ''),
|
|
985
|
+
// task 1005087: sky-draw.js resolves a colour token by computing it on a probe
|
|
986
|
+
// (style.color = 'var(--ink)'); `styles` answers it with the computed colour.
|
|
987
|
+
get color() { const m = /var\((--[\w-]+)\)/.exec(String((el && el.style && el.style.color) || '')); return (m && styles[m[1]]) || ''; },
|
|
988
|
+
});
|
|
984
989
|
sandbox.globalThis = sandbox;
|
|
985
990
|
vm.createContext(sandbox);
|
|
986
991
|
for (const [name, src] of SKY_SRC) vm.runInContext(src, sandbox, { filename: name });
|
|
@@ -1355,3 +1360,22 @@ export async function insertTestBuilders(db, rows, { onConflict = 'ON CONFLICT (
|
|
|
1355
1360
|
);
|
|
1356
1361
|
return out;
|
|
1357
1362
|
}
|
|
1363
|
+
|
|
1364
|
+
// The governor city's iso palette moved onto the pack's tokens (task 1005087): city-draw.js
|
|
1365
|
+
// writes var(--bg-card), var(--accent) and color-mix()es of --ink / --sea / --accent where
|
|
1366
|
+
// docs/design/mocks/governor-city/Street.dc.html (the design of record) has the neutral pack's
|
|
1367
|
+
// literals. Mapping the prototype's output through the same substitution keeps the "draws
|
|
1368
|
+
// exactly the prototype's paths" tests honest: every path, every other colour, unchanged.
|
|
1369
|
+
export function cityTokens(x) {
|
|
1370
|
+
const pct = (a) => `${Math.round(parseFloat(a) * 1000) / 10}%`;
|
|
1371
|
+
const one = (s) => s
|
|
1372
|
+
.replace(/^rgba\(23,24,26,([.\d]+)\)$/, (m, a) => `color-mix(in srgb, var(--ink) ${pct(a)}, transparent)`)
|
|
1373
|
+
.replace(/^rgba\(91,111,208,([.\d]+)\)$/, (m, a) => `color-mix(in srgb, var(--sea) ${pct(a)}, transparent)`)
|
|
1374
|
+
.replace(/^rgba\(251,149,105,([.\d]+)\)$/, (m, a) => `color-mix(in srgb, var(--accent) ${pct(a)}, transparent)`)
|
|
1375
|
+
.replace(/^#fbfafc$/, 'var(--bg-card)')
|
|
1376
|
+
.replace(/^#fb9569$/, 'var(--accent)');
|
|
1377
|
+
if (typeof x === 'string') return one(x);
|
|
1378
|
+
if (Array.isArray(x)) return x.map(cityTokens);
|
|
1379
|
+
if (x && typeof x === 'object') return Object.fromEntries(Object.entries(x).map(([k, v]) => [k, cityTokens(v)]));
|
|
1380
|
+
return x;
|
|
1381
|
+
}
|
package/tests/welcome_boot.mjs
CHANGED
|
@@ -62,7 +62,7 @@ test('both paintings ship, as WebP, in the one shape the motion is laid out for'
|
|
|
62
62
|
|
|
63
63
|
test('every light sits inside the painting, and every pulse climbs inside its tower', () => {
|
|
64
64
|
const inside = ([x, y]) => x >= 0 && x <= W.AW && y >= 0 && y <= W.AH;
|
|
65
|
-
|
|
65
|
+
assert.equal(W.BEACONS, undefined, 'the blinking mast lights are gone (task 1005087)');
|
|
66
66
|
for (const t of W.TOWERS) assert.ok(inside([t.x, t.y]) && inside([t.x + t.w, t.y + t.h]), 'tower inside the painting');
|
|
67
67
|
const runs = W.pulseRuns(W.TOWERS, 3, 7);
|
|
68
68
|
assert.equal(runs.length, W.TOWERS.length * 3);
|
|
@@ -264,7 +264,7 @@ const look = (page) => page.evaluate(() => {
|
|
|
264
264
|
test('by day the day painting opens from a white veil; by night the night one from black, with its lights', { skip }, async () => {
|
|
265
265
|
for (const [time, want] of [
|
|
266
266
|
[new Date(2026, 9, 23, 10, 0), { day: true, night: false, veil: 'rgb(255, 255, 255)', art: '/builders/welcome-day.webp', beacons: 0 }],
|
|
267
|
-
[new Date(2026, 9, 23, 22, 0), { day: false, night: true, veil: 'rgb(0, 0, 0)', art: '/builders/welcome-night.webp', beacons:
|
|
267
|
+
[new Date(2026, 9, 23, 22, 0), { day: false, night: true, veil: 'rgb(0, 0, 0)', art: '/builders/welcome-night.webp', beacons: 0 }], // task 1005087 took the beacons out
|
|
268
268
|
]) {
|
|
269
269
|
const ctx = await hall({ time });
|
|
270
270
|
const { page } = await open(ctx);
|