@bongos/core 1.21.51 → 1.21.53

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 (70) hide show
  1. package/.bongos-core.json +88 -73
  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 +7 -0
  6. package/clients/bongos-client/index.mjs +4 -0
  7. package/docs/api/openapi.json +135 -3
  8. package/docs/api-reference.md +4 -2
  9. package/docs/copy-inventory.md +195 -150
  10. package/docs/copy-registry.json +740 -281
  11. package/docs/module-api-changelog.md +4 -0
  12. package/docs/page-inventory.json +8 -4
  13. package/docs/page-readings.json +336 -333
  14. package/modules/hall-ui/public/atlas.html +3 -3
  15. package/modules/hall-ui/public/blockers.html +3 -3
  16. package/modules/hall-ui/public/board-room.html +3 -3
  17. package/modules/hall-ui/public/city.html +3 -3
  18. package/modules/hall-ui/public/collab.html +3 -3
  19. package/modules/hall-ui/public/commands.html +3 -3
  20. package/modules/hall-ui/public/copy-desk.html +3 -3
  21. package/modules/hall-ui/public/craft-mode.js +7 -1
  22. package/modules/hall-ui/public/deploy.html +3 -3
  23. package/modules/hall-ui/public/diagrams.html +3 -3
  24. package/modules/hall-ui/public/drachmae.html +3 -3
  25. package/modules/hall-ui/public/fleet.html +3 -3
  26. package/modules/hall-ui/public/gate.html +3 -3
  27. package/modules/hall-ui/public/goal-map.html +3 -3
  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 +3 -3
  33. package/modules/hall-ui/public/lightbulb.js +213 -0
  34. package/modules/hall-ui/public/modules.html +3 -3
  35. package/modules/hall-ui/public/primer.html +3 -3
  36. package/modules/hall-ui/public/profile.html +3 -3
  37. package/modules/hall-ui/public/project-settings.html +3 -3
  38. package/modules/hall-ui/public/ranks.html +3 -3
  39. package/modules/hall-ui/public/roadmap.html +3 -3
  40. package/modules/hall-ui/public/roster.html +3 -3
  41. package/modules/hall-ui/public/sessions.html +3 -3
  42. package/modules/hall-ui/public/settings.html +3 -3
  43. package/modules/hall-ui/public/street.html +3 -3
  44. package/modules/hall-ui/public/studio.html +3 -3
  45. package/modules/hall-ui/public/style.css +1 -0
  46. package/modules/hall-ui/public/task.html +3 -3
  47. package/modules/hall-ui/public/thinking.html +3 -3
  48. package/modules/hall-ui/public/tweak-editor.html +3 -3
  49. package/modules/hall-ui/public/watch.html +3 -3
  50. package/modules/hall-ui/public/work.html +3 -3
  51. package/modules/hall-ui/records/craft-mode.md +8 -0
  52. package/modules/page-notes/db.js +34 -0
  53. package/modules/page-notes/notes.js +54 -8
  54. package/modules/page-notes/routes/page-notes.js +49 -1
  55. package/modules/page-notes/split.js +39 -8
  56. package/modules/public-landing/public/account.html +12 -101
  57. package/modules/public-landing/public/assets/hub-bar.js +127 -0
  58. package/modules/public-landing/public/contact.html +60 -9
  59. package/modules/public-landing/public/privacy.html +60 -9
  60. package/modules/public-landing/public/terms.html +60 -9
  61. package/package-lock.json +2 -2
  62. package/package.json +1 -1
  63. package/release-notes.json +12 -0
  64. package/src/module-api.js +1 -1
  65. package/tests/hall_tweak_editor.mjs +1 -1
  66. package/tests/hub_account_page.mjs +22 -5
  67. package/tests/landing_legal_pages.mjs +275 -0
  68. package/tests/lightbulb.mjs +385 -0
  69. package/tests/page_notes_panel.mjs +3 -1
  70. package/tests/paintbrush.mjs +2 -1
