@bongos/core 1.24.50 → 1.24.52

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 (86) hide show
  1. package/.bongos-core.json +104 -89
  2. package/docs/copy-inventory.md +259 -277
  3. package/docs/copy-registry.json +461 -634
  4. package/docs/file-map.md +1 -1
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/page-inventory.json +35 -2
  7. package/docs/page-readings.json +518 -547
  8. package/modules/hall-ui/public/ai-studio.html +1 -0
  9. package/modules/hall-ui/public/approval-queue.css +3 -11
  10. package/modules/hall-ui/public/approval-queue.js +13 -20
  11. package/modules/hall-ui/public/atlas.html +1 -0
  12. package/modules/hall-ui/public/blockers.html +1 -0
  13. package/modules/hall-ui/public/board-room.html +1 -0
  14. package/modules/hall-ui/public/bulk-toolbar.js +5 -1
  15. package/modules/hall-ui/public/city.html +1 -0
  16. package/modules/hall-ui/public/collab.html +1 -0
  17. package/modules/hall-ui/public/commands.html +1 -0
  18. package/modules/hall-ui/public/controls.css +100 -0
  19. package/modules/hall-ui/public/copy-desk.css +7 -11
  20. package/modules/hall-ui/public/copy-desk.html +17 -16
  21. package/modules/hall-ui/public/copy-desk.js +13 -9
  22. package/modules/hall-ui/public/deploy.html +1 -0
  23. package/modules/hall-ui/public/goal-inbox.js +32 -17
  24. package/modules/hall-ui/public/goal-map.html +1 -0
  25. package/modules/hall-ui/public/goals-page.js +7 -1
  26. package/modules/hall-ui/public/goals.html +1 -0
  27. package/modules/hall-ui/public/government.html +1 -0
  28. package/modules/hall-ui/public/government.js +10 -2
  29. package/modules/hall-ui/public/hall-dialog.js +255 -0
  30. package/modules/hall-ui/public/hall-kit.js +9 -2
  31. package/modules/hall-ui/public/idea.html +1 -0
  32. package/modules/hall-ui/public/ideas.html +1 -0
  33. package/modules/hall-ui/public/index.html +1 -0
  34. package/modules/hall-ui/public/market-watch-item.html +2 -1
  35. package/modules/hall-ui/public/market-watch-lib.js +16 -9
  36. package/modules/hall-ui/public/market-watch-rules.html +1 -0
  37. package/modules/hall-ui/public/market-watch.css +2 -3
  38. package/modules/hall-ui/public/market-watch.html +2 -1
  39. package/modules/hall-ui/public/modules.html +1 -0
  40. package/modules/hall-ui/public/money.html +1 -0
  41. package/modules/hall-ui/public/my-agents.html +1 -0
  42. package/modules/hall-ui/public/oversight.css +3 -33
  43. package/modules/hall-ui/public/primer.html +1 -0
  44. package/modules/hall-ui/public/profile.html +1 -0
  45. package/modules/hall-ui/public/project-settings.html +1 -0
  46. package/modules/hall-ui/public/roster.html +1 -0
  47. package/modules/hall-ui/public/settings-ai-keys.js +5 -1
  48. package/modules/hall-ui/public/settings-passkeys.js +5 -1
  49. package/modules/hall-ui/public/settings-sessions.js +10 -2
  50. package/modules/hall-ui/public/settings.html +1 -0
  51. package/modules/hall-ui/public/settings.js +5 -1
  52. package/modules/hall-ui/public/street.html +1 -0
  53. package/modules/hall-ui/public/studio.css +6 -32
  54. package/modules/hall-ui/public/studio.html +4 -3
  55. package/modules/hall-ui/public/studio.js +40 -35
  56. package/modules/hall-ui/public/style.css +2 -8
  57. package/modules/hall-ui/public/task.html +1 -0
  58. package/modules/hall-ui/public/task.js +26 -12
  59. package/modules/hall-ui/public/thinking.html +1 -0
  60. package/modules/hall-ui/public/tweak-editor.html +1 -0
  61. package/modules/hall-ui/public/watch.html +1 -0
  62. package/modules/hall-ui/public/work.html +1 -0
  63. package/modules/hall-ui/public/work.js +7 -1
  64. package/modules/status-ui/public/index.html +43 -43
  65. package/modules/status-ui/public/status.js +26 -27
  66. package/package-lock.json +2 -2
  67. package/package.json +1 -1
  68. package/release-notes.json +12 -0
  69. package/src/module-api.js +1 -1
  70. package/tests/bulk_toolbar.mjs +1 -1
  71. package/tests/hall_approval_queue.mjs +8 -2
  72. package/tests/hall_audit.mjs +1 -1
  73. package/tests/hall_city.mjs +1 -1
  74. package/tests/hall_collab_world.mjs +9 -6
  75. package/tests/hall_dialog.mjs +401 -0
  76. package/tests/hall_error_envelope.mjs +1 -1
  77. package/tests/hall_oversight_world.mjs +9 -7
  78. package/tests/hall_record_world.mjs +1 -1
  79. package/tests/hall_street.mjs +1 -1
  80. package/tests/hall_studio_home.mjs +1 -1
  81. package/tests/home_attention_cards.mjs +2 -0
  82. package/tests/kind_axis_unset.mjs +1 -1
  83. package/tests/loading_mark.mjs +6 -6
  84. package/tests/market_watch_pages.mjs +3 -1
  85. package/tests/status_company_attribution.mjs +1 -1
  86. package/tests/status_page_wording.mjs +129 -0
