@bongos/core 1.24.81 → 1.24.83

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 (77) hide show
  1. package/.bongos-core.json +95 -80
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +4 -0
  4. package/clients/bongos-client/index.cjs +4 -0
  5. package/clients/bongos-client/index.d.ts +5 -0
  6. package/clients/bongos-client/index.mjs +4 -0
  7. package/docs/adr/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md +4 -0
  8. package/docs/api/openapi.json +86 -2
  9. package/docs/api-reference.md +4 -2
  10. package/docs/copy-inventory.md +39 -33
  11. package/docs/copy-registry.json +107 -52
  12. package/docs/module-api-changelog.md +4 -0
  13. package/docs/page-inventory.json +1 -0
  14. package/docs/page-readings.json +207 -201
  15. package/modules/builder-settings/hall-view-prefs.js +50 -0
  16. package/modules/builder-settings/routes/settings.js +32 -0
  17. package/modules/hall-ui/public/ai-studio.html +3 -3
  18. package/modules/hall-ui/public/atlas.html +3 -3
  19. package/modules/hall-ui/public/blockers.html +3 -3
  20. package/modules/hall-ui/public/board-room.html +3 -3
  21. package/modules/hall-ui/public/city.html +5 -5
  22. package/modules/hall-ui/public/collab.html +3 -3
  23. package/modules/hall-ui/public/commands.html +3 -3
  24. package/modules/hall-ui/public/controls.css +10 -2
  25. package/modules/hall-ui/public/copy-desk.html +3 -3
  26. package/modules/hall-ui/public/deploy.html +3 -3
  27. package/modules/hall-ui/public/goal-map.html +5 -5
  28. package/modules/hall-ui/public/goals.html +3 -3
  29. package/modules/hall-ui/public/government.html +3 -3
  30. package/modules/hall-ui/public/idea.html +3 -3
  31. package/modules/hall-ui/public/ideas.html +3 -3
  32. package/modules/hall-ui/public/index.html +6 -6
  33. package/modules/hall-ui/public/market-watch-item.html +3 -3
  34. package/modules/hall-ui/public/market-watch-rules.html +3 -3
  35. package/modules/hall-ui/public/market-watch.html +3 -3
  36. package/modules/hall-ui/public/modules.html +3 -3
  37. package/modules/hall-ui/public/money.html +3 -3
  38. package/modules/hall-ui/public/my-agents.html +3 -3
  39. package/modules/hall-ui/public/options-menu.js +22 -1
  40. package/modules/hall-ui/public/primer.html +3 -3
  41. package/modules/hall-ui/public/profile.html +3 -3
  42. package/modules/hall-ui/public/project-settings.html +3 -3
  43. package/modules/hall-ui/public/roster.html +3 -3
  44. package/modules/hall-ui/public/settings-view.js +75 -0
  45. package/modules/hall-ui/public/settings.css +113 -46
  46. package/modules/hall-ui/public/settings.html +18 -5
  47. package/modules/hall-ui/public/shell-game.css +10 -0
  48. package/modules/hall-ui/public/shell-game.js +98 -33
  49. package/modules/hall-ui/public/shell.js +4 -3
  50. package/modules/hall-ui/public/street.html +3 -3
  51. package/modules/hall-ui/public/studio.html +3 -3
  52. package/modules/hall-ui/public/style.css +149 -61
  53. package/modules/hall-ui/public/task.html +3 -3
  54. package/modules/hall-ui/public/thinking.html +5 -5
  55. package/modules/hall-ui/public/tweak-editor.html +3 -3
  56. package/modules/hall-ui/public/visor.css +2 -2
  57. package/modules/hall-ui/public/visor.js +7 -1
  58. package/modules/hall-ui/public/watch.html +3 -3
  59. package/modules/hall-ui/public/work.html +3 -3
  60. package/modules/hall-ui/records/craft-mode.md +9 -0
  61. package/modules/hall-ui/records/game-mode.md +5 -4
  62. package/modules/hall-ui/records/options-menu.md +11 -0
  63. package/modules/hall-ui/records/start-screen.md +4 -0
  64. package/package-lock.json +2 -2
  65. package/package.json +1 -1
  66. package/release-notes.json +12 -0
  67. package/scripts/hall-preview/pref-writes.js +8 -1
  68. package/src/module-api.js +1 -1
  69. package/tests/design_tokens_shared.mjs +5 -1
  70. package/tests/hall_controls.mjs +1 -1
  71. package/tests/hall_fullscreen.mjs +42 -21
  72. package/tests/hall_helm.mjs +103 -1
  73. package/tests/hall_start_screen.mjs +38 -3
  74. package/tests/hall_topbar_320.mjs +7 -4
  75. package/tests/hall_view_prefs.mjs +42 -0
  76. package/tests/hall_visor.mjs +3 -2
  77. package/tests/nav_permission_atoms.mjs +1 -1
