@bongos/core 1.24.26 → 1.24.28

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 (109) hide show
  1. package/.bongos-core.json +164 -109
  2. package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +12 -0
  3. package/docs/copy-inventory.md +242 -223
  4. package/docs/copy-registry.json +559 -358
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/page-inventory.json +18 -6
  7. package/docs/page-readings.json +4399 -4315
  8. package/docs/recipes/ui-look-before-you-ship.md +1 -0
  9. package/modules/autonomy/fence.js +16 -1
  10. package/modules/government/protected-surfaces.json +2 -0
  11. package/modules/hall-ui/public/ai-studio.html +1 -1
  12. package/modules/hall-ui/public/atlas.html +1 -1
  13. package/modules/hall-ui/public/blockers.html +1 -1
  14. package/modules/hall-ui/public/board-room.html +1 -1
  15. package/modules/hall-ui/public/city.html +1 -1
  16. package/modules/hall-ui/public/collab.html +1 -1
  17. package/modules/hall-ui/public/commands.html +1 -1
  18. package/modules/hall-ui/public/copy-desk.html +1 -1
  19. package/modules/hall-ui/public/core-move-notice.js +9 -6
  20. package/modules/hall-ui/public/deploy-handoff.js +203 -0
  21. package/modules/hall-ui/public/deploy.html +4 -2
  22. package/modules/hall-ui/public/deploy.js +7 -1
  23. package/modules/hall-ui/public/diagrams.html +1 -1
  24. package/modules/hall-ui/public/drachmae.html +1 -1
  25. package/modules/hall-ui/public/fleet.html +1 -1
  26. package/modules/hall-ui/public/gate.html +1 -1
  27. package/modules/hall-ui/public/goal-map.html +1 -1
  28. package/modules/hall-ui/public/goals.html +1 -1
  29. package/modules/hall-ui/public/government.html +1 -1
  30. package/modules/hall-ui/public/idea.html +1 -1
  31. package/modules/hall-ui/public/ideas.html +1 -1
  32. package/modules/hall-ui/public/index.html +1 -1
  33. package/modules/hall-ui/public/market-watch-item.html +1 -1
  34. package/modules/hall-ui/public/market-watch-rules.html +1 -1
  35. package/modules/hall-ui/public/market-watch-settings.html +1 -1
  36. package/modules/hall-ui/public/market-watch.html +1 -1
  37. package/modules/hall-ui/public/modules.html +1 -1
  38. package/modules/hall-ui/public/my-agents.css +72 -1
  39. package/modules/hall-ui/public/my-agents.html +31 -7
  40. package/modules/hall-ui/public/my-agents.js +63 -54
  41. package/modules/hall-ui/public/my-agents.states.json +18 -0
  42. package/modules/hall-ui/public/primer.html +1 -1
  43. package/modules/hall-ui/public/profile.html +1 -1
  44. package/modules/hall-ui/public/project-settings.html +1 -1
  45. package/modules/hall-ui/public/ranks.html +1 -1
  46. package/modules/hall-ui/public/roadmap.html +1 -1
  47. package/modules/hall-ui/public/roster.html +1 -1
  48. package/modules/hall-ui/public/sessions.html +1 -1
  49. package/modules/hall-ui/public/settings-software-update.js +22 -8
  50. package/modules/hall-ui/public/settings.css +2 -132
  51. package/modules/hall-ui/public/settings.html +15 -53
  52. package/modules/hall-ui/public/shell.js +25 -73
  53. package/modules/hall-ui/public/speciality-tools.css +140 -0
  54. package/modules/hall-ui/public/{settings-specialities.js → speciality-tools.js} +83 -6
  55. package/modules/hall-ui/public/street.html +1 -1
  56. package/modules/hall-ui/public/studio.html +1 -1
  57. package/modules/hall-ui/public/style.css +119 -39
  58. package/modules/hall-ui/public/task.html +1 -1
  59. package/modules/hall-ui/public/thinking.html +1 -1
  60. package/modules/hall-ui/public/tweak-editor.html +1 -1
  61. package/modules/hall-ui/public/update-pill.js +623 -0
  62. package/modules/hall-ui/public/watch.html +1 -1
  63. package/modules/hall-ui/public/work.html +1 -1
  64. package/modules/hall-ui/records/my-agents.md +4 -2
  65. package/modules/hall-ui/records/panel-family.md +2 -0
  66. package/modules/hall-ui/records/update-pill.md +12 -0
  67. package/modules/lifecycle/github-push.js +1 -0
  68. package/modules/lifecycle/routes/tasks.js +2 -2
  69. package/modules/lifecycle/runner-self-guard.js +122 -0
  70. package/modules/lifecycle/runner-self.js +154 -0
  71. package/modules/public-landing/public/account.html +3 -3
  72. package/modules/public-landing/public/assets/cosmos.css +45 -0
  73. package/modules/public-landing/public/community.html +4 -4
  74. package/modules/public-landing/public/connections.html +1 -1
  75. package/modules/public-landing/public/guild.html +1 -1
  76. package/modules/public-landing/public/invitations.html +4 -4
  77. package/modules/public-landing/public/projects.html +26 -26
  78. package/modules/status-ui/public/index.html +2 -2
  79. package/modules/status-ui/public/index.states.json +12 -0
  80. package/modules/status-ui/public/status.js +10 -2
  81. package/modules/status-ui/public/style.css +57 -0
  82. package/modules/ui-design/kit/README.md +1 -1
  83. package/modules/ui-design/kit/serve.js +8 -1
  84. package/package-lock.json +2 -2
  85. package/package.json +1 -1
  86. package/release-notes.json +20 -0
  87. package/scripts/bongos/autobongos-loop.js +4 -0
  88. package/scripts/bongos/autobongos-run.js +21 -4
  89. package/scripts/bongos/autobongos-tweak-lane.js +5 -3
  90. package/scripts/bongos/ship-flow.js +20 -0
  91. package/scripts/hall-preview/server.js +4 -0
  92. package/src/module-api.js +1 -1
  93. package/tests/autobongos_host_access.mjs +5 -3
  94. package/tests/autobongos_self_modification.mjs +420 -0
  95. package/tests/core_update_banner.mjs +6 -92
  96. package/tests/core_upgrade_owner_door.mjs +4 -56
  97. package/tests/deploy_handoff.mjs +206 -0
  98. package/tests/government_protected_surfaces.mjs +15 -0
  99. package/tests/hall_loading.mjs +3 -1
  100. package/tests/hall_my_agents.mjs +104 -2
  101. package/tests/helpers.mjs +161 -0
  102. package/tests/hub_connections_page.mjs +1 -1
  103. package/tests/hub_invitations_page.mjs +1 -1
  104. package/tests/loading_mark.mjs +203 -0
  105. package/tests/nav_permission_atoms.mjs +3 -2
  106. package/tests/software_update.mjs +6 -2
  107. package/tests/speciality_offer_ui.mjs +182 -149
  108. package/tests/speciality_walkthrough_ui.mjs +180 -161
  109. package/tests/update_pill.mjs +431 -0