@@ -0,0 +1,127 @@
1
+ /* assets/hub-bar.js — THE HUB'S BAR on the pages that have no views of their own
2
+ (task 1004489, after task 1004455).
3
+
4
+ /projects draws the bar and its two bands — the Projects band and the profile
5
+ band behind the identity chip. The apex pages a person reaches from it wear the
6
+ same bar so the chrome does not swap under them: the account page (/account) and
7
+ the three footer doors (/privacy, /terms, /contact). Each page keeps the bar's
8
+ MARKUP (real markup, pinned byte-equal to the hub's); this file is the one copy
9
+ of what makes it work there:
10
+ - the chip, filled from GET /me — signed out there is no chip and no
11
+ Your-projects item, and the sign-in door takes the chip's slot;
12
+ - the bands' disclosure: a trigger toggles its band, opening one closes the
13
+ other, Escape closes and returns focus to the trigger, a click outside or
14
+ Tab out closes, the arrows walk the items, an item activated closes it;
15
+ - sign out: the profile band's plain anchor upgraded to the hub's logout POST.
16
+
17
+ projects.html keeps its own copy (its bands also fold the map search and switch
18
+ views), so a change to the disclosure is made there AND here.
19
+ tests/hub_account_page.mjs and tests/landing_legal_pages.mjs execute this file.
20
+
21
+ A page that knows more about who is signed in than GET /me says paints the chip
22
+ itself through window.HubBar.paint(me); window.HubBar.ready settles with what
23
+ GET /me answered (null when nobody is signed in or the read failed). */
24
+ (function () {
25
+ 'use strict';
26
+ var API = '/api/bongos';
27
+ function $(id) { return document.getElementById(id); }
28
+
29
+ // ---- the chip: who is signed in (null = nobody) ----
30
+ function paint(me) {
31
+ var login = me ? (me.github_login || 'signed in') : '';
32
+ var avatar = me && me.avatar_url;
33
+ $('who').hidden = !me;
34
+ $('signinTop').hidden = !!me;
35
+ $('mineCell').hidden = !me;
36
+ [['whoImg', 'whoLogin'], ['bandWhoImg', 'bandWhoLogin']].forEach(function (ids) {
37
+ $(ids[0]).hidden = !avatar;
38
+ if (avatar) $(ids[0]).src = avatar;
39
+ $(ids[1]).textContent = login;
40
+ });
41
+ if (me) $('who').setAttribute('aria-label', 'Account, ' + login);
42
+ else closeMenus();
43
+ }
44
+
45
+ // ---- the bands: the hub's disclosure (projects.html "THE BANDS") without the map search ----
46
+ function openBand() { return document.querySelector('.menuBand:not([hidden])'); }
47
+ function closeMenus() {
48
+ var open = openBand();
49
+ if (!open) return;
50
+ var trig = document.querySelector('.trig[aria-expanded="true"]');
51
+ var inside = open.contains(document.activeElement);
52
+ open.hidden = true;
53
+ if (trig) trig.setAttribute('aria-expanded', 'false');
54
+ if (inside && trig) trig.focus();
55
+ }
56
+ function openMenu(trig, byKeyboard) {
57
+ closeMenus();
58
+ trig.setAttribute('aria-expanded', 'true');
59
+ var band = $(trig.getAttribute('aria-controls'));
60
+ band.hidden = false;
61
+ // Enter/Space on the trigger lands on the first item; a pointer stays put
62
+ if (byKeyboard) { var first = band.querySelector('.bandCell:not([hidden]) a'); if (first) first.focus(); }
63
+ }
64
+ document.querySelectorAll('.trig').forEach(function (t) {
65
+ t.addEventListener('click', function (ev) {
66
+ if (t.getAttribute('aria-expanded') === 'true') closeMenus();
67
+ else openMenu(t, ev.detail === 0);
68
+ });
69
+ });
70
+ document.addEventListener('click', function (ev) {
71
+ if (!openBand()) return;
72
+ if (ev.target.closest && (ev.target.closest('.menuBand') || ev.target.closest('.trig'))) return;
73
+ closeMenus();
74
+ });
75
+ document.addEventListener('keydown', function (ev) {
76
+ var open = openBand();
77
+ if (!open) return;
78
+ if (ev.key === 'Escape') { closeMenus(); return; }
79
+ if (ev.key !== 'ArrowDown' && ev.key !== 'ArrowUp') return;
80
+ var items = Array.prototype.filter.call(open.querySelectorAll('a'), function (a) { return !a.closest('[hidden]'); });
81
+ if (!items.length) return;
82
+ var at = items.indexOf(document.activeElement);
83
+ ev.preventDefault();
84
+ items[(at + (ev.key === 'ArrowDown' ? 1 : items.length - 1) + items.length) % items.length].focus();
85
+ });
86
+ document.querySelectorAll('.menuBand').forEach(function (b) {
87
+ b.addEventListener('click', function (ev) {
88
+ if (ev.target.closest && ev.target.closest('a')) closeMenus();
89
+ });
90
+ // Tab out of the band closes it; a null relatedTarget is the window losing
91
+ // focus or a click on nothing, which the document click covers
92
+ b.addEventListener('focusout', function (ev) {
93
+ var to = ev.relatedTarget;
94
+ if (!to || b.contains(to) || (to.closest && to.closest('.trig'))) return;
95
+ closeMenus();
96
+ });
97
+ });
98
+
99
+ // ---- the profile band: the current place, and sign out (projects.html doLogout) ----
100
+ $('menuProfile').addEventListener('click', function (ev) {
101
+ if (!ev.target.closest) return;
102
+ // an item marked as this page: a reload would only throw away what was typed
103
+ if (ev.target.closest('[aria-current="page"]')) { ev.preventDefault(); return; }
104
+ if (!ev.target.closest('a.signout')) return;
105
+ ev.preventDefault();
106
+ var finish = function () {
107
+ var apex = location.hostname.split('.').slice(-2).join('.');
108
+ ['bongos_session', 'gds_session', 'pms_session'].forEach(function (name) {
109
+ document.cookie = name + '=; Max-Age=0; path=/';
110
+ document.cookie = name + '=; Max-Age=0; path=/; Domain=.' + apex;
111
+ });
112
+ window.location.assign(location.pathname);
113
+ };
114
+ fetch(API + '/auth/logout', { method: 'POST', credentials: 'same-origin' }).then(finish, finish);
115
+ });
116
+
117
+ // ---- the identity read: the hub's own GET /me ----
118
+ var ready = fetch(API + '/me', { method: 'GET', credentials: 'same-origin', headers: { Accept: 'application/json' } })
119
+ .then(function (res) { return res.ok ? res.json().catch(function () { return null; }) : null; })
120
+ .then(function (d) {
121
+ var me = d ? (d.builder || d.me || d) : null;
122
+ paint(me);
123
+ return me;
124
+ }, function () { paint(null); return null; });
125
+
126
+ window.HubBar = { paint: paint, ready: ready };
127
+ })();
@@ -20,9 +20,14 @@
20
20
  <!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