@@ -7,18 +7,25 @@
7
7
  // the same html[data-fullscreen="on"] state, the same corner button and the same
8
8
  // Escape hand-off (a page listener that calls preventDefault keeps the press).
9
9
  //
10
- // The mode is one localStorage key (`hall-game`), so it follows the browser across
11
- // every hall page, and one attribute, html[data-game="on"], for a sheet or script
12
- // that wants to read it. Leaving full screen in game mode (the corner button)
13
- // brings the bars back for THAT page load only: the next page opens without them
14
- // again, and a page's own remembered choice is never overwritten. `?game=on` /
15
- // `?game=off` on any hall URL sets it by hand; the start screen's Start sets it on (R03, below).
10
+ // GAME OR CLASSIC, never both (task 1005200; owner, 2026-11-11). Classic is the hall
11
+ // as it always was; game is full screen with NO way back to the bars inside it (no
12
+ // corner button, no Escape-to-leave: shell.js) and only the game PLACES below, so any
13
+ // other hall page sends you to the city. The view is chosen at every sign-in (the
14
+ // chooser in front of the start screen) and switched only in Options → Preferences →
15
+ // View. It is kept on the ACCOUNT (GET/PATCH /api/bongos/me/hall-view, builder-settings
16
+ // hall-view-prefs.js) so it follows the builder to any browser; one localStorage key
17
+ // (`hall-game`) is only the copy a page paints from before that read answers, and one
18
+ // attribute, html[data-game="on"], is what a sheet or script reads.
16
19
  // Loaded BEFORE shell.js on every hall page; a page without this file is simply
17
20
  // never in game mode.