@@ -3,8 +3,8 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>{{productName}} — the public ledger</title>
7
- <meta name="description" content="The public ledger of {{worldName}} — {{currency}} spent, works wrought, and the citizens who shape her stones.">
6
+ <title>{{productName}} — build status</title>
7
+ <meta name="description" content="How {{worldName}} is being built, in the open: what shipped, what it cost, and who built it.">
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="apple-touch-icon" href="/modules/ui-design/public/brand/apple-touch-icon.png">
10
10
  <!-- The faces are the pack's: the server (and the off-box mirror) splice the pack's
@@ -30,12 +30,12 @@
30
30
 
31
31
  <section class="herm" aria-labelledby="herm-title">
32
32
  <h1 id="herm-title">{{worldName}}</h1>
33
- <p class="herm__epigraph">The Public Ledger of the City's Making</p>
34
- <p class="herm__sub">A shipping container has washed ashore upon the coast south of Athens. About it a new civilization rises. Set out below: the city's affairs, openly recorded.</p>
33
+ <p class="herm__epigraph">Build status, in the open</p>
34
+ <p class="herm__sub">Everything here is built in public. This page shows what shipped, what it cost, and who built it, and it updates every minute.</p>
35
35
  <p class="herm__meta">
36
- <a href="{{publicOrigin}}" target="_blank" rel="noopener">Cross into the city →</a>
36
+ <a href="{{publicOrigin}}" target="_blank" rel="noopener">Go to {{productName}} →</a>
37
37
  <span class="dot" aria-hidden="true">·</span>
38
- <span class="herm__updated" id="last-updated">Tablet being consulted…</span>
38
+ <span class="herm__updated" id="last-updated">Checking for updates…</span>
39
39
  </p>
40
40
  </section>
41
41
 
@@ -43,7 +43,7 @@
43
43
  (public-builders/style.css .hall-nav__*). Built from a SECTIONS constant
44
44
  in status.js. Empty until populated — [hidden] keeps the strip from
45
45
  flashing as an empty band before chips arrive. -->
46
- <nav id="status-nav" class="status-nav" aria-label="Sections of the ledger" hidden>
46
+ <nav id="status-nav" class="status-nav" aria-label="Sections of the page" hidden>
47
47
  <div class="status-nav__inner">
48
48
  <ul class="status-nav__list" id="status-nav-list"></ul>
49
49
  </div>
@@ -57,22 +57,22 @@
57
57
  loadHeadline() in status.js, fed by the existing public endpoints
58
58
  (recent-shipped, cost-summary, grades). -->
59
59
  <section class="tablet tablet--headline" id="headline-tablet" aria-labelledby="headline-heading">
60
- <h2 id="headline-heading" class="visually-hidden">The City At A Glance</h2>
60
+ <h2 id="headline-heading" class="visually-hidden">At a glance</h2>
61
61
  <div class="headline-grid">
62
62
  <div class="stat headline-stat" id="headline-shipped">
63
63
  <div class="stat__num" id="headline-shipped-num">—</div>
64
- <div class="stat__label">works wrought, last 7 days</div>
64
+ <div class="stat__label">tasks shipped, last 7 days</div>
65
65
  </div>
66
66
  <div class="stat headline-stat" id="headline-treasury">
67
67
  <div class="stat__num" id="headline-treasury-num">—</div>
68
- <div class="stat__label" id="headline-treasury-label">of the year's purse spent</div>
68
+ <div class="stat__label" id="headline-treasury-label">of this year's budget spent</div>
69
69
  <div class="headline-bar" aria-hidden="true">
70
70
  <div class="headline-bar__fill" id="headline-treasury-bar" style="width: 0%"></div>
71
71
  </div>
72
72
  </div>
73
73
  <div class="stat headline-stat" id="headline-grade">
74
74
  <div class="stat__num" id="headline-grade-num">—</div>
75
- <div class="stat__label">passed the inspector, last 7 days</div>
75
+ <div class="stat__label">passed review, last 7 days</div>
76
76
  </div>
77
77
  </div>
78
78
  </section>
@@ -80,49 +80,49 @@
80
80
  <section class="tablet" id="leader-tablet" aria-labelledby="leader-heading">
81
81
  <div class="tablet__head">
82
82
  <div class="tablet__heading">
83
- <h2 id="leader-heading">Architects of {{worldName}}</h2>
84
- <p class="tablet__subtitle">Citizens who have set the most stone.</p>
83
+ <h2 id="leader-heading">Top builders</h2>
84
+ <p class="tablet__subtitle">Ranked by the {{currency}} each has earned.</p>
85
85
  </div>
86
86
  </div>
87
87
  <ol id="leaderboard" class="leader">
88
- <li class="empty">No architects yet — be the first to lay a stone.</li>
88
+ <li class="empty">No builders yet — be the first.</li>
89
89
  </ol>
90
90
  </section>
91
91
 