21
21
  <style>
22
22
  /* The reading column only. No :root block — the pack's injected theme owns the
23
- tokens (landing direction C1); everything below reads them. */
23
+ tokens (landing direction C1); everything below reads them. The bar and the
24
+ bands are cosmos.css's own rules, not restyled here. */
25
+ /* the skip link un-hides itself on focus, reversing everything .vh sets (the hub's rule) */
26
+ .skip:focus{position:fixed;top:8px;left:8px;z-index:100;width:auto;height:auto;margin:0;overflow:visible;clip-path:none;white-space:normal;
27
+ font-size:14px;font-weight:800;text-decoration:none;color:var(--ink);background:var(--bg-card);
28
+ border:2px solid var(--ctl-line);border-radius:12px;padding:10px 16px;}
24
29
  .legal{position:relative;z-index:1;max-width:720px;margin:0 auto;padding:clamp(6px,1.5vw,18px) clamp(16px,4vw,40px) 48px;}
25
- .legal h1{font-family:var(--font-display);font-weight:700;font-size:clamp(26px,3.8vw,36px);letter-spacing:-0.5px;color:var(--chrome-ink);text-shadow:var(--chrome-glow);margin-bottom:6px;}
30
+ .legal h1{font-family:var(--font-display);font-weight:700;font-size:clamp(26px,3.8vw,36px);letter-spacing:-0.5px;color:var(--chrome-ink);text-shadow:var(--chrome-glow);margin:18px 0 6px;}
26
31
  .legal .lede{color:var(--hero-lede);text-shadow:var(--chrome-glow);margin-bottom:18px;}
27
32
  .legal h2{font-family:var(--font-display);font-weight:700;font-size:var(--fs-h3);letter-spacing:-0.3px;line-height:1.25;margin:22px 0 8px;}
28
33
  .legal .card h2:first-child{margin-top:0;}
@@ -34,15 +39,60 @@
34
39
  </style>
35
40
  </head>
36
41
  <body>
