@bongos/core 1.24.61 → 1.24.63

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.
Files changed (40) hide show
  1. package/.bongos-core.json +54 -44
  2. package/docs/copy-inventory.md +152 -151
  3. package/docs/copy-registry.json +232 -203
  4. package/docs/module-api-changelog.md +4 -0
  5. package/docs/page-inventory.json +1 -0
  6. package/docs/page-readings.json +281 -249
  7. package/modules/hall-ui/public/approval-queue.css +5 -3
  8. package/modules/hall-ui/public/controls.css +10 -0
  9. package/modules/hall-ui/public/gate.js +11 -2
  10. package/modules/hall-ui/public/hall-dialog.js +50 -2
  11. package/modules/hall-ui/public/hall-kit.js +8 -3
  12. package/modules/hall-ui/public/roster.js +13 -5
  13. package/modules/hall-ui/public/settings-passkeys.js +4 -1
  14. package/modules/hall-ui/public/studio.css +19 -20
  15. package/modules/hall-ui/public/studio.html +1 -2
  16. package/modules/hall-ui/public/studio.js +6 -2
  17. package/modules/hall-ui/public/style.css +17 -5
  18. package/modules/hall-ui/public/thinking.css +18 -1
  19. package/modules/hall-ui/records/studio.md +2 -1
  20. package/modules/hall-ui/specimens/components.css +7 -0
  21. package/modules/npm-release/public/work.js +6 -1
  22. package/modules/public-landing/public/assets/cosmos.css +36 -13
  23. package/modules/public-landing/public/index.html +9 -1
  24. package/modules/public-landing/public/projects.html +11 -3
  25. package/modules/raise/page.js +3 -1
  26. package/modules/raise/public/edit.js +10 -3
  27. package/modules/raise/public/raise.css +31 -4
  28. package/modules/render-deploy/public/deploy.js +2 -2
  29. package/package-lock.json +2 -2
  30. package/package.json +1 -1
  31. package/release-notes.json +12 -0
  32. package/src/module-api.js +1 -1
  33. package/tests/hall_dialog.mjs +169 -17
  34. package/tests/hall_studio_home.mjs +8 -1
  35. package/tests/hall_topbar_320.mjs +145 -0
  36. package/tests/landing_shared_controls.mjs +157 -0
  37. package/tests/npm_release_release.mjs +1 -1
  38. package/tests/projects_hub.mjs +1 -1
  39. package/tests/projects_hub_pre_uat.mjs +3 -1
  40. package/tests/roster_government_columns.mjs +3 -1