92
92
  <section class="tablet" id="foundry-tablet" aria-labelledby="foundry-heading">
93
93
  <div class="tablet__head">
94
94
  <div class="tablet__heading">
95
- <h2 id="foundry-heading">The Foundry</h2>
96
- <p class="tablet__subtitle">How the city's craft is changing — works shipped and time per work over the last ninety days. The whole, not the hands.</p>
95
+ <h2 id="foundry-heading">How the work is changing</h2>
96
+ <p class="tablet__subtitle">Tasks shipped and the time each took, over the last 90 days. The project as a whole, never one builder.</p>
97
97
  </div>
98
98
  </div>
99
99
  <div class="cost-summary" id="foundry-stats">
100
- <div class="stat"><div class="stat__num" id="foundry-shipped">—</div><div class="stat__label">works shipped (90d)</div></div>
101
- <div class="stat"><div class="stat__num" id="foundry-builders">—</div><div class="stat__label">hands at the forge</div></div>
102
- <div class="stat"><div class="stat__num" id="foundry-duration">—</div><div class="stat__label">avg time / work</div></div>
100
+ <div class="stat"><div class="stat__num" id="foundry-shipped">—</div><div class="stat__label">tasks shipped (90 days)</div></div>
101
+ <div class="stat"><div class="stat__num" id="foundry-builders">—</div><div class="stat__label">active builders</div></div>
102
+ <div class="stat"><div class="stat__num" id="foundry-duration">—</div><div class="stat__label">average time per task</div></div>
103
103
  </div>
104
104
  <div class="foundry__charts" id="foundry-charts"></div>
105
105
  <div class="foundry__kinds" id="foundry-kinds"></div>
106
- <p class="chart-note" id="foundry-empty" hidden>Not enough shipped works yet to draw the curve.</p>
106
+ <p class="chart-note" id="foundry-empty" hidden>Not enough tasks have shipped yet to draw a trend.</p>
107
107
  </section>
108
108
 
109
109
  <section class="tablet" id="versions-tablet" aria-labelledby="versions-heading">
110
110
  <div class="tablet__head">
111
111
  <div class="tablet__heading">
112
- <h2 id="versions-heading">Works in Progress</h2>
113
- <p class="tablet__subtitle">The endeavours now underway in the city.</p>
112
+ <h2 id="versions-heading">In progress</h2>
113
+ <p class="tablet__subtitle">The versions being built now.</p>
114
114
  </div>
115
115
  </div>
116
116
  <div id="versions" class="versions">
117
- <p class="empty loading-mark" role="status">The masons are at their tools…</p>
117
+ <p class="empty loading-mark" role="status">Loading the versions…</p>
118
118
  </div>
119
119
  </section>
120
120
 
121
121
  <section class="tablet" id="cost-tablet" aria-labelledby="cost-heading">
122
122
  <div class="tablet__head">
123
123
  <div class="tablet__heading">
124
- <h2 id="cost-heading">The Treasury</h2>
125
- <p class="tablet__subtitle">{{Currency}} spent, against the value wrought.</p>
124
+ <h2 id="cost-heading">Spending</h2>
125
+ <p class="tablet__subtitle">What the build has cost, against the value it has produced.</p>
126
126
  </div>
127
127
  </div>
128
128
  <div class="cost-summary">
@@ -132,21 +132,21 @@
132
132
  </div>
133
133
  <div class="stat">
134
134
  <div class="stat__num" id="value-total">—</div>
135
- <div class="stat__label">value wrought</div>
135
+ <div class="stat__label">value produced</div>
136
136
  </div>
137
137
  <div class="stat">
138
138
  <div class="stat__num" id="cost-budget">5,000</div>
139
- <div class="stat__label">year's purse</div>
139
+ <div class="stat__label">yearly budget</div>
140
140
  </div>
141
141
  <div class="stat">
142
142
  <div class="stat__num" id="cost-percent">—</div>
143
- <div class="stat__label">of the purse</div>
143
+ <div class="stat__label">of the budget</div>
144
144
  </div>
145
145
  </div>
146
146
  <div class="chart-wrap">
147
- <canvas id="cost-chart" aria-label="cumulative {{currency}} spent against cumulative value wrought, day by day" role="img"></canvas>
147
+ <canvas id="cost-chart" aria-label="cumulative {{currency}} spent against cumulative value produced, day by day" role="img"></canvas>
148
148
  </div>
149
- <p class="chart-note">The line of value above the line of spend means the city is wrought richer than it is dear. <em>The chart above always shows the whole project; the filters below slice the breakdown.</em></p>
149
+ <p class="chart-note">When the value line sits above the spend line, the work is worth more than it cost. <em>The chart always shows the whole project; the filters below change only the breakdown.</em></p>
150
150
  <div class="cost-filters" role="group" aria-label="Filter the spend breakdown" style="display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end;margin:6px 0 10px;">
151
151
  <div class="tablet__filter">
152
152
  <label for="cost-filter-source" class="tablet__filter-label">Source</label>
@@ -172,7 +172,7 @@
172
172
  </div>
173
173
  <p id="cost-filter-note" class="cost-split" aria-live="polite"></p>
174
174
  <div id="cost-categories" class="cost-cats"></div>