18
21
  (function () {
19
22
  'use strict';
20
23
  var KEY = 'hall-game';
21
- var HINT_KEY = 'hall-game-hint';
24
+ var VIEW_API = '/api/bongos/me/hall-view';
25
+ // The game's places, by page id (body data-page). Every other page is classic and is
26
+ // not reachable in game mode; a page joins this list when it has a game version.
27
+ var PLACES = ['home', 'city', 'goal-map', 'thinking', 'atlas', 'settings', 'profile'];
28
+ var embedded = false; // set below, once: inside the options menu's frame
22
29
  function read(store, key) { try { return window[store].getItem(key); } catch (_) { return null; } }
23
30
  function write(store, key, value) { try { window[store].setItem(key, value); } catch (_) { /* private mode */ } }
24
31
  function on() { return read('localStorage', KEY) === 'on'; }
@@ -26,21 +33,57 @@
26
33
  var root = document.documentElement;
27
34
  if (on()) root.dataset.game = 'on'; else delete root.dataset.game;
28
35
  }
29
- // Turn the mode on or off, and have the shell show what this page should now show.
30
- function set(value) {
36
+ // '/builders' on the apex, '' on the builders host: the hall's own base.
37
+ function base() {
38
+ var p = String((window.location && window.location.pathname) || '');
39
+ return p.indexOf('/builders/') === 0 || p === '/builders' ? '/builders' : '';
40
+ }
41
+ // The page id an in-hall path names ('/city' → 'city', '/' → 'home'), or null.
42
+ function pageOf(path) {
43
+ var p = String(path || '').split(/[?#]/)[0].replace(/^\/builders(?=\/|$)/, '').replace(/\.html$/, '');
44
+ p = p.replace(/^\/+|\/+$/g, '');
45
+ if (p === '' || p === 'index') return 'home';
46
+ return p.indexOf('/') >= 0 ? null : p;
47
+ }
48
+ function isPlace(path) { return PLACES.indexOf(pageOf(path)) >= 0; }
49
+ function herePage() {
50
+ var b = document.body;
51
+ return (b && b.getAttribute && b.getAttribute('data-page')) || pageOf(window.location.pathname);
52
+ }
53
+ // In game mode a classic page is not there: go to the city. True when leaving.
54
+ function keepToPlaces() {
55
+ if (!on() || embedded) return false;
56
+ var page = herePage();
57
+ if (!page || PLACES.indexOf(page) >= 0) return false;
58
+ window.location.replace(base() + '/');
59
+ return true;
60
+ }
61
+ function save(view) {
62
+ if (typeof fetch !== 'function') return;
63
+ fetch(VIEW_API, { method: 'PATCH', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ view: view }) })
64
+ .catch(function () { /* the browser's copy stands; the next choice saves again */ });
65
+ }
66
+ // Switch views: the browser's copy, the account (unless o.local: the account said so),
67
+ // then what this page shows. A classic page switched to game goes to the city.
68
+ function set(value, o) {
31
69
  write('localStorage', KEY, value ? 'on' : 'off');
70
+ if (!(o && o.local)) save(value ? 'game' : 'classic');
32
71
  stamp();
72
+ if (keepToPlaces()) return;
33
73
  if (window.OTBShell && window.OTBShell.setFullscreen) window.OTBShell.setFullscreen(null);
34
74
  }
35
- // Whether the shell should say how to leave full screen. Outside game mode,
36
- // always (its own rule); inside it, once per tab — every page opens in full
37
- // screen there, and the same sentence on every page load is noise.
38
- function hint() {
39
- if (!on()) return true;
40
- if (read('sessionStorage', HINT_KEY)) return false;
41
- write('sessionStorage', HINT_KEY, '1');
42
- return true;
75
+ // The account's view wins over the browser's copy (another browser may have switched).
76
+ // Never chosen (null), signed out, or no answer: the copy stands.
77
+ function sync() {
78
+ if (embedded || typeof fetch !== 'function') return;
79
+ fetch(VIEW_API, { credentials: 'same-origin' }).then(function (r) { return r.ok ? r.json() : null; }).then(function (d) {
80
+ var view = d && d.view;
81
+ if (view !== 'game' && view !== 'classic') return;
82
+ if ((view === 'game') !== on()) set(view === 'game', { local: true });
83
+ }, function () { /* offline: the copy stands */ });
43
84
  }
85
+ // The shell's "how to leave full screen" line. Game mode has no way to leave it.
86
+ function hint() { return !on(); }
44
87
 
45
88
  // ---------- the start screen and the load screen (task 1005143, BONGOS-V3 R03) ----------
46
89
  // A fresh sign-in opens on the start screen: Start, Options, Achievements, Extras, Quit over a
@@ -58,7 +101,6 @@
58
101
  var EXTRAS = [
59
102
  { label: 'The thinking sky', href: '/thinking' },
60
103
  { label: 'The atlas', href: '/atlas' },
61
- { label: 'The docs', href: '/docs' },
62
104
  ];
63
105
  var TIPS = [
64
106
  'Escape opens the options menu, where every setting lives.',
@@ -91,6 +133,19 @@
91
133
  if (m.href) return '<li><a' + inner + ' href="' + esc(m.href) + '">' + esc(m.label) + '</a></li>';
92
134
  return '<li><button type="button"' + inner + (m.id === 'extras' ? ' aria-expanded="false" aria-controls="gs-extras"' : '') + '>' + esc(m.label) + '</button></li>';
93
135
  }
136
+ // The chooser in front of the start screen (task 1005200): game or classic, the view
137
+ // chosen last time marked and focused, so Enter keeps it.
138
+ var VIEWS = [
139
+ { id: 'game', label: 'Game', sub: 'The city, full screen' },
140
+ { id: 'classic', label: 'Classic', sub: 'The hall with its sidebar' },
141
+ ];
142
+ function lastView() { var v = read('localStorage', KEY); return v === 'on' ? 'game' : v === 'off' ? 'classic' : null; }
143
+ function viewHtml(v) {
144
+ var last = lastView() === v.id;
145
+ return '<li><button type="button" class="gs__item gs__item--view' + (last ? ' gs__item--last' : '') + '" data-gs="view-' + v.id + '">' +
146
+ '<span class="gs__view">' + esc(v.label) + (last ? '<span class="gs__last">Last time</span>' : '') + '</span>' +
147
+ '<span class="gs__sub">' + esc(v.sub) + '</span></button></li>';
148
+ }
94
149
  // The project's own logo where it has one (ADR 0358), else the shared mark, as the sidebar draws it.
95
150
  function markHtml() {
96
151
  var theme = (window.__BRANDING__ && window.__BRANDING__.theme) || {};
@@ -106,7 +161,9 @@
106
161
  '<h2 class="gs__title" id="gs-title">' + esc(o.name || 'This project') + '</h2>' +
107
162
  '</div>' +
108
163
  '<div class="gs__list">' +
109
- '<ul class="gs__items">' + MENU.map(itemHtml).join('') + '</ul>' +
164
+ '<p class="gs__ask" id="gs-ask">How do you want to see it?</p>' +
165
+ '<ul class="gs__items" id="gs-views">' + VIEWS.map(viewHtml).join('') + '</ul>' +
166
+ '<ul class="gs__items" id="gs-items" hidden>' + MENU.map(itemHtml).join('') + '</ul>' +
110
167
  '<ul class="gs__items gs__items--extras" id="gs-extras" hidden>' + EXTRAS.map(function (x) {
111
168
  return '<li><a class="gs__item gs__item--extra" href="' + esc(x.href) + '">' + esc(x.label) + '</a></li>';
112
169
  }).join('') + '</ul>' +
@@ -244,10 +301,24 @@
244
301
  btn.setAttribute('aria-expanded', open ? 'true' : 'false');
245
302
  if (open) box.querySelector('a').focus();
246
303
  }
304
+ // Game: on to the start screen. Classic: the screen goes and the hall is as it always was.
305
+ function choose(view) {
306
+ if (phase !== 'choose') return;
307
+ if (view === 'classic') { set(false); close(); return; }
308
+ set(true);
309
+ phase = 'menu';
310
+ root.querySelector('#gs-ask').hidden = true;
311
+ root.querySelector('#gs-views').hidden = true;
312
+ root.querySelector('#gs-items').hidden = false;
313
+ var first = root.querySelector('[data-gs="start"]');
314
+ if (first && first.focus) first.focus();
315
+ }
247
316
  function onClick(e) {
248
317
  var el = e.target.closest && e.target.closest('.gs__item');
249
- if (!el || phase !== 'menu') return;
318
+ if (!el) return;
250
319
  var id = el.getAttribute('data-gs');
320
+ if (id === 'view-game' || id === 'view-classic') { choose(id.slice(5)); return; }
321
+ if (phase !== 'menu') return;
251
322
  if (id === 'start') load();
252
323
  else if (id === 'extras') toggleExtras(el);
253
324
  else if (id === 'quit') quit();
@@ -262,7 +333,7 @@
262
333
  if (phase === 'menu' && !box.hidden) { var b = root.querySelector('[data-gs="extras"]'); toggleExtras(b); b.focus(); }
263
334
  return;
264
335
  }
265
- if (phase !== 'menu') return;
336
+ if (phase !== 'menu' && phase !== 'choose') return;
266
337
  var f = items(), at = f.indexOf(document.activeElement), step = 0;
267
338
  if (e.key === 'ArrowDown') step = 1;
268
339
  else if (e.key === 'ArrowUp') step = -1;
@@ -277,7 +348,7 @@
277
348
  function start(o) {
278
349
  if (root || !document.body) return !!root;
279
350
  opts = o || {};
280
- phase = 'menu';
351
+ phase = 'choose';
281
352
  root = document.createElement('div');
282
353
  root.className = 'gs';
283
354
  root.id = 'game-start';
@@ -289,15 +360,11 @@
289
360
  document.addEventListener('keydown', onKey, true);
290
361
  document.body.appendChild(root);
291
362
  document.documentElement.classList.add('gs-open');
292
- var first = root.querySelector('.gs__item');
363
+ var first = root.querySelector('.gs__item--last') || root.querySelector('.gs__item');
293
364
  if (first && first.focus) first.focus();
294
365
  return true;
295
366
  }
296
367
 
297
- try {
298
- var asked = new URLSearchParams(window.location.search || '').get('game');
299
- if (asked === 'on' || asked === 'off') write('localStorage', KEY, asked);
300
- } catch (_) { /* old engine: the stored value stands */ }
301
368
  stamp();
302
369
 
303
370
  // ---------- the options menu (task 1005144, BONGOS-V3 R04, ADR 0401) ----------
@@ -309,7 +376,6 @@
309
376
  // Escape nobody used goes to the menu. Only inside a frame: the same URL opened
310
377
  // on its own is the page with its chrome (the server sends a plain visit of the
311
378
  // old address to the menu, serve-internal.js).
312
- var embedded = false;
313
379
  try {
314
380
  embedded = new URLSearchParams(window.location.search || '').get('embed') === '1'
315
381
  && !!window.parent && window.parent !== window;
@@ -320,10 +386,9 @@
320
386
  if (document.body && document.body.setAttribute) document.body.setAttribute('data-fullscreen-switch', 'off');
321
387
  }
322
388
  // The hall's address for the menu: '/settings' on the builders host, '/builders/settings' on the apex.
323
- function menuPath() {
324
- var p = String((window.location && window.location.pathname) || '');
325
- return (p.indexOf('/builders/') === 0 || p === '/builders' ? '/builders' : '') + '/settings';
326
- }
389
+ function menuPath() { return base() + '/settings'; }
390
+ keepToPlaces();
391
+ sync();
327
392
  function inField(el) {
328
393
  var tag = el && el.tagName ? String(el.tagName).toLowerCase() : '';
329
394
  return tag === 'input' || tag === 'textarea' || tag === 'select' || !!(el && el.isContentEditable);
@@ -359,5 +424,5 @@
359
424
  }, true);
360
425
  }
361
426
  window.OTBShellGame = { on: on, set: set, hint: hint, start: start, tipAt: tipAt, historySteps: historySteps, whenLabel: whenLabel, segsDone: segsDone, MENU: MENU, TIPS: TIPS,
362
- embedded: function () { return embedded; }, menuPath: menuPath };
427
+ embedded: function () { return embedded; }, menuPath: menuPath, PLACES: PLACES, pageOf: pageOf, isPlace: isPlace };
363
428
  })();
