@bongos/core 1.21.41 → 1.21.43

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 (60) hide show
  1. package/.bongos-core.json +64 -64
  2. package/docs/copy-inventory.md +10 -11
  3. package/docs/copy-registry.json +11 -20
  4. package/docs/design/craft-mode-direction.md +1 -1
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/page-inventory.json +4 -2
  7. package/docs/page-readings.json +3467 -3507
  8. package/modules/autonomy/governor-docket.js +6 -1
  9. package/modules/hall-ui/public/atlas.html +3 -3
  10. package/modules/hall-ui/public/blockers.html +3 -3
  11. package/modules/hall-ui/public/board-room.html +3 -3
  12. package/modules/hall-ui/public/city.html +3 -3
  13. package/modules/hall-ui/public/collab.html +3 -3
  14. package/modules/hall-ui/public/commands.html +3 -3
  15. package/modules/hall-ui/public/copy-desk.html +3 -3
  16. package/modules/hall-ui/public/craft-mode.js +10 -15
  17. package/modules/hall-ui/public/deploy.html +3 -3
  18. package/modules/hall-ui/public/diagrams.html +3 -3
  19. package/modules/hall-ui/public/drachmae.html +3 -3
  20. package/modules/hall-ui/public/fleet.html +3 -3
  21. package/modules/hall-ui/public/gate.html +3 -3
  22. package/modules/hall-ui/public/goal-map.html +3 -3
  23. package/modules/hall-ui/public/goals.html +3 -3
  24. package/modules/hall-ui/public/government.html +3 -3
  25. package/modules/hall-ui/public/idea.html +3 -3
  26. package/modules/hall-ui/public/ideas.html +3 -3
  27. package/modules/hall-ui/public/index.html +3 -3
  28. package/modules/hall-ui/public/modules.html +3 -3
  29. package/modules/hall-ui/public/primer.html +3 -3
  30. package/modules/hall-ui/public/profile.html +3 -3
  31. package/modules/hall-ui/public/project-settings.html +3 -3
  32. package/modules/hall-ui/public/ranks.html +3 -3
  33. package/modules/hall-ui/public/roadmap.html +3 -3
  34. package/modules/hall-ui/public/roster.html +3 -3
  35. package/modules/hall-ui/public/sessions.html +3 -3
  36. package/modules/hall-ui/public/settings.html +3 -3
  37. package/modules/hall-ui/public/street-card.css +13 -0
  38. package/modules/hall-ui/public/street-card.js +61 -9
  39. package/modules/hall-ui/public/street-card.states.json +72 -0
  40. package/modules/hall-ui/public/street.html +3 -3
  41. package/modules/hall-ui/public/studio.html +3 -3
  42. package/modules/hall-ui/public/style.css +4 -22
  43. package/modules/hall-ui/public/task.html +3 -3
  44. package/modules/hall-ui/public/thinking.html +3 -3
  45. package/modules/hall-ui/public/tweak-editor.html +3 -3
  46. package/modules/hall-ui/public/watch.html +3 -3
  47. package/modules/hall-ui/public/work.html +3 -3
  48. package/modules/hall-ui/records/craft-mode.md +5 -5
  49. package/modules/hall-ui/records/governor-street.md +1 -0
  50. package/package-lock.json +2 -2
  51. package/package.json +1 -1
  52. package/release-notes.json +12 -0
  53. package/src/module-api.js +1 -1
  54. package/tests/craft_mode.mjs +27 -10
  55. package/tests/craft_mode_looks.mjs +5 -5
  56. package/tests/craft_mode_nav.mjs +7 -7
  57. package/tests/governor_docket.mjs +3 -1
  58. package/tests/hall_matter_card.mjs +71 -0
  59. package/tests/hall_tweak_editor.mjs +1 -1
  60. package/tests/page_notes_panel.mjs +1 -1
@@ -1,8 +1,8 @@
1
1
  // tests/craft_mode_nav.mjs — the hall nav follows the craft mode (task 1004493,
2
2
  // goal 1000095 WA6).
