@bongos/core 1.19.656 → 1.19.657

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,25 +9,14 @@
9
9
  rows are the oversight family's (oversight.css); the ?v= cache-bust is
10
10
  stamped from this file's content hash at boot (src/asset-version.js).
11
11
 
12
- TWO RULES ARE DUPLICATED FROM government.css, KNOWINGLY. `.gov-grid-note` is
13
- the government family's framing-line voice, and the sitting card spends it in
14
- three places (decidedByHtml, the own-idea note, the objection hint) while
15
- the Constitution panel still spends it over there. Copying two short rules is
16
- the cheaper of two bad options: the alternative is this page loading a
17
- stylesheet named for a different room in order to inherit them, which is what
18
- the first cut of this carve did and what the review called out. The NAMES stay
12
+ `.gov-grid-note` AND `.gov-held-note` LIVE IN oversight.css (task 1003761).
13
+ They were duplicated here under a note calling the copy the cheaper of two bad
14
+ options, the alternative being this page loading a stylesheet named for a
15
+ different room. oversight.css is named for neither room and is already loaded
16
+ by both pages, which is the option that note did not weigh. The NAMES stay
19
17
  `gov-*` because board-room.js emits them and
20
18
  tests/government_board_room_copy.mjs reads them as literal markup. */
21
19
 