@@ -157,138 +157,8 @@ select.voices__select { min-width: 140px; }
157
157
  .settings-btn--discord { background: #5865F2; border-color: #4853c4; color: #ffffff; }
158
158
  .settings-btn--discord:hover:not(:disabled) { background: #4853c4; border-color: #4853c4; color: #ffffff; }
159
159
 
160
- /* Specialities panel (task 1003855, ADR 0296). A speciality is a named bundle of
161
- knowledge and terms under a craft; this styles the list, the train form and the
162
- create form. Tokens only — no literal colours, so light/dark follow the page. */
163
- .speciality-craft {
164
- font-size: 0.78rem;
165
- text-transform: lowercase;
166
- opacity: 0.72;
167
- margin-left: 0.4rem;
168
- }
169
- .speciality-badge {
170
- font-size: 0.7rem;
171
- padding: 0.05rem 0.4rem;
172
- border-radius: 0.5rem;
173
- margin-left: 0.4rem;
174
- border: 1px solid currentColor;
175
- }
176
- .speciality-counts { opacity: 0.82; }
177
- .speciality-card { flex-wrap: wrap; }
178
- .speciality-train,
179
- .speciality-create {
180
- flex-basis: 100%;
181
- margin-top: 0.75rem;
182
- padding-top: 0.75rem;
183
- border-top: 1px solid var(--rule, rgba(128, 128, 128, 0.25));
184
- display: flex;
185
- flex-direction: column;
186
- gap: 0.6rem;
187
- }
188
- .speciality-create__h { margin: 0; font-size: 0.95rem; }
189
- .speciality-field { display: flex; flex-direction: column; gap: 0.25rem; }
190
- .speciality-field > span { font-size: 0.85rem; }
191
- .speciality-field small { display: block; opacity: 0.7; font-weight: normal; }
192
- .speciality-field input[type='text'],
193
- .speciality-field select,
194
- .speciality-field textarea {
195
- width: 100%;
196
- box-sizing: border-box;
197
- }
198
- /* The checkbox row reads as one sentence, so it runs inline rather than stacked. */
199
- .speciality-field--check { flex-direction: row; align-items: flex-start; gap: 0.5rem; }
200
- .speciality-field--check input { margin-top: 0.2rem; }
201
- .speciality-actions { display: flex; gap: 0.5rem; }
202
- /* What a speciality OFFERS (task 1004404): the owner's pick-list of this
203
- instance's skills, each with its plain line. Sixty-odd rows, so the list
204
- scrolls inside its own box rather than pushing the form's buttons off-screen;
205
- the reading lines keep the measure. */
206
- .speciality-field input[type='search'] { width: 100%; box-sizing: border-box; }
207
- .speciality-offer,
208
- .speciality-offer-box { flex-basis: 100%; display: flex; flex-direction: column; gap: 0.6rem; }
209
- .speciality-offer { margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--rule); }
210
- .speciality-offer > *, .speciality-offer-box > * { max-width: var(--measure); }
211
- .speciality-offer-box > summary { cursor: pointer; font-size: 0.85rem; min-height: 24px; padding-block: 2px; box-sizing: border-box; }
212
- .speciality-offer-box > summary small { opacity: 0.7; }
213
- .speciality-offer__choices, #speciality-create-skills { display: flex; flex-direction: column; gap: 0.6rem; }
214
- .speciality-offer__list {
215
- list-style: none;
216
- margin: 0;
217
- padding: 0 0.25rem 0 0;
218
- display: flex;
219
- flex-direction: column;
220
- gap: 0.5rem;
221
- max-height: 24rem;
222
- overflow-y: auto;
223
- overflow-x: hidden;
224
- }
225
- .speciality-offer__list li[hidden] { display: none; }
226
- .speciality-offer__list strong { font-family: var(--font-mono); font-weight: 600; }
227
- .speciality-offer__list .speciality-field--check > span { font-size: 0.85rem; line-height: 1.45; }
228
- /* The adopted contract, shown in full before "Use this" — another builder's prose
229
- reaching your sessions is the one thing here that must never be folded away. */
230
- .speciality-contract { flex-basis: 100%; margin-top: 0.5rem; font-size: 0.85rem; }
231
- /* 24px tall: the panel was never revealed until task 1004073, so the kit had
232
- never measured this toggle; it came back 21px. */
233
- .speciality-contract > summary { cursor: pointer; opacity: 0.85; min-height: 24px; padding-block: 2px; box-sizing: border-box; }
234
- .speciality-contract__body {
235
- white-space: pre-wrap;
236
- margin: 0.4rem 0 0;
237
- padding: 0.5rem 0.65rem;
238
- border-left: 2px solid var(--rule, rgba(128, 128, 128, 0.35));
239
- border-radius: 0;
240
- font-family: inherit;
241
- opacity: 0.92;
242
- }
243
- /* The skills walkthrough (task 1004073, ADR 0310 §3): one offered skill at a
244
- time, three answers as a definition list, then a decision. It sits inside the
245
- card like the train form, so it inherits the card's ground in both modes. */
246
- .skill-walk {
247
- flex-basis: 100%;
248
- margin-top: 0.75rem;
249
- padding-top: 0.75rem;
250
- border-top: 1px solid var(--rule);
251
- display: flex;
252
- flex-direction: column;
253
- gap: 0.6rem;
254
- }
255
- /* The measure caps the reading lines, never the section: a capped flex-basis
256
- lets the section fit beside the card's actions instead of wrapping under them. */
257
- .skill-walk > * { max-width: var(--measure); }
258
- .skill-walk__progress { margin: 0; font-size: 0.8rem; color: var(--ink-soft); }
259
- .skill-walk__name { margin: 0; font-family: var(--font-mono); font-size: 1rem; color: var(--ink); }
260
- .skill-walk__facts { margin: 0; display: grid; gap: 0.2rem 0; }
261
- .skill-walk__facts dt { font-size: 0.8rem; color: var(--ink-soft); margin-top: 0.4rem; }
262
- .skill-walk__facts dt:first-child { margin-top: 0; }
263
- .skill-walk__facts dd { margin: 0; color: var(--ink); line-height: 1.5; }
264
- .skill-walk__note { font-size: 0.8rem; }
265
- /* "Leave it off" and "Turn it on" are the panel family's plain pill at EQUAL
266
- weight: the default is off, so the page must not lean on the reader toward on.
267
- Only "Save my choices" takes the accent. A decision already made reads through
268
- aria-pressed when the person steps Back to it. */
269
- .skill-walk__actions { flex-wrap: wrap; }
270
- .skill-walk__actions .pbtn[aria-pressed="true"] { border-color: var(--ink-soft); }
271
- .skill-walk__recap { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
272
- .skill-walk__recap li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.4rem; }
273
- .skill-walk__recap .prow__hint { flex-basis: 100%; margin: 0; }
274
- .skill-walk__recap strong { font-family: var(--font-mono); font-weight: 600; }
275
- /* "Finish later" is a way out, not a choice about a skill, so it reads as a quiet
276
- link below the decision rather than a third button beside it. */
277
- .skill-walk__later {
278
- align-self: flex-start;
279
- background: none;
280
- border: 0;
281
- padding: 0 0.25rem;
282
- margin-inline-start: -0.25rem;
283
- min-height: 24px;
284
- font: inherit;
285
- font-size: 0.8rem;
286
- color: var(--ink-soft);
287
- text-decoration: underline;
288
- text-underline-offset: 3px;
289
- cursor: pointer;
290
- }
291
- .skill-walk__later:hover { color: var(--ink); }
160
+ /* The specialities panel's rules moved to speciality-tools.css (task 1005052),
161
+ which Settings links right after this sheet and My agents links too. */
292
162
 
293
163
  /* ---- Software update (task 1004296) -------------------------------------- */
294
164
  /* A version is read digit by digit and compared against another, so it takes the mono
@@ -18,6 +18,8 @@
18
18
  has — the rail layout, the group head, the token ladder, the voices grid. -->
19
19
  <link rel="stylesheet" href="/builders/panel.css">
20
20
  <link rel="stylesheet" href="/builders/settings.css">
21
+ <!-- the speciality tools sheet (task 1005052), shared with My agents -->
22
+ <link rel="stylesheet" href="/builders/speciality-tools.css">
21
23
  </head>
22
24
  <body data-page="settings" data-page-title="Settings">
23
25
 
@@ -305,53 +307,9 @@
305
307
  <div id="speciality-suggestions"></div>
306
308
  <ul class="prows" id="specialities-rows"></ul>
307
309
 
308
- <form id="speciality-create" class="speciality-create">
309
- <h3 class="speciality-create__h">Create one</h3>
310
- <label class="speciality-field">
311
- <span>Name</span>
312
- <input type="text" name="name" maxlength="80" required placeholder="Economics" />
313
- </label>
314
- <label class="speciality-field">
315
- <span>Role it sits under</span>
316
- <select name="discipline" required>
317
- <option value="">Pick one…</option>
318
- <option value="engineer">Engineer</option>
319
- <option value="ideator">Ideator</option>
320
- <option value="artist">Artist</option>
321
- <option value="ui">UI design</option>
322
- </select>
323
- </label>
324
- <label class="speciality-field">
325
- <span>Summary <small>(optional)</small></span>
326
- <input type="text" name="summary" maxlength="500" placeholder="Markets, incentives, and how to price them." />
327
- </label>
328
- <label class="speciality-field">
329
- <span>How Claude should engage you in it <small>(optional)</small></span>
330
- <textarea name="contract_md" rows="4" maxlength="4000" placeholder="Argue from mechanism, not vibes. Always name the assumption a number rests on."></textarea>
331
- </label>
332
- <label class="speciality-field">
333
- <span>Who can use it</span>
334
- <select name="visibility">
335
- <option value="private">Only me</option>
336
- <option value="project">Anyone in the owning project</option>
337
- <option value="public">Anyone on this instance</option>
338
- </select>
339
- </label>
340
- <label class="speciality-field speciality-field--check">
341
- <input type="checkbox" name="sellable" />
342
- <span>
343
- May be offered for sale later
344
- <small>Records your decision only — there is no marketplace yet, and nothing is listed anywhere.</small>
345
- </span>
346
- </label>
347
- <details id="speciality-create-offer" class="speciality-offer-box">
348
- <summary>Skills it offers <small>(optional)</small></summary>
349
- <div id="speciality-create-skills"><p class="hall-loading hall-loading--inline" role="status">Loading this project’s skills…</p></div>
350
- </details>
351
- <div class="speciality-actions">
352
- <button type="submit" class="btn">Create speciality</button>
353
- </div>
354
- </form>
310
+ <!-- The create form is rendered by speciality-tools.js (task 1005052), the
311
+ one copy Settings and My agents share; this is only its host. -->
312
+ <form id="speciality-create" class="speciality-create"></form>
355
313
 
356
314
  <p class="settings-status" id="specialities-status" role="status" aria-live="polite"></p>
357
315
  </section>
@@ -475,9 +433,9 @@
475
433
  <!-- SOFTWARE UPDATE — this project's version, the newest release its update
476
434
  channel allows, and what the versions between them carry (task 1004296).
477
435
  Like a phone's Software Update screen: every builder reads it; the Update
478
- button is drawn only for those the upgrade door lets through, and it LEADS
479
- to that door (the two-step confirm and auto-rollback of ADR 0293) — the
480
- act never happens on this page. Rendered by settings-software-update.js
436
+ button is drawn only for those the upgrade door lets through, and it runs
437
+ the Update pill's in-place flow (update-pill.js, task 1005041): the act is
438
+ still the door's, with its auto-rollback (ADR 0293). Rendered by settings-software-update.js
481
439
  from GET /api/bongos/software-update, read only when this group opens:
482
440
  an update's notes come out of a package download. -->
483
441
  <section class="scroll" id="software-update-scroll" aria-labelledby="software-update-h" data-group="update" hidden>
@@ -514,7 +472,7 @@
514
472
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
515
473
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
516
474
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
517
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
475
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
518
476
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
519
477
  <script src="/builders/palette.js" defer></script>
520
478
  <!-- shared component kit (window.OTBKit) — section rail (task 2166). -->
@@ -534,13 +492,17 @@
534
492
  the same precedent and for the same reason — settings.js is against the
535
493
  1500-line ratchet, and this panel shares nothing with the rest of the page. -->
536
494
  <script src="/builders/settings-interaction.js?v=2026-10-17-laws"></script>
537
- <script src="/builders/settings-specialities.js?v=2026-09-17-specialities"></script>
495
+ <!-- task 1005052: the speciality tools are ONE component Settings and My agents
496
+ both mount (it was settings-specialities.js). Task 1005044 removes this mount. -->
497
+ <script src="/builders/speciality-tools.js?v=2026-11-03-shared"></script>
538
498
  <!-- task 1002518: the card-richness ladder. Own file on the same precedent —
539
499
  the panel grew from one switch to a rung picker with a derived default, and
540
500
  settings.js is against the 1500-line ratchet. -->
541
501
  <script src="/builders/settings-render.js?v=2026-09-16-render-levels"></script>
542
502
  <!-- task 1004296: Settings → Software update. Own file on the same precedent. -->
543
- <script src="/builders/settings-software-update.js?v=2026-10-18-history"></script>
503
+ <!-- the Update pill's flow, which the panel's Update button runs in place (task 1005041) -->
504
+ <script src="/builders/update-pill.js?v=2026-11-03-pill"></script>
505
+ <script src="/builders/settings-software-update.js?v=2026-11-03-pill"></script>
544
506
  <!-- task 1004501: Settings → Your Autobongos runner. Own file on the same precedent. -->
545
507
  <script src="/builders/settings-autobongos.js?v=2026-10-01-autobongos"></script>
546
508
  <!-- task 1003793: Settings → Module install reports. Own file on the same precedent. -->
@@ -751,8 +751,11 @@
751
751
  history.replaceState(null, '', window.location.pathname + rest + window.location.hash);
752
752
  }
753
753
 
754
- // task 1004045 — the core-update banner. The owner's ask: "a popup banner at the
755
- // top of every owners builders hall that a core update has been released".
754
+ // task 1004045 → task 1005041 — the Update pill. The owner's first ask was a banner at the top
755
+ // of every owner's hall when a core update is released; the second (2026-11-03) was that it
756
+ // was "way too obstructing and not obvious what to do", and should feel like the Claude
757
+ // app's "restart to update". So the news is a quiet pill in the top bar now, and the
758
+ // update is taken in place from it: update-pill.js owns all of it, this only asks.
756
759
  //
757
760
  // WHY THE SERVER IS ASKED RATHER THAN TOLD. The version this box runs is known
758
761
  // here, but what is PUBLISHED is not, and the fact lives behind an outbound
@@ -764,6 +767,11 @@
764
767
  // ASKED ONCE PER PAGE LOAD. applyAccess runs on every settle (the /me probe, a
765
768
  // page's own report), and re-firing a network call on each would be a request per
766
769
  // nav interaction for a fact with a six-hour life on the server.
770
+ //
771
+ // SAID FROM AN ANSWER, OR FROM AN UPDATE ALREADY UNDER WAY. The pill script loads only
772
+ // when the answer has something to say (behind, held, or just moved), or when this
773
+ // browser remembers an update it started: mid-restart the read itself may fail, and the
774
+ // pill must still follow the owner from page to page.
767
775
  var coreUpdateAsked = false;
768
776
  function maybeRenderCoreUpdate() {
769
777
  if (coreUpdateAsked) return;
@@ -771,10 +779,20 @@
771
779
  if (!perms || perms['core.pin.move'] !== true) return;
772
780
  coreUpdateAsked = true;
773
781
  api.request('GET', '/api/bongos/core-update').then(function (res) {
774
- if (!res || res.status !== 200 || !res.data) return;
775
- if (res.data.update) renderCoreUpdateBanner(res.data.update, res.data.held);
776
- if (res.data.moved) renderCoreMoveNotice(res.data.moved); // task 1004731: loads core-move-notice.js, which draws it
777
- }, function () { /* fail-soft: no banner is the correct answer to no answer */ });
782
+ mountUpdatePill(res && res.status === 200 && res.data ? res.data : null);
783
+ }, function () { mountUpdatePill(null); });
784
+ }
785
+ function updateUnderWay() {
786
+ try { return !!(window.localStorage && window.localStorage.getItem('bongos.update.run')); } catch (e) { return false; }
787
+ }
788
+ function mountUpdatePill(data) {
789
+ var u = data && data.update;
790
+ var news = !!(u && u.reading === 'ok' && u.behind) || !!(data && data.held);
791
+ if (news || updateUnderWay()) {
792
+ var go = function () { if (window.OTBUpdatePill) window.OTBUpdatePill.mount(data, { api: api }); };
793
+ if (window.OTBUpdatePill) go(); else loadHallScript('update-pill.js', go);
794
+ }
795
+ if (data && data.moved) renderCoreMoveNotice(data.moved); // task 1004731: loads core-move-notice.js, which draws it
778
796
  }