37
- <header class="top">
38
- <a class="mark" href="/">{{productName}}</a>
39
- <div class="top-right">
40
- <a class="btn-space" href="/projects">Projects</a>
41
- <a class="btn-space" data-link-hall href="{{buildersOrigin}}">Hall</a>
42
+ <!-- THE BAR and its two bands are the hub's own (projects.html; task 1004489,
43
+ after the account page's task 1004455): same markup, same cosmos.css rules,
44
+ so following a footer link from /projects or /account keeps the chrome. No
45
+ map search (it only filters the map), the band links name /projects in full
46
+ because this page has no views of its own, and no item is the current place.
47
+ assets/hub-bar.js fills the chip from GET /me and runs the bands. -->
48
+ <a class="skip vh" href="#legal">Skip to content</a>
49
+ <header class="shell bar">
50
+ <div class="barLeft">
51
+ <a class="barMark" id="barMark" href="/">
52
+ <svg viewBox="0 0 26 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.4"
53
+ stroke-linecap="round" stroke-linejoin="round">
54
+ <ellipse cx="8.4" cy="5.6" rx="6.6" ry="2.9"/>
55
+ <path d="M1.8 5.6v6.6a6.6 2.9 0 0 0 13.2 0V5.6"/>
56
+ <ellipse cx="19.6" cy="8.4" rx="4.7" ry="2.1"/>
57
+ <path d="M14.9 8.4v4.6a4.7 2.1 0 0 0 9.4 0V8.4"/>
58
+ </svg>
59
+ <span>{{productName}}</span>
60
+ </a>
61
+ </div>
62
+ <div class="barRight">
63
+ <button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
64
+ <!-- the identity chip IS the profile trigger; assets/hub-bar.js fills it from
65
+ GET /me. Signed out it stays hidden and the sign-in door takes its slot. -->
66
+ <button class="trig who" type="button" id="who" data-menu="profile" aria-expanded="false" aria-controls="menuProfile" hidden><img id="whoImg" alt="" hidden><span class="whoName" id="whoLogin"></span><svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
67
+ <a class="signinDoor" id="signinTop" hidden href="/api/bongos/auth/web/start?return=/contact&amp;repos=1">Sign in</a>
42
68
  </div>
43
69
  </header>
44
70
 
45
- <main class="legal">
71
+ <div class="menuBand" id="menuProjects" data-menu="projects" hidden>
72
+ <div class="bandIn">
73
+ <ul class="bandRow">
74
+ <li class="bandCell"><a class="bandT" href="/projects?view=map">Sky</a><span class="bandC">the sky of every project</span></li>
75
+ <li class="bandCell"><a class="bandT" href="/projects?view=map&amp;sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
76
+ <li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own or build on</span></li>
77
+ <li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
78
+ </ul>
79
+ </div>
80
+ </div>
81
+ <div class="menuBand" id="menuProfile" data-menu="profile" hidden>
82
+ <div class="bandIn bandProfile">
83
+ <p class="bandWho" id="bandWho"><img id="bandWhoImg" alt="" hidden><span class="whoName" id="bandWhoLogin"></span></p>
84
+ <ul class="bandRow">
85
+ <li class="bandCell"><a class="bandT" href="/projects?view=mine">My projects</a></li>
86
+ <!-- apex-only because /account is: it is served only where platform-identity
87
+ runs, so a child instance loses the item whole (as on projects.html) -->
88
+ <!--apex-only:start--><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
89
+ <!-- the honest hover target; assets/hub-bar.js upgrades the click to the POST -->
90
+ <li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
91
+ </ul>
92
+ </div>
93
+ </div>
94
+
95
+ <main class="legal" id="legal">
46
96
  <h1>Contact</h1>
47
97
  <p class="lede">Two doors. Both are public, and both are how {{productName}} itself works day to day.</p>
48
98
 
@@ -68,6 +118,7 @@
68
118
  </section>
69
119
  </main>
70
120
 
71
- <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud&nbsp;Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy">Privacy</a><a href="/terms">Terms</a><a href="/contact" aria-current="page">Contact</a></nav></footer>
121
+ <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud&nbsp;Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy">Privacy</a><a href="/terms">Terms</a><a href="/contact" aria-current="page">Contact</a><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a></nav></footer>
122
+ <script src="/assets/hub-bar.js"></script>
72
123
  </body>
73
124
  </html>
@@ -18,9 +18,14 @@
18
18
  <!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
19
19
  <style>
20
20
  /* The reading column only. No :root block — the pack's injected theme owns the
21
- tokens (landing direction C1); everything below reads them. */
21
+ tokens (landing direction C1); everything below reads them. The bar and the
22
+ bands are cosmos.css's own rules, not restyled here. */
23
+ /* the skip link un-hides itself on focus, reversing everything .vh sets (the hub's rule) */
24
+ .skip:focus{position:fixed;top:8px;left:8px;z-index:100;width:auto;height:auto;margin:0;overflow:visible;clip-path:none;white-space:normal;
25
+ font-size:14px;font-weight:800;text-decoration:none;color:var(--ink);background:var(--bg-card);
26
+ border:2px solid var(--ctl-line);border-radius:12px;padding:10px 16px;}
22
27
  .legal{position:relative;z-index:1;max-width:720px;margin:0 auto;padding:clamp(6px,1.5vw,18px) clamp(16px,4vw,40px) 48px;}