@@ -560,7 +560,8 @@
560
560
  // button and Escape bring them back. Opt out with <body data-fullscreen-switch="off">;
561
561
  // the bare attribute means the page lays itself out for it, else it is `plain`.
562
562
  // State: html[data-fullscreen="on"], remembered PER PAGE (`hall-fullscreen:<data-page>`).
563
- // task 1005142: in game mode (shell-game.js) EVERY page opens in it, whatever it remembers.
563
+ // task 1005142: in game mode (shell-game.js) EVERY page opens in it, whatever it remembers,
564
+ // and since task 1005200 it STAYS in it: no button (style.css), no Escape-to-leave.
564
565
  var GAME = window.OTBShellGame || { on: function () { return false; }, hint: function () { return true; } };
565
566
  var FULLSCREEN_KEY = 'hall-fullscreen:';
566
567
  function fullscreenAttr() {
@@ -604,7 +605,7 @@
604
605
  leave: '<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 3v4H3M15 7h-4V3M11 15v-4h4M3 11h4v4"/></svg>',
605
606
  };
606
607
  function applyFullscreen(on) {
607
- if (!fullscreenPage()) on = false;
608
+ if (!fullscreenPage()) on = false; else if (GAME.on()) on = true;
608
609
  var root = document.documentElement;
609
610
  if (on) root.dataset.fullscreen = 'on'; else delete root.dataset.fullscreen;
610
611
  if (on && fullscreenAttr() === null) root.dataset.fullscreenPlain = 'on'; else delete root.dataset.fullscreenPlain;
@@ -641,7 +642,7 @@
641
642
  // a text field, where Escape belongs to the field. Page listeners may be on
642
643
  // the window AFTER this one, so the verdict waits for the dispatch to finish.
643
644
  function onFullscreenKey(e) {
644
- if (e.key !== 'Escape' || !isFullscreen() || e.defaultPrevented || inTextField(e.target)) return;
645
+ if (e.key !== 'Escape' || !isFullscreen() || GAME.on() || e.defaultPrevented || inTextField(e.target)) return;
645
646
  window.setTimeout(function () { if (!e.defaultPrevented && isFullscreen()) setFullscreen(false); }, 0);
646
647
  }
647
648
 
@@ -10,7 +10,7 @@
10
10
  <!-- The faces (Manrope, JetBrains Mono) come from the branding pack's font
11
11
  links, injected into this head by the server like every hall page's
12
12
  (tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet). -->
13
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm">
13
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm2">
14
14
  <link rel="stylesheet" href="/builders/street.css?v=2026-11-11-helm">
15
15
  <link rel="stylesheet" href="/builders/street-card.css?v=2026-10-04-record1">
16
16
  <link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-17-tidy">
@@ -225,10 +225,10 @@
225
225
  </div>
226
226
 
227
227
  <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
228
- <script src="/builders/shell-game.js?v=2026-11-11-helm"></script>
228
+ <script src="/builders/shell-game.js?v=2026-11-11-view2"></script>
229
229
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
230
230
  <script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
231
- <script src="/builders/shell.js?v=2026-11-11-helm"></script>
231
+ <script src="/builders/shell.js?v=2026-11-11-view2"></script>
232
232
  <script src="/builders/hall-dialog.js"></script>
233
233
  <script src="/builders/palette.js" defer></script>
234
234
  <script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
@@ -12,7 +12,7 @@
12
12
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
13
13
  <link rel="preconnect" href="https://fonts.googleapis.com">
14
14
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm">
15
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm2">
16
16
  <link rel="stylesheet" href="/builders/studio.css?v=2026-10-09-day">
17
17
  <link rel="stylesheet" href="/builders/approval-queue.css?v=2026-09-29-aqfix">
18
18
  <link rel="stylesheet" href="/builders/home-gallery.css?v=2026-11-04-studio">
@@ -222,10 +222,10 @@
222
222
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
223
223
 
224
224
  <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
225
- <script src="/builders/shell-game.js?v=2026-11-11-helm"></script>
225
+ <script src="/builders/shell-game.js?v=2026-11-11-view2"></script>
226
226
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
227
227
  <script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
228
- <script src="/builders/shell.js?v=2026-11-11-helm"></script>
228
+ <script src="/builders/shell.js?v=2026-11-11-view2"></script>
229
229
  <script src="/builders/hall-dialog.js"></script>
230
230
  <script src="/builders/palette.js" defer></script>
231
231
  <!-- The kit, for its ONE renderer: the full-size ship-time visual figure