779
797
  // One hall script on demand, stamped with the shell's own ?v= (task 1004996 made it one helper).
780
798
  function loadHallScript(file, onload, onerror) {
@@ -785,7 +803,7 @@
785
803
  head.appendChild(tag);
786
804
  }
787
805
  function renderCoreMoveNotice(moved) {
788
- loadHallScript('core-move-notice.js', function () { if (window.CoreMoveNotice) window.CoreMoveNotice.render(moved, { api: api, href: SOFTWARE_UPDATE_HREF }); });
806
+ loadHallScript('core-move-notice.js', function () { if (window.CoreMoveNotice) window.CoreMoveNotice.render(moved, { api: api, href: '/settings#software-update' }); });
789
807
  }
790
808
  var githubChipAsked = false; // task 1004996: the Link GitHub chip (github-chip.js), asked once a page
791
809
  function loadGithubChip(builder) {
@@ -795,72 +813,6 @@
795
813
  loadHallScript('github-chip.js', function () { if (window.OTBGithubChip) window.OTBGithubChip.mount({ api: api, builder: builder, hub: hub }); });
796
814
  }
797
815
 
798
- // Where the banner's Upgrade goes (task 1004296). The platform's hall runs the deploy
799
- // door itself, so there it is /deploy. Every other hall — a hosted project, a
800
- // self-hosted instance — takes its update from Settings → Software update, which says
801
- // what the update carries and, on a hosted project, hands the owner to the hub's door
802
- // for this project (ADR 0339 D3). The banner no longer links off-site itself.
803
- var SOFTWARE_UPDATE_HREF = '/settings#software-update';
804
-
805
- // SAY NOTHING WHEN THERE IS NOTHING TO SAY. `behind` is false both when the box
806
- // is current AND when the registry could not be read; `reading` separates them,
807
- // and neither renders. A banner is a claim about being out of date, and a wrong
808
- // one on a project that is actually current is worse than silence.
809
- function renderCoreUpdateBanner(update, held) {
810
- if (!update || update.reading !== 'ok' || !update.behind || !update.latest) return;
811
- // A dismissal is remembered PER VERSION, not forever: the next release is new
812
- // news and must re-announce itself, or the surface is dead after one click.
813
- // localStorage throws in a private window and is absent in the unit tests' bare
814
- // vm context, so every touch is guarded and its loss only costs the memory.
815
- try {
816
- var seen = window.localStorage && window.localStorage.getItem('bongos.coreUpdate.dismissed');
817
- if (seen && seen === update.latest) return;
818
- } catch (e) { /* no storage: always show */ }
819
- var topbar = document.getElementById('app-topbar');
820
- if (!topbar) return;
821
- var el = document.createElement('div');
822
- el.className = 'core-update-notice';
823
- el.id = 'core-update-notice';
824
- el.setAttribute('role', 'status');
825
- // The action goes where the update can actually be taken. The deploy door is the
826
- // provisioning module's, and a hosted project does NOT run that module (probed:
827
- // /api/bongos/provisioning/health is 404 on a tenant, 200 on the platform) — so on
828
- // those it goes to Settings → Software update (SOFTWARE_UPDATE_HREF above).
829
- // NOT A LINK TO THE PAGE YOU ARE ALREADY ON (task 1004145). This reads as the action
830
- // button — it is labelled with the verb and styled like one — but it is a plain
831
- // navigation to /deploy. On /deploy itself that is a no-op, and the owner who
832
- // pressed it there reasonably reported "I clicked update, but I didn't see anything
833
- // happen": nothing had, and nothing had been asked for. The control is on that page
834
- // already, so on it the banner informs and points, and does not pretend to act.
835
- // The same rule on Settings → Software update: that panel IS the control there.
836
- var here = window.location ? String(window.location.pathname || '').replace(/\/+$/, '') : '';
837
- var onDeploy = here === '/deploy';
838
- var onUpdatePanel = here === '/settings' && String(window.location.hash || '') === '#software-update';
839
- var action = moduleOn('provisioning')
840
- ? (onDeploy
841
- ? '<span class="core-update-notice__hint">Use the control below to take it.</span>'
842
- : '<a class="core-update-notice__action" href="/deploy">Upgrade</a>')
843
- : (onUpdatePanel
844
- ? '<span class="core-update-notice__hint">Its details are below.</span>'
845
- : '<a class="core-update-notice__action" href="' + SOFTWARE_UPDATE_HREF + '">Upgrade</a>');
846
- // task 1004730: the newest version already failed here and was rolled back. Say so, never
847
- // "available" with a button to take it again; Software update carries the full sentence.
848
- var hold = held && held.version === update.latest ? held : null;
849
- el.innerHTML = (hold
850
- ? '<span>Core <strong>' + esc(hold.version) + '</strong> was tried on this project and rolled back. It is still on ' +
851
- esc(hold.still_on) + ' and working.</span><a class="core-update-notice__action" href="' + SOFTWARE_UPDATE_HREF + '">Details</a>'
852
- : '<span>Core <strong>' + esc(update.latest) + '</strong> is available — this project runs ' +
853
- esc(update.running || 'an unknown version') + '.</span>' + action) +
854
- '<button type="button" class="core-update-notice__close" aria-label="Dismiss">×</button>';
855
- el.querySelector('.core-update-notice__close').addEventListener('click', function () {
856
- try {
857
- if (window.localStorage) window.localStorage.setItem('bongos.coreUpdate.dismissed', update.latest);
858
- } catch (e) { /* dismissal is best-effort */ }
859
- el.remove();
860
- });
861
- topbar.insertAdjacentElement('afterend', el);
862
- }
863
-
864
816
  // task 1003303: the skip link, rendered once here rather than pasted into 27
