@bongos/core 1.19.484 → 1.19.485

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.
@@ -9,9 +9,11 @@
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
12
- <!-- Page-local styles (task 2172) — extracted from the old inline <style>
13
- block per the hall placement rules; the rail comes from style.css's
14
- shared .section-rail/.rail-link rules. -->
12
+ <!-- The PANEL archetype (task 1003308): the row, the switch, the field, the
13
+ chip control and the family's voices, shared with profile, pair and
14
+ not-ready. settings.css then holds only what this page
15
+ has — the rail layout, the group head, the token ladder, the voices grid. -->
16
+ <link rel="stylesheet" href="/builders/panel.css">
15
17
  <link rel="stylesheet" href="/builders/settings.css">
16
18
  </head>
17
19
  <body data-page="settings" data-page-title="Settings">
@@ -22,15 +24,22 @@
22
24
  <div class="app-body">
23
25
  <header class="app-topbar" id="app-topbar"></header>
24
26
  <div class="app-content">
25
- <div class="page-head"><p class="page-head__sub" id="settings-sub">Loading your preferences…</p></div>
27
+ <!-- The page's ONE h1 (one-header-main-h1): the page's own name in the
28
+ room voice, because here the document is the page. The sub-line is
29
+ settings.js's — "Your preferences, <name>." and the two failure states. -->
30
+ <div class="page-head page-head--room page-head--panel">
31
+ <h1 id="settings-h1" class="page-head__h">settings</h1>
32
+ <p class="page-head__sub" id="settings-sub">Loading your preferences…</p>
33
+ </div>
26
34
 
27
35
  <main id="hall-main">
28
36
  <!-- Not-signed-in notice — shown by settings.js before it redirects, and
29
37
  as a fallback if reading the viewer fails. Deliberately OUTSIDE the
30
38
  grouped layout: full-width, ungrouped. -->
31
- <section class="scroll" id="gate-scroll" aria-labelledby="gate-h" hidden>
32
- <h2 id="gate-h" class="scroll__h">Not signed in</h2>
33
- <p class="scroll__lede" id="gate-body">You're not signed in yet. Sign in to manage your preferences.</p>
39
+ <section class="scroll gate" id="gate-scroll" aria-labelledby="gate-h" hidden>
40
+ <h2 id="gate-h" class="gate__h">Not signed in</h2>
41
+ <p class="gate__lede" id="gate-body">You're not signed in yet. Sign in to manage your connections, your keys, your dev box and the way the hall behaves.</p>
42
+ <div class="row-end"><a class="pbtn pbtn--save" href="/api/bongos/auth/web/start">Sign in</a></div>
34
43
  </section>
35
44
 
36
45
  <!-- Grouped layout (task 1723 → kit, task 2172): the sections sit in six
@@ -68,10 +77,11 @@
68
77
  and the work feed. Your sign-in and the credit for shipped work stay
69
78
  tied to your account, not this label.
70
79
  </p>
80
+ <label class="field-label" for="display-name-input">Display name</label>
71
81
  <div class="display-name-row">
72
82
  <input type="text" class="display-name-input" id="display-name-input"
73
83
  maxlength="60" autocomplete="off" spellcheck="false"
74
- aria-label="Display name" placeholder="Your display name">
84
+ placeholder="Your display name">
75
85
  <button type="button" class="craft-save" id="display-name-save">Save</button>
76
86
  </div>
77
87
  <p class="settings-status" id="display-name-status" role="status" aria-live="polite"></p>
@@ -158,7 +168,7 @@
158
168
  </p>
159
169
  <div id="sessions-list" class="sessions-list" aria-live="polite"></div>
160
170
  <div class="settings-card">
161
- <button type="button" class="settings-btn" id="sessions-revoke-all">Sign out everywhere</button>
171
+ <button type="button" class="settings-btn settings-btn--quiet" id="sessions-revoke-all">Sign out everywhere</button>
162
172
  <span class="settings-card__hint">ends every session including this one — you will be asked to sign in again.</span>
163
173
  </div>
164
174
  <p class="section-note">Ending a session takes effect immediately; the token stops working on its very next request.</p>
@@ -213,11 +223,11 @@
213
223
  <div class="settings-card">
214
224
  <a class="settings-btn" href="/downloads/devbox/mac-arm64">Download for Mac</a>
215
225
  <a class="settings-btn" href="/downloads/devbox/win-x64">Download for Windows</a>
