@bongos/core 1.25.9 → 1.25.11
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 +32 -27
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +174 -174
- package/modules/government/docket-vocab.js +1 -1
- package/modules/hall-ui/public/controls.css +3 -0
- package/modules/hall-ui/public/street-card.js +4 -0
- package/modules/hall-ui/public/street-desk.js +3 -3
- package/modules/hall-ui/public/street-inside.js +2 -2
- package/modules/hall-ui/public/style.css +48 -0
- package/modules/hall-ui/records/visor.md +1 -1
- package/modules/ui-design/public/brand.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/bongos/db-kernel.js +6 -2
- package/src/bongos/routes/builders.js +26 -0
- package/src/module-api.js +1 -1
- package/tests/builder_card_requirement.mjs +28 -0
- package/tests/hall_smoke.mjs +116 -0
- package/tests/hall_street_desk.mjs +1 -1
- package/tests/hall_street_inside.mjs +1 -1
- package/tests/hall_visor.mjs +3 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// tests/hall_smoke.mjs — the smoke family across the hall (task 1005267, follow-up to task 1005252).
|
|
2
|
+
//
|
|
3
|
+
// The owner picked "all six" from the Smoke across Helm lab: the hall's glass is one family.
|
|
4
|
+
// Light smoke (--smoke) for small things (a toast, the city's labels), deep smoke (--smoke-deep)
|
|
5
|
+
// for big ones (panels over art, the visor's sheet and card, a dialog, the top bar and rail), a
|
|
6
|
+
// smoke scrim behind a dialog. A phone and "less transparency" turn the family back to solid night
|
|
7
|
+
// by its TOKENS, so no rule needs a twin. The status page links controls.css without the hall's
|
|
8
|
+
// tier, so the one rule there falls back to no blur.
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import { test, after } from 'node:test';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
19
|
+
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8').replace(/\r\n/g, '\n');
|
|
20
|
+
const STYLE = read('style.css');
|
|
21
|
+
const CONTROLS = read('controls.css');
|
|
22
|
+
|
|
23
|
+
const familyAt = STYLE.indexOf('/* THE SMOKE FAMILY ON THE HALL');
|
|
24
|
+
const FAMILY = STYLE.slice(familyAt, STYLE.indexOf('/* The Kill Switch Rule', familyAt));
|
|
25
|
+
const ruleFor = (sel) => { const at = FAMILY.indexOf(sel); assert.ok(at >= 0, sel + ' is in the family'); return FAMILY.slice(at, FAMILY.indexOf('}', at)); };
|
|
26
|
+
|
|
27
|
+
test('source: one family block, behind @supports, lifted by the hall\'s own stamp', () => {
|
|
28
|
+
assert.ok(familyAt > 0, 'the family block exists');
|
|
29
|
+
assert.match(FAMILY, /@supports \(\(backdrop-filter: blur\(1px\)\) or \(-webkit-backdrop-filter: blur\(1px\)\)\) \{/, 'a browser without blur keeps today\'s surfaces');
|
|
30
|
+
const sels = FAMILY.replace(/\/\*[\s\S]*?\*\//g, '').match(/[^{}]+\{/g).join(' ');
|
|
31
|
+
for (const s of sels.match(/\.[a-z][\w-]*/g)) {
|
|
32
|
+
if (/^\.(app|game|gs|opt|craft|visor|hall|toast|city|page|hc)/.test(s)) continue;
|
|
33
|
+
assert.fail('unexpected selector in the family: ' + s);
|
|
34
|
+
}
|
|
35
|
+
for (const line of FAMILY.replace(/\/\*[\s\S]*?\*\//g, '').split('\n').filter((l) => /\{\s*$/.test(l) && !/@supports/.test(l))) {
|
|
36
|
+
for (const part of line.replace(/\{\s*$/, '').split(',').map((p) => p.trim()).filter(Boolean)) {
|
|
37
|
+
assert.match(part, /^html\[data-theme="dark"\]/, 'every selector starts at the hall\'s stamp: ' + part);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('source: the six surfaces spend the right weight of smoke', () => {
|
|
43
|
+
for (const sel of ['.game-menu__box', '.gs__menu', '.gs__load', '.opt-rail', '.craft-door', '.visor__sheet', '.visor__focus']) {
|
|
44
|
+
const r = ruleFor(sel);
|
|
45
|
+
assert.ok(r.includes('var(--smoke-deep)') && r.includes('backdrop-filter: var(--smoke-deep-blur)'), sel + ' takes deep smoke');
|
|
46
|
+
}
|
|
47
|
+
assert.ok(ruleFor('.hall-dialog {').includes('box-shadow: var(--smoke-edge), var(--ds-shadow-pop)'), 'a dialog keeps its lift under the pane');
|
|
48
|
+
const bars = ruleFor('.app-topbar');
|
|
49
|
+
assert.ok(bars.includes('.app-sidebar') && bars.includes('background: var(--smoke-deep);') && !bars.includes('sheen'), 'the bars: deep smoke, no sheen');
|
|
50
|
+
assert.ok(ruleFor('.hall-scrim {').includes('backdrop-filter: var(--smoke-scrim)'), 'a div overlay\'s scrim blurs the world');
|
|
51
|
+
const toast = ruleFor('.toast:not([data-kind="error"])');
|
|
52
|
+
assert.ok(toast.includes('var(--smoke)') && toast.includes('color: var(--ink)'), 'a toast is light smoke with white words; an error keeps its colour');
|
|
53
|
+
const labels = ruleFor('.city-line');
|
|
54
|
+
for (const s of ['.city-mark', '.city-plaque', '.city-archived', '.page-head__h', '.hc-ladder::before']) assert.ok(labels.includes(s), s + ' is a city label');
|
|
55
|
+
assert.ok(labels.includes('backdrop-filter: var(--smoke-blur)'), 'the city labels take the visor\'s light smoke');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('source: the native dialog\'s scrim blurs in the hall and nowhere else', () => {
|
|
59
|
+
assert.match(CONTROLS, /\.hall-dialog::backdrop \{ background: var\(--scrim\); \}/, 'the flat scrim stays the base');
|
|
60
|
+
assert.match(CONTROLS, /\.hall-dialog::backdrop \{ -webkit-backdrop-filter: var\(--smoke-scrim, none\); backdrop-filter: var\(--smoke-scrim, none\); \}/,
|
|
61
|
+
'no --smoke-scrim (the status page) falls back to no blur');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('source: the fallbacks are tokens — less transparency and a phone turn every pane to solid night', () => {
|
|
65
|
+
const NIGHT = 'html:root { --smoke: var(--night-plate); --smoke-deep: var(--bg); --smoke-sheen: none; --smoke-edge: 0 0 0 0 transparent;\n --smoke-blur: none; --smoke-deep-blur: none; --smoke-scrim: none; }';
|
|
66
|
+
const rt = STYLE.indexOf('@media (prefers-reduced-transparency: reduce) {');
|
|
67
|
+
assert.ok(STYLE.indexOf(NIGHT, rt) > rt && STYLE.indexOf(NIGHT, rt) < familyAt, 'less transparency: solid night, by the tokens');
|
|
68
|
+
const phone = STYLE.match(/tasks 1005148 and 1005195, the visor[\s\S]*?\n\}/)[0];
|
|
69
|
+
assert.ok(phone.includes(NIGHT), 'a phone: solid night, by the tokens (no live blur on the weakest GPU)');
|
|
70
|
+
assert.ok(STYLE.match(/--smoke-edge: 0 0 0 0 transparent/g).length === 2, 'the edge falls back to a no-op shadow, so the dialog\'s pair stays valid');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ---------- in a browser: the computed glass ----------
|
|
74
|
+
const lib = require(path.join(ROOT, 'modules', 'ui-design', 'kit', 'lib.js'));
|
|
75
|
+
let browser = null;
|
|
76
|
+
try { browser = await lib.launchBrowser(); } catch (e) { if (!(e instanceof lib.NoBrowserError)) throw e; }
|
|
77
|
+
after(() => browser && browser.close());
|
|
78
|
+
const skip = !browser && 'no system Edge or Chrome';
|
|
79
|
+
const ORIGIN = 'http://hall.test';
|
|
80
|
+
const PAGE = `<!doctype html><html data-theme="dark" data-faces="helm"><head><meta charset="utf-8">
|
|
81
|
+
<link rel="stylesheet" href="/builders/style.css"><link rel="stylesheet" href="/builders/controls.css"></head>
|
|
82
|
+
<body><div class="app-topbar">t</div><div class="app-sidebar">r</div><div class="game-menu__box">m</div><div class="opt-rail">o</div>
|
|
83
|
+
<div class="visor__sheet">s</div><div class="toast" data-visible="1">ok</div><div class="toast" data-kind="error">no</div>
|
|
84
|
+
<div class="city-plaque">p</div><dialog class="hall-dialog">d</dialog></body></html>`;
|
|
85
|
+
|
|
86
|
+
async function computed(width) {
|
|
87
|
+
const ctx = await browser.newContext({ viewport: { width, height: 800 } });
|
|
88
|
+
await ctx.route(ORIGIN + '/**', (route) => {
|
|
89
|
+
const p = new URL(route.request().url()).pathname;
|
|
90
|
+
if (p === '/') return route.fulfill({ contentType: 'text/html', body: PAGE });
|
|
91
|
+
const f = path.join(HALL, p.replace(/^\/builders\//, ''));
|
|
92
|
+
if (p.endsWith('.css') && fs.existsSync(f)) return route.fulfill({ contentType: 'text/css', body: fs.readFileSync(f) });
|
|
93
|
+
return route.fulfill({ status: 404, body: '' });
|
|
94
|
+
});
|
|
95
|
+
const page = await ctx.newPage();
|
|
96
|
+
await page.goto(ORIGIN + '/');
|
|
97
|
+
const out = await page.evaluate(() => {
|
|
98
|
+
const bf = (s) => getComputedStyle(document.querySelector(s)).backdropFilter;
|
|
99
|
+
return { bar: bf('.app-topbar'), rail: bf('.app-sidebar'), menu: bf('.game-menu__box'), opt: bf('.opt-rail'), sheet: bf('.visor__sheet'),
|
|
100
|
+
dialog: bf('.hall-dialog'), toast: bf('.toast[data-visible]'), err: bf('.toast[data-kind="error"]'), plaque: bf('.city-plaque') };
|
|
101
|
+
});
|
|
102
|
+
await ctx.close();
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
test('on a desk the six surfaces are smoked; an error toast is not', { skip }, async () => {
|
|
107
|
+
const g = await computed(1280);
|
|
108
|
+
for (const k of ['bar', 'rail', 'menu', 'opt', 'sheet', 'dialog']) assert.match(g[k], /blur\(24px\)/, k + ' is deep smoke');
|
|
109
|
+
for (const k of ['toast', 'plaque']) assert.match(g[k], /blur\(14px\)/, k + ' is light smoke');
|
|
110
|
+
assert.equal(g.err, 'none', 'an error toast keeps its own colour and no glass');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('on a phone every pane is solid night', { skip }, async () => {
|
|
114
|
+
const g = await computed(390);
|
|
115
|
+
for (const k of ['bar', 'rail', 'menu', 'opt', 'sheet', 'dialog', 'toast', 'plaque']) assert.equal(g[k], 'none', k + ' has no live blur on a phone');
|
|
116
|
+
});
|
|
@@ -190,7 +190,7 @@ test('a row\'s buttons are the card\'s acts under the board\'s words, and send t
|
|
|
190
190
|
assert.equal(ana.name, 'Ana Ruiz', 'a person is named by their name');
|
|
191
191
|
assert.deepEqual(ana.actions.map((a) => [a.key, a.label, a.tone]), [['fine', 'let in', 'primary'], ['refuse', 'not now', 'quiet']]);
|
|
192
192
|
assert.deepEqual(j(C.request(ana.card, 'fine')), { method: 'PATCH', url: '/api/bongos/access-requests/11', body: { status: 'invited' } });
|
|
193
|
-
assert.deepEqual(j(C.request(row('people',
|
|
193
|
+
assert.deepEqual(j(C.request(row('people', 7).card, 'refuse')), { method: 'POST', url: '/api/bongos/goals/42/requests/12/respond', body: { action: 'deny' } });
|
|
194
194
|
const rel = D.rowsFor('release', 0, dk, { now: NOW });
|
|
195
195
|
assert.deepEqual(rel[0].actions.map((a) => a.label), ['release', 'hold']);
|
|
196
196
|
assert.equal(C.request(rel[0].card, 'release').url, '/api/bongos/override-requests/13/decide');
|
|
@@ -587,7 +587,7 @@ test('a building address draws the interior in place of the street, from the doc
|
|
|
587
587
|
assert.equal(p.byId.get('streetInside').hidden, false);
|
|
588
588
|
assert.equal(p.byId.get('streetSizer').hidden, true, 'the street gives way');
|
|
589
589
|
assert.equal(p.byId.get('insideName').textContent, 'People');
|
|
590
|
-
assert.equal(p.byId.get('insideFloors').children.length,
|
|
590
|
+
assert.equal(p.byId.get('insideFloors').children.length, 8, 'a button per floor in use');
|
|
591
591
|
assert.equal(p.byId.get('insideFloors').children[4].attrs['aria-pressed'], 'true', 'the floor being decided is chosen');
|
|
592
592
|
assert.equal(p.byId.get('insideLift').style.transform, 'translateY(-208px)');
|
|
593
593
|
assert.equal(p.byId.get('insideTable'), undefined, 'no separate list of matters');
|
package/tests/hall_visor.mjs
CHANGED
|
@@ -219,7 +219,9 @@ test('source: the minimal visor spends the Helm tokens and types in no colour, s
|
|
|
219
219
|
assert.ok(plainAt >= 0 && plain.includes('html .visor__place, html .visor__who, html .visor__hint, html .visor__progress-words {'), 'less transparency is answered in style.css, lifted over visor.css');
|
|
220
220
|
assert.ok(plain.includes('background: var(--night-plate);') && plain.includes(' backdrop-filter: none;'), 'less transparency: the night plate again');
|
|
221
221
|
const smokeTokens = STYLE.match(/--smoke[a-z-]*:[^;]*;/g) || [];
|
|
222
|
-
|
|
222
|
+
const smokeNames = [...new Set(smokeTokens.map((t) => t.split(':')[0]))].sort();
|
|
223
|
+
assert.deepEqual(smokeNames, ['--smoke', '--smoke-blur', '--smoke-deep', '--smoke-deep-blur', '--smoke-edge', '--smoke-scrim', '--smoke-sheen'],
|
|
224
|
+
'one smoke family: the light pane, the deep pane, the scrim, the sheen and the edge (tasks 1005252, 1005267)');
|
|
223
225
|
assert.doesNotMatch(smokeTokens.join(' '), /rgba?\(|#[0-9a-f]{3,8}\b/i, 'every smoke colour derives from a Helm token, no literal');
|
|
224
226
|
assert.ok(STYLE.includes('html[data-game="on"] .visor__who, html[data-game="on"] .visor__hint { background: var(--night-plate); box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; }'), 'a phone keeps the night plate: no live blur on the weakest GPU');
|
|
225
227
|
assert.ok(STYLE.indexOf('backdrop-filter: none; }') > STYLE.indexOf('tasks 1005148 and 1005195, the visor'), 'and that rule sits in the shared phone block');
|