23
- .legal h1{font-family:var(--font-display);font-weight:700;font-size:clamp(26px,3.8vw,36px);letter-spacing:-0.5px;color:var(--chrome-ink);text-shadow:var(--chrome-glow);margin-bottom:6px;}
28
+ .legal h1{font-family:var(--font-display);font-weight:700;font-size:clamp(26px,3.8vw,36px);letter-spacing:-0.5px;color:var(--chrome-ink);text-shadow:var(--chrome-glow);margin:18px 0 6px;}
24
29
  .legal .lede{color:var(--hero-lede);text-shadow:var(--chrome-glow);margin-bottom:18px;}
25
30
  .legal h2{font-family:var(--font-display);font-weight:700;font-size:var(--fs-h3);letter-spacing:-0.3px;line-height:1.25;margin:22px 0 8px;}
26
31
  .legal .card h2:first-child{margin-top:0;}
@@ -33,15 +38,60 @@
33
38
  </style>
34
39
  </head>
35
40
  <body>
36
- <header class="top">
37
- <a class="mark" href="/">{{productName}}</a>
38
- <div class="top-right">
39
- <a class="btn-space" href="/projects">Projects</a>
40
- <a class="btn-space" data-link-hall href="{{buildersOrigin}}">Hall</a>
41
+ <!-- THE BAR and its two bands are the hub's own (projects.html; task 1004489,
42
+ after the account page's task 1004455): same markup, same cosmos.css rules,
43
+ so following a footer link from /projects or /account keeps the chrome. No
44
+ map search (it only filters the map), the band links name /projects in full
45
+ because this page has no views of its own, and no item is the current place.
46
+ assets/hub-bar.js fills the chip from GET /me and runs the bands. -->
47
+ <a class="skip vh" href="#legal">Skip to content</a>
48
+ <header class="shell bar">
49
+ <div class="barLeft">
50
+ <a class="barMark" id="barMark" href="/">
51
+ <svg viewBox="0 0 26 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.4"
52
+ stroke-linecap="round" stroke-linejoin="round">
53
+ <ellipse cx="8.4" cy="5.6" rx="6.6" ry="2.9"/>
54
+ <path d="M1.8 5.6v6.6a6.6 2.9 0 0 0 13.2 0V5.6"/>
55
+ <ellipse cx="19.6" cy="8.4" rx="4.7" ry="2.1"/>
56
+ <path d="M14.9 8.4v4.6a4.7 2.1 0 0 0 9.4 0V8.4"/>
57
+ </svg>
58
+ <span>{{productName}}</span>
59
+ </a>
60
+ </div>
61
+ <div class="barRight">
62
+ <button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
63
+ <!-- the identity chip IS the profile trigger; assets/hub-bar.js fills it from
64
+ GET /me. Signed out it stays hidden and the sign-in door takes its slot. -->
65
+ <button class="trig who" type="button" id="who" data-menu="profile" aria-expanded="false" aria-controls="menuProfile" hidden><img id="whoImg" alt="" hidden><span class="whoName" id="whoLogin"></span><svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
66
+ <a class="signinDoor" id="signinTop" hidden href="/api/bongos/auth/web/start?return=/privacy&amp;repos=1">Sign in</a>
41
67
  </div>
42
68
  </header>
43
69
 
44
- <main class="legal">
70
+ <div class="menuBand" id="menuProjects" data-menu="projects" hidden>
71
+ <div class="bandIn">
72
+ <ul class="bandRow">
73
+ <li class="bandCell"><a class="bandT" href="/projects?view=map">Sky</a><span class="bandC">the sky of every project</span></li>
74
+ <li class="bandCell"><a class="bandT" href="/projects?view=map&amp;sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
75
+ <li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own or build on</span></li>
76
+ <li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
77
+ </ul>
78
+ </div>
79
+ </div>
80
+ <div class="menuBand" id="menuProfile" data-menu="profile" hidden>
81
+ <div class="bandIn bandProfile">
82
+ <p class="bandWho" id="bandWho"><img id="bandWhoImg" alt="" hidden><span class="whoName" id="bandWhoLogin"></span></p>
83
+ <ul class="bandRow">
84
+ <li class="bandCell"><a class="bandT" href="/projects?view=mine">My projects</a></li>
85
+ <!-- apex-only because /account is: it is served only where platform-identity
86
+ runs, so a child instance loses the item whole (as on projects.html) -->
87
+ <!--apex-only:start--><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
88
+ <!-- the honest hover target; assets/hub-bar.js upgrades the click to the POST -->
89
+ <li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
90
+ </ul>
91
+ </div>
92
+ </div>
93
+
94
+ <main class="legal" id="legal">
45
95
  <h1>Privacy</h1>
46
96
  <p class="lede">What {{productName}} keeps about you and your project, who else sees it, and how long it stays — in plain words. The code that does all of this is open, so every sentence here can be checked.</p>
47
97
 
@@ -77,6 +127,7 @@
77
127
  </section>
78
128
  </main>
