@bongos/core 1.21.72 → 1.21.73
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 +55 -55
- package/docs/copy-inventory.md +22 -22
- package/docs/copy-registry.json +25 -25
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +324 -325
- package/modules/hall-ui/public/atlas.html +3 -3
- package/modules/hall-ui/public/blockers.html +3 -3
- package/modules/hall-ui/public/board-room.html +3 -3
- package/modules/hall-ui/public/city.html +3 -3
- package/modules/hall-ui/public/collab.html +3 -3
- package/modules/hall-ui/public/commands.html +3 -3
- package/modules/hall-ui/public/copy-desk.html +3 -3
- package/modules/hall-ui/public/craft-mode.js +69 -1
- package/modules/hall-ui/public/deploy.html +3 -3
- package/modules/hall-ui/public/diagrams.html +3 -3
- package/modules/hall-ui/public/drachmae.html +3 -3
- package/modules/hall-ui/public/fleet.html +3 -3
- package/modules/hall-ui/public/gate.html +3 -3
- package/modules/hall-ui/public/goal-map.html +3 -3
- package/modules/hall-ui/public/goals.html +3 -3
- package/modules/hall-ui/public/government.html +3 -3
- package/modules/hall-ui/public/idea.html +3 -3
- package/modules/hall-ui/public/ideas.html +3 -3
- package/modules/hall-ui/public/index.html +3 -3
- package/modules/hall-ui/public/modules.html +3 -3
- package/modules/hall-ui/public/primer.html +3 -3
- package/modules/hall-ui/public/profile.html +3 -3
- package/modules/hall-ui/public/project-settings.html +3 -3
- package/modules/hall-ui/public/ranks.html +3 -3
- package/modules/hall-ui/public/roadmap.html +3 -3
- package/modules/hall-ui/public/roster.html +3 -3
- package/modules/hall-ui/public/sessions.html +3 -3
- package/modules/hall-ui/public/settings.html +3 -3
- package/modules/hall-ui/public/shell.js +9 -0
- package/modules/hall-ui/public/street.html +3 -3
- package/modules/hall-ui/public/studio.html +3 -3
- package/modules/hall-ui/public/task.html +3 -3
- package/modules/hall-ui/public/thinking.html +3 -3
- package/modules/hall-ui/public/tweak-editor.html +3 -3
- package/modules/hall-ui/public/watch.html +3 -3
- package/modules/hall-ui/public/work.html +3 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/craft_mode_nav.mjs +117 -1
- package/tests/hall_city.mjs +5 -3
- package/tests/hall_nav.mjs +3 -1
- package/tests/hall_street.mjs +2 -1
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -171,9 +171,9 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell-trail.js?v=2026-10-
|
|
175
|
-
<script src="/builders/craft-mode.js?v=2026-10-
|
|
176
|
-
<script src="/builders/shell.js?v=2026-10-
|
|
174
|
+
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
175
|
+
<script src="/builders/craft-mode.js?v=2026-10-08-city-tab"></script>
|
|
176
|
+
<script src="/builders/shell.js?v=2026-10-08-city-tab"></script>
|
|
177
177
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
178
178
|
<script src="/builders/palette.js" defer></script>
|
|
179
179
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.73",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.73",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.73",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -9242,5 +9242,11 @@
|
|
|
9242
9242
|
"id": "1004578",
|
|
9243
9243
|
"text": "While a new project is being set up, founders now get a little cloud-hopping game: each cloud is a public project on the platform, in its own planet colour. When the project goes live, clouds and stars fill the screen and fade"
|
|
9244
9244
|
}
|
|
9245
|
+
],
|
|
9246
|
+
"1.21.73": [
|
|
9247
|
+
{
|
|
9248
|
+
"id": "1004625",
|
|
9249
|
+
"text": "The governor city now has its own tab, and in Governing mode it leads the sidebar with every other governing page tucked into one dropdown under it."
|
|
9250
|
+
}
|
|
9245
9251
|
]
|
|
9246
9252
|
}
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.73'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/craft_mode_nav.mjs
CHANGED
|
@@ -187,7 +187,7 @@ test('a mode never shows a tab the gate hid, and a later gate pass keeps the mod
|
|
|
187
187
|
p.signIn('thetes');
|
|
188
188
|
assert.ok(!p.showing().includes('watch'), 'a thetes in Governing does not get the metic-floored Watch tab');
|
|
189
189
|
assert.ok(!p.headShowing('government'), 'nor its group head');
|
|
190
|
-
assert.deepEqual(p.showing(), ['blockers', 'board-room', 'profile', 'collab', 'roster'], 'what a thetes board member in Governing sees');
|
|
190
|
+
assert.deepEqual(p.showing(), ['blockers', 'board-room', 'city', 'profile', 'collab', 'roster'], 'what a thetes board member in Governing sees');
|
|
191
191
|
p.signIn('archon');
|
|
192
192
|
assert.ok(p.showing().includes('watch'), 'promoted, the gate lets it through and the mode keeps it');
|
|
193
193
|
assert.ok(p.headShowing('government'), 'and its group head comes back with it, which only a mode pass after the gate can know');
|
|
@@ -202,3 +202,119 @@ test('shell.js applies the mode AFTER its gate loop, and style.css hides what th
|
|
|
202
202
|
assert.match(read('style.css'), /#app-sidebar \[data-craft-off\] \{ display: none !important; \}/);
|
|
203
203
|
assert.doesNotMatch(CRAFT, /\.hidden\s*=/, 'the mode never writes `hidden`, which the gate owns');
|
|
204
204
|
});
|
|
205
|
+
|
|
206
|
+
// ---------- the Governing fold (owner, 2026-10-08) ----------
|
|
207
|
+
// Governing leads with the City and folds its other own tabs into one dropdown
|
|
208
|
+
// under it. The fold MOVES nodes, which the parsed sidebar above cannot model, so
|
|
209
|
+
// these cases run the real craft-mode.js against a small working tree: nodes with
|
|
210
|
+
// parents, insertBefore and comments, laid out as shell.js's NAV groups are.
|
|
211
|
+
function treeNode(tag, nodeType = 1) {
|
|
212
|
+
const attrs = {};
|
|
213
|
+
const n = {
|
|
214
|
+
tagName: tag, nodeType, id: '', className: '', dataset: {}, hidden: false, childNodes: [], parentNode: null, _html: '', type: '',
|
|
215
|
+
setAttribute(k, v) { attrs[k] = String(v); }, getAttribute(k) { return k in attrs ? attrs[k] : null; },
|
|
216
|
+
hasAttribute(k) { return k in attrs; }, removeAttribute(k) { delete attrs[k]; },
|
|
217
|
+
get classList() {
|
|
218
|
+
const self = this;
|
|
219
|
+
const list = () => self.className.split(/\s+/).filter(Boolean);
|
|
220
|
+
return {
|
|
221
|
+
contains: (c) => list().includes(c),
|
|
222
|
+
add: (c) => { if (!list().includes(c)) self.className = list().concat(c).join(' '); },
|
|
223
|
+
remove: (c) => { self.className = list().filter((x) => x !== c).join(' '); },
|
|
224
|
+
toggle(c, on) { if (on === undefined) on = !this.contains(c); if (on) this.add(c); else this.remove(c); return on; },
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
get nextSibling() { const p = this.parentNode; return p ? p.childNodes[p.childNodes.indexOf(this) + 1] || null : null; },
|
|
228
|
+
set innerHTML(v) { this._html = String(v); }, get innerHTML() { return this._html; },
|
|
229
|
+
appendChild(c) { return this.insertBefore(c, null); },
|
|
230
|
+
insertBefore(c, ref) {
|
|
231
|
+
if (c.parentNode) c.parentNode.removeChild(c);
|
|
232
|
+
const i = ref ? this.childNodes.indexOf(ref) : -1;
|
|
233
|
+
if (i === -1) this.childNodes.push(c); else this.childNodes.splice(i, 0, c);
|
|
234
|
+
c.parentNode = this;
|
|
235
|
+
return c;
|
|
236
|
+
},
|
|
237
|
+
removeChild(c) { this.childNodes.splice(this.childNodes.indexOf(c), 1); c.parentNode = null; return c; },
|
|
238
|
+
walk(fn) { for (const c of this.childNodes) { if (c.nodeType === 1) { fn(c); c.walk(fn); } } },
|
|
239
|
+
querySelectorAll(sel) { const out = []; this.walk((c) => { if (sel.startsWith('.') && c.classList.contains(sel.slice(1))) out.push(c); }); return out; },
|
|
240
|
+
querySelector(sel) { return this.querySelectorAll(sel)[0] || null; },
|
|
241
|
+
addEventListener() {}, focus() {},
|
|
242
|
+
};
|
|
243
|
+
return n;
|
|
244
|
+
}
|
|
245
|
+
function bootTree({ mode = null, activeId = null } = {}) {
|
|
246
|
+
const el = (tag, cls, id) => { const n = treeNode(tag); n.className = cls || ''; n.id = id || ''; return n; };
|
|
247
|
+
const sidebar = el('NAV', '', 'app-sidebar');
|
|
248
|
+
// shell.js's NAV, group by group: the unlabeled one holds its items directly,
|
|
249
|
+
// a labeled one a head and a .nav-items wrapper (renderSidebar's shape).
|
|
250
|
+
for (const block of SHELL.slice(SHELL.indexOf('var NAV = ['), SHELL.indexOf('// ---------- nav group collapse')).split('{ group: ').slice(1)) {
|
|
251
|
+
const label = /^'([^']*)'/.exec(block)[1];
|
|
252
|
+
const group = el('DIV', 'nav-group' + (label ? ' nav-group--collapsible' : ''));
|
|
253
|
+
let into = group;
|
|
254
|
+
if (label) {
|
|
255
|
+
const head = el('BUTTON', 'nav-group__head');
|
|
256
|
+
head.dataset.group = label.toLowerCase();
|
|
257
|
+
group.appendChild(head);
|
|
258
|
+
into = group.appendChild(el('DIV', 'nav-items', 'nav-items-' + label.toLowerCase()));
|
|
259
|
+
}
|
|
260
|
+
for (const m of block.matchAll(/\{ id: '([a-z-]+)', label:/g)) {
|
|
261
|
+
into.appendChild(el('A', 'nav-item' + (m[1] === activeId ? ' nav-item--active' : ''), 'nav-' + m[1]));
|
|
262
|
+
}
|
|
263
|
+
sidebar.appendChild(group);
|
|
264
|
+
}
|
|
265
|
+
const root = treeNode('HTML');
|
|
266
|
+
const byId = (id) => { if (sidebar.id === id) return sidebar; let hit = null; sidebar.walk((c) => { if (!hit && c.id === id) hit = c; }); return hit; };
|
|
267
|
+
const mk = (obj) => ({ getItem: (k) => (k in obj ? obj[k] : null), setItem: (k, v) => { obj[k] = String(v); }, removeItem: (k) => { delete obj[k]; } });
|
|
268
|
+
const documentObj = {
|
|
269
|
+
body: treeNode('BODY'), documentElement: root, activeElement: null,
|
|
270
|
+
getElementById: byId, querySelector: () => null, querySelectorAll: () => [],
|
|
271
|
+
createElement: (t) => treeNode(t.toUpperCase()), createComment: (t) => Object.assign(treeNode('#comment', 8), { data: t }),
|
|
272
|
+
addEventListener() {}, removeEventListener() {},
|
|
273
|
+
};
|
|
274
|
+
const windowObj = {
|
|
275
|
+
location: { search: '' }, matchMedia: () => ({ matches: false }),
|
|
276
|
+
localStorage: mk({}), sessionStorage: mk(mode ? { 'hall-craft': mode } : {}),
|
|
277
|
+
};
|
|
278
|
+
const sandbox = { window: windowObj, document: documentObj, console, CustomEvent: class { constructor(t, i) { this.type = t; Object.assign(this, i); } } };
|
|
279
|
+
sandbox.globalThis = sandbox;
|
|
280
|
+
vm.createContext(sandbox);
|
|
281
|
+
vm.runInContext(CRAFT, sandbox, { filename: 'craft-mode.js' });
|
|
282
|
+
const craft = windowObj.OTBCraftMode;
|
|
283
|
+
craft.mount();
|
|
284
|
+
const fold = () => byId('craft-fold');
|
|
285
|
+
const ids = (n) => n.querySelectorAll('.nav-item').map((a) => a.id.slice(4));
|
|
286
|
+
const order = () => ids(sidebar);
|
|
287
|
+
return { craft, sidebar, fold, ids, order, byId };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
test('GOVERNING LEADS WITH THE CITY: its other tabs fold into one dropdown right under it', () => {
|
|
291
|
+
const before = bootTree().order();
|
|
292
|
+
const p = bootTree({ mode: 'governing' });
|
|
293
|
+
const fold = p.fold();
|
|
294
|
+
assert.ok(fold, 'the dropdown exists in Governing');
|
|
295
|
+
assert.equal(p.byId('nav-city').nextSibling, fold, 'directly under the City tab');
|
|
296
|
+
const own = p.craft.tabsFor('governing').filter((id) => !p.craft.ALWAYS_TABS.includes(id) && id !== 'city');
|
|
297
|
+
assert.deepEqual(JSON.parse(JSON.stringify(p.ids(fold))), JSON.parse(JSON.stringify(own)), 'every other Governing tab is in it, in the mode\'s order');
|
|
298
|
+
assert.ok(fold.classList.contains('nav-group--collapsed'), 'a dropdown starts closed');
|
|
299
|
+
const head = fold.querySelector('.nav-group__head');
|
|
300
|
+
assert.equal(head.getAttribute('aria-expanded'), 'false');
|
|
301
|
+
assert.equal(head.getAttribute('aria-controls'), 'nav-items-craft-fold', 'the head names the region it opens');
|
|
302
|
+
assert.match(head.innerHTML, /More governing/);
|
|
303
|
+
assert.ok(head.hasAttribute('data-craft-off') === false, 'its head shows while a tab inside does');
|
|
304
|
+
assert.ok(p.sidebar.querySelectorAll('.nav-group__head').find((h) => h.dataset.group === 'government').hasAttribute('data-craft-off'),
|
|
305
|
+
'the emptied Government head hides, so no label sits over nothing');
|
|
306
|
+
p.craft.choose('building');
|
|
307
|
+
assert.equal(p.fold(), null, 'leaving Governing removes the dropdown');
|
|
308
|
+
assert.deepEqual(p.order(), before, 'and puts every tab back exactly where it was');
|
|
309
|
+
p.craft.choose('governing');
|
|
310
|
+
p.craft.choose('governing');
|
|
311
|
+
assert.equal(p.sidebar.querySelectorAll('.craft-fold').length, 1, 'choosing it again builds no second dropdown');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('the dropdown opens on its own when the page you are on is inside it', () => {
|
|
315
|
+
const p = bootTree({ mode: 'governing', activeId: 'watch' });
|
|
316
|
+
assert.ok(!p.fold().classList.contains('nav-group--collapsed'), 'on Watch, the dropdown is open');
|
|
317
|
+
assert.equal(p.fold().querySelector('.nav-group__head').getAttribute('aria-expanded'), 'true');
|
|
318
|
+
assert.equal(bootTree({ mode: 'artistry' }).fold(), null, 'no other mode folds anything');
|
|
319
|
+
assert.equal(bootTree().fold(), null, 'nor does no mode');
|
|
320
|
+
});
|
package/tests/hall_city.mjs
CHANGED
|
@@ -271,8 +271,9 @@ test('the page holds only the wordmark, the switch, the city, one button and one
|
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
// task 1004544 (owner decision 2026-10-02): the city sits in the hall chrome
|
|
274
|
-
// behind the shell's full-screen switch, the sky's mechanism (task 1004327)
|
|
275
|
-
//
|
|
274
|
+
// behind the shell's full-screen switch, the sky's mechanism (task 1004327).
|
|
275
|
+
// The owner put the CITY on the sidebar on 2026-10-08 (the Governing mode's
|
|
276
|
+
// lead tab); the street still has no link of its own.
|
|
276
277
|
test('the city sits in the hall chrome, behind the shell\'s full-screen switch', () => {
|
|
277
278
|
const html = read('city.html').replace(/<!--[\s\S]*?-->/g, '');
|
|
278
279
|
assert.match(html, /<body data-page="city"[^>]*\sdata-fullscreen-switch>/, 'the bare attribute: the page lays itself out for full screen');
|
|
@@ -285,7 +286,8 @@ test('the city sits in the hall chrome, behind the shell\'s full-screen switch',
|
|
|
285
286
|
assert.match(css, /html\[data-fullscreen="on"\] \.city-main \{ top: 0; left: 0; \}/);
|
|
286
287
|
assert.match(css, /html\[data-fullscreen="on"\] \.city-top \{ right: 184px; \}/, 'the strip steps clear of the corner exit');
|
|
287
288
|
const shell = read('shell.js');
|
|
288
|
-
assert.
|
|
289
|
+
assert.match(shell, /\{ id: 'city', label: 'City', icon: 'city', href: '\/city', gate: 'perm:board\.vote\.cast' \}/, 'the city is a tab, gated on its page\'s atom');
|
|
290
|
+
assert.doesNotMatch(shell, /href:\s*'(\/builders)?\/street'/, 'the street has no sidebar link: the way in is the city');
|
|
289
291
|
});
|
|
290
292
|
|
|
291
293
|
test('motion is CSS only and dies under reduced motion; no page :root; no innerHTML', () => {
|
package/tests/hall_nav.mjs
CHANGED
|
@@ -267,7 +267,9 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
267
267
|
// purpose (task 1003479, and tests/goal_map_page.mjs pinned it); the owner
|
|
268
268
|
// granted the sky that slot on 2026-09-24 (ADR 0227 §11), under Goals because
|
|
269
269
|
// it is the goals seen as one picture.
|
|
270
|
-
|
|
270
|
+
// 2026-10-08: 'city' — the governor city — joins after the Board Room, on the
|
|
271
|
+
// same atom and for the same reason (owner call; it leads the Governing mode).
|
|
272
|
+
assert.deepEqual(nav[0].items.map((i) => i.id), ['home', 'roadmap', 'goals', 'goal-map', 'work', 'blockers', 'board-room', 'city', 'thinking', 'studio'],
|
|
271
273
|
'the unlabeled group is the project itself (task 1002425 order)');
|
|
272
274
|
assert.deepEqual(nav[1].items.map((i) => i.id), ['standing', 'profile'], 'You = your own numbers');
|
|
273
275
|
// task 1003444: 'people' LEFT the group — the owner deleted the hall's
|
package/tests/hall_street.mjs
CHANGED
|
@@ -504,7 +504,8 @@ test('the street sits in the hall chrome, behind the shell\'s full-screen switch
|
|
|
504
504
|
assert.match(css, /\.street-stage \{ position: fixed; top: var\(--topbar-h, 56px\); left: var\(--sidebar-w-rail, 64px\);[^}]*contain: layout paint;/);
|
|
505
505
|
assert.match(css, /html\[data-fullscreen="on"\] \.street-stage \{ top: 0; left: 0; \}/);
|
|
506
506
|
assert.match(css, /html\[data-fullscreen="on"\] \.street-top \{ padding-right: 184px; \}/, 'the strip steps clear of the corner exit');
|
|
507
|
-
|
|
507
|
+
// The city became a tab on 2026-10-08 (owner); the street is still reached through it.
|
|
508
|
+
assert.doesNotMatch(read('shell.js'), /href:\s*'(\/builders)?\/street'/, 'no sidebar link (owner: leave it off the sidebar)');
|
|
508
509
|
});
|
|
509
510
|
|
|
510
511
|
test('motion is CSS only and dies under reduced motion; no page :root; no innerHTML', () => {
|
|
@@ -635,7 +635,7 @@ try {
|
|
|
635
635
|
assert.match(HTML, /<body data-page="tweak-editor"[^>]*data-fullscreen-switch/);
|
|
636
636
|
assert.equal((HTML.match(/<h1\b/g) || []).length, 1);
|
|
637
637
|
assert.ok(HTML.indexOf('tweak-editor-lib.js') < HTML.indexOf('tweak-editor.js?'), 'the lib loads first');
|
|
638
|
-
assert.match(HTML, /shell\.js\?v=2026-10-
|
|
638
|
+
assert.match(HTML, /shell\.js\?v=2026-10-08-city-tab/);
|
|
639
639
|
assert.equal(/fonts\.googleapis\.com\/css/.test(HTML), false);
|
|
640
640
|
for (const id of ['tw-bar', 'tw-counter', 'tw-open', 'tw-open-more', 'tw-divider', 'tw-openwrap', 'tw-open-menu', 'tw-open-tab', 'tw-open-split', 'tw-frame', 'tw-frame-view', 'tw-split-close', 'tw-download', 'tw-upload', 'tw-file', 'tw-submit', 'tw-paper', 'tw-doc', 'tw-notice', 'tw-done', 'tw-take', 'tw-pick', 'tw-picks', 'tw-refused', 'tw-refused-title', 'tw-refused-list', 'tw-refused-next', 'tw-refused-download', 'tw-refused-close']) {
|
|
641
641
|
assert.ok(new RegExp(`id="${id}"`).test(HTML), `#${id} is in the markup`);
|
|
@@ -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 = '02e600a176c32f6a';
|
|
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);
|