@bongos/core 1.24.55 → 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.
@@ -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 dark (template) token block is found');
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
- assert.ok(declLines(light).length >= 6, 'and the block is not vacuously empty');
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
- // What the stage's computed style answers in each theme: the two blocks above,
589
- // read the way the browser would.
590
- const stageStyles = (body) => Object.fromEntries([...body.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)].map((m) => [m[1], m[2].trim()]));
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 { dark, light } = skyBlocks();
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 template white');
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, stageStyles(light));
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, stageStyles(light)['--sky-ink-rgb'].split(',').map(Number), 'light: the ink is the stage\'s');
609
- same(SKY.VOID, stageStyles(light)['--sky-void-rgb'].split(',').map(Number), 'light: the void is paper');
610
- same(SKY.ACC, stageStyles(light)['--sky-accent-rgb'].split(',').map(Number), 'light: the accent darkens');
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
- const k = Number(stageStyles(light)['--sky-temp']);
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, stageStyles(dark));
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();
@@ -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) => ({ getPropertyValue: (name) => (el && String(el.className || '').split(/\s+/).includes('sky-stage') && name in styles ? styles[name] : '') });
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
+ }
@@ -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
- for (const b of W.BEACONS) assert.ok(inside(b), 'beacon ' + b);
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: 4 }],
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);