216
- <span class="settings-card__hint">Intel Mac? <a href="/downloads/devbox/mac-x64">This one</a>.
217
- ARM Windows (Surface, Snapdragon, Parallels)? <a href="/downloads/devbox/win-arm64">This one</a>.</span>
218
- <!-- #924: latest-release courtesy line; populated from /downloads/devbox/version -->
219
- <span class="settings-card__hint" id="boxapp-latest" hidden></span>
220
226
  </div>
227
+ <p class="panel-hint">Intel Mac? <a href="/downloads/devbox/mac-x64">This one</a>.
228
+ ARM Windows (Surface, Snapdragon, Parallels)? <a href="/downloads/devbox/win-arm64">This one</a>.
229
+ <!-- #924: latest-release courtesy line; populated from /downloads/devbox/version -->
230
+ <span id="boxapp-latest" hidden></span></p>
221
231
  <p class="section-note">
222
232
  First open: your computer warns about an unknown developer — expected
223
233
  (we haven't bought the signing certificates yet; the app is ours).
@@ -252,7 +262,7 @@
252
262
  claimed task. It never limits the work on the task itself. Your rank
253
263
  sets the starting point; newcomers stay locked low.
254
264
  </p>
255
- <div class="sound-rows" id="wander-rows"></div>
265
+ <div class="prows" id="wander-rows" role="radiogroup" aria-labelledby="wandering-h"></div>
256
266
  <p class="settings-status" id="wandering-status" role="status" aria-live="polite"></p>
257
267
  <p class="section-note" id="wandering-note"></p>
258
268
  </section>
@@ -287,7 +297,7 @@
287
297
  <div class="voices-wrap">
288
298
  <table class="voices" id="voices-table">
289
299
  <thead>
290
- <tr><th>Skill</th><th>Default</th><th>Your choice</th></tr>
300
+ <tr><th scope="col">Skill</th><th scope="col">Default</th><th scope="col">Your choice</th></tr>
291
301
  </thead>
292
302
  <tbody id="voices-body"></tbody>
293
303
  </table>
@@ -305,9 +315,8 @@
305
315
  any you would rather not hear.
306
316
  </p>
307
317
  <div class="sound-volume" id="sound-volume" hidden>
308
- <span class="sound-volume__label">Volume</span>
309
- <input type="range" id="sound-volume-range" min="0" max="100" step="5" value="100"
310
- aria-label="Master sound volume">
318
+ <label class="field-label" for="sound-volume-range">Volume</label>
319
+ <input type="range" id="sound-volume-range" min="0" max="100" step="5" value="100">
311
320
  <span class="sound-volume__pct" id="sound-volume-pct">100%</span>
312
321
  </div>
313
322
  <div class="sound-rows" id="sound-rows"></div>
@@ -360,7 +369,7 @@
360
369
  <script src="/builders/task-detail.js?v=2026-07-03-taskdetail"></script>
361
370
  <!-- #807: shared dev-box panel — loaded before settings.js so OTBBoxPanel exists. -->
362
371
  <script src="/builders/box-panel.js?v=2026-06-14-domutils"></script>
363
- <script src="/builders/settings.js?v=2026-07-10-regroup"></script>
372
+ <script src="/builders/settings.js?v=2026-09-02-panel"></script>
364
373
  <!-- task 1003341: the Active sessions panel. Its own file rather than more of
365
374
  settings.js, which sits close to the 1500-line size ratchet. -->
366
375
  <script src="/builders/settings-sessions.js?v=2026-08-29-sessions"></script>
@@ -134,6 +134,15 @@
134
134
 
135
135
  // Render one row per sound from `effective`. Each row: label + description and
136
136
  // an On/Off toggle button whose data-on reflects the effective enabled-state.
137
+ // The family switch (panel.css `.sw`): a role=switch button whose state is
138
+ // aria-checked, drawn as a track and a knob. Keeps the `.sound-toggle` class
139
+ // and `data-on`, which every delegated click handler on this page reads.
140
+ function switchHtml(on, label, extraAttrs = '') {
141
+ return `<button type="button" class="sound-toggle sw" role="switch" aria-checked="${on ? 'true' : 'false'}" ` +
142
+ `aria-label="${escapeHtml(label)}" data-on="${on ? '1' : '0'}"${extraAttrs}>` +
143
+ '<span class="sw__track"></span><span class="sw__knob"></span></button>';
144
+ }
145
+
137
146
  function renderSounds(data) {
138
147
  const wrap = $('#sound-rows');
139
148
  if (!wrap) return;
@@ -150,9 +159,7 @@
150
159
  `<div class="sound-row__label">${escapeHtml(row.label || row.sound)}</div>` +
151
160
  `<div class="sound-row__desc">${escapeHtml(row.desc || '')}</div>` +
152
161
  '</div>' +
153
- `<button type="button" class="sound-toggle" data-sound="${escapeHtml(row.sound)}" ` +
154
- `data-on="${on ? '1' : '0'}" aria-pressed="${on ? 'true' : 'false'}">` +
155
- `${on ? 'On' : 'Off'}</button>` +
162
+ switchHtml(on, `Sound: ${row.label || row.sound}`, ` data-sound="${escapeHtml(row.sound)}"`) +
156
163
  '</div>'
157
164
  );
158
165
  }).join('');