22
- /* The framing line under a heading — the family voice, duplicated per the header
23
- note above rather than inherited across pages. */
24
- .gov-grid-note { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); max-width: var(--measure); }
25
- .gov-grid-note strong { color: var(--ink); font-weight: 500; }
26
- .gov-grid-note code { font-family: var(--font-mono); font-size: 12.5px; color: var(--ink); overflow-wrap: anywhere; }
27
-
28
- /* "your vote" beside the reader's own row in the open ballot. */
29
- .gov-held-note { font-size: 12.5px; color: var(--ok); white-space: nowrap; }
30
-
31
20
  /* ---- the Board Room: one sitting, one panel -------------------------------------
32
21
  The card order is task 1003270's (title, ballot, the idea, the apparatus); the
33
22
  surface is the shell's .scroll (task 1003469). The subject of the vote is the
@@ -65,6 +65,9 @@
65
65
  <script src="/builders/palette.js" defer></script>
66
66
  <script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
67
67
  <script src="/builders/hall-kit.js"></script>
68
+ <!-- task 1003761: the vocabulary this page and /government both speak. Must load
69
+ BEFORE board-room.js, which destructures window.OTBBoardLib at IIFE entry. -->
70
+ <script src="/builders/board-lib.js?v=2026-09-10-boardlib1"></script>
68
71
  <script src="/builders/board-room.js?v=2026-09-09-board-carve"></script>
69
72
  </body>
70
73
  </html>
@@ -35,14 +35,23 @@
35
35
  // reason and names one section. That requirement is why voting lives in the
36
36
  // hall and not on a chat reaction.
37
37
  //
38
- // ON THE DUPLICATED HELPERS. `pill` / `groupHead` / `when` / `fetchJson` /
39
- // `sendJson` are per-page in this module by convention (gate.js, harbor.js and
40
- // watch.js each carry their own a hall page script is a self-contained IIFE
41
- // over the DOM). Two are a real drift risk rather than boilerplate and are
42
- // called out where they sit: `membershipRankKeys` mirrors the SERVER's predicate
43
- // grammar, and `fmtWindow` is also read by the Constitution panel that stayed in
44
- // government.js. tests/government_board_vote_ui.mjs holds the first equal to the
45
- // server; keep the second in step by hand.
38
+ // ON THE SHARED HELPERS (task 1003761). The vocabulary this page and the
39
+ // Constitution panel on /government BOTH speak now lives in board-lib.js, loaded
40
+ // before this script as window.OTBBoardLib: the ladder and `membershipRankKeys`
41
+ // (which mirror the SERVER's predicate grammar), `rankNames`, `fmtWindow`, the
42
+ // two pass-rule maps, and the `pill` / `groupHead` / `when` row pieces. The carve
43
+ // that split the two pages copied all of it, which is the two-copies state
44
+ // modules/government/CLAUDE.md forbids — "the grammar is written ONCE".
45
+ // tests/government_constitution_view.mjs holds the lib's parser equal to the
46
+ // server's. (This header used to name tests/government_board_vote_ui.mjs for
47
+ // that; it never asserted the parser, so the copy that lived here was pinned by
48
+ // nothing.)
49
+ //
50
+ // `fetchJson` / `sendJson` stay per-page: a hall page script is a self-contained
51
+ // IIFE over the DOM, and those two carry this page's own error copy.
52
+ //
53
+ // SECTION_LABELS and fmtRemaining stay HERE, not in the lib — this page is their
54
+ // only reader, and the lib is for what the two pages share.
46
55
  //
47
56
  // The function ORDER here is deliberate and matches the pre-carve source:
48
57
  // tests/government_board_room_copy.mjs slices this file by function-name
@@ -57,8 +66,13 @@
57
66
  // /lib/bongos-client.js. baseUrl:'' passes the full `${API}${path}` URLs below
58
67
  // through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
59
68
  const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
60
- const { escapeHtml, fmtInt } = window.OTB;
69
+ const { escapeHtml } = window.OTB;
61
70
  const { emptyStateHtml } = window.OTBKit;
71
+ // The vocabulary /government and this page share (task 1003761). board-lib.js
72
+ // is loaded before this script; see the header note. The ladder and fmtInt went
73
+ // with it — groupHead was this page's only fmtInt reader.
74
+ const { membershipRankKeys, rankNames, fmtWindow,
75
+ PASS_RULE_PLAIN, pill, groupHead, when } = window.OTBBoardLib;
62
76
  const toast = (msg) => window.OTB.toast(msg);
63
77
 
64
78
  // ---- fetch helpers (mirror gate.js) --------------------------------------
@@ -98,52 +112,6 @@
98
112
  };
99
113
  const friendlyError = (code) => ERROR_COPY[code] || null;
100
114
 
101
- // ---- shared row pieces ---------------------------------------------------
102
-
103
- // A state pill in the shell's vocabulary. `mod` is one of the .fact-pill
104
- // states (ok / warn / danger / info / quiet); the label is what the reader sees.
105
- const pill = (mod, label) => `<span class="fact-pill fact-pill--${mod}">${escapeHtml(label)}</span>`;
106
- const groupHead = (name, n, note) =>
107
- `<div class="ov-group__head"><span class="ov-group__name">${escapeHtml(name)}</span>` +
108
- (n === undefined ? '' : `<span class="ov-group__n">${fmtInt(n)}</span>`) +
109
- (note ? `<span class="ov-group__note">${escapeHtml(note)}</span>` : '') +
110
- '</div>';
111
- // A timestamp the way the rows have always shown one: "2026-08-30 14:05".
112
- const when = (iso) => escapeHtml(String(iso || '').slice(0, 16).replace('T', ' '));
113
-
114
- // The predicate grammar, in plain language (task 1003094). This MIRRORS
115
- // modules/government/membership-predicate.js — browser code cannot import the
116
- // server module, the established pattern here (the five section labels do the
117
- // same) — and tests/government_board_vote_ui.mjs holds the two equal.
118
- //
119
- // Rendering the raw predicate was fine while the only form was `rank:<key>`.
120
- // It is not fine now: `rank:metic+` would read as "builders holding the metic+
121
- // rank", which names a rank that does not exist and hides the very thing the
122
- // suffix says.
123
- const STANDARD_LADDER = ['xenos', 'thetes', 'metic', 'archon'];
124
-
125
- function membershipRankKeys(predicate) {
126
- const m = String(predicate || '');
127
- if (!m.startsWith('rank:')) return null;
128
- const body = m.slice(5);
129
- if (!body) return null;
130
- const keys = [];
131
- for (const element of body.split(',')) {
132
- const match = /^([a-z][a-z0-9_-]*)(\+?)$/.exec(element);
133
- if (!match) return null;
134
- let expanded;
135
- if (match[2] === '+') {
136
- const i = STANDARD_LADDER.indexOf(match[1]);
137
- if (i === -1) return null; // "and above" is undefined off the ladder
138
- expanded = STANDARD_LADDER.slice(i);
139
- } else {
140
- expanded = [match[1]];
141
- }
142
- for (const k of expanded) if (keys.indexOf(k) === -1) keys.push(k);
143
- }
144
- return keys.length ? keys : null;
145
- }
146
-
147
115
  // ---- the Board Room panel (R16) --------------------------------------------
148
116
 
149
117
  // The room renders what the server assembled: open items with the database's
@@ -257,37 +225,9 @@
257
225
  //
258
226
  // Read from the item's SNAPSHOT constitution (the rules in force when the
259
227
  // window opened), never today's config — the same rules the server tallies
260
- // under. Rank KEYS never surface: the display label comes from the branding
261
- // pack via OTB.rankLabel, as on every other hall surface. The one place a raw
262
- // predicate still shows is an UNREADABLE one, where the broken string is the
263
- // actionable fact and hiding it would help nobody.
264
- function rankNames(keys) {
265
- const labels = keys.map((k) => escapeHtml(window.OTB.rankLabel(k)));
266
- if (labels.length === 1) return labels[0];
267
- return `${labels.slice(0, -1).join(', ')} or ${labels[labels.length - 1]}`;
268
- }
269
-
270
- // A sitting length in the unit a person would say it in. The config stores
271
- // minutes; "2880 minutes" is a number nobody converts in their head.
272
- //
273
- // DUPLICATED, KNOWINGLY: government.js keeps its own copy for the Constitution
274
- // panel's windowSentence. Two readers, two files, one wording — if you change
275
- // the phrasing here, change it there.
276
- function fmtWindow(minutes) {
277
- const m = Number(minutes);
278
- if (!Number.isFinite(m) || m <= 0) return `${escapeHtml(String(minutes))} minutes`;
279
- const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
280
- if (m % 1440 === 0) return plural(m / 1440, 'day');
281
- if (m % 60 === 0) return plural(m / 60, 'hour');
282
- return plural(m, 'minute');
283
- }
284
-
285
- const PASS_RULE_PLAIN = {
286
- first_ratifier: 'The first vote decides it: a yes ratifies, an objection returns it to its author.',
287
- consent: 'It passes unless someone objects — silence is agreement, and one reasoned objection returns it.',
288
- majority: 'It passes only when more than half the board votes yes. Silence is not agreement here.',
289
- unanimous: 'Everyone who votes must vote yes, and at least one member must. One objection sends it back for revision.',
290
- };
228
+ // under. `rankNames`, `fmtWindow` and `PASS_RULE_PLAIN` come from board-lib.js
229
+ // (task 1003761) the Constitution panel on /government says the same three
230
+ // things and used to say them from its own copies.
291
231
 
292
232
  function decidedByHtml(item) {
293
233
  const c = item.constitution || {};
@@ -23,11 +23,9 @@
23
23
  color: var(--ink);
24
24
  text-shadow: var(--type-halo);
25
25
  }
26
- .gov-grid-note { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); max-width: var(--measure); }
27
- .gov-grid-note strong { color: var(--ink); font-weight: 500; }
28
- .gov-grid-note code, .ov-row__sub code, .ov-row__title code, .ov-foot code {
29
- font-family: var(--font-mono); font-size: 12.5px; color: var(--ink); overflow-wrap: anywhere;
30
- }
26
+ /* `.gov-grid-note` (and the `.ov-*` code rule it shared a declaration with) moved
27
+ to oversight.css in task 1003761 /board-room renders it too and had a copy.
28
+ `.gov-h` stays: this page is its only renderer. */
31
29
 
