@bongos/core 1.24.73 → 1.24.74

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 (52) hide show
  1. package/.bongos-core.json +67 -57
  2. package/docs/copy-inventory.md +1 -1
  3. package/docs/copy-registry.json +1 -1
  4. package/docs/module-api-changelog.md +2 -0
  5. package/docs/page-inventory.json +33 -0
  6. package/docs/page-readings.json +36 -36
  7. package/modules/hall-ui/public/ai-studio.html +2 -1
  8. package/modules/hall-ui/public/atlas.html +2 -1
  9. package/modules/hall-ui/public/blockers.html +2 -1
  10. package/modules/hall-ui/public/board-room.html +2 -1
  11. package/modules/hall-ui/public/city.html +2 -1
  12. package/modules/hall-ui/public/collab.html +2 -1
  13. package/modules/hall-ui/public/commands.html +2 -1
  14. package/modules/hall-ui/public/copy-desk.html +2 -1
  15. package/modules/hall-ui/public/deploy.html +2 -1
  16. package/modules/hall-ui/public/goal-map.html +2 -1
  17. package/modules/hall-ui/public/goals.html +2 -1
  18. package/modules/hall-ui/public/government.html +2 -1
  19. package/modules/hall-ui/public/idea.html +2 -1
  20. package/modules/hall-ui/public/ideas.html +2 -1
  21. package/modules/hall-ui/public/index.html +2 -1
  22. package/modules/hall-ui/public/market-watch-item.html +2 -1
  23. package/modules/hall-ui/public/market-watch-rules.html +2 -1
  24. package/modules/hall-ui/public/market-watch.html +2 -1
  25. package/modules/hall-ui/public/modules.html +2 -1
  26. package/modules/hall-ui/public/money.html +2 -1
  27. package/modules/hall-ui/public/my-agents.html +2 -1
  28. package/modules/hall-ui/public/primer.html +2 -1
  29. package/modules/hall-ui/public/profile.html +2 -1
  30. package/modules/hall-ui/public/project-settings.html +2 -1
  31. package/modules/hall-ui/public/roster.html +2 -1
  32. package/modules/hall-ui/public/settings.html +2 -1
  33. package/modules/hall-ui/public/shell-game.js +50 -0
  34. package/modules/hall-ui/public/shell.js +11 -11
  35. package/modules/hall-ui/public/street.html +2 -1
  36. package/modules/hall-ui/public/studio.html +2 -1
  37. package/modules/hall-ui/public/task.html +2 -1
  38. package/modules/hall-ui/public/thinking.html +2 -1
  39. package/modules/hall-ui/public/tweak-editor.html +2 -1
  40. package/modules/hall-ui/public/watch.html +2 -1
  41. package/modules/hall-ui/public/work.html +2 -1
  42. package/modules/hall-ui/records/game-mode.md +20 -0
  43. package/package-lock.json +2 -2
  44. package/package.json +1 -1
  45. package/release-notes.json +6 -0
  46. package/src/module-api.js +1 -1
  47. package/tests/goal_map_page.mjs +2 -2
  48. package/tests/hall_city.mjs +1 -1
  49. package/tests/hall_fullscreen.mjs +101 -0
  50. package/tests/hall_my_agents.mjs +1 -1
  51. package/tests/hall_street.mjs +1 -1
  52. package/tests/nav_permission_atoms.mjs +1 -1
@@ -25,6 +25,8 @@ const HALL = path.join(ROOT, 'modules/hall-ui/public');
25
25
  const SHELL = fs.readFileSync(path.join(HALL, 'shell.js'), 'utf8');
26
26
  // task 1004492: the way back lives in shell-trail.js, which every page loads first.
27
27
  const TRAIL = fs.readFileSync(path.join(HALL, 'shell-trail.js'), 'utf8');
28
+ // task 1005142: game mode, the hall-wide half of the switch; every page loads it before the shell.
29
+ const GAME = fs.readFileSync(path.join(HALL, 'shell-game.js'), 'utf8');
28
30
  const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