79
129
 
80
- <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud&nbsp;Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy" aria-current="page">Privacy</a><a href="/terms">Terms</a><a href="/contact">Contact</a></nav></footer>
130
+ <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud&nbsp;Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy" aria-current="page">Privacy</a><a href="/terms">Terms</a><a href="/contact">Contact</a><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a></nav></footer>
131
+ <script src="/assets/hub-bar.js"></script>
81
132
  </body>
82
133
  </html>
@@ -18,9 +18,14 @@
18
18
  <!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
19
19
  <style>
20
20
  /* The reading column only. No :root block — the pack's injected theme owns the
21
- tokens (landing direction C1); everything below reads them. */
21
+ tokens (landing direction C1); everything below reads them. The bar and the
22
+ bands are cosmos.css's own rules, not restyled here. */
23
+ /* the skip link un-hides itself on focus, reversing everything .vh sets (the hub's rule) */
24
+ .skip:focus{position:fixed;top:8px;left:8px;z-index:100;width:auto;height:auto;margin:0;overflow:visible;clip-path:none;white-space:normal;
25
+ font-size:14px;font-weight:800;text-decoration:none;color:var(--ink);background:var(--bg-card);
26
+ border:2px solid var(--ctl-line);border-radius:12px;padding:10px 16px;}
22
27
  .legal{position:relative;z-index:1;max-width:720px;margin:0 auto;padding:clamp(6px,1.5vw,18px) clamp(16px,4vw,40px) 48px;}
23
- .legal h1{font-family:var(--font-display);font-weight:700;font-size:clamp(26px,3.8vw,36px);letter-spacing:-0.5px;color:var(--chrome-ink);text-shadow:var(--chrome-glow);margin-bottom:6px;}
28
+ .legal h1{font-family:var(--font-display);font-weight:700;font-size:clamp(26px,3.8vw,36px);letter-spacing:-0.5px;color:var(--chrome-ink);text-shadow:var(--chrome-glow);margin:18px 0 6px;}
24
29
  .legal .lede{color:var(--hero-lede);text-shadow:var(--chrome-glow);margin-bottom:18px;}
25
30
  .legal h2{font-family:var(--font-display);font-weight:700;font-size:var(--fs-h3);letter-spacing:-0.3px;line-height:1.25;margin:22px 0 8px;}
26
31
  .legal .card h2:first-child{margin-top:0;}
@@ -33,15 +38,60 @@
33
38
  </style>
34
39
  </head>
35
40
  <body>
36
- <header class="top">
37
- <a class="mark" href="/">{{productName}}</a>
38
- <div class="top-right">
39
- <a class="btn-space" href="/projects">Projects</a>
40
- <a class="btn-space" data-link-hall href="{{buildersOrigin}}">Hall</a>
41
+ <!-- THE BAR and its two bands are the hub's own (projects.html; task 1004489,
42
+ after the account page's task 1004455): same markup, same cosmos.css rules,
43
+ so following a footer link from /projects or /account keeps the chrome. No
44
+ map search (it only filters the map), the band links name /projects in full
45
+ because this page has no views of its own, and no item is the current place.
46
+ assets/hub-bar.js fills the chip from GET /me and runs the bands. -->
47
+ <a class="skip vh" href="#legal">Skip to content</a>
48
+ <header class="shell bar">
49
+ <div class="barLeft">
50
+ <a class="barMark" id="barMark" href="/">
51
+ <svg viewBox="0 0 26 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.4"
52
+ stroke-linecap="round" stroke-linejoin="round">
53
+ <ellipse cx="8.4" cy="5.6" rx="6.6" ry="2.9"/>
54
+ <path d="M1.8 5.6v6.6a6.6 2.9 0 0 0 13.2 0V5.6"/>
55
+ <ellipse cx="19.6" cy="8.4" rx="4.7" ry="2.1"/>
56
+ <path d="M14.9 8.4v4.6a4.7 2.1 0 0 0 9.4 0V8.4"/>
57
+ </svg>
58
+ <span>{{productName}}</span>
59
+ </a>
60
+ </div>
61
+ <div class="barRight">
62
+ <button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
63
+ <!-- the identity chip IS the profile trigger; assets/hub-bar.js fills it from
64
+ GET /me. Signed out it stays hidden and the sign-in door takes its slot. -->
65
+ <button class="trig who" type="button" id="who" data-menu="profile" aria-expanded="false" aria-controls="menuProfile" hidden><img id="whoImg" alt="" hidden><span class="whoName" id="whoLogin"></span><svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
66
+ <a class="signinDoor" id="signinTop" hidden href="/api/bongos/auth/web/start?return=/terms&amp;repos=1">Sign in</a>
41
67
  </div>