32
30
  /* Holds the ladder-rank reset button. Empty for every other rank, so it collapses
33
31
  rather than reserving a gap — hence no margin on the empty box itself. */
@@ -57,9 +55,13 @@
57
55
  }
58
56
 
59
57
  /* ---- the Board Room: MOVED OUT to board-room.css (ADR 0266) ----------------
60
- The sitting card, the ballot controls and `.gov-held-note` went with the room
61
- when it became its own page. Nothing on THIS page renders them, so they are
62
- not kept here "just in case" board-room.css is where they live. */
58
+ The sitting card and the ballot controls went with the room when it became its
59
+ own page, and nothing here renders them.
60
+ CORRECTION (task 1003761): `.gov-held-note` went with them under the same
61
+ "nothing on THIS page renders them" reasoning, and that was wrong —
62
+ renderRankList marks a held Archon rank with one, so it rendered unstyled here
63
+ from the carve until this task. It lives in oversight.css now, which both
64
+ pages load. */
63
65
 
64
66
  /* ---- the charter library ------------------------------------------------------- */
65
67
  /* The configuration line under a charter: what an owner would type, in the
@@ -139,6 +139,9 @@
139
139
  <script src="/builders/palette.js" defer></script>
140
140
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
141
141
  <script src="/builders/hall-kit.js"></script>
142
+ <!-- task 1003761: the vocabulary this page and /board-room both speak. Must load
143
+ BEFORE government.js, which destructures window.OTBBoardLib at IIFE entry. -->
144
+ <script src="/builders/board-lib.js?v=2026-09-10-boardlib1"></script>
142
145
  <script src="/builders/government.js?v=2026-09-02-gov-rows"></script>
143
146
  </body>
144
147
  </html>
@@ -79,6 +79,10 @@
79
79
  const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
80
80
  const { escapeHtml, fmtInt } = window.OTB;
81
81
  const { emptyStateHtml } = window.OTBKit;
82
+ // The vocabulary this page and the Board Room share (task 1003761).
83
+ // board-lib.js is loaded before this script.
84
+ const { STANDARD_LADDER, membershipRankKeys, rankNames, fmtWindow,
85
+ PASS_RULE_NAME, pill, groupHead, when } = window.OTBBoardLib;
82
86
  const toast = (msg) => window.OTB.toast(msg);
83
87
 
84
88
  // ---- fetch helpers (mirror gate.js) --------------------------------------
@@ -137,19 +141,6 @@
137
141
 
138
142
  const selectedRank = () => ranks.find((r) => r.rank_key === selectedRankKey) || null;
139
143
 
140
- // ---- shared row pieces ---------------------------------------------------
141
-
142
- // A state pill in the shell's vocabulary. `mod` is one of the .fact-pill
143
- // states (ok / warn / danger / info / quiet); the label is what the reader sees.
144
- const pill = (mod, label) => `<span class="fact-pill fact-pill--${mod}">${escapeHtml(label)}</span>`;
145
- const groupHead = (name, n, note) =>
146
- `<div class="ov-group__head"><span class="ov-group__name">${escapeHtml(name)}</span>` +
147
- (n === undefined ? '' : `<span class="ov-group__n">${fmtInt(n)}</span>`) +
148
- (note ? `<span class="ov-group__note">${escapeHtml(note)}</span>` : '') +
149
- '</div>';
150
- // A timestamp the way the rows have always shown one: "2026-08-30 14:05".
151
- const when = (iso) => escapeHtml(String(iso || '').slice(0, 16).replace('T', ' '));
152
-
153
144
  // ---- render: rank list ---------------------------------------------------
154
145
 
155
146
  // One rank, one selectable oversight row: the label, the DESCRIPTION the
@@ -539,39 +530,12 @@
539
530
  // switch can never be flipped quietly, and every change is auditable against
540
531
  // the constitution of its day.
541
532
 
542
- // The predicate grammar, in plain language (task 1003094). This MIRRORS
543
- // modules/government/membership-predicate.js browser code cannot import the
544
- // server module, the established pattern here (the five section labels do the
545
- // same) and tests/government_board_vote_ui.mjs holds the two equal.
546
- //
547
- // Rendering the raw predicate was fine while the only form was `rank:<key>`.
548
- // It is not fine now: `rank:metic+` would read as "builders holding the metic+
549
- // rank", which names a rank that does not exist and hides the very thing the
550
- // suffix says. A constitution panel that misdescribes who governs is the
551
- // failure this whole task exists to fix, one layer up.
552
- const STANDARD_LADDER = ['xenos', 'thetes', 'metic', 'archon'];
553
-
554
- function membershipRankKeys(predicate) {
555
- const m = String(predicate || '');
556
- if (!m.startsWith('rank:')) return null;
557
- const body = m.slice(5);
558
- if (!body) return null;
559
- const keys = [];
560
- for (const element of body.split(',')) {
561
- const match = /^([a-z][a-z0-9_-]*)(\+?)$/.exec(element);
562
- if (!match) return null;
563
- let expanded;
564
- if (match[2] === '+') {
565
- const i = STANDARD_LADDER.indexOf(match[1]);
566
- if (i === -1) return null; // "and above" is undefined off the ladder
567
- expanded = STANDARD_LADDER.slice(i);
568
- } else {
569
- expanded = [match[1]];
570
- }
571
- for (const k of expanded) if (keys.indexOf(k) === -1) keys.push(k);
572
- }
573
- return keys.length ? keys : null;
574
- }
533
+ // The predicate grammar and the ladder it expands against come from
534
+ // board-lib.js (task 1003761). They MIRROR modules/government/
535
+ // membership-predicate.js browser code cannot import the server module
536
+ // and the carve that took the Board Room onto its own page had left a second
537
+ // copy here. A constitution panel that misdescribes who governs is the failure
538
+ // this whole area exists to fix, and two copies is how it gets there.
575
539
 
576
540
  function membershipSentence(board) {
577
541
  const raw = String(board.membership || '');
@@ -682,18 +646,10 @@
682
646
  close_early_on_full_turnout: 'Closing early on full turnout',
683
647
  };
684
648
 
685
- // A list of rank keys as their display labels, in a phrase a person would say
686
- // ("Metic or Archon"). Carved-region residue with a twist (ADR 0266): the copy
687
- // that lived here went to board-room.js with the sitting card, and this one
688
- // came BACK because the divergence warning below (task 1003739, ADR 0268)
689
- // landed a new reader on the Constitution side while the room was moving out.
690
- // Its only caller is fieldValueHtml; membershipSentence above spells its own
691
- // labels inline, so do not route that through here expecting one wording.
692
- function rankNames(keys) {
693
- const labels = keys.map((k) => escapeHtml(window.OTB.rankLabel(k)));
694
- if (labels.length === 1) return labels[0];
695
- return `${labels.slice(0, -1).join(', ')} or ${labels[labels.length - 1]}`;
696
- }
649
+ // `rankNames` comes from board-lib.js (task 1003761). Its only caller here is
650
+ // fieldValueHtml; membershipSentence above spells its own labels inline because
651
+ // it wraps each in <strong>, so do not route that through it expecting one
652
+ // wording.
697
653
 
698
654
  // One constitution VALUE, in the words a person would use — the same rule the
699
655
  // dials keep, applied to the two halves of a divergence. Without this the
@@ -716,32 +672,11 @@
716
672
 
717
673
  // One dial, one row: the dial's name as the title, the live sentence as the
718
674
  // sub line. The sentence is trusted HTML from the helpers above.
719
- // A sitting length in the unit a person would say it in. The config stores
720
- // minutes; "2880 minutes" is a number nobody converts in their head.
721
675
  //
722
- // Lived in the Board Room region until ADR 0266 carved that room onto its own
723
- // page. It stayed because windowSentence above reads it; board-room.js keeps
724
- // its own copy for the sitting card. Two readers, two files, one wording — if
725
- // you change the phrasing here, change it there.
726
- function fmtWindow(minutes) {
727
- const m = Number(minutes);
728
- if (!Number.isFinite(m) || m <= 0) return `${escapeHtml(String(minutes))} minutes`;
729
- const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
730
- if (m % 1440 === 0) return plural(m / 1440, 'day');
731
- if (m % 60 === 0) return plural(m / 60, 'hour');
732
- return plural(m, 'minute');
733
- }
734
-
735
- // The short name of a pass rule, for a place a whole sentence will not fit —
736
- // the change-history line below. Also carved-region residue (ADR 0266): the
737
- // sentence-length twin PASS_RULE_PLAIN went to board-room.js with the card,
738
- // this one stayed with its only reader, historyRow.
739
- const PASS_RULE_NAME = {
740
- first_ratifier: 'by first ratifier',
741
- consent: 'by consent',
742
- majority: 'by majority',
743
- unanimous: 'by unanimous agreement',
744
- };
676
+ // `fmtWindow` and `PASS_RULE_NAME` come from board-lib.js (task 1003761).
677
+ // fmtWindow had a twin in board-room.js under a "keep the phrasing in step by
678
+ // hand" note; PASS_RULE_NAME sits beside its sentence-length twin
679
+ // PASS_RULE_PLAIN there, so the two renderings of one pass rule cannot drift.
745
680
 
746
681
  function dialRow(name, sentenceHtml) {
747
682
  return `<li class="ov-row"><div class="ov-row__main"><span class="ov-row__title">${escapeHtml(name)}</span><p class="ov-row__sub">${sentenceHtml}</p></div></li>`;
@@ -794,6 +729,14 @@
794
729
  // `predicate: null` means the predicate is the owner's to choose (Foundation
795
730
  // names their own custom rank), which is honest rather than unchecked — the
796
731
  // test asserts such a charter says so in its `how`.
732
+ //
733
+ // A SECOND test pins these too, for a different reason. The `rank:xenos+` row
734
+ // spells the full ladder out in `seatsRanks`, and that literal is why this file
735
+ // stays in tests/rank_tier_single_source.mjs's STATIC_LADDER_FILES even though
736
+ // task 1003761 moved the page's real ladder to board-lib.js. It is documentation
737
+ // of the grammar rather than a second implementation of it — but a worked
738
+ // example that drifts teaches a ladder nobody has, so it is held to the
739
+ // canonical order.
797
740
  const CHARTERS = [
798
741
  { key: 'monarchy', name: 'Monarchy / BDFL', selectable: true,
799
742
  predicate: 'rank:archon', seatsRanks: ['archon'],
@@ -919,8 +862,11 @@
919
862
  // What stayed behind, and why, so neither reads as a leftover:
920
863
  // • fmtWindow — windowSentence (the Constitution's clock dial) reads it.
921
864
  // • PASS_RULE_NAME — historyRow (the amendment history line) reads it.
922
- // Both are duplicated in board-room.js rather than shared; a hall page script
923
- // is a self-contained IIFE, which is the convention here.
865
+ // Both READERS are still here; the two helpers themselves are not. Task 1003761
866
+ // hoisted them into board-lib.js, which this page destructures at IIFE entry.
867
+ // This block used to end "both are duplicated in board-room.js rather than
868
+ // shared; a hall page script is a self-contained IIFE, which is the convention
869
+ // here" — that was the justification for the copies, and it is retired.
924
870
  //
925
871
  // The old deep link is redirected below in init() — a fragment never reaches
926
872
  // the server, so #board-room could not be caught by a route.
@@ -178,6 +178,37 @@ a.fact-chip:hover { color: var(--accent-ink); border-color: var(--accent-ink); t
178
178
  .ov-danger { color: var(--danger); }
179
179
  .ov-ok { color: var(--ok); }
180
180
 
181
+ /* ---- the government family's two shared lines (task 1003761) -----------------
182
+ Both are rendered by /government AND /board-room, so they belong to the family
183
+ sheet both pages load rather than to either page's own. ADR 0266's carve had
184
+ put them elsewhere, and each half of that was wrong in its own way:
185
+
186
+ `.gov-grid-note` was COPIED into board-room.css under a header note calling
187
+ the duplication the cheaper of two bad options — the alternative it weighed
188
+ was one page loading a sheet named for the other page's room. This sheet is
189
+ named for neither room, which is the option that was missing.
190
+
191
+ `.gov-held-note` was MOVED to board-room.css under a note in government.css
192
+ saying "nothing on THIS page renders them". government.js does: it marks a
193
+ held Archon rank with one (renderRankList). So that line has rendered unstyled
194
+ on /government since the carve — a live defect, not a tidiness point.
195
+
196
+ The NAMES stay `gov-*`: board-room.js and government.js emit them, and
197
+ tests/government_board_room_copy.mjs reads `.gov-grid-note` as literal markup.
198
+ The `.ov-*` selectors on the code rule came with it from government.css, where
199
+ they were already sharing the declaration. */
200
+
201
+ /* The framing line under a heading — the family's voice. */
202
+ .gov-grid-note { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); max-width: var(--measure); }
203
+ .gov-grid-note strong { color: var(--ink); font-weight: 500; }
204
+ .gov-grid-note code, .ov-row__sub code, .ov-row__title code, .ov-foot code {
205
+ font-family: var(--font-mono); font-size: 12.5px; color: var(--ink); overflow-wrap: anywhere;
206
+ }
207
+
208
+ /* "your vote" beside the reader's own row in the open ballot (/board-room), and
209
+ "held" beside an Archon-held rank (/government). */
210
+ .gov-held-note { font-size: 12.5px; color: var(--ok); white-space: nowrap; }
211
+
181
212
  /* Detail lines under a row: a reason per line (the gate's holds), or a key and
182
213
  a value (the harbor's make and cost). Hairlines between, none on the first. */