29
31
 
30
32
  // Boot the shell once — one page load. `store` is the browser's localStorage and
@@ -125,11 +127,16 @@ function boot({ page = 'thinking', optIn = true, store = {}, storageThrows = fal
125
127
  };
126
128
  sandbox.globalThis = sandbox;
127
129
  vm.createContext(sandbox);
130
+ vm.runInContext(GAME, sandbox, { filename: 'shell-game.js' });
128
131
  vm.runInContext(TRAIL, sandbox, { filename: 'shell-trail.js' });
129
132
  vm.runInContext(SHELL, sandbox, { filename: 'shell.js' });
130
133
  const root = documentObj.documentElement;
131
134
  return {
132
135
  store,
136
+ game: () => windowObj.OTBShellGame,
137
+ gameStamp: () => root.dataset.game === 'on',
138
+ // The "how to leave" line: showing = the shell created it and it is not hidden.
139
+ hintShowing: () => els.has('fullscreen-hint') && els.get('fullscreen-hint').hidden === false,
133
140
  button: () => getElementById('fullscreen-toggle'),
134
141
  on: () => root.dataset.fullscreen === 'on',
135
142
  plain: () => root.dataset.fullscreenPlain === 'on',
@@ -477,3 +484,97 @@ test('source: full screen hides the sidebar and foot, and collapses the top bar
477
484
  assert.match(css, /html\[data-fullscreen="on"\] \.topbar-side > :not\(\.fullscreen-toggle\)/);
478
485
  assert.doesNotMatch(css, /html\[data-fullscreen="on"\] \.app-topbar \{ display: none/);
479
486
  });
487
+
488
+ // GAME MODE (task 1005142, BONGOS-V3 R02). The spec's done-when: no page shows the
489
+ // sidebar in game mode. Game mode is hall-wide — one key, read by the SAME switch
490
+ // — so these drive the real shell-game.js + shell.js the way a reader moves
491
+ // through the hall: page after page over one browser store.
492
+ test('game mode: EVERY page opens without its bars — plain, laid-out, and one that remembers "off"', () => {
493
+ const store = { 'hall-game': 'on', 'hall-fullscreen:goals': 'off' };
494
+ for (const [page, optIn] of [['goals', false], ['work', false], ['thinking', true], ['city', true]]) {
495
+ const p = boot({ page, optIn, store });
496
+ assert.equal(p.on(), true, `${page} opens in full screen`);
497
+ assert.equal(p.gameStamp(), true, `${page}: html[data-game="on"]`);
498
+ assert.equal(p.plain(), optIn === false, `${page}: the plain mark still follows the page`);
499
+ }
500
+ assert.equal(boot({ page: 'goals', optIn: false, store: {} }).on(), false, 'and outside game mode nothing changed');
501
+ });
502
+
503
+ test('game mode: every hall page carries the switch — none opts out, and each loads shell-game.js before the shell', () => {
504
+ const pages = fs.readdirSync(HALL).filter((f) => f.endsWith('.html'));
505
+ assert.ok(pages.length >= 30, `found ${pages.length} hall pages`);
506
+ for (const f of pages) {
507
+ const html = read(f);
508
+ assert.doesNotMatch(html, /data-fullscreen-switch="off"/, `${f} must not opt out: it would keep its sidebar in game mode`);
509
+ assert.match(html, /id="app-topbar"/, `${f}: the top bar is where the shell renders the switch`);
510
+ const tags = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js[^"]*"( defer)?><\/script>/g)].map((m) => [m[1], !!m[2]]);
511
+ const game = tags.findIndex((t) => t[0] === 'shell-game');
512
+ const shell = tags.findIndex((t) => t[0] === 'shell');
513
+ assert.ok(game >= 0 && game < shell, `${f} loads shell-game.js before shell.js`);
514
+ assert.equal(tags[game][1], tags[shell][1], `${f}: both load the same way, so the order holds`);
515
+ }
516
+ });
517
+
518
+ test('game mode: leaving brings the bars back for THIS page only, and never rewrites a page\'s own memory', () => {
519
+ const store = { 'hall-game': 'on', 'hall-fullscreen:thinking': 'on' };
520
+ const goals = boot({ page: 'goals', optIn: false, store });
521
+ goals.press('Escape');
522
+ assert.equal(goals.on(), false, 'Escape still leaves');
523
+ assert.equal(store['hall-fullscreen:goals'], undefined, 'nothing remembered for the page');
524
+ assert.equal(boot({ page: 'goals', optIn: false, store }).on(), true, 'the next load opens without bars again');
525
+ const sky = boot({ page: 'thinking', store });
526
+ sky.button().click();
527
+ assert.equal(sky.on(), false, 'the corner button leaves too');
528
+ assert.equal(store['hall-fullscreen:thinking'], 'on', 'the sky\'s own choice survives game mode');
529
+ });
530
+
531
+ test('game mode: the Escape hand-off is unchanged — a press the page used, or one in a field, does not leave', () => {
532
+ const p = boot({ page: 'goals', optIn: false, store: { 'hall-game': 'on' } });
533
+ p.press('Escape', { pageUses: true });
534
+ assert.equal(p.on(), true, 'a page listener that called preventDefault keeps the press');
535
+ p.press('Escape', { target: { tagName: 'TEXTAREA' } });
536
+ assert.equal(p.on(), true);
537
+ });
538
+
539
+ test('game mode: set() switches the page live, and turning it off hands each page back its own memory', () => {
540
+ const store = { 'hall-fullscreen:thinking': 'on' };
541
+ const goals = boot({ page: 'goals', optIn: false, store });
542
+ goals.game().set(true);
543
+ assert.equal(store['hall-game'], 'on');
544
+ assert.equal(goals.on(), true, 'on at once, no reload');
545
+ assert.ok(goals.resizes() >= 1, 'a page that sizes to its box is told to re-measure');
546
+ goals.game().set(false);
547
+ assert.equal(goals.on(), false, 'goals remembered nothing, so its bars return');
548
+ assert.equal(goals.gameStamp(), false);
549
+ assert.equal(store['hall-fullscreen:goals'], undefined, 'and switching the mode wrote nothing for the page');
550
+ const sky = boot({ page: 'thinking', store: Object.assign(store, { 'hall-game': 'on' }) });
551
+ sky.game().set(false);
552
+ assert.equal(sky.on(), true, 'the sky had chosen full screen itself, and keeps it');
553
+ });
554
+
555
+ test('game mode: ?game=on / ?game=off set it from the URL; anything else is ignored', () => {
556
+ const store = {};
557
+ assert.equal(boot({ page: 'work', optIn: false, store, search: '?game=on' }).on(), true);
558
+ assert.equal(store['hall-game'], 'on');
559
+ assert.equal(boot({ page: 'goals', optIn: false, store }).on(), true, 'and it holds on the next page');
560
+ assert.equal(boot({ page: 'work', optIn: false, store, search: '?game=off' }).on(), false);
561
+ boot({ page: 'work', optIn: false, store, search: '?game=%22%3E%3Cimg%3E' });
562
+ assert.equal(store['hall-game'], 'off', 'a value that is not on/off is not stored');
563
+ });
564
+
565
+ test('game mode: how to leave is said once per tab, not on every page; outside it the shell\'s rule stands', () => {
566
+ const store = { 'hall-game': 'on' };
567
+ const session = {};
568
+ assert.equal(boot({ page: 'goals', optIn: false, store, session }).hintShowing(), true, 'the first page says it');
569
+ assert.equal(boot({ page: 'work', optIn: false, store, session }).hintShowing(), false, 'the next does not repeat it');
570
+ const own = { 'hall-fullscreen:thinking': 'on' };
571
+ assert.equal(boot({ page: 'thinking', store: own, session: {} }).hintShowing(), true);
572
+ assert.equal(boot({ page: 'thinking', store: own, session: {} }).hintShowing(), true, 'a remembered page says it on every open, as before');
573
+ });
574
+
575
+ test('game mode: storage that throws leaves the hall out of game mode, with the switch working', () => {
576
+ const p = boot({ page: 'goals', optIn: false, storageThrows: true, search: '?game=on' });
577
+ assert.equal(p.on(), false);
578
+ p.button().click();
579
+ assert.equal(p.on(), true);
580
+ });
@@ -443,7 +443,7 @@ test('PAGE: in the hall family, with no prototype key', () => {
443
443
  assert.deepEqual(sheets, ['style.css', 'oversight.css', 'speciality-tools.css', 'my-agents.css'],
444
444
  'rides the oversight archetype; the shared speciality sheet (task 1005052) before its own, so the page skin wins');
445
445
  const scripts = [...html.matchAll(/src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
446
- assert.deepEqual(scripts.slice(0, 4), ['dom-utils', 'shell-trail', 'craft-mode', 'shell']); // dom-utils first: the shell escapes through it (task 1004876)
446
+ assert.deepEqual(scripts.slice(0, 5), ['dom-utils', 'shell-game', 'shell-trail', 'craft-mode', 'shell']); // dom-utils first: the shell escapes through it (task 1004876)
447
447
  for (const s of ['gov-living', 'gov-positions', 'my-agents', 'speciality-tools']) assert.ok(scripts.includes(s), `loads ${s}.js`);
448
448
  assert.doesNotMatch(html, /ma-key|data-proto|Prototype data/, 'nothing on the page is prototype data, so there is no key');
449
449
  assert.doesNotMatch(read('my-agents.js'), /governor-overlay/, 'and it reads no overlay');
@@ -490,7 +490,7 @@ test('the page holds the prototype\'s header, the street, the readings and the c
490
490
  assert.match(html, /<a class="street-mark" id="streetHome" href="\/builders\/city"[^>]*>\{\{productName\}\}</);
491
491
  for (const id of ['hudNeeds', 'hudMoving', 'hudShare', 'streetMode', 'streetSigns', 'streetPins', 'streetDoors', 'streetCard']) assert.match(html, new RegExp(`id="${id}"`), id);
492
492
  const scripts = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
493
- assert.deepEqual(scripts.filter((s) => !/^street-(deck|office)$/.test(s)), ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'hall-dialog', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-depth', 'street-desk', 'street-amend', 'street'], 'the hall shell (task 1004544), the living model and its one reader (task 1004685: city-draw.js counts with it, so it loads first), the shared kit, then the card (task 1004528), the interiors (task 1004526), the living street\'s exterior (street-depth.js), the floor desk (task 1004558) and the drafted amendment (task 1004530); the deck rides beside them');
493
+ assert.deepEqual(scripts.filter((s) => !/^street-(deck|office)$/.test(s)), ['dom-utils', 'shell-game', 'shell-trail', 'craft-mode', 'shell', 'hall-dialog', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-depth', 'street-desk', 'street-amend', 'street'], 'the hall shell (task 1004544), the living model and its one reader (task 1004685: city-draw.js counts with it, so it loads first), the shared kit, then the card (task 1004528), the interiors (task 1004526), the living street\'s exterior (street-depth.js), the floor desk (task 1004558) and the drafted amendment (task 1004530); the deck rides beside them');
494
494
  assert.equal(City.STREET_HREF, '/builders/street', 'the city\'s door is this page');
495
495
  });
496
496
 
@@ -102,7 +102,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
102
102
  // task 1005051: Roadmap left it (folded into Goals; /roadmap redirects).
103
103
  // task 1005070: the remembered nav (the rail paints before /me).
104
104
  // task 1005088: the sidebar mark is drawn by reference to the one brand mark.
105
- const SHELL_SHA = '2845bfedfaf7846f';
105
+ const SHELL_SHA = 'e9c13c142bcd2a95';
106
106
 
107
107
  test('shell.js has not changed without its cache-bust being bumped', () => {
108
108
  const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);