3
3
  //
4
- // Each craft mode shows only the tabs its craft needs; Legacy mode and no mode
5
- // show every tab, exactly as before. The owner's rule (2026-10-01): some tabs
4
+ // Each craft mode shows only the tabs its craft needs; no mode
5
+ // shows every tab, exactly as before. The owner's rule (2026-10-01): some tabs
6
6
  // stay in EVERY mode, Builders first among them, written down as ONE shared list
7
7
  // that each mode's list adds to; on 2026-10-03 Collab and Profile joined it. These cases boot the REAL shell-trail.js +
8
8
  // craft-mode.js + shell.js and read the sidebar the shell actually rendered, so a
@@ -157,15 +157,15 @@ test('Government shows only in Governing, and Governing is the shortest list', (
157
157
  assert.equal(Math.min(...lengths), lengths[CRAFTS.indexOf('governing')], 'outside its own Government group, Governing shows the fewest tabs');
158
158
  });
159
159
 
160
- test('NO MODE AND LEGACY ARE TODAY\'S NAV: every tab the gate allows, nothing marked', () => {
160
+ test('NO MODE IS TODAY\'S NAV: every tab the gate allows, nothing marked — and a stored Legacy is no mode', () => {
161
161
  const none = boot();
162
162
  none.signIn();
163
163
  const all = none.showing();
164
164
  assert.deepEqual(all, NAV_IDS, 'an archon with no mode sees every tab');
165
165
  const legacy = boot({ session: { 'hall-craft': 'legacy' } });
166
166
  legacy.signIn();
167
- assert.deepEqual(legacy.showing(), all, 'Legacy mode shows exactly the same');
168
- assert.equal(legacy.craft.tabsFor('legacy'), null);
167
+ assert.equal(legacy.root.dataset.craft, undefined, 'Legacy was removed (task 1004567), so it reads as no mode');
168
+ assert.deepEqual(legacy.showing(), all, 'and shows exactly the same');
169
169
  });
170
170
 
171
171
  test('each mode shows its own tabs, and switching updates the nav live', () => {
@@ -178,8 +178,8 @@ test('each mode shows its own tabs, and switching updates the nav live', () => {
178
178
  for (const id of ['roster', 'collab', 'profile']) assert.ok(p.showing().includes(id), `${id} stays in ${m}`);
179
179
  assert.equal(p.headShowing('government'), m === 'governing', `the Government head shows only where its tabs do (${m})`);
180
180
  }
181
- p.craft.choose('legacy');
182
- assert.deepEqual(p.showing(), NAV_IDS, 'back to Legacy, every tab returns without a reload');
181
+ p.craft.choose('building');
182
+ assert.deepEqual(new Set(p.showing()), new Set(p.craft.tabsFor('building')), 'back to Building, its tabs return without a reload');
183
183
  });
184
184
 
185
185
  test('a mode never shows a tab the gate hid, and a later gate pass keeps the mode', () => {
@@ -399,7 +399,9 @@ test('the fence: a matter only when it needs a governor, and only a fence manage
399
399
  const off = autonomyDocket.fenceDocketMatters({ enabled: false, paused_reason: 'payments touched', goals: [{ goal_id: 1 }], updated_by: 3 }, true);
400
400
  assert.equal(off.length, 1);
401
401
  assert.equal(off[0].title, 'Unattended building is switched off');
402
- assert.equal(off[0].where, 'automation · the switch · payments touched');
402
+ assert.equal(off[0].where, 'automation · the switch', 'the kicker is building · floor only (task 1004568)');
403
+ assert.equal(off[0].detail, 'payments touched', 'the reason rides in detail: the fact, and the more');
404
+ assert.equal(autonomyDocket.fenceDocketMatters({ enabled: false, goals: [] }, true)[0].detail, undefined, 'no reason, no detail');
403
405
  assert.equal(off[0].state, 'needs');
404
406
  assert.equal(autonomyDocket.fenceDocketMatters({ enabled: false, goals: [] }, false)[0].state, 'settled');
405
407
  assert.equal(autonomyDocket.fenceDocketMatters({ enabled: true, goals: [] }, true)[0].title, 'Unattended building is on, but no goal is allowed');
@@ -507,6 +507,77 @@ test('the docket hands a matter its detail only as text, bounded, and only when
507
507
  }
508
508
  });
509
509
 
510
+ // ── the compact card sizes to its words (task 1004568) ─────────────────────
511
+
512
+ const allText = (n) => [n._text || ''].concat((n.children || []).map(allText)).join('\n');
513
+
514
+ test('a very long reason stays within the compact card and is whole under more; the kicker is building · floor only', () => {
515
+ const docketJs = require(path.join(ROOT, 'modules/government/docket.js'));
516
+ const fence = require(path.join(ROOT, 'modules/autonomy/governor-docket.js'));
517
+ const reason = 'lars: stop runner for performance evaluation (2026-10-02) while the grader is rebuilt, then switch it back on once the new numbers are in and the owner has read them';
518
+ // the whole path: the autonomy module's matter, through the docket's normaliser, into the card
519
+ const raw = fence.fenceDocketMatters({ enabled: false, paused_reason: reason, goals: [], updated_by: 3, updated_at: '2026-10-02T10:00:00.000Z' }, true)[0];
520
+ const m = docketJs.normalizeMatter(raw, { source: 'autobongos_fence', building: 'automation' });
521
+ assert.equal(m.where, 'automation · the switch', 'the reason is not joined into the kicker');
522
+ const d = docket([m]);
523
+ const cv = D.cardFor(m.id, d, { now: NOW });
524
+ assert.equal(cv.where, 'automation · the switch');
525
+ assert.ok(cv.fact.length <= C.LINE.fact && /…$/.test(cv.fact), 'the fact is one line, cut, and says so: ' + cv.fact);
526
+ assert.ok(reason.startsWith(cv.fact.replace(/…$/, '')), 'the fact is the reason, cut');
527
+ assert.equal(reason.charAt(cv.fact.length - 1), ' ', 'never mid-word');
528
+ const { document } = fakeDom();
529
+ const shut = document.createElement('div');
530
+ C.render(document, shut, cv, { act() {}, openForm() {}, ask() {}, more() {}, pick() {} }, { more: false });
531
+ assert.ok(!allText(shut).includes(reason), 'the compact card does not carry the whole reason');
532
+ assert.equal(walk(shut, (n) => n.className === 'mcard-fact').length, 1, 'one fact line');
533
+ const open = document.createElement('div');
534
+ C.render(document, open, cv, { act() {}, openForm() {}, ask() {}, more() {}, pick() {} }, { more: true });
535
+ assert.ok(allText(open).includes(reason), 'more shows the reason whole');
536
+ // no reason, no fact
537
+ const bare = docketJs.normalizeMatter(fence.fenceDocketMatters({ enabled: false, goals: [] }, true)[0], { source: 'autobongos_fence', building: 'automation' });
538
+ assert.equal(D.cardFor(bare.id, docket([bare]), { now: NOW }).fact, '');
539
+ });
540
+
541
+ test('every part of the compact card is held to its line: the kicker, the title, the listed lines, a voice\'s reason', () => {
542
+ const long = (w) => Array.from({ length: 40 }, (_, i) => w + i).join(' ');
543
+ const m = matter({ id: 'blockers:9', source: 'blockers', building: 'review', rule: 'decide', where: long('where'), title: long('title'), detail: long('detail'), ref: { type: 'blocker', id: '9' } });
544
+ const cv = C.model({ docket: docket([m]), key: m.id, now: NOW });
545
+ for (const [k, n] of [['where', C.LINE.where], ['title', C.LINE.title], ['fact', C.LINE.fact]]) {
546
+ assert.ok(cv[k].length <= n && /…$/.test(cv[k]), k + ' is cut: ' + cv[k]);
547
+ const whole = long(k === 'fact' ? 'detail' : k);
548
+ assert.ok(whole.startsWith(cv[k].replace(/…$/, '')), k + ' keeps its own words');
549
+ assert.equal(whole.charAt(cv[k].length - 1), ' ', k + ' cut at a word, not inside one');
550
+ }
551
+ const words = cv.more.text.map((x) => x.words);
552
+ assert.ok(words.includes(long('title')), 'the whole title is under more');
553
+ assert.ok(words.includes(long('where').toLowerCase()), 'the whole kicker is under more');
554
+ assert.ok(words.includes(long('detail')), 'the whole detail is under more');
555
+ // the queue's listed lines and an objection's reason
556
+ const lines = C.compact({ where: '', title: '', fact: '', lines: [long('line')], voices: [{ side: 'against', by: 'Kai', words: long('because') }], more: null });
557
+ assert.ok(lines.lines[0].length <= C.LINE.line && lines.voices[0].words.length <= C.LINE.reason);
558
+ assert.deepEqual(lines.more.text.map((x) => x.words).sort(), [long('because'), long('line')].sort(), 'both whole under more');
559
+ // a text "more" already holds, shortened by its own bound, is not added twice
560
+ const huge = long('w').repeat(40);
561
+ assert.ok(huge.length > 4000, 'longer than more keeps');
562
+ const twice = C.compact({ where: '', title: huge, fact: '', lines: [], voices: [], more: { text: [{ label: '', words: huge.slice(0, 3999).trim() + '…' }], who: '', when: '', history: [] } });
563
+ assert.equal(twice.more.text.length, 1, 'no duplicate under more');
564
+ // short words are left alone, and add nothing to more
565
+ const short = C.compact({ where: 'review · blockers', title: 'Pick a host', fact: 'waits on you', lines: ['one'], voices: [], more: null });
566
+ assert.deepEqual([short.where, short.title, short.fact, short.lines, short.more], ['review · blockers', 'Pick a host', 'waits on you', ['one'], null]);
567
+ });
568
+
569
+ test('the card has no fixed height: no min-height on its parts, every one-line part is held to one line', () => {
570
+ const css = read('street-card.css') + read('street.css');
571
+ const rules = [...css.matchAll(/([^{}]*street-card[^{}]*)\{([^}]*)\}/g)];
572
+ for (const [, sel, body] of rules) if (!/__close/.test(sel)) assert.doesNotMatch(body, /(^|;)\s*(min-height|height)\s*:/, 'no fixed height on ' + sel.trim());
573
+ const card = read('street-card.css');
574
+ for (const sel of ['.street-card__head .street-label', '.mcard-fact', '.street-card__lines li']) {
575
+ const body = card.split(sel + ' {')[1];
576
+ assert.ok(body && /white-space: nowrap/.test(body.split('}')[0]) && /text-overflow: ellipsis/.test(body.split('}')[0]), sel + ' is one line');
577
+ }
578
+ assert.match(card, /\.street-card__title \{[^}]*-webkit-line-clamp: 3/);
579
+ });
580
+
510
581
  // ── the Board Room names the new subjects ───────────────────────────────────
511
582
 
512
583
  test('the Board Room labels a building\'s matter as that building\'s matter, and a who-decides change as its own amendment', () => {
@@ -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-palette/);
638
+ assert.match(HTML, /shell\.js\?v=2026-10-04-nolegacy/);
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`);
@@ -79,7 +79,7 @@ test('THE PALETTE: in the top bar in Artistry alone, right before the switch, an
79
79
  assert.deepEqual(p.tools().map((t) => t.id), ['craft-tool-palette']);
80
80
  assert.equal(p.tools()[0].nextSibling, p.sw, 'it sits right before the mode switch');
81
81
  assert.equal(p.tools()[0].getAttribute('aria-label'), 'Write feedback on this page');
82
- for (const m of ['building', 'ideating', 'governing', 'legacy']) {
82
+ for (const m of ['building', 'ideating', 'governing']) {
83
83
  p.craft.choose(m);
84
84
  assert.equal(p.tools().length, 0, `no palette in ${m}`);
85
85
  }