175
- <p class="chart-note">Split by hand — {{currency}} spent by each builder at the workshop. <em>Unattributed</em> is the shared keep: infrastructure and domains the whole city pays for, plus the early days before each hand carried its own purse — never quietly charged to one builder.</p>
175
+ <p class="chart-note">Spending by builder. <em>Unattributed</em> is shared cost: the infrastructure and domains the whole project pays for, plus early spending from before costs were tracked per builder. It is never charged to one builder.</p>
176
176
  <p id="cost-attribution-split" class="cost-split" aria-live="polite"></p>
177
177
  <div id="cost-builders" class="cost-cats"></div>
178
178
  </section>
@@ -180,12 +180,12 @@
180
180
  <section class="tablet" id="shipped-tablet" aria-labelledby="shipped-heading">
181
181
  <div class="tablet__head">
182
182
  <div class="tablet__heading">
183
- <h2 id="shipped-heading">Lately Inscribed</h2>
184
- <p class="tablet__subtitle">Newest entries set down upon the ledger.</p>
183
+ <h2 id="shipped-heading">Recently shipped</h2>
184
+ <p class="tablet__subtitle">The newest finished tasks.</p>
185
185
  </div>
186
186
  <div class="tablet__filter">
187
187
  <label for="shipped-range" class="tablet__filter-label">Window</label>
188
- <select id="shipped-range" class="tablet__filter-select" aria-label="Filter inscriptions by date range">
188
+ <select id="shipped-range" class="tablet__filter-select" aria-label="Filter shipped tasks by date range">
189
189
  <option value="7">Last 7 days</option>
190
190
  <option value="30" selected>Last 30 days</option>
191
191
  <option value="90">Last 90 days</option>
@@ -194,9 +194,9 @@
194
194
  </div>
195
195
  </div>
196
196
  <ul id="shipped" class="shipped">
197
- <li class="empty">No works yet inscribed.</li>
197
+ <li class="empty">Nothing has shipped yet.</li>
198
198
  </ul>
199
- <nav id="shipped-pager" class="shipped-pager" hidden aria-label="Lately Inscribed pages">
199
+ <nav id="shipped-pager" class="shipped-pager" hidden aria-label="Recently shipped pages">
200
200
  <button type="button" id="shipped-prev" class="btn btn--ghost btn--sm shipped-pager__btn" aria-label="Previous page">‹ earlier page</button>
201
201
  <span id="shipped-pageinfo" class="shipped-pager__info" aria-live="polite">page 1 of 1</span>
202
202
  <button type="button" id="shipped-next" class="btn btn--ghost btn--sm shipped-pager__btn" aria-label="Next page">later page ›</button>
@@ -206,30 +206,30 @@
206
206
  <section class="tablet tablet--wide" id="world-tablet" aria-labelledby="world-heading">
207
207
  <div class="tablet__head">
208
208
  <div class="tablet__heading">
209
- <h2 id="world-heading">The World</h2>
210
- <p class="tablet__subtitle">A true-to-life portrait of {{worldName}} as she stands today, rendered from the city's own stones.</p>
209
+ <h2 id="world-heading">Snapshot</h2>
210
+ <p class="tablet__subtitle">{{worldName}} as it looks today, rendered from the live project.</p>
211
211
  </div>
212
212
  </div>
213
213
  <div id="world-snapshot" class="world-snapshot">
214
- <p class="empty loading-mark" role="status">The cartographers are still at their survey…</p>
214
+ <p class="empty loading-mark" role="status">Loading the latest snapshot…</p>
215
215
  </div>
216
216
  <!-- #409: snapshot history collapsed behind a toggle. The latest snapshot
217
217
  above is the prime view; the timeline of older snapshots is reference
218
218
  material that previously ate ~300px of prime real estate. -->
219
219
  <details class="world-history" id="world-history">
220
- <summary class="world-history__summary">Show history of earlier portraits</summary>
220
+ <summary class="world-history__summary">Show earlier snapshots</summary>
221
221
  <div id="world-timeline" class="world-timeline" aria-label="World snapshots over time"></div>
222
- <p class="world-note" id="world-note" hidden>A new portrait is set down each time the city's stones are reshaped. Scroll to see how {{worldName}} has grown.</p>
222
+ <p class="world-note" id="world-note" hidden>A new snapshot is taken each time the project changes. Scroll to see how {{worldName}} has grown.</p>
223
223
  </details>
224
224
  </section>
225
225
 
226
226
  </main>
227
227
 
228
228
  <footer class="foot">
229
- <p class="foot__seal">By order of the Senate</p>
229
+ <p class="foot__seal">{{productName}} build status</p>
230
230
  <!-- task 1002853: every company field is optional, so each clause drops whole when its pack field is empty -->
231
231
  <!--brand-if:companyName--><p>{{worldName}} is the work of <!--brand-if:companySite--><a href="{{companySite}}" target="_blank" rel="noopener"><!--/brand-if:companySite--><strong>{{companyName}}</strong><!--brand-if:companySite--></a><!--/brand-if:companySite--><!--brand-if:companyTagline-->, {{companyTagline}}<!--/brand-if:companyTagline-->.</p><!--/brand-if:companyName-->
232
- <p>{{Currency}} are wrought by the lawmen's review; they measure the work, and are never coin.</p>
232
+ <p>{{Currency}} are awarded by review. They measure work done and are never money.</p>
233
233
  </footer>