183
214
  .ov-lines { list-style: none; margin: 10px 0 0; padding: 0; max-width: 72ch; }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.656",
3
+ "version": "1.19.657",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.656",
9
+ "version": "1.19.657",
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.656",
3
+ "version": "1.19.657",
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",
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.656'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.657'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -36,6 +36,10 @@ const CLIENT = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', '
36
36
  const HTML = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'government.html'), 'utf8');
37
37
  const BOARD = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'board-room.js'), 'utf8');
38
38
  const BOARD_HTML = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'board-room.html'), 'utf8');
39
+ // The vocabulary both pages speak (task 1003761). The pass-rule maps and
40
+ // rankNames were one copy per page until the hoist; the assertions that read
41
+ // them come here now, and there is one place to read rather than two.
42
+ const LIB = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'board-lib.js'), 'utf8');
39
43
  const { PASS_RULES } = require(path.join(ROOT, 'modules', 'government', 'config.js'));
40
44
 
41
45
  const card = () => BOARD.slice(BOARD.indexOf('function itemCard'), BOARD.indexOf('async function loadBoard'));
@@ -47,7 +51,13 @@ const card = () => BOARD.slice(BOARD.indexOf('function itemCard'), BOARD.indexOf
47
51
  const CODE = BOARD.split(/\r?\n/).filter((l) => !/^\s*\/\//.test(l)).join('\n');
48
52
 
49
53
  test('every pass rule the server can hold has a plain-language sentence', () => {
50
- const block = BOARD.slice(BOARD.indexOf('const PASS_RULE_PLAIN'), BOARD.indexOf('function decidedByHtml'));
54
+ // In board-lib.js since task 1003761. Both landmarks are asserted before the
55
+ // slice for the reason the next test spells out: an indexOf that misses
56
+ // returns -1, and slice(start, -1) would hand this most of the file.
57
+ const start = LIB.indexOf('const PASS_RULE_PLAIN');
58
+ const end = LIB.indexOf('shared row pieces');
59
+ assert.ok(start !== -1 && end > start, 'PASS_RULE_PLAIN sits above the row pieces in board-lib.js');
60
+ const block = LIB.slice(start, end);
51
61
  assert.ok(PASS_RULES.length >= 3, `sanity: found the real rule set (${PASS_RULES})`);
52
62
  for (const rule of PASS_RULES) {
53
63
  assert.ok(block.includes(`${rule}:`), `PASS_RULE_PLAIN must explain '${rule}' — a rule with no copy renders its enum key`);
@@ -60,24 +70,24 @@ test('every pass rule the server can hold has a plain-language sentence', () =>
60
70
  });
61
71
 
62
72
  test('the short name map covers the same rule set (the history line)', () => {
63
- // Its reader (historyRow, the amendment change-history) stayed on the
64
- // government page when ADR 0266 carved the room out, so this map stayed with
65
- // it. The slice therefore ends at a landmark that is still IN government.js —
66
- // it used to end at `const PASS_RULE_PLAIN`, which has moved to board-room.js,
67
- // and an indexOf that misses returns -1: `slice(start, -1)` would have handed
68
- // this assertion most of the file and passed for the wrong reason.
69
- const start = CLIENT.indexOf('const PASS_RULE_NAME');
70
- const end = CLIENT.indexOf('function dialRow');
71
- assert.ok(start !== -1 && end > start, 'PASS_RULE_NAME sits above dialRow in government.js');
72
- const block = CLIENT.slice(start, end);
73
+ // Its reader (historyRow, the amendment change-history) is on the government
74
+ // page; the map itself joined its sentence-length twin in board-lib.js in task
75
+ // 1003761, so the two renderings of one pass rule sit adjacent and cannot
76
+ // describe the same rule differently. The landmark check stays and matters:
77
+ // an indexOf that misses returns -1, and `slice(start, -1)` would hand this
78
+ // assertion most of the file and pass for the wrong reason.
79
+ const start = LIB.indexOf('const PASS_RULE_NAME');
80
+ const end = LIB.indexOf('const PASS_RULE_PLAIN');
81
+ assert.ok(start !== -1 && end > start, 'PASS_RULE_NAME sits above PASS_RULE_PLAIN in board-lib.js');
82
+ const block = LIB.slice(start, end);
73
83
  for (const rule of PASS_RULES) {
74
84
  assert.ok(block.includes(`${rule}:`), `PASS_RULE_NAME must name '${rule}'`);
75
85
  }
76
86
  });
77
87
 
78
88
  test('a rank reaches the reader as a LABEL, never as the enum key', () => {
79
- assert.match(BOARD, /function rankNames\(keys\)[\s\S]*?window\.OTB\.rankLabel\(k\)/,
80
- 'the board card resolves rank keys through the branding pack');
89
+ assert.match(LIB, /function rankNames\(keys\)[\s\S]*?window\.OTB\.rankLabel\(k\)/,
90
+ 'the shared vocabulary resolves rank keys through the branding pack');
81
91
  const membership = CLIENT.slice(CLIENT.indexOf('function membershipSentence'), CLIENT.indexOf('function passRuleSentence'));
82
92
  assert.match(membership, /window\.OTB\.rankLabel\(k\)/,
83
93
  'so does the Constitution sentence — `archon` is an enum, "Owner" is what a person reads');
@@ -101,7 +111,13 @@ test('the card order is title → action → the idea → the apparatus', () =>
101
111
  });
102
112
 
103
113
  test('the idea renders OPEN — never behind a summary', () => {
104
- const sections = BOARD.slice(BOARD.indexOf('function ideaSectionsHtml'), BOARD.indexOf('function rankNames'));
114
+ // Ends at decidedByHtml, the declaration that now follows: rankNames was the
115
+ // old landmark and moved to board-lib.js in task 1003761. Asserted, not
116
+ // assumed — a missing landmark is -1 and would silently invert this test.
117
+ const from = BOARD.indexOf('function ideaSectionsHtml');
118
+ const to = BOARD.indexOf('function decidedByHtml');
119
+ assert.ok(from !== -1 && to > from, 'ideaSectionsHtml sits above decidedByHtml in board-room.js');
120
+ const sections = BOARD.slice(from, to);
105
121
  assert.ok(!/<details/.test(sections),
106
122
  'the substance of the vote is not a disclosure — that was the reported bug');
107
123
  assert.match(sections, /gov-idea__body/, 'and it is set as prose, not as a config row');
@@ -95,12 +95,17 @@ test('the read rides the vote atom, and the client renders the SERVER value (sou
95
95
  // of a grammar is exactly the drift that produced the bug this task fixes, so
96
96
  // they are held equal here over every form.
97
97
  //
98
+ // The mirror lives in modules/hall-ui/public/board-lib.js since task 1003761.
99
+ // It was in government.js, and ADR 0266's carve left a SECOND copy in
100
+ // board-room.js — so this test lifted one of two implementations and proved
101
+ // nothing about the other. One copy, one lift.
102
+ //
98
103
  // This matters more than a normal mirror: the Constitution panel is where an
99
104
  // owner READS who governs before deciding whether to change it. A panel that
100
105
  // misdescribes the franchise is the same failure as a resolver that mis-seats
101
106
  // it, one layer up.
102
107
  test('the hall renders every predicate form the server actually resolves', () => {
103
- const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/government.js'), 'utf8');
108
+ const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/board-lib.js'), 'utf8');
104
109
 
105
110
  // Lift the browser mirror out of the IIFE and run it.
106
111
  const ladder = /const STANDARD_LADDER = (\[[^\]]*\]);/.exec(src);
@@ -131,7 +136,7 @@ test('the hall renders every predicate form the server actually resolves', () =>
131
136
  });
132
137
 
133
138
  test('the ladder the hall expands against IS the government ladder', () => {
134
- const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/government.js'), 'utf8');
139
+ const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/board-lib.js'), 'utf8');
135
140
  const { RANK_ORDER } = require(path.join(ROOT, 'modules/government/catalog.js'));
136
141
  const m = /const STANDARD_LADDER = \[([^\]]*)\]/.exec(src);
137
142
  assert.ok(m);