865
817
  // heads. The hall had none, which is a platform floor (DESIGN.md
866
818
  // §Accessibility Floors) and, with the sidebar first in the DOM, meant a
@@ -0,0 +1,140 @@
1
+ /* speciality-tools.css — the SPECIALITY TOOLS sheet (task 1005052): the structural
2
+ rules of the one component Settings and My agents both mount
3
+ (speciality-tools.js) — the card, the train, offer and create forms, the
4
+ instructions block and the skills walkthrough. Moved out of settings.css
5
+ verbatim, so Settings draws exactly as it did; each page sheet adds its own
6
+ look on top (Settings: panel.css, linked before this; My agents: the `.ma-know`
7
+ skin in my-agents.css, linked after). */
8
+
9
+ /* Specialities panel (task 1003855, ADR 0296). A speciality is a named bundle of
10
+ knowledge and terms under a craft; this styles the list, the train form and the
11
+ create form. Tokens only — no literal colours, so light/dark follow the page. */
12
+ .speciality-craft {
13
+ font-size: 0.78rem;
14
+ text-transform: lowercase;
15
+ opacity: 0.72;
16
+ margin-left: 0.4rem;
17
+ }
18
+ .speciality-badge {
19
+ font-size: 0.7rem;
20
+ padding: 0.05rem 0.4rem;
21
+ border-radius: 0.5rem;
22
+ margin-left: 0.4rem;
23
+ border: 1px solid currentColor;
24
+ }
25
+ .speciality-counts { opacity: 0.82; }
26
+ .speciality-card { flex-wrap: wrap; }
27
+ .speciality-train,
28
+ .speciality-create {
29
+ flex-basis: 100%;
30
+ margin-top: 0.75rem;
31
+ padding-top: 0.75rem;
32
+ border-top: 1px solid var(--rule, rgba(128, 128, 128, 0.25));
33
+ display: flex;
34
+ flex-direction: column;
35
+ gap: 0.6rem;
36
+ }
37
+ .speciality-create__h { margin: 0; font-size: 0.95rem; }
38
+ .speciality-field { display: flex; flex-direction: column; gap: 0.25rem; }
39
+ .speciality-field > span { font-size: 0.85rem; }
40
+ .speciality-field small { display: block; opacity: 0.7; font-weight: normal; }
41
+ .speciality-field input[type='text'],
42
+ .speciality-field select,
43
+ .speciality-field textarea {
44
+ width: 100%;
45
+ box-sizing: border-box;
46
+ }
47
+ /* The checkbox row reads as one sentence, so it runs inline rather than stacked. */
48
+ .speciality-field--check { flex-direction: row; align-items: flex-start; gap: 0.5rem; }
49
+ .speciality-field--check input { margin-top: 0.2rem; }
50
+ .speciality-actions { display: flex; gap: 0.5rem; }
51
+ /* What a speciality OFFERS (task 1004404): the owner's pick-list of this
52
+ instance's skills, each with its plain line. Sixty-odd rows, so the list
53
+ scrolls inside its own box rather than pushing the form's buttons off-screen;
54
+ the reading lines keep the measure. */
55
+ .speciality-field input[type='search'] { width: 100%; box-sizing: border-box; }
56
+ .speciality-offer,
57
+ .speciality-offer-box { flex-basis: 100%; display: flex; flex-direction: column; gap: 0.6rem; }
58
+ .speciality-offer { margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--rule); }
59
+ .speciality-offer > *, .speciality-offer-box > * { max-width: var(--measure); }
60
+ .speciality-offer-box > summary { cursor: pointer; font-size: 0.85rem; min-height: 24px; padding-block: 2px; box-sizing: border-box; }
61
+ .speciality-offer-box > summary small { opacity: 0.7; }
62
+ .speciality-offer__choices, #speciality-create-skills { display: flex; flex-direction: column; gap: 0.6rem; }
63
+ .speciality-offer__list {
64
+ list-style: none;
65
+ margin: 0;
66
+ padding: 0 0.25rem 0 0;
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 0.5rem;
70
+ max-height: 24rem;
71
+ overflow-y: auto;
72
+ overflow-x: hidden;
73
+ }
74
+ .speciality-offer__list li[hidden] { display: none; }
75
+ .speciality-offer__list strong { font-family: var(--font-mono); font-weight: 600; }
76
+ .speciality-offer__list .speciality-field--check > span { font-size: 0.85rem; line-height: 1.45; }
77
+ /* The adopted contract, shown in full before "Use this" — another builder's prose
78
+ reaching your sessions is the one thing here that must never be folded away. */
79
+ .speciality-contract { flex-basis: 100%; margin-top: 0.5rem; font-size: 0.85rem; }
80
+ /* 24px tall: the panel was never revealed until task 1004073, so the kit had
81
+ never measured this toggle; it came back 21px. */
82
+ .speciality-contract > summary { cursor: pointer; opacity: 0.85; min-height: 24px; padding-block: 2px; box-sizing: border-box; }
83
+ .speciality-contract__body {
84
+ white-space: pre-wrap;
85
+ margin: 0.4rem 0 0;
86
+ padding: 0.5rem 0.65rem;
87
+ border-left: 2px solid var(--rule, rgba(128, 128, 128, 0.35));
88
+ border-radius: 0;
89
+ font-family: inherit;
90
+ opacity: 0.92;
91
+ }
92
+ /* The skills walkthrough (task 1004073, ADR 0310 §3): one offered skill at a
93
+ time, three answers as a definition list, then a decision. It sits inside the
94
+ card like the train form, so it inherits the card's ground in both modes. */
95
+ .skill-walk {
96
+ flex-basis: 100%;
97
+ margin-top: 0.75rem;
98
+ padding-top: 0.75rem;
99
+ border-top: 1px solid var(--rule);
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 0.6rem;
103
+ }
104
+ /* The measure caps the reading lines, never the section: a capped flex-basis
105
+ lets the section fit beside the card's actions instead of wrapping under them. */
106
+ .skill-walk > * { max-width: var(--measure); }
107
+ .skill-walk__progress { margin: 0; font-size: 0.8rem; color: var(--ink-soft); }
108
+ .skill-walk__name { margin: 0; font-family: var(--font-mono); font-size: 1rem; color: var(--ink); }
109
+ .skill-walk__facts { margin: 0; display: grid; gap: 0.2rem 0; }
110
+ .skill-walk__facts dt { font-size: 0.8rem; color: var(--ink-soft); margin-top: 0.4rem; }
111
+ .skill-walk__facts dt:first-child { margin-top: 0; }
112
+ .skill-walk__facts dd { margin: 0; color: var(--ink); line-height: 1.5; }
113
+ .skill-walk__note { font-size: 0.8rem; }
114
+ /* "Leave it off" and "Turn it on" are the panel family's plain pill at EQUAL
115
+ weight: the default is off, so the page must not lean on the reader toward on.
116
+ Only "Save my choices" takes the accent. A decision already made reads through
117
+ aria-pressed when the person steps Back to it. */
118
+ .skill-walk__actions { flex-wrap: wrap; }
119
+ .skill-walk__actions .pbtn[aria-pressed="true"] { border-color: var(--ink-soft); }
120
+ .skill-walk__recap { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
121
+ .skill-walk__recap li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.4rem; }
122
+ .skill-walk__recap .prow__hint { flex-basis: 100%; margin: 0; }
123
+ .skill-walk__recap strong { font-family: var(--font-mono); font-weight: 600; }
124
+ /* "Finish later" is a way out, not a choice about a skill, so it reads as a quiet
125
+ link below the decision rather than a third button beside it. */
126
+ .skill-walk__later {
127
+ align-self: flex-start;
128
+ background: none;
129
+ border: 0;
130
+ padding: 0 0.25rem;
131
+ margin-inline-start: -0.25rem;
132
+ min-height: 24px;
133
+ font: inherit;
134
+ font-size: 0.8rem;
135
+ color: var(--ink-soft);
136
+ text-decoration: underline;
137
+ text-underline-offset: 3px;
138
+ cursor: pointer;
139
+ }
140
+ .skill-walk__later:hover { color: var(--ink); }
@@ -1,5 +1,6 @@
1
- // modules/hall-ui/public/settings-specialities.js — the "Your specialities" panel
2
- // on /builders/settings (task 1003855; ADR 0296).
1
+ // modules/hall-ui/public/speciality-tools.js — THE SPECIALITY TOOLS, one component
2
+ // mounted by two pages: Settings' "Your specialities" panel (task 1003855; ADR
3
+ // 0296) and My agents' "What it knows" section (task 1005052).
3
4
  //