234
234
 
235
235
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
@@ -56,10 +56,9 @@ function fmtUsd(n) {
56
56
  return Math.round(Number(n)).toLocaleString();
57
57
  }
58
58
 
59
- function fmtDr(n) {
59
+ function fmtMoney(n) {
60
60
  if (n == null) return '—';
61
- if (n < 100) return `${Number(n).toFixed(2)} dr.`;
62
- return `${Math.round(Number(n)).toLocaleString()} dr.`;
61
+ return `$${fmtUsd(n)}`;
63
62
  }
64
63
 
65
64
  function fmtRelativeTime(iso) {
@@ -99,13 +98,13 @@ async function fetchJson(path) {
99
98
  // in ~756px of content width without wrapping.
100
99
 
101
100
  const SECTIONS = [
102
- { id: 'headline-tablet', label: 'Pulse' },
103
- { id: 'leader-tablet', label: 'Architects' },
104
- { id: 'foundry-tablet', label: 'Foundry' },
105
- { id: 'versions-tablet', label: 'Works' },
106
- { id: 'cost-tablet', label: 'Treasury' },
107
- { id: 'shipped-tablet', label: 'Inscribed' },
108
- { id: 'world-tablet', label: 'World' },
101
+ { id: 'headline-tablet', label: 'Overview' },
102
+ { id: 'leader-tablet', label: 'Builders' },
103
+ { id: 'foundry-tablet', label: 'Trends' },
104
+ { id: 'versions-tablet', label: 'In progress' },
105
+ { id: 'cost-tablet', label: 'Spending' },
106
+ { id: 'shipped-tablet', label: 'Shipped' },
107
+ { id: 'world-tablet', label: 'Snapshot' },
109
108
  ];
110
109
 
111
110
  let navObserver = null;
@@ -354,7 +353,7 @@ async function loadVersions() {
354
353
  if (rows.length === 0) {
355
354
  const p = document.createElement('p');
356
355
  p.className = 'empty';
357
- p.textContent = 'No endeavors underway just now.';
356
+ p.textContent = 'Nothing is in progress right now.';
358
357
  els.versions.appendChild(p);
359
358
  return;
360
359
  }
@@ -497,7 +496,7 @@ function renderCostFilterNote(data) {
497
496
  const filtered = Number(data.filtered_total_usd) || 0;
498
497
  const whole = Number(data.total_usd) || 0;
499
498
  const pct = whole > 0 ? Math.round((filtered / whole) * 100) : 0;
500
- note.textContent = `Breakdown filtered to ${parts.join(' · ')}: ${fmtDr(filtered)} of ${fmtDr(whole)} (${pct}% of all spend).`;
499
+ note.textContent = `Breakdown filtered to ${parts.join(' · ')}: ${fmtMoney(filtered)} of ${fmtMoney(whole)} (${pct}% of all spend).`;
501
500
  }
502
501
 
503
502
  async function loadCosts() {
@@ -522,7 +521,7 @@ async function loadCosts() {
522
521
  if (cats.length === 0) {
523
522
  const tag = document.createElement('span');
524
523
  tag.className = 'cost-cat';
525
- tag.textContent = `No ${curLabel()} yet spent.`;
524
+ tag.textContent = `No ${curLabel()} spent yet.`;
526
525
  els.costCats.appendChild(tag);
527
526
  } else {
528
527
  for (const c of cats) {
@@ -531,7 +530,7 @@ async function loadCosts() {
531
530
  const strong = document.createElement('strong');
532
531
  strong.textContent = c.category;
533
532
  tag.appendChild(strong);
534
- tag.append(` · ${fmtDr(c.total_usd)}`);
533
+ tag.append(` · ${fmtMoney(c.total_usd)}`);
535
534
  els.costCats.appendChild(tag);
536
535
  }
537
536
  }
@@ -557,7 +556,7 @@ async function loadCosts() {
557
556
  if (total > 0) {
558
557
  const pct = Math.round((unattributed / total) * 100);
559
558
  els.costAttributionSplit.textContent =
560
- `Attributed to builders: ${fmtDr(attributed)} · Shared keep (unattributed): ${fmtDr(unattributed)} (${pct}%).`;
559
+ `Attributed to builders: ${fmtMoney(attributed)} · Shared (unattributed): ${fmtMoney(unattributed)} (${pct}%).`;
561
560
  } else {
562
561
  els.costAttributionSplit.textContent = '';
563
562
  }
@@ -577,14 +576,14 @@ async function loadCosts() {
577
576
  : (b.display_name || b.github_login || `builder #${b.builder_id}`);
578
577
  strong.textContent = label;
579
578
  tag.appendChild(strong);
580
- tag.append(` · ${fmtDr(b.total_usd)}`);
579
+ tag.append(` · ${fmtMoney(b.total_usd)}`);
581
580
  els.costBuilders.appendChild(tag);
582
581
  rendered++;
583
582
  }
584
583
  if (rendered === 0) {
585
584
  const tag = document.createElement('span');
586
585
  tag.className = 'cost-cat';
587
- tag.textContent = `No ${curLabel()} yet attributed to a hand.`;
586
+ tag.textContent = `No ${curLabel()} attributed to a builder yet.`;
588
587
  els.costBuilders.appendChild(tag);
589
588
  }
590
589
  }
@@ -666,7 +665,7 @@ async function loadCosts() {
666
665
  borderWidth: 1.5,
667
666
  },
668
667
  {
669
- label: 'value wrought',
668
+ label: 'value produced',
670
669
  data: cumValue,
671
670
  fill: false,
672
671
  borderColor: t.info,
@@ -702,7 +701,7 @@ async function loadCosts() {
702
701
  bodyFont: { family: t.body, size: 13 },
703
702
  padding: 10,
704
703
  callbacks: {
705
- label: (c) => `${c.dataset.label}: ${c.parsed.y.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 })} dr.`,
704
+ label: (c) => `${c.dataset.label}: ${c.parsed.y.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 })}`,
706
705
  },
707
706
  },
708
707
  },
@@ -716,7 +715,7 @@ async function loadCosts() {
716
715
  color: t.inkSoft,
717
716
  },
718
717
  ticks: {
719
- callback: (v) => `${v} dr.`,
718
+ callback: (v) => `${v}`,
720
719
  font: { family: t.body, size: 12 },
721
720
  color: t.inkSoft,
722
721
  },
@@ -808,7 +807,7 @@ function renderShippedPage() {
808
807
  if (items.length === 0) {
809
808
  const li = document.createElement('li');
810
809
  li.className = 'empty';
811
- li.textContent = 'No works yet inscribed in this window.';
810
+ li.textContent = 'Nothing shipped in this window.';
812
811
  els.shipped.appendChild(li);
813
812
  // GDS #191: pad with invisible rows so an empty window still occupies
814
813
  // the same vertical space as a full page. Keeps the section height
@@ -850,7 +849,7 @@ function renderShippedPage() {
850
849
  const by = t.display_name || t.github_login;
851
850
  // task 1004432: an approved page is the artist's; the session that applied it is a helper.
852
851
  const helper = t.helper_display_name || t.helper_login;
853
- meta.textContent = `${by ? `inscribed by ${by} · ` : ''}${helper ? `with help from ${helper} · ` : ''}${fmtRelativeTime(t.shipped_at)}`;
852
+ meta.textContent = `${by ? `shipped by ${by} · ` : ''}${helper ? `with help from ${helper} · ` : ''}${fmtRelativeTime(t.shipped_at)}`;
854
853
  li.appendChild(title);
855
854
  if (thumb) li.appendChild(thumb);
856
855
  li.appendChild(meta);
@@ -1063,7 +1062,7 @@ async function loadLeaderboard() {
1063
1062
  function stampRefreshTime() {
1064
1063
  if (!els.lastUpdated) return;
1065
1064
  const t = new Date().toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
1066
- els.lastUpdated.textContent = `tablet last consulted at ${t}`;
1065
+ els.lastUpdated.textContent = `updated at ${t}`;
1067
1066
  }
1068
1067
 
1069
1068
  // ---------- the Foundry — ecosystem dev curve (#291, C5) ----------
@@ -1121,8 +1120,8 @@ async function loadEcosystem() {
1121
1120
  const shipVals = (data.ship_rate || []).map((r) => Number(r.n));
1122
1121
  const durVals = (data.duration_trend || []).map((r) => Number(r.avg_actual));
1123
1122
  chartsEl.innerHTML =
1124
- `<div class="foundry__chart"><div class="foundry__chart-title">Works shipped / day</div>${foundrySparkline(shipVals, 'ink') || '<p class="empty">no data</p>'}</div>` +
1125
- `<div class="foundry__chart"><div class="foundry__chart-title">Avg time per work (min)</div>${foundrySparkline(durVals, 'info') || '<p class="empty">no data</p>'}</div>`;
1123
+ `<div class="foundry__chart"><div class="foundry__chart-title">Tasks shipped per day</div>${foundrySparkline(shipVals, 'ink') || '<p class="empty">no data</p>'}</div>` +
1124
+ `<div class="foundry__chart"><div class="foundry__chart-title">Average time per task (min)</div>${foundrySparkline(durVals, 'info') || '<p class="empty">no data</p>'}</div>`;
1126
1125
 
1127
1126
  const kinds = (data.top_kinds || []).slice(0, 6);
1128
1127
  if (kindsEl) {
@@ -1218,13 +1217,13 @@ function renderStatusDeepCard(kind, id, state) {
1218
1217
  const label = `#${id}`;
1219
1218
  panel.appendChild(mk('p', 'deeplink__kind', kind.toUpperCase()));
1220
1219
  if (state.loading) {
1221
- panel.appendChild(mk('p', 'deeplink__title', `Consulting the archive for ${label}…`));
1220
+ panel.appendChild(mk('p', 'deeplink__title', `Looking up ${label}…`));
1222
1221
  } else if (state.notFound) {
1223
1222
  panel.appendChild(mk('p', 'deeplink__title', `${label} — no such ${kind}`));
1224
1223
  panel.appendChild(mk('p', 'deeplink__note', 'Records number tasks, ideas, and blockers separately from GitHub pull requests — this may be a PR, not a record here.'));
1225
1224
  } else if (state.error) {
1226
1225
  panel.appendChild(mk('p', 'deeplink__title', `Could not resolve ${label}`));
1227
- panel.appendChild(mk('p', 'deeplink__note', 'The archive could not be reached. Try again in a moment.'));
1226
+ panel.appendChild(mk('p', 'deeplink__note', 'The server could not be reached. Try again in a moment.'));
1228
1227
  } else {
1229
1228
  const r = state.ref || {};
1230
1229
  panel.appendChild(mk('p', 'deeplink__title', r.title ? `${label} — ${r.title}` : label));
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.50",
3
+ "version": "1.24.52",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.50",
9
+ "version": "1.24.52",
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.24.50",
3
+ "version": "1.24.52",
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",
@@ -11271,5 +11271,17 @@
11271
11271
  "id": "1005089",
11272
11272
  "text": "The public status page now looks like the rest of Cloud Bongos: same colours, fonts, logo and panels, with a dark mode, and its off-site backup copy is built from the same source so the two always match."
11273
11273
  }
11274
+ ],
11275
+ "1.24.51": [
11276
+ {
11277
+ "id": "1005090",
11278
+ "text": "The public status page now talks plainly: 'Top builders', 'Recently shipped', 'Spending' instead of the old ancient-city words, and costs show in dollars."
11279
+ }
11280
+ ],
11281
+ "1.24.52": [
11282
+ {
11283
+ "id": "1005085",
11284
+ "text": "The hall's pop-up questions now use one Cloud Bongos dialog instead of the browser's grey box: every 'are you sure?' says plainly what will happen, and the six home-made pop-ups on different pages now share one look and the sa"
11285
+ }
11274
11286
  ]
11275
11287
  }
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.50'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.52'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -46,7 +46,7 @@ function stubPage(present) {
46
46
  const els = {};
47
47
  for (const id of present) els[id] = { hidden: true, disabled: false, textContent: '', title: '', listeners: {}, addEventListener(t, fn) { this.listeners[t] = fn; } };
48
48
  globalThis.document = { getElementById: (id) => els[id] || null };
49
- globalThis.window = { confirm: () => true };
49
+ globalThis.window = { OTBDialog: { confirm: async () => true } }; // the branded confirm (task 1005085)
50
50
  globalThis.location = loc;
51
51
  return els;
52
52
  }
@@ -90,6 +90,9 @@ function bootPanel({ answers = {}, writes = null, mode = 'light' } = {}) {
90
90
  getElementById: byId,
91
91
  createElement: (tag) => augment(makeEl(tag, {})),
92
92
  get activeElement() { return boot.focused || null; },
93
+ // the shared dialog (hall-dialog.js, task 1005085) hears its keys on the document
94
+ listeners: {},
95
+ addEventListener(type, fn) { (this.listeners[type] = this.listeners[type] || []).push(fn); },
93
96
  };
94
97
  const api = {
95
98
  // The REAL client's shape: request(method, path, { body }). This fake once
@@ -110,6 +113,7 @@ function bootPanel({ answers = {}, writes = null, mode = 'light' } = {}) {
110
113
  win.window = win;
111
114
  vm.runInContext(read('brand-holes.js'), ctx, { filename: 'brand-holes.js' });
112
115
  vm.runInContext(read('approval-queue-lib.js'), ctx, { filename: 'approval-queue-lib.js' });
116
+ vm.runInContext(read('hall-dialog.js'), ctx, { filename: 'hall-dialog.js' }); // the shared dialog behaviour (task 1005085)
113
117
  vm.runInContext(read('approval-queue.js'), ctx, { filename: 'approval-queue.js' });
114
118
  const panel = win.OTBApprovalQueue.mount({
115
119
  api,
@@ -124,7 +128,9 @@ function bootPanel({ answers = {}, writes = null, mode = 'light' } = {}) {
124
128
  const q = (sel) => scrim.querySelector(sel);
125
129
  const qa = (sel) => scrim.querySelectorAll(sel);
126
130
  const click = async (el) => { assert.ok(el, 'the control exists'); scrim.fire('click', { target: el }); await settle(); await settle(); };
127
- boot = { body, scrim, q, qa, click, requests, counts, panel, classes, win, focused: null };
131
+ boot = { body, scrim, q, qa, click, requests, counts, panel, classes, win, focused: null,
132
+ // a key pressed anywhere in the page reaches the document, where the shared dialog listens
133
+ key: (ev) => (documentObj.listeners.keydown || []).forEach((fn) => fn({ preventDefault() {}, stopPropagation() {}, ...ev })) };
128
134
  return boot;
129
135
  }
130
136
 
@@ -330,7 +336,7 @@ await test('empty, closed with Escape, and focus goes back where it was', async
330
336
  await b.panel.open(); await settle();
331
337
  assert.equal(text(b.q('.aq-title')), 'Nothing waiting');
332
338
  assert.match(text(b.q('.aq-frame__note')), /Nothing is waiting for you/);
333
- b.scrim.fire('keydown', { key: 'Escape', preventDefault() {}, target: b.scrim });
339
+ b.key({ key: 'Escape', target: b.scrim });
334
340
  assert.equal(b.scrim.hidden, true);
335
341
  assert.equal(b.focused, opener);
336
342
  assert.ok(!b.classes.has('aq-open'));
@@ -510,7 +510,7 @@ test('the verdict classes and the scrim exist in the token tier, in both modes',
510
510
  assert.match(css, /\.ledger__verdict--danger\s*\{[^}]*var\(--danger\)/, 'fell short → --danger');
511
511
  assert.match(css, /\.ledger__verdict--none\s*\{[^}]*var\(--ink-soft\)/, 'not graded → --ink-soft');
512
512
  assert.match(css, /--scrim:/, 'the modal scrim is a token, not a sheet-local literal');
513
- assert.match(read('copy-desk.css'), /::backdrop\s*\{[^}]*var\(--scrim\)/, 'the copy-desk backdrop takes it');
513
+ assert.match(read('controls.css'), /\.hall-dialog::backdrop \{ background: var\(--scrim\); \}/, 'the one dialog backdrop takes it (task 1005085)');
514
514
  });
515
515
 
516
516
  // ---- 10. an outline: none always names its replacement (task 1003496) -----
@@ -288,7 +288,7 @@ test('the city sits in the hall chrome, behind the shell\'s full-screen switch',
288
288
  // task 1004685 (the living city): gov-living.js, the model under the drawing, and
289
289
  // gov-feeds.js, its one reader per page (shared with the shell's tally), load before it
290
290
  // task 1004702: the card answers its first ask in place, so the deck's card and sender (and the desk rows under them) load before the page
291
- assert.deepEqual(scripts, ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'hall-kit', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city-decide', 'city-switchboard', 'city'], 'the shell first, as on every hall page');
291
+ assert.deepEqual(scripts, ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'hall-dialog', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'hall-kit', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city-decide', 'city-switchboard', 'city'], 'the shell first, then the shared dialog (task 1005085), as on every hall page');
292
292
  // task 1004925: every script but dom-utils.js is deferred, so none holds up the
293
293
  // parser; defer runs them in document order, which is why the order above still
294
294
  // holds. dom-utils.js stays synchronous: the one-escaper guard
@@ -53,7 +53,9 @@ test('the page sheets spend only the fifteen, in px, and never opacity', () => {
53
53
  const cd = read('copy-desk.css');
54
54
  const literals = cd.match(/rgba?\([^)]*\)/g) || [];
55
55
  assert.deepEqual(literals, [], 'not even the scrim is a literal here any more');
56
- assert.match(cd, /\.cd-dialog::backdrop \{ background: var\(--scrim\); \}/, 'the backdrop spends the token');
56
+ // task 1005085: the dialogs are the hall's one dialog, whose backdrop lives in controls.css
57
+ assert.doesNotMatch(cd, /::backdrop/, 'no backdrop of its own');
58
+ assert.match(read('controls.css'), /\.hall-dialog::backdrop \{ background: var\(--scrim\); \}/, 'the shared backdrop spends the token');
57
59
  });
58
60
 
59
61
  test('every list is the family\'s row, and the card-in-a-card is gone', () => {
@@ -335,15 +337,16 @@ test('the filters and the search are the kit\'s bar; the duplicates are paged th
335
337
  assert.match(read('copy-desk.html'), /src="\/builders\/hall-kit\.js/, 'the page loads the kit');
336
338
  });
337
339
 
338
- test('the three dialogs are the family modal on a native <dialog>, with the number input in a field', () => {
340
+ test('the three dialogs are the hall\'s one dialog on a native <dialog>, with the number input in a field', () => {
339
341
  const html = read('copy-desk.html');
340
- // Three since task 1003114 (R03): flag, close, and the proposal composer.
341
- assert.equal((html.match(/<dialog id="cd-(close-dialog|dialog|propose-dialog)" class="ov-modal cd-dialog">/g) || []).length, 3);
342
+ // Three since task 1003114 (R03): flag, close, and the proposal composer; the
343
+ // shared .hall-dialog since task 1005085, each named by its own title.
344
+ assert.equal((html.match(/<dialog id="cd-(close-dialog|dialog|propose-dialog)" class="hall-dialog cd-dialog" aria-labelledby="cd-(close|dialog|propose)-h">/g) || []).length, 3);
342
345
  assert.match(html, /<div class="ov-field ov-field--wide">\s*<label class="ov-field__l" for="cd-close-task">[^<]*<\/label>\s*<input type="number" id="cd-close-task"/, 'the number input sits in the family field, which restates the 30px pill');
343
346
  assert.ok(!/<label[^>]*>[^<]*<span class="cd-dialog__hint"/.test(html), 'no hint lives inside a label (the label voice would uppercase it)');
344
347
  assert.equal((html.match(/class="ov-form__why"/g) || []).length, 4, 'the four hints are the form\'s own explainer line');
345
- assert.match(html, /class="btn-accent" id="cd-submit"/, 'the accent door files the flag');
346
- assert.match(html, /class="btn-accent" id="cd-propose-submit"/, 'and the proposal composer has its own');
348
+ assert.match(html, /class="btn" id="cd-submit"/, 'the accent door files the flag');
349
+ assert.match(html, /class="btn" id="cd-propose-submit"/, 'and the proposal composer has its own');
347
350
  });
348
351
 
349
352
  test('the proposal composer is prefilled, and says what filing it does and does not do', () => {