@bongos/core 1.19.1051 → 1.19.1053
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 +66 -61
- package/docs/copy-inventory.md +138 -146
- package/docs/copy-registry.json +294 -458
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +18 -24
- package/modules/hall-ui/public/goal-map.states.json +2 -2
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +77 -1
- package/modules/hall-ui/public/sky-data.js +34 -19
- package/modules/hall-ui/public/sky-interaction.js +36 -30
- package/modules/hall-ui/public/sky-layout.js +11 -6
- package/modules/hall-ui/public/sky-panel.js +3 -2
- package/modules/hall-ui/public/sky-tour.js +37 -26
- package/modules/hall-ui/public/studio.html +10 -4
- package/modules/hall-ui/public/studio.states.json +76 -1
- package/modules/hall-ui/public/style.css +53 -0
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.css +56 -16
- package/modules/hall-ui/public/thinking.html +52 -41
- package/modules/hall-ui/public/thinking.states.json +20 -3
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/sky.md +20 -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/tests/goal_map_page.mjs +54 -28
- package/tests/hall_audit.mjs +2 -1
- package/tests/hall_fullscreen.mjs +196 -0
- package/tests/hall_sky.mjs +49 -12
- package/tests/helpers.mjs +2 -1
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// tests/hall_fullscreen.mjs — the hall's shared full-screen switch (task 1004327,
|
|
2
|
+
// BV2.TW20, goal 1000095; owner decision 2026-09-27).
|
|
3
|
+
//
|
|
4
|
+
// The studio and Your thinking sit in the hall chrome, and each carries ONE
|
|
5
|
+
// switch that hides the sidebar and the top bar so the page fills the window.
|
|
6
|
+
// The switch is the shell's, not a page's: a page opts in with
|
|
7
|
+
// <body data-fullscreen-switch>, and the shell renders the button, keeps the
|
|
8
|
+
// state on html[data-fullscreen="on"], remembers it PER PAGE in localStorage and
|
|
9
|
+
// lets Escape leave. These cases boot the REAL shell.js in a DOM stub and drive
|
|
10
|
+
// it the way a reader does — press the button, reload, press Escape — so a
|
|
11
|
+
// switch that renders but does not remember, or an Escape that fights the sky's
|
|
12
|
+
// own use of the key, fails here. The two source assertions at the end are
|
|
13
|
+
// labelled as such: the CSS half (what "hidden" means) is proven by the kit
|
|
14
|
+
// renders in the ship notes, not by a regex.
|
|
15
|
+
import assert from 'node:assert/strict';
|
|
16
|
+
import { test } from 'node:test';
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import vm from 'node:vm';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
+
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
24
|
+
const SHELL = fs.readFileSync(path.join(HALL, 'shell.js'), 'utf8');
|
|
25
|
+
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
26
|
+
|
|
27
|
+
// Boot the shell once — one page load. `store` is the browser's localStorage and
|
|
28
|
+
// is SHARED across boots on purpose: a second boot over the same store is a reload.
|
|
29
|
+
function boot({ page = 'thinking', optIn = true, store = {}, storageThrows = false } = {}) {
|
|
30
|
+
const els = new Map();
|
|
31
|
+
const windowListeners = {};
|
|
32
|
+
const timers = [];
|
|
33
|
+
const makeEl = (id) => {
|
|
34
|
+
const attrs = {};
|
|
35
|
+
return {
|
|
36
|
+
id, dataset: {}, style: {}, textContent: '', hidden: false, _html: '', title: '',
|
|
37
|
+
classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } },
|
|
38
|
+
setAttribute(k, v) { attrs[k] = String(v); }, getAttribute(k) { return k in attrs ? attrs[k] : null; }, removeAttribute(k) { delete attrs[k]; },
|
|
39
|
+
addEventListener(ev, fn) { (this._on ||= {})[ev] = fn; },
|
|
40
|
+
querySelector() { return null; }, querySelectorAll() { return []; },
|
|
41
|
+
appendChild() {}, focus() {}, click() { if (this._on && this._on.click) this._on.click({ target: this }); },
|
|
42
|
+
insertAdjacentElement() {}, remove() {},
|
|
43
|
+
set innerHTML(v) { this._html = String(v); }, get innerHTML() { return this._html; },
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
// The topbar's innerHTML is the shell's own markup; an id the shell rendered
|
|
47
|
+
// into it resolves only if that markup really carries it, so a button the
|
|
48
|
+
// shell forgot to render cannot be clicked into existence by the stub.
|
|
49
|
+
const topbar = makeEl('app-topbar');
|
|
50
|
+
const getElementById = (id) => {
|
|
51
|
+
if (id === 'app-topbar') return topbar;
|
|
52
|
+
if (/^(fullscreen-toggle|theme-toggle|zoom-toggle|topbar-menu|topbar-title|topbar-jump)$/.test(id)
|
|
53
|
+
&& !topbar._html.includes(`id="${id}"`)) return null;
|
|
54
|
+
if (!els.has(id)) els.set(id, makeEl(id));
|
|
55
|
+
return els.get(id);
|
|
56
|
+
};
|
|
57
|
+
const body = {
|
|
58
|
+
dataset: { page },
|
|
59
|
+
hasAttribute: (k) => optIn && k === 'data-fullscreen-switch',
|
|
60
|
+
};
|
|
61
|
+
const documentObj = {
|
|
62
|
+
body,
|
|
63
|
+
documentElement: { dataset: {}, style: {}, classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } } },
|
|
64
|
+
getElementById,
|
|
65
|
+
querySelectorAll() { return []; },
|
|
66
|
+
createElement() { return makeEl(null); },
|
|
67
|
+
addEventListener() {}, dispatchEvent() {},
|
|
68
|
+
get cookie() { return ''; }, set cookie(_v) {},
|
|
69
|
+
};
|
|
70
|
+
const location = { hostname: 'h', origin: 'https://h', pathname: '/' + page, hash: '', search: '', assign() {}, replace() {} };
|
|
71
|
+
const localStorage = {
|
|
72
|
+
getItem: (k) => { if (storageThrows) throw new Error('SecurityError'); return k in store ? store[k] : null; },
|
|
73
|
+
setItem: (k, v) => { if (storageThrows) throw new Error('SecurityError'); store[k] = String(v); },
|
|
74
|
+
};
|
|
75
|
+
let resizes = 0;
|
|
76
|
+
const windowObj = {
|
|
77
|
+
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
78
|
+
BongosClient: { createClient: () => ({ request: () => new Promise(() => {}) }) },
|
|
79
|
+
matchMedia: () => ({ matches: false, addEventListener() {} }),
|
|
80
|
+
localStorage, location,
|
|
81
|
+
requestAnimationFrame() {}, scrollTo() {},
|
|
82
|
+
addEventListener(type, fn) { (windowListeners[type] ||= []).push(fn); },
|
|
83
|
+
dispatchEvent(ev) { if (ev && ev.type === 'resize') resizes += 1; return true; },
|
|
84
|
+
setTimeout: (fn) => { timers.push(fn); return timers.length; },
|
|
85
|
+
};
|
|
86
|
+
const sandbox = {
|
|
87
|
+
window: windowObj, document: documentObj, location, console, clearTimeout() {}, queueMicrotask,
|
|
88
|
+
setTimeout: windowObj.setTimeout,
|
|
89
|
+
Event: class { constructor(t) { this.type = t; } },
|
|
90
|
+
CustomEvent: class { constructor(t, i) { this.type = t; Object.assign(this, i); } },
|
|
91
|
+
};
|
|
92
|
+
sandbox.globalThis = sandbox;
|
|
93
|
+
vm.createContext(sandbox);
|
|
94
|
+
vm.runInContext(SHELL, sandbox, { filename: 'shell.js' });
|
|
95
|
+
const root = documentObj.documentElement;
|
|
96
|
+
return {
|
|
97
|
+
store,
|
|
98
|
+
button: () => getElementById('fullscreen-toggle'),
|
|
99
|
+
on: () => root.dataset.fullscreen === 'on',
|
|
100
|
+
resizes: () => resizes,
|
|
101
|
+
// Press a key the way the browser dispatches it: every window listener,
|
|
102
|
+
// then the page may mark the press used, then the queued timers run.
|
|
103
|
+
press(key, { target = { tagName: 'CANVAS' }, pageUses = false } = {}) {
|
|
104
|
+
const ev = { key, target, defaultPrevented: false, preventDefault() { this.defaultPrevented = true; } };
|
|
105
|
+
for (const fn of windowListeners.keydown || []) fn(ev);
|
|
106
|
+
if (pageUses) ev.preventDefault();
|
|
107
|
+
while (timers.length) timers.shift()();
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
test('a page that does not opt in gets no switch, and never goes full screen', () => {
|
|
113
|
+
const store = { 'hall-fullscreen:work': 'on' };
|
|
114
|
+
const p = boot({ page: 'work', optIn: false, store });
|
|
115
|
+
assert.equal(p.button(), null, 'no button rendered');
|
|
116
|
+
assert.equal(p.on(), false, 'a stored value for a page with no switch is ignored');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('an opted-in page renders ONE switch in the top bar, off by default', () => {
|
|
120
|
+
const p = boot();
|
|
121
|
+
const btn = p.button();
|
|
122
|
+
assert.ok(btn, 'the switch is in the top bar');
|
|
123
|
+
assert.equal(p.on(), false);
|
|
124
|
+
assert.equal(btn.getAttribute('aria-pressed'), 'false');
|
|
125
|
+
assert.equal(btn.getAttribute('aria-label'), 'Full screen');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('the switch hides the chrome, the same button brings it back', () => {
|
|
129
|
+
const p = boot();
|
|
130
|
+
p.button().click();
|
|
131
|
+
assert.equal(p.on(), true, 'html[data-fullscreen="on"]');
|
|
132
|
+
assert.equal(p.button().getAttribute('aria-pressed'), 'true');
|
|
133
|
+
assert.equal(p.button().getAttribute('aria-label'), 'Leave full screen');
|
|
134
|
+
assert.ok(p.resizes() >= 1, 'a page that sizes to its box is told to re-measure');
|
|
135
|
+
p.button().click();
|
|
136
|
+
assert.equal(p.on(), false, 'and back');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('THE CHOICE SURVIVES A RELOAD — and it is remembered per page', () => {
|
|
140
|
+
const store = {};
|
|
141
|
+
boot({ page: 'thinking', store }).button().click();
|
|
142
|
+
assert.equal(store['hall-fullscreen:thinking'], 'on');
|
|
143
|
+
assert.equal(boot({ page: 'thinking', store }).on(), true, 'the reload opens in full screen');
|
|
144
|
+
assert.equal(boot({ page: 'studio', store }).on(), false, 'the studio did not follow the sky');
|
|
145
|
+
boot({ page: 'thinking', store }).button().click();
|
|
146
|
+
assert.equal(boot({ page: 'thinking', store }).on(), false, 'leaving is remembered too');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('Escape leaves full screen', () => {
|
|
150
|
+
const p = boot({ store: { 'hall-fullscreen:thinking': 'on' } });
|
|
151
|
+
assert.equal(p.on(), true);
|
|
152
|
+
p.press('Escape');
|
|
153
|
+
assert.equal(p.on(), false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('an Escape the PAGE used (the sky\'s tour, menu or pick) does not also leave', () => {
|
|
157
|
+
const p = boot({ store: { 'hall-fullscreen:thinking': 'on' } });
|
|
158
|
+
p.press('Escape', { pageUses: true });
|
|
159
|
+
assert.equal(p.on(), true, 'the page marked the press used, after the shell heard it');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('an Escape inside a text field belongs to the field', () => {
|
|
163
|
+
const p = boot({ store: { 'hall-fullscreen:thinking': 'on' } });
|
|
164
|
+
p.press('Escape', { target: { tagName: 'INPUT' } });
|
|
165
|
+
assert.equal(p.on(), true);
|
|
166
|
+
p.press('x');
|
|
167
|
+
assert.equal(p.on(), true, 'other keys are not the switch\'s');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('storage that throws (a private window) leaves the chrome showing and the switch working', () => {
|
|
171
|
+
const p = boot({ storageThrows: true });
|
|
172
|
+
assert.equal(p.on(), false);
|
|
173
|
+
p.button().click();
|
|
174
|
+
assert.equal(p.on(), true, 'the switch still works for this page load');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('the studio and Your thinking opt in, inside the hall chrome; the project sky does not (yet)', () => {
|
|
178
|
+
for (const f of ['studio.html', 'thinking.html']) {
|
|
179
|
+
const html = read(f);
|
|
180
|
+
assert.match(html, /<body[^>]*\bdata-fullscreen-switch\b/, `${f} opts in`);
|
|
181
|
+
for (const id of ['app-sidebar', 'app-topbar']) assert.match(html, new RegExp(`id="${id}"`), `${f} carries #${id}`);
|
|
182
|
+
assert.match(html, /<main id="hall-main"/, `${f}: the skip link's target`);
|
|
183
|
+
assert.equal((html.replace(/<!--[\s\S]*?-->/g, '').match(/<header\b/g) || []).length, 1, `${f}: the top bar is the one <header>`);
|
|
184
|
+
}
|
|
185
|
+
assert.doesNotMatch(read('goal-map.html'), /data-fullscreen-switch|shell\.js/, '/goal-map keeps its standalone form');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// SOURCE ASSERTION (the CSS half): full screen hides the sidebar and the foot
|
|
189
|
+
// but keeps the top bar IN the document, collapsed to its switch — so the page
|
|
190
|
+
// keeps one <header> and the way back is where the way in was.
|
|
191
|
+
test('source: full screen hides the sidebar and foot, and collapses the top bar rather than removing it', () => {
|
|
192
|
+
const css = read('style.css');
|
|
193
|
+
assert.match(css, /html\[data-fullscreen="on"\] \.app-sidebar,\s*html\[data-fullscreen="on"\] \.app-scrim,\s*html\[data-fullscreen="on"\] \.app-foot \{ display: none; \}/);
|
|
194
|
+
assert.match(css, /html\[data-fullscreen="on"\] \.topbar-side > :not\(\.fullscreen-toggle\)/);
|
|
195
|
+
assert.doesNotMatch(css, /html\[data-fullscreen="on"\] \.app-topbar \{ display: none/);
|
|
196
|
+
});
|
package/tests/hall_sky.mjs
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// tests/hall_thinking_room.mjs.
|
|
21
21
|
|
|
22
22
|
import assert from 'node:assert/strict';
|
|
23
|
+
import * as FS from 'node:fs';
|
|
23
24
|
import { makeRunner, settle, bootSkyPage } from './helpers.mjs';
|
|
24
25
|
|
|
25
26
|
const { test, summary } = makeRunner();
|
|
@@ -367,11 +368,38 @@ await test('the bands come from the versions read: building at the centre, shipp
|
|
|
367
368
|
for (const f of src) assert.doesNotMatch(fs.readFileSync(path.join('modules/hall-ui/public', f), 'utf8'), /BONGOS-V\d|GDS-V\d|PMS-V\d|CB-V\d/, `${f} names no instance version`);
|
|
368
369
|
});
|
|
369
370
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
// ═══ only your thinking (task 1004328, owner decision 2026-09-27) ═══════════
|
|
372
|
+
// The whole-project lens and the project map's features live only on
|
|
373
|
+
// /goal-map (tests/goal_map_page.mjs). Here they are ABSENT from the markup,
|
|
374
|
+
// not merely hidden, and nothing — a button, the tour, a jump, the panel —
|
|
375
|
+
// takes this sky into "the whole project" or leaves for that page.
|
|
376
|
+
|
|
377
|
+
await test('ONLY YOUR THINKING: no "the whole project" lens, no "my ideas", no explore — absent from the page, not hidden', async () => {
|
|
378
|
+
const p = await boot({ mine: mine(FIVE()), sky: sky([well()]), versions: versions() });
|
|
379
|
+
same(p.S().lenses, ['simple', 'mine']);
|
|
380
|
+
for (const id of ['lensAll', 'ideasBtn', 'exploreBtn', 'start', 'startOpts']) assert.equal(p.el(id), null, `#${id} is the project map's, not this page's`);
|
|
381
|
+
for (const id of ['lensSimple', 'lensMine', 'recentBtn', 'claudeBtn', 'influenceBtn', 'rehearse']) assert.ok(p.el(id), `#${id} is your thinking's own tool`);
|
|
382
|
+
// The markup a person meets — the header comments may explain the split, and do.
|
|
383
|
+
const html = FS.readFileSync('modules/hall-ui/public/thinking.html', 'utf8').replace(/<!--[\s\S]*?-->/g, '');
|
|
384
|
+
assert.doesNotMatch(html, /the whole project/, 'the words are gone from the markup too');
|
|
385
|
+
assert.doesNotMatch(html, /goal-map/, 'and this page never names the project map');
|
|
386
|
+
assert.doesNotMatch(p.el('legend').innerHTML, /unexplored/, 'the legend does not teach explore, which this sky does not have');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
await test('nothing takes /thinking into the whole project: setLens, the panel and the switcher all stay in your sky', async () => {
|
|
390
|
+
const p = await boot({ mine: mine(FIVE()), sky: sky([well()]), versions: versions() });
|
|
391
|
+
p.SKY().setLens('all', true);
|
|
392
|
+
assert.equal(p.S().lens, 'mine', 'a lens this page does not have is never entered');
|
|
393
|
+
p.click('lensSimple'); assert.equal(p.S().lens, 'simple');
|
|
394
|
+
p.click('lensMine'); assert.equal(p.S().lens, 'mine');
|
|
395
|
+
assert.equal(p.navigated, null, 'no lens leaves for another page');
|
|
396
|
+
p.SKY().pick({ kind: 'goal', ref: p.S().goals[0] }, false);
|
|
397
|
+
p.click('descendBtn');
|
|
398
|
+
assert.equal(p.S().lens, 'mine', 'descending into a well stays in your thinking');
|
|
399
|
+
p.click('lensSimple'); p.SKY().pick({ kind: 'goal', ref: p.S().goals[0] }, false); p.click('descendBtn');
|
|
400
|
+
assert.equal(p.S().lens, 'mine', 'from "simple", descending steps up to your thinking — the one lens here that zooms');
|
|
401
|
+
const src = FS.readFileSync('modules/hall-ui/public/sky-interaction.js', 'utf8');
|
|
402
|
+
assert.doesNotMatch(src, /location\.assign|'\/goal-map'|'\/thinking'/, 'the switcher has no door to the other page');
|
|
375
403
|
});
|
|
376
404
|
|
|
377
405
|
// ═══ the words are never markup ══════════════════════════════════════════════
|
|
@@ -392,25 +420,34 @@ await test('a title never becomes markup — in the drawer, the panel and the ho
|
|
|
392
420
|
|
|
393
421
|
// ═══ the tour, the lenses and the keys ═══════════════════════════════════════
|
|
394
422
|
|
|
395
|
-
await test('the tour is the template\'s thirteen steps — ? opens it, Escape closes it', async () => {
|
|
423
|
+
await test('the tour is your thinking\'s nine of the template\'s thirteen steps, in order and never in the whole project — ? opens it, Escape closes it', async () => {
|
|
396
424
|
const p = await boot({ mine: mine(FIVE()), sky: sky([well()]), versions: versions() });
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
same(STEPS.map((s) => s.h), [
|
|
425
|
+
// The template's thirteen are all still in the list; the four the project map owns are /goal-map's (task 1004328).
|
|
426
|
+
const template = [
|
|
400
427
|
'This is your sky.', 'A goal is a gravity well.', 'A ring is a criterion.', 'A burning thought is a star.', 'Some thoughts became the structure.',
|
|
401
428
|
'Unclaimed thoughts drift.', 'An alive well glows.', 'Routes run between goals that wait on each other.', 'A settled system is a goal that finished.',
|
|
402
429
|
'The sky has a clock.', 'By default, you explore.', 'Release a thought here.', 'When it is too much, go simple.',
|
|
430
|
+
];
|
|
431
|
+
same(p.SKY().ALL_STEPS.map((s) => s.h).filter((h) => template.includes(h)), template, 'every template step survives, in its order');
|
|
432
|
+
const STEPS = p.SKY().STEPS;
|
|
433
|
+
assert.equal(STEPS.length, 9);
|
|
434
|
+
same(STEPS.map((s) => s.h), [
|
|
435
|
+
'This is your sky.', 'A goal is a gravity well.', 'A ring is a criterion.', 'A burning thought is a star.', 'Some thoughts became the structure.',
|
|
436
|
+
'Unclaimed thoughts drift.', 'The sky has a clock.', 'Release a thought here.', 'When it is too much, go simple.',
|
|
403
437
|
]);
|
|
404
438
|
assert.equal(p.el('tour').hidden, true);
|
|
405
439
|
p.key('?');
|
|
406
440
|
assert.equal(p.el('tour').hidden, false);
|
|
407
|
-
assert.equal(p.el('tourN').textContent, 'how to read the sky · 1 of
|
|
408
|
-
|
|
409
|
-
|
|
441
|
+
assert.equal(p.el('tourN').textContent, 'how to read the sky · 1 of 9');
|
|
442
|
+
const lenses = new Set([p.S().lens]);
|
|
443
|
+
for (let n = 0; n < 8; n++) { p.click('tourNext'); lenses.add(p.S().lens); }
|
|
444
|
+
assert.equal(lenses.has('all'), false, 'no step of this tour enters the whole project');
|
|
445
|
+
assert.equal(p.el('tourN').textContent, 'how to read the sky · 9 of 9');
|
|
410
446
|
assert.equal(p.el('tourNext').textContent, 'start reading');
|
|
411
447
|
p.key('Escape');
|
|
412
448
|
assert.equal(p.el('tour').hidden, true);
|
|
413
449
|
assert.equal(p.S().lens, 'mine', 'the tour hands the sky back to your thinking');
|
|
450
|
+
assert.equal(p.navigated, null);
|
|
414
451
|
});
|
|
415
452
|
|
|
416
453
|
await test('the tour opens by itself on a first visit, and only then', async () => {
|
package/tests/helpers.mjs
CHANGED
|
@@ -904,7 +904,8 @@ export function bootSkyPage({
|
|
|
904
904
|
};
|
|
905
905
|
|
|
906
906
|
// ── the window ───────────────────────────────────────────────────────────
|
|
907
|
-
|
|
907
|
+
// Each page keeps its own tour-seen key (task 1004328); tourSeen marks both.
|
|
908
|
+
const store = { ...(tourSeen ? { 'sky-tour-seen': '4', 'sky-map-tour-seen': '4' } : {}), ...storage };
|
|
908
909
|
let raf = null;
|
|
909
910
|
const windowObj = {
|
|
910
911
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
@@ -87,7 +87,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
87
87
|
// the file and left the query alone, so a returning browser kept the cached copy —
|
|
88
88
|
// a fix that reaches nobody looks identical to a fix that does not work.
|
|
89
89
|
|
|
90
|
-
const SHELL_SHA = '
|
|
90
|
+
const SHELL_SHA = '7f66604d74d544e8';
|
|
91
91
|
|
|
92
92
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
93
93
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|