4
5
  // WHAT A SPECIALITY IS, for anyone reading this file first: a craft says HOW you
5
6
  // work (engineer / ideator / artist / ui); a speciality says what you work ON and
@@ -7,10 +8,21 @@
7
8
  // contract you write here is injected into every session where you hold a claim
8
9
  // in that craft.
9
10
  //
10
- // Its own file rather than more of settings.js, on the settings-interaction.js /
11
- // settings-sessions.js precedent and for the same stated reason: settings.js sits
12
- // against the 1500-line size ratchet. Self-contained — it shares nothing with the
13
- // rest of the page but `window.OTB` and the generated client, both loaded before it.
11
+ // ONE COMPONENT, TWO MOUNTS (task 1005052). This was settings-specialities.js;
12
+ // it was carved out so My agents gets every tool Settings has without a second
13
+ // copy. A page mounts it by carrying the hosts, by id, inside one section:
14
+ // #specialities-scroll the section (ships `hidden`; revealed by load())
15
+ // #speciality-suggestions what the project suggests
16
+ // #specialities-rows the catalogue, one card per speciality (a <ul>)
17
+ // #speciality-create an EMPTY <form>: createFormHtml() below fills it,
18
+ // so the create form's markup exists once, here
19
+ // #specialities-status the status line
20
+ // Nothing else is page-specific: each page's own sheet draws the same markup in
21
+ // its own look (Settings through panel.css, My agents through my-agents.css's
22
+ // `.ma-know` skin; the structural rules are speciality-tools.css). Task 1005044
23
+ // removes the Settings mount (its section and this file's script tag); the code
24
+ // stays. Self-contained — it shares nothing with either page but `window.OTB`,
25
+ // loaded before it, and one DOM event it dispatches (changed(), below).
14
26
  //