@@ -405,25 +412,23 @@
405
412
  wrap.innerHTML = window.OTBKit.emptyStateHtml('No levels registered.');
406
413
  return;
407
414
  }
415
+ // One pick among four: a radio row per level (panel.css `.prow--pick`), the
416
+ // whole row its label. A level above the rank ceiling is a disabled radio
417
+ // that says so in the label voice rather than a dead "Rank-locked" button.
408
418
  wrap.innerHTML = levels.map((lv) => {
409
419
  const chosen = lv.n === eff;
410
- const tag = lv.n === def ? ' <span class="voices__default">· default for your rank</span>' : '';
411
- let btn;
412
- if (!lv.allowed) {
413
- btn = `<button type="button" class="sound-toggle" disabled data-level="${lv.n}" data-on="0">Rank-locked</button>`;
414
- } else if (chosen) {
415
- btn = `<button type="button" class="sound-toggle" data-level="${lv.n}" data-on="1" aria-pressed="true">Chosen</button>`;
416
- } else {
417
- btn = `<button type="button" class="sound-toggle" data-level="${lv.n}" data-on="0" aria-pressed="false">Choose</button>`;
418
- }
420
+ const locked = !lv.allowed;
421
+ const id = `wander-${lv.n}`;
422
+ const tags = (lv.n === def ? '<span class="prow__tag">default for your rank</span>' : '') +
423
+ (locked ? '<span class="prow__tag">rank-locked</span>' : '');
419
424
  return (
420
- `<div class="sound-row" data-level="${lv.n}">` +
421
- '<div class="sound-row__text">' +
422
- `<div class="sound-row__label">${escapeHtml(lv.label)}${tag}</div>` +
423
- `<div class="sound-row__desc">${escapeHtml(lv.blurb || '')}</div>` +
424
- '</div>' +
425
- btn +
426
- '</div>'
425
+ `<label class="prow prow--pick" for="${id}" data-level="${lv.n}"${locked ? ' aria-disabled="true"' : ''}>` +
426
+ `<input class="pradio" type="radio" name="wandering" id="${id}" value="${lv.n}"` +
427
+ `${chosen ? ' checked' : ''}${locked ? ' disabled' : ''}>` +
428
+ '<span class="prow__main">' +
429
+ `<span class="prow__label">${escapeHtml(lv.label)}${tags}</span>` +
430
+ `<span class="prow__desc">${escapeHtml(lv.blurb || '')}</span>` +
431
+ '</span></label>'
427
432
  );
428
433
  }).join('');
429
434
  const note = $('#wandering-note');
@@ -466,13 +471,13 @@
466
471
  const data = res.data;
467
472
  renderWandering(data);
468
473
  $('#wandering-scroll').hidden = false;
469
- // Delegate clicks so post-render buttons stay wired (single-select).
474
+ // Delegate the change so post-render radios stay wired (single-select).
470
475
  const wrap = $('#wander-rows');
471
- wrap.addEventListener('click', (e) => {
472
- const b = e.target.closest('.sound-toggle');
473
- if (!b || b.disabled || b.dataset.on === '1') return; // ignore locked + already-chosen
474
- const level = Number(b.dataset.level);
475
- if (Number.isInteger(level)) saveWandering(level, b);
476
+ wrap.addEventListener('change', (e) => {
477
+ const r = e.target.closest('.pradio');
478
+ if (!r || r.disabled || !r.checked) return; // ignore locked; a radio only fires on becoming checked
479
+ const level = Number(r.value);
480
+ if (Number.isInteger(level)) saveWandering(level, r);
476
481
  });
477
482
  } catch (err) {
478
483
  console.error('[settings] failed to load wandering', err);
@@ -497,11 +502,11 @@
497
502
  '<div class="sound-row" data-render="widgets">' +
498
503
  '<div class="sound-row__text">' +
499
504
  '<div class="sound-row__label">Rich in-feed cards' +
500
- (isDefault ? ' <span class="voices__default">· default (on)</span>' : '') +
505
+ (isDefault ? '<span class="prow__tag">default (on)</span>' : '') +
501
506
  '</div>' +
502
507
  '<div class="sound-row__desc">Render session cards as widgets (prettier, costs tokens) instead of plain text (cheaper, identical info).</div>' +
503
508
  '</div>' +
504
- `<button type="button" class="sound-toggle" data-on="${on ? '1' : '0'}" aria-pressed="${on ? 'true' : 'false'}">${on ? 'On' : 'Off'}</button>` +
509
+ switchHtml(on, 'Rich in-feed cards') +
505
510
  '</div>';
506
511
  }
507
512
 
@@ -579,7 +584,7 @@
579
584
  '<div class="sound-row__label">Show my cross-project record</div>' +
580
585
  `<div class="sound-row__desc">${escapeHtml(totalsLine + consentNote)} You always see your own numbers here.</div>` +
581
586
  '</div>' +
582
- `<button type="button" class="sound-toggle" data-on="${on ? '1' : '0'}" aria-pressed="${on ? 'true' : 'false'}">${on ? 'Shown' : 'Hidden'}</button>` +
587
+ switchHtml(on, 'Show my cross-project record') +
583
588
  '</div>';
584
589
  }