42
68
  </header>
43
69
 
44
- <main class="legal">
70
+ <div class="menuBand" id="menuProjects" data-menu="projects" hidden>
71
+ <div class="bandIn">
72
+ <ul class="bandRow">
73
+ <li class="bandCell"><a class="bandT" href="/projects?view=map">Sky</a><span class="bandC">the sky of every project</span></li>
74
+ <li class="bandCell"><a class="bandT" href="/projects?view=map&amp;sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
75
+ <li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own or build on</span></li>
76
+ <li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
77
+ </ul>
78
+ </div>
79
+ </div>
80
+ <div class="menuBand" id="menuProfile" data-menu="profile" hidden>
81
+ <div class="bandIn bandProfile">
82
+ <p class="bandWho" id="bandWho"><img id="bandWhoImg" alt="" hidden><span class="whoName" id="bandWhoLogin"></span></p>
83
+ <ul class="bandRow">
84
+ <li class="bandCell"><a class="bandT" href="/projects?view=mine">My projects</a></li>
85
+ <!-- apex-only because /account is: it is served only where platform-identity
86
+ runs, so a child instance loses the item whole (as on projects.html) -->
87
+ <!--apex-only:start--><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
88
+ <!-- the honest hover target; assets/hub-bar.js upgrades the click to the POST -->
89
+ <li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
90
+ </ul>
91
+ </div>
92
+ </div>
93
+
94
+ <main class="legal" id="legal">
45
95
  <h1>Terms</h1>
46
96
  <p class="lede">The short version: this is free software run by its owner, your code stays yours, and you are responsible for what you ship.</p>
47
97
 
@@ -82,6 +132,7 @@
82
132
  </section>
83
133
  </main>
84
134
 
85
- <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud&nbsp;Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy">Privacy</a><a href="/terms" aria-current="page">Terms</a><a href="/contact">Contact</a></nav></footer>
135
+ <footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud&nbsp;Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy">Privacy</a><a href="/terms" aria-current="page">Terms</a><a href="/contact">Contact</a><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a></nav></footer>
136
+ <script src="/assets/hub-bar.js"></script>
86
137
  </body>
87
138
  </html>
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.51",
3
+ "version": "1.21.53",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.51",
9
+ "version": "1.21.53",
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.51",
3
+ "version": "1.21.53",
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",
@@ -9074,5 +9074,17 @@
9074
9074
  "id": "1004531",
9075
9075
  "text": "We walked the whole Governor City against its design, in light and dark and on a phone: the home city, the street, every building, a vote decided from its card, and an amendment drafted from a building. Fixes: drafting a chang"
9076
9076
  }
9077
+ ],
9078
+ "1.21.52": [
9079
+ {
9080
+ "id": "1004489",
9081
+ "text": "The Privacy, Terms and Contact pages now have the same top bar as the projects and account pages — the Projects menu and your profile menu — so following a footer link no longer swaps the whole header."
9082
+ }
9083
+ ],
9084
+ "1.21.53": [
9085
+ {
9086
+ "id": "1004498",
9087
+ "text": "Ideating mode now has its own tool. An \"Ideas\" button in the top bar opens a side panel where an ideator writes about the page they are on. \"Turn into ideas\" has an AI split the text into separate ideas, each filed under their"
9088
+ }
9077
9089
  ]
9078
9090
  }
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.51'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.53'; // 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');
@@ -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-04-paintbrush/);
638
+ assert.match(HTML, /shell\.js\?v=2026-10-04-lightbulb/);
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`);
@@ -18,7 +18,9 @@
18
18
  // header of its own — the same markup as projects.html, cosmos.css's rules,
19
19
  // no map search, links into /projects, Account as the current place — and
20
20
  // the bands EXECUTED: the chip from GET /me, open/close, Escape, the arrows,
21
- // a click outside, and sign out as the POST.
21
+ // a click outside, and sign out as the POST. The bands script is the shared
22
+ // assets/hub-bar.js since task 1004489 (the three footer doors load it too),
23
+ // so the page's own script and that file run together here, as in a browser.
22
24
  //
23
25
  // DB-free. Run: node --test tests/hub_account_page.mjs
24
26
 
@@ -37,6 +39,7 @@ const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
37
39
  const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
38
40
  const PAGE = fs.readFileSync(path.join(LANDING, 'account.html'), 'utf8').replace(/\r\n/g, '\n');
39
41
  const SCRIPT = PAGE.split('<script>').pop().split('</script>')[0];
42
+ const BAR = fs.readFileSync(path.join(LANDING, 'assets', 'hub-bar.js'), 'utf8');
40
43
  const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
41
44
  const COSMOS = fs.readFileSync(path.join(LANDING, 'assets', 'cosmos.css'), 'utf8');
42
45
 
@@ -62,7 +65,9 @@ test('portable and dark only: stamped title, balanced sentinels, no :root, no ba
62
65
  });
63
66
 
64
67
  test('the script builds no markup from strings, and the controls are real markup', () => {
65
- assert.doesNotMatch(SCRIPT, /innerHTML|insertAdjacentHTML|outerHTML/, 'live DOM is textContent / createElement only');
68
+ for (const [name, src] of [['the page', SCRIPT], ['assets/hub-bar.js', BAR]]) {
69
+ assert.doesNotMatch(src, /innerHTML|insertAdjacentHTML|outerHTML/, `${name}: live DOM is textContent / createElement only`);
70
+ }
66
71
  for (const id of ['fName', 'fBio', 'fHandle', 'fLinkWebsite', 'fLinkX', 'fLinkLinkedin', 'fLinkDiscord', 'profileSave', 'handleSave', 'privacySave', 'recordSave']) {
67
72
  assert.match(PAGE, new RegExp(`id="${id}"`), `#${id} ships in the markup`);