15
27
  // TRAINING IS TWO-FOLD, and only half of it is visible here. Documents you add are
16
28
  // the half you drive; the other half accumulates on its own from learnings captured
@@ -33,6 +45,13 @@
33
45
  const el = (id) => document.getElementById(id);
34
46
  const statusLine = (msg) => { const s = el('specialities-status'); if (s) s.textContent = msg || ''; };
35
47
 
48
+ // What a session is sent just moved (an adopt, a stop, a skills save): say so,
49
+ // so a page that shows it can read it again. My agents' "What your agent reads
50
+ // first" listens (task 1005052); Settings does not, and nothing here waits on it.
51
+ function changed() {
52
+ if (typeof CustomEvent === 'function' && document.dispatchEvent) document.dispatchEvent(new CustomEvent('otb:specialities-changed'));
53
+ }
54
+
36
55
  async function call(method, path, body) {
37
56
  const res = await fetch(`${API}${path}`, {
38
57
  method,
@@ -203,6 +222,7 @@
203
222
  state.walk = null;
204
223
  toast(`Saved — ${enabled.length} of ${steps.length} skill${steps.length === 1 ? '' : 's'} on.`);
205
224
  await load();
225
+ changed();
206
226
  return;
207
227
  } catch (err) {
208
228
  w.saving = false;
@@ -458,6 +478,7 @@
458
478
  if (state.walk && state.walk.id === String(id)) state.walk = null;
459
479
  }
460
480
  await load();
481
+ if (action === 'adopt' || action === 'abandon') changed();
461
482
  // Adopting is the moment the ruling is about: a speciality that offers
462
483
  // skills opens straight into the walkthrough, with every one still off.
463
484
  if (action === 'adopt') {
@@ -585,8 +606,64 @@
585
606
  });
586
607
  }