585
590
 
@@ -637,10 +642,10 @@
637
642
  function privacyRow(key, label, state, on, desc, note) {
638
643
  return `<div class="sound-row" data-privacy="${key}">` +
639
644
  '<div class="sound-row__text">' +
640
- `<div class="sound-row__label">${escapeHtml(label)}${note}</div>` +
645
+ `<div class="sound-row__label">${escapeHtml(label)}<span class="prow__tag">${escapeHtml(state)}</span>${note}</div>` +
641
646
  `<div class="sound-row__desc">${escapeHtml(desc)}</div>` +
642
647
  '</div>' +
643
- `<button type="button" class="sound-toggle" data-on="${on ? '1' : '0'}" aria-pressed="${on ? 'true' : 'false'}">${state}</button>` +
648
+ switchHtml(on, `${label}: ${state}`) +
644
649
  '</div>';
645
650
  }
646
651
 
@@ -976,11 +981,14 @@
976
981
  const body = (res.data || {});
977
982
  if (!res.ok) throw new Error(body.message || `HTTP ${res.status}`);
978
983
  const expires = body.expires_at ? new Date(body.expires_at).toLocaleString() : 'unknown';
984
+ // The secret is QUOTED (settings.css `.quote`): a 64-character token cannot
985
+ // be a line of prose, so it sits one tonal step down with its own foot.
979
986
  result.innerHTML =
980
- '<p>Fresh CLI token issued. Paste it in your terminal:</p>' +
981
- `<pre>${escapeHtml(body.paste_command || '')}</pre>` +
982
- '<p><button type="button" class="settings-btn" id="cli-reissue-copy">Copy</button>' +
983
- `<span class="settings-card__hint" style="margin-left:0.6rem;">Expires ${escapeHtml(expires)} · refreshes on use.</span></p>`;
987
+ '<p class="cli-result__say">Fresh CLI token issued. Paste it in your terminal:</p>' +
988
+ '<div class="quote"><p class="quote__eyebrow">paste command</p>' +
989
+ `<pre class="quote__pre">${escapeHtml(body.paste_command || '')}</pre>` +
990
+ '<div class="quote__foot"><button type="button" class="settings-btn settings-btn--quiet" id="cli-reissue-copy">Copy</button>' +
991
+ `<span class="settings-card__hint">Expires ${escapeHtml(expires)} · refreshes on use.</span></div></div>`;
984
992
  result.hidden = false;
985
993
  const copyBtn = $('#cli-reissue-copy');