68
73
  }
@@ -76,8 +81,17 @@ test('the script builds no markup from strings, and the controls are real markup
76
81
  test('the door returns here, and the page calls only its own-scoped routes', () => {
77
82
  assert.match(PAGE, /href="\/api\/bongos\/auth\/web\/start\?return=\/account&amp;repos=1"/, 'sign-in lands back on /account, asking for repo access once (task 1004549)');
78
83
  const calls = [...SCRIPT.matchAll(/request\('(GET|PATCH|POST)', '([^']+)'/g)].map((m) => `${m[1]} ${m[2]}`);
79
- assert.deepEqual([...new Set(calls)].sort(), ['GET /me', 'GET /me/cross-project','PATCH /me/cross-project', 'PATCH /me/handle', 'PATCH /me/profile'].sort());
80
- assert.match(SCRIPT, /fetch\(API \+ '\/auth\/logout', \{ method: 'POST'/, 'sign out is the hub logout POST');
84
+ assert.deepEqual([...new Set(calls)].sort(), ['GET /me/cross-project', 'PATCH /me/cross-project', 'PATCH /me/handle', 'PATCH /me/profile'].sort());
85
+ // the bar's two calls live in the shared asset: the identity read and sign out
86
+ const barCalls = [...BAR.matchAll(/fetch\(API \+ '([^']+)', \{ method: '(GET|POST)'/g)].map((m) => `${m[2]} ${m[1]}`);
87
+ assert.deepEqual(barCalls.sort(), ['GET /me', 'POST /auth/logout']);
88
+ assert.match(BAR, /var API = '\/api\/bongos';/);
89
+ });
90
+
91
+ test('the bar’s script is the shared asset, loaded before the page’s own (task 1004489)', () => {
92
+ assert.match(PAGE, /<script src="\/assets\/hub-bar\.js"><\/script>\n<script>\n\(function \(\) \{/, 'hub-bar.js runs first, so window.HubBar exists when the page script asks for it');
93
+ assert.doesNotMatch(SCRIPT, /function (openBand|closeMenus|openMenu|paintBar)\b|\/auth\/logout/, 'no second copy of the bands, the chip or sign out in the page');
94
+ assert.match(SCRIPT, /var bar = window\.HubBar \|\| \{ paint: function \(\) \{\}, ready: Promise\.resolve\(null\) \};/, 'and the page survives the asset failing to load');
81
95
  });
82
96
 
83
97
  // ── 1b. the bar is the hub's (task 1004455) ──────────────────────────────────
@@ -259,7 +273,10 @@ function boot(answer) {
259
273
  return Promise.resolve({ status, ok: status >= 200 && status < 300, json: () => Promise.resolve(data) });
260
274
  };
261
275
  const location = { hostname: 'cloudbongos.com', pathname: '/account' };
262
- vm.runInNewContext(SCRIPT, { document, fetch, location, window: { location: { assign(p) { dom.assigned = p; } } }, URL, Array, Object, JSON, Promise });
276
+ // one global, as in the browser: hub-bar.js sets window.HubBar, then the page reads it
277
+ const ctx = vm.createContext({ document, fetch, location, window: { location: { assign(p) { dom.assigned = p; } } }, URL, Array, Object, JSON, Promise });
278
+ vm.runInContext(BAR, ctx);
279
+ vm.runInContext(SCRIPT, ctx);
263
280
  return dom;
264
281
  }
265
282
  const settle = async () => { for (let i = 0; i < 8; i++) await new Promise((r) => setImmediate(r)); };