@@ -0,0 +1,157 @@
1
+ // tests/landing_shared_controls.mjs — the landing, the hub and the raise page ask in the shared
2
+ // dialog and draw their buttons and dialog frame on the shared scales (task 1005136, goal 1000138).
3
+ //
4
+ // The UI check found the hub's one question (start another project while one builds) and the raise
5
+ // editor's "discard your changes?" still in the browser's confirm box, the hub's project card and
6
+ // both pages' buttons on typed-in corners, sizes and durations, and the signed-in landing bar at
7
+ // 320 overflowing (331px) with the mark under the "projects" label. This pins:
8
+ // 1. both pages load the shared dialog (hall-dialog.js, after the shared escaper) and ask in it;
9
+ // 2. the dialog the shared component builds has a frame on each page, on the shared scales;
10
+ // 3. the buttons and the project card spend the shared corner, line, layer, size and duration;
11
+ // 4. the signed-in bar folds the second-account door into the profile band below 900, keeping
12
+ // the same door there;
13
+ // 5. in a real browser (skips without a system Edge or Chrome): the hub's confirm opens in the
14
+ // shared component wearing the landing's frame and buttons, and the signed-in landing bar,
15
+ // built from index.html's own sheet and markup, holds one row at 320 with the mark clear of
16
+ // the "projects" trigger. The full page is the kit's: render.js --page / (0 problems).
17
+ import { test, after } from 'node:test';
18
+ import assert from 'node:assert/strict';
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+ import { createRequire } from 'node:module';
23
+
24
+ const require = createRequire(import.meta.url);
25
+ const kit = require('../modules/ui-design/kit/lib.js');
26
+
27
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
28
+ const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
29
+ const LANDING = 'modules/public-landing/public/';
30
+ const COSMOS = read(`${LANDING}assets/cosmos.css`);
31
+ const RAISE_CSS = read('modules/raise/public/raise.css');
32
+
33
+ // The declarations of the first rule whose selector list is exactly `sel` (compact or spaced).
34
+ function rule(css, sel) {
35
+ const esc = sel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
36
+ const m = css.match(new RegExp(`(?:^|\\n|\\})\\s*${esc}\\s*\\{([^}]*)\\}`));
37
+ assert.ok(m, `a rule for ${sel}`);
38
+ return m[1];
39
+ }
40
+
41
+ test('the hub and the raise editor load the shared dialog after the shared escaper, and ask in it', () => {
42
+ const hub = read(`${LANDING}projects.html`);
43
+ assert.match(hub, /<script src="\/modules\/hall-ui\/public\/dom-utils\.js"><\/script>\n(?:<!--[^\n]*-->\n)?<script src="\/modules\/hall-ui\/public\/hall-dialog\.js"><\/script>/);
44
+ assert.match(hub, /window\.OTBDialog\.confirm\(\{ title: 'Start a new one\?'/, 'the hub asks in the shared confirm');
45
+ const page = read('modules/raise/page.js');
46
+ assert.match(page, /dom-utils\.js" defer><\/script><script src="\/modules\/hall-ui\/public\/hall-dialog\.js" defer><\/script><script src="\$\{BASE\}\/assets\/edit\.js/,
47
+ 'the editor gets the escaper, then the dialog, then itself');
48
+ assert.match(read('modules/raise/public/edit.js'), /window\.OTBDialog\.confirm\(\{ title: 'Discard your unsaved changes\?'/);
49
+ });
50
+
51
+ test('the shared dialog has a frame on each page, on the shared scales', () => {
52
+ for (const [name, css] of [['cosmos.css', COSMOS], ['raise.css', RAISE_CSS]]) {
53
+ const frame = rule(css, '.hall-dialog');
54
+ for (const want of [/z-index:\s*var\(--ds-z-modal\)/, /border-radius:\s*var\(--ds-radius-panel\)/, /border:\s*var\(--ds-line\) solid var\(--rule\)/]) {
55
+ assert.match(frame, want, `${name} .hall-dialog: ${want}`);
56
+ }
57
+ // the raise page floats it on the shared shadow; the apex world spends no shadow on UI
58
+ assert.match(frame, name === 'raise.css' ? /box-shadow:\s*var\(--ds-shadow-pop\)/ : /box-shadow:none/, `${name}: the shadow its world allows`);
59
+ assert.match(rule(css, '.hall-dialog::backdrop'), /background:\s*rgba\(0,\s*0,\s*0,\s*\.55\)/, `${name}: a black scrim, the one legal literal`);
60
+ for (const part of ['.hall-dialog__head', '.hall-dialog__title', '.hall-dialog__x', '.hall-dialog__actions']) rule(css, part);
61
+ }
62
+ // the buttons the component builds (btn btn--ghost / btn btn--danger) answer on the landing
63
+ assert.match(COSMOS, /\.btn\.ghost,\.btn\.btn--ghost\{/);
64
+ assert.match(COSMOS, /\.btn\.danger,\.btn\.btn--danger\{/);
65
+ assert.match(RAISE_CSS, /\.btn--ghost \{/);
66
+ });
67
+
68
+ test('the buttons and the project card spend the shared scales, not typed-in values', () => {
69
+ const btn = rule(COSMOS, '.btn');
70
+ assert.match(btn, /border-radius:var\(--ds-radius-pill\)/);
71
+ assert.match(btn, /font-size:var\(--ds-text-lg\)/);
72
+ assert.doesNotMatch(btn, /\.15s|999px|font-size:\d/, 'no typed-in duration, corner or size');
73
+ const rbtn = rule(RAISE_CSS, '.btn');
74
+ assert.match(rbtn, /border-radius: var\(--ds-radius-pill\)/);
75
+ assert.match(rbtn, /var\(--ds-text-xl\)/);
76
+ assert.doesNotMatch(rbtn, /\.12s|999px/, 'no typed-in duration or corner');
77
+ const scrim = rule(COSMOS, '.cardScrim');
78
+ assert.match(scrim, /z-index:var\(--ds-z-modal\)/, 'the card sits on the shared modal layer');
79
+ const card = rule(COSMOS, '.projCard');
80
+ assert.match(card, /border:var\(--ds-line\) solid var\(--rule\)/);
81
+ assert.match(card, /border-radius:var\(--ds-radius-panel\)/);
82
+ });
83
+
84
+ test('signed in, the narrow landing bar folds the second-account door into the profile band', () => {
85
+ const html = read(`${LANDING}index.html`);
86
+ const narrow = html.slice(html.indexOf('@media (max-width:900px){'));
87
+ assert.match(narrow.slice(0, narrow.indexOf('\n}\n')), /\.barRight:has\(\.who\) \.newacct\{display:none\}/, 'the pill leaves the bar once the chip is in it');
88
+ const pill = html.match(/<a class="pill newacct" href="([^"]+)"/);
89
+ assert.ok(pill, 'the bar still carries the door');
90
+ const band = html.slice(html.indexOf('id="menuProfile"'));
91
+ const fold = band.match(/<li class="bandCell bandFold"><a class="bandT" href="([^"]+)"[^>]*>New account<\/a><\/li>/);
92
+ assert.ok(fold, 'the profile band carries its folded twin');
93
+ assert.equal(fold[1], pill[1], 'the same door, to the same place');
94
+ });
95
+
96
+ // ── in a real browser ───────────────────────────────────────────────────────
97
+
98
+ let browser = null;
99
+ try { browser = await kit.launchBrowser(); } catch (e) { if (!(e instanceof kit.NoBrowserError)) throw e; }
100
+ after(() => browser && browser.close());
101
+ const skip = !browser && 'no system Edge or Chrome';
102
+ const TOKENS = read('modules/ui-design/public/tokens.css');
103
+
104
+ test('the hub\'s confirm opens in the shared dialog, in the landing\'s frame and buttons', { skip }, async () => {
105
+ const ctx = await browser.newContext({ viewport: { width: 390, height: 700 } });
106
+ const page = await ctx.newPage();
107
+ await page.setContent('<!doctype html><html data-mode="dark"><head><meta charset="utf-8"></head><body><button id="go">Start another project</button></body></html>');
108
+ await page.addStyleTag({ content: TOKENS + COSMOS });
109
+ await page.addScriptTag({ content: read('modules/hall-ui/public/dom-utils.js') });
110
+ await page.addScriptTag({ content: read('modules/hall-ui/public/hall-dialog.js') });
111
+ await page.focus('#go');
112
+ await page.evaluate(() => { window.__r = window.OTBDialog.confirm({ title: 'Start a new one?', body: 'Acme keeps building.', confirmLabel: 'Start a new one' }); });
113
+ const s = await page.evaluate(() => {
114
+ const d = document.querySelector('dialog.hall-dialog');
115
+ const cs = getComputedStyle(d);
116
+ const ghost = getComputedStyle(d.querySelector('.btn.btn--ghost'));
117
+ const ok = d.querySelector('.hall-dialog__actions .btn:not(.btn--ghost)');
118
+ return { modal: d.matches(':modal'), radius: cs.borderTopLeftRadius, line: cs.borderTopWidth, shadow: cs.boxShadow, z: cs.zIndex,
119
+ ghostBg: ghost.backgroundColor, ghostEdge: ghost.borderTopWidth, okH: ok.getBoundingClientRect().height, okR: getComputedStyle(ok).borderTopLeftRadius };
120
+ });
121
+ assert.equal(s.modal, true, 'the shared component, opened modal');
122
+ assert.equal(s.radius, '16px', 'the panel corner');
123
+ assert.equal(s.line, '1px', 'one hairline');
124
+ assert.equal(s.shadow, 'none', 'the apex world spends no shadow on UI');
125
+ assert.equal(s.z, '160', 'the shared modal layer');
126
+ assert.equal(s.ghostBg, 'rgba(0, 0, 0, 0)', 'Cancel takes the landing\'s ghost button through the shared name');
127
+ assert.equal(s.ghostEdge, '2px');
128
+ assert.ok(s.okH >= 24, 'the confirm clears the tap floor');
129
+ assert.equal(s.okR, '999px', 'the pill corner');
130
+ await page.click('.hall-dialog__actions .btn:not(.btn--ghost)');
131
+ assert.equal(await page.evaluate(() => window.__r), true);
132
+ await ctx.close();
133
+ });
134
+
135
+ test('signed in, the landing bar holds one row at 320 with the mark clear of the triggers', { skip }, async () => {
136
+ const html = read(`${LANDING}index.html`);
137
+ const styles = [...html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/g)].map((m) => m[1]).join('\n');
138
+ const bar = html.slice(html.indexOf('<header class="topbar bar">'), html.indexOf('</header>') + 9)
139
+ .replace(/<!--apex-only:(start|end)-->/g, '')
140
+ .replace(/<span class="whoSlot" id="who">[\s\S]*?<\/span>/, '<span class="whoSlot" id="who"><button type="button" class="trig who"><img width="26" height="26" alt=""><span class="who-name">builder</span></button></span>');
141
+ // the artist-review door the signed-in bar also carries (page-notes.css draws it 36px wide here)
142
+ const withReview = bar.replace('<span class="whoSlot"', '<button type="button" class="arv-btn" style="width:36px;min-height:36px">R</button><span class="whoSlot"');
143
+ const ctx = await browser.newContext({ viewport: { width: 320, height: 640 } });
144
+ const page = await ctx.newPage();
145
+ for (const [name, markup, signedIn] of [['signed in', withReview, true], ['signed out', bar.replace(/<span class="whoSlot" id="who">[\s\S]*?<\/span>/, '<span class="whoSlot" id="who"><a class="pill signin" href="#">Sign in</a></span>'), false]]) {
146
+ await page.setContent(`<!doctype html><html data-mode="dark"><head><meta charset="utf-8"></head><body>${markup}</body></html>`);
147
+ await page.addStyleTag({ content: TOKENS + styles });
148
+ const r = await page.evaluate(() => {
149
+ const box = (s) => { const e = document.querySelector(s); const b = e && e.getBoundingClientRect(); return b && b.width ? { l: b.left, r: b.right } : null; };
150
+ return { sw: document.documentElement.scrollWidth, mark: box('.barMark'), proj: box('#projTrig'), newacct: box('.newacct') };
151
+ });
152
+ assert.ok(r.sw <= 320, `${name}: no sideways scroll at 320 (scrollWidth ${r.sw})`);
153
+ assert.ok(r.mark.r <= r.proj.l, `${name}: the mark ends before the projects trigger starts (${r.mark.r} vs ${r.proj.l})`);
154
+ assert.equal(!!r.newacct, !signedIn, signedIn ? 'the second-account door has folded into the profile band' : 'signed out, the door stays in the bar');
155
+ }
156
+ await ctx.close();
157
+ });
@@ -227,5 +227,5 @@ test('the page renders the Release section and posts to the route', () => {
227
227
  const src = fs.readFileSync(new URL('../modules/npm-release/public/work.js', import.meta.url), 'utf8');
228
228
  assert.match(src, /\$\{releaseHtml\(data\)\}/);
229
229
  assert.match(src, /client\.request\('POST', `\$\{API\}\/npm-release\/release`, \{ body: \{ version \} \}\)/);
230
- assert.match(src, /window\.confirm\(/, 'a release is confirmed before it is sent');
230
+ assert.match(src, /await window\.OTBDialog\.confirm\(\{/, 'a release is confirmed in the hall\'s own dialog before it is sent (task 1005136)');
231
231
  });
@@ -607,7 +607,7 @@ test('the platform answers its own figures by the same rule: faces from its lead
607
607
  test('the poster is in the world: 560 wide, one hairline, no shadow, the disc surviving over the art, the facts a ledger', () => {
608
608
  const block = (COSMOS.match(/\.projCard\{[^}]*\}/) || [''])[0];
609
609
  assert.match(block, /width:min\(560px,100%\)/);
610
- assert.match(block, /border:1px solid var\(--rule\)/);
610
+ assert.match(block, /border:var\(--ds-line\) solid var\(--rule\)/, 'one hairline, on the shared line (task 1005136)');
611
611
  assert.match(block, /box-shadow:none/);
612
612
  assert.match(block, /container-type:inline-size/, 'the disc and the callouts measure the card, not the viewport');
613
613
  assert.match(COSMOS, /\.projCard \.cardArt\{[^}]*aspect-ratio:5\/2;object-fit:cover/, 'a fixed 5:2 crop');
@@ -225,7 +225,9 @@ test('the manifest step acknowledges code_received, offers a restart after a sta
225
225
  });
226
226
 
227
227
  test('"Start another project" asks before abandoning a standup still being watched', () => {
228
- assert.match(SCRIPT, /if \(instanceId && pollTimer && !confirm\(/);
228
+ // asked in the shared dialog since task 1005136, never the browser's box
229
+ assert.match(SCRIPT, /var asked = instanceId && pollTimer\n\s*\? window\.OTBDialog\.confirm\(\{/);
230
+ assert.match(SCRIPT, /asked\.then\(function \(yes\) \{ if \(yes\) startAnother\(\); \}\);/, 'it starts over only on yes');
229
231
  });
230
232
 
231
233
  /* ── every recovery sign-in from the wizard keeps the repo scope ── */
@@ -139,7 +139,9 @@ async function boot(opts = {}) {
139
139
  OTBBranding: { currencyLabel: () => 'Credits' },
140
140
  BongosClient: { createClient: () => api },
141
141
  location: locationObj,
142
- prompt: (msg) => { prompts.push(msg); return 'left for a new job'; },
142
+ // the reason is asked in the hall's own dialog (task 1005136); the browser box must not run
143
+ OTBDialog: { prompt: async (o) => { prompts.push(o.title); return 'left for a new job'; } },
144
+ prompt: () => { throw new Error('window.prompt was called'); },
143
145
  addEventListener() {},
144
146
  };
145
147
  // The shell's own /me read, shared on window.__hallReads (shell.js probeMe).