986
994
  if (copyBtn) copyBtn.addEventListener('click', async () => {
@@ -1037,10 +1045,11 @@
1037
1045
 
1038
1046
  function artKeyEntryHtml() {
1039
1047
  return (
1048
+ '<label class="field-label" for="artkey-input">Your key</label>' +
1040
1049
  '<div class="artkey-entry-row">' +
1041
1050
  '<input type="password" class="artkey-input" id="artkey-input" autocomplete="off" ' +
1042
1051
  'spellcheck="false" placeholder="Paste your Google AI Studio key">' +
1043
- '<button type="button" class="settings-btn" id="artkey-save">Save key</button>' +
1052
+ '<button type="button" class="settings-btn craft-save" id="artkey-save">Save key</button>' +
1044
1053
  '</div>' +
1045
1054
  `<p class="settings-card__hint">${getOneLink} · Stored encrypted; never shown again after saving.</p>`
1046
1055
  );
@@ -0,0 +1,16 @@
1
+ {
2
+ "_": "The hall's SETTINGS page through modules/ui-design/kit/render.js (task 1003308). Every panel on it is a form the page script fills from a dozen /me/* reads, so the floors run against the hall-preview harness rather than the kit's own stub:\n\n node scripts/hall-preview/server.js --port 4621 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/settings.states.json --base-in http://builders.localhost:4621 --base-out http://builders.localhost:4621\n\nOne group renders at a time behind the section rail, so each rail group is its own state. There is no `out` state on purpose: settings.js answers a 401 by navigating to the sign-in flow, and a state whose page navigates away measures the destination, not this page (the not-signed-in gate panel is the non-401 failure render, pinned statically in tests/hall_settings_world.mjs).",
3
+ "page": "/settings",
4
+ "surface": "hall-ui",
5
+ "stub": { "prefix": "/builders/" },
6
+ "modeQuery": false,
7
+ "ignoreRequests": ["/api/(gds|bongos)/", "/downloads/devbox/"],
8
+ "ignoreConsole": ["/api/(gds|bongos)/", "/downloads/devbox/", "\b401\b", "\b404\b", "Unauthorized", "Not Found", "load failed", "Password field"],
9
+ "states": {
10
+ "account": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#settings-h1", "#display-name-input", "#craft-chips", "#privacy-rows"] } },
11
+ "access": { "auth": true, "url": "/settings#access", "actions": [["wait", 900]], "expect": { "visible": ["#cli-reissue-btn", "#sessions-list", "#artkey-card"] } },
12
+ "preferences": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900]], "expect": { "visible": ["#wander-rows", "#render-rows"] } },
13
+ "sound": { "auth": true, "url": "/settings#sound", "actions": [["wait", 900]], "expect": { "visible": ["#voices-table", "#sound-rows", "#event-sound-rows"] } }
14
+ },
15
+ "reducedMotion": { "auth": true, "actions": [["wait", 900]] }
16
+ }
@@ -1693,7 +1693,10 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1693
1693
  color: var(--ink-soft);
1694
1694
  }
1695
1695
  .profile__disc-label { color: var(--ink-faint); }
1696
- .profile__disc-edit { font-size: 12.5px; }
1696
+ /* task 1003308: a link in the Standing card's fact line is a control and owes
1697
+ the 24px floor (it measured 123x19); the prose-link recipe buys the height
1698
+ with padding and hands the width back with the negative margin. */
1699
+ .profile__disc-edit { display: inline-block; min-width: 24px; min-height: 24px; padding: 3px 4px; margin: -3px -4px; font-size: 12.5px; }
1697
1700
  .profile__credits {
1698
1701
  display: flex;
1699
1702
  flex-direction: column;
@@ -1731,7 +1734,7 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1731
1734
  .profile__budget--over { background: var(--danger-weak); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 25%, transparent); }
1732
1735
  .profile__needs--action { background: var(--warn-weak); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); }
1733
1736
  .profile__needs--covered { background: var(--ok-weak); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 22%, transparent); }
1734
- .profile__needs-act { font-weight: 600; white-space: nowrap; }
1737
+ .profile__needs-act { display: inline-block; min-width: 24px; min-height: 24px; padding: 3px 4px; margin: -3px -4px; font-weight: 600; white-space: nowrap; }
1735
1738
  /* ADR 0120 part 5: owed-rebase gate banner — danger-tinted (it blocks new claims). */
1736
1739
  .profile__rebase { background: var(--danger-weak); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 25%, transparent); }
1737
1740
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.484",
3
+ "version": "1.19.485",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.484",
9
+ "version": "1.19.485",
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.19.484",
3
+ "version": "1.19.485",
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",
@@ -118,7 +118,6 @@ const GOAL_VOCAB_SURFACE = [
118
118
  // The FIFTH use (task 1003090): the hub's coarse project membership.
119
119
  'modules/platform-identity/platform-identity.js',
120
120
  'modules/platform-identity/routes/my-projects.js',
121
- 'modules/hall-ui/public/my-projects.js',
122
121
  ];
123
122
  // modules/lifecycle/db.js is 5k+ lines and legitimately says "role" about ranks
124
123
  // (the rank-sync port, builders.system_role). Scanning it whole would need an
package/src/module-api.js CHANGED
@@ -34,7 +34,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
34
34
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
35
35
  // the entry to that file. Look for a version's history there, not here.
36
36
  // ---------------------------------------------------------------------------
37
- const CORE_VERSION = '1.19.484'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
37
+ const CORE_VERSION = '1.19.485'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
38
38
 
39
39
  // A namespaced logger so a module's log lines are attributable + consistent.
40
40
  // Usage: const log = api.logger('dev-box'); log.info('mounted');