587
608
 
609
+ // THE CREATE FORM, rendered into the page's empty `<form id="speciality-create">`
610
+ // (task 1005052). It lived in settings.html until a second page mounted these
611
+ // tools; here it is one copy both pages share, filled before wire() reads its
612
+ // fields. The markup is the one settings.html carried, unchanged.
613
+ function createFormHtml() {
614
+ return `
615
+ <h3 class="speciality-create__h">Create one</h3>
616
+ <label class="speciality-field">
617
+ <span>Name</span>
618
+ <input type="text" name="name" maxlength="80" required placeholder="Economics" />
619
+ </label>
620
+ <label class="speciality-field">
621
+ <span>Role it sits under</span>
622
+ <select name="discipline" required>
623
+ <option value="">Pick one…</option>
624
+ <option value="engineer">Engineer</option>
625
+ <option value="ideator">Ideator</option>
626
+ <option value="artist">Artist</option>
627
+ <option value="ui">UI design</option>
628
+ </select>
629
+ </label>
630
+ <label class="speciality-field">
631
+ <span>Summary <small>(optional)</small></span>
632
+ <input type="text" name="summary" maxlength="500" placeholder="Markets, incentives, and how to price them." />
633
+ </label>
634
+ <label class="speciality-field">
635
+ <span>How Claude should engage you in it <small>(optional)</small></span>
636
+ <textarea name="contract_md" rows="4" maxlength="4000" placeholder="Argue from mechanism, not vibes. Always name the assumption a number rests on."></textarea>
637
+ </label>
638
+ <label class="speciality-field">
639
+ <span>Who can use it</span>
640
+ <select name="visibility">
641
+ <option value="private">Only me</option>
642
+ <option value="project">Anyone in the owning project</option>
643
+ <option value="public">Anyone on this instance</option>
644
+ </select>
645
+ </label>
646
+ <label class="speciality-field speciality-field--check">
647
+ <input type="checkbox" name="sellable" />
648
+ <span>
649
+ May be offered for sale later
650
+ <small>Records your decision only — there is no marketplace yet, and nothing is listed anywhere.</small>
651
+ </span>
652
+ </label>
653
+ <details id="speciality-create-offer" class="speciality-offer-box">
654
+ <summary>Skills it offers <small>(optional)</small></summary>
655
+ <div id="speciality-create-skills"><p class="hall-loading hall-loading--inline" role="status">Loading this project’s skills…</p></div>
656
+ </details>
657
+ <div class="speciality-actions">
658
+ <button type="submit" class="btn">Create speciality</button>
659
+ </div>
660
+ `;
661
+ }
662
+
588
663
  function boot() {
589
664
  if (!el('specialities-rows')) return; // panel not on this page
665
+ const create = el('speciality-create');
666
+ if (create) create.innerHTML = createFormHtml();
590
667
  wire();
591
668
  load();
592
669
  }