@bongos/core 1.24.50 → 1.24.51

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.
@@ -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.51",
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.51",
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.51",
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,11 @@
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
+ }
11274
11280
  ]
11275
11281
  }
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.51'; // 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');
@@ -170,11 +170,11 @@ test('apex: each page waits with the size that fits it', () => {
170
170
 
171
171
  test('status: its two waiting tablets wear the loader, and a failed or empty read stops it', () => {
172
172
  const html = read(STATUS, 'index.html');
173
- assert.ok(html.includes('<div id="versions" class="versions">\n <p class="empty loading-mark" role="status">The masons are at their tools…</p>'));
174
- assert.ok(html.includes('<div id="world-snapshot" class="world-snapshot">\n <p class="empty loading-mark" role="status">The cartographers are still at their survey…</p>'));
175
- // the header's "Tablet being consulted…" is rewritten in place before any read
173
+ assert.ok(html.includes('<div id="versions" class="versions">\n <p class="empty loading-mark" role="status">Loading the versions…</p>'));
174
+ assert.ok(html.includes('<div id="world-snapshot" class="world-snapshot">\n <p class="empty loading-mark" role="status">Loading the latest snapshot…</p>'));
175
+ // the header's "Checking for updates…" is rewritten in place before any read
176
176
  // starts (stampRefreshTime), so it is never a wait and keeps its plain words
177
- assert.match(html, /<span class="herm__updated" id="last-updated">Tablet being consulted…<\/span>/);
177
+ assert.match(html, /<span class="herm__updated" id="last-updated">Checking for updates…<\/span>/);
178
178
  // The Kill Switch is the last thing in the status sheet, so nothing can outrank it.
179
179
  assert.match(STATUS_CSS.trimEnd(), /@media \(prefers-reduced-motion: reduce\) \{\n {2}\*, \*::before, \*::after \{ animation: none !important; transition: none !important; \}\n\}$/);
180
180
 
@@ -186,12 +186,12 @@ test('status: its two waiting tablets wear the loader, and a failed or empty rea
186
186
  // ...and stopWaiting itself, run: it takes the class off and leaves the words alone.
187
187
  const src = js.match(/function stopWaiting\(el\) \{[\s\S]*?\n\}/)[0];
188
188
  const removed = [];
189
- const wait = { textContent: 'The masons are at their tools…', classList: { remove: (c) => removed.push(c) } };
189
+ const wait = { textContent: 'Loading the versions…', classList: { remove: (c) => removed.push(c) } };
190
190
  const host = { querySelectorAll: (sel) => (sel === '.loading-mark' ? [wait] : []) };
191
191
  const ctx = vm.createContext({});
192
192
  vm.runInContext(`${src}; this.stopWaiting = stopWaiting;`, ctx);
193
193
  ctx.stopWaiting(host);
194
194
  ctx.stopWaiting(null);
195
195
  assert.deepEqual(removed, ['loading-mark']);
196
- assert.equal(wait.textContent, 'The masons are at their tools…');
196
+ assert.equal(wait.textContent, 'Loading the versions…');
197
197
  });
@@ -26,7 +26,7 @@ test('a pack with no company drops the whole attribution sentence, not half of i
26
26
  const out = stamp({ name: '', site: 'https://ignored.example', tagline: 'ignored' });
27
27
  assert.doesNotMatch(out, /is the work of|ignored/);
28
28
  assert.doesNotMatch(out, /\{\{|brand-if/);
29
- assert.match(out, /Tokens are wrought/, 'the neighbouring footer lines survive');
29
+ assert.match(out, /Tokens are awarded by review/, 'the neighbouring footer lines survive');
30
30
  });
31
31
 
32
32
  test('a full company renders the linked name and its tagline', () => {
@@ -0,0 +1,129 @@
1
+ // tests/status_page_wording.mjs — the status page speaks the project's current
2
+ // voice, not the old Greek-city ledger (task 1005090, goal 1000138; docs/voice.md).
3
+ //
4
+ // The page moved onto the chrome world in task 1005089 with its words untouched;
5
+ // this task rewrote them. What this pins: no word of the city vocabulary
6
+ // ("citizens who shape her stones", "tablet last consulted", "works wrought",
7
+ // "architects", drachmas…) is left anywhere a visitor can read it — the page's
8
+ // text, its title and meta, its accessible names, and every string status.js
9
+ // writes into the page. Class names, ids and code comments are not read, so a
10
+ // selector like `.foundry__chart` is not a finding; a sentence is.
11
+ //
12
+ // Run: node --test tests/status_page_wording.mjs
13
+
14
+ import { test } from 'node:test';
15
+ import assert from 'node:assert/strict';
16
+ import fs from 'node:fs';
17
+ import http from 'node:http';
18
+ import path from 'node:path';
19
+ import vm from 'node:vm';
20
+ import { createRequire } from 'node:module';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const require = createRequire(import.meta.url);
24
+
25
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
26
+ const DIR = path.join(ROOT, 'modules', 'status-ui', 'public');
27
+ const read = (f) => fs.readFileSync(path.join(DIR, f), 'utf8');
28
+
29
+ // The retired vocabulary, as whole words.
30
+ const CITY = /\b(citizens?|stones?|tablets?|wrought|architects?|inscri(be|bed|ption|ptions)|lately|masons?|cartographers?|purse|treasury|foundry|forge|senate|lawmen|inspector|athens|civili[sz]ation|endeavou?rs?|portraits?|consult(ed|ing)?|archive|her stones|city|city's|ledger|hand at|hands)\b|\bdr\./i;
31
+
32
+ // What a visitor reads in the markup: text between tags, and the attributes that
33
+ // are read aloud or shown (title, alt, aria-label, placeholder, meta content).
34
+ function visibleHtml(html) {
35
+ const noComments = html.replace(/<!--[\s\S]*?-->/g, ' ').replace(/<(script|style)\b[\s\S]*?<\/\1>/gi, ' ');
36
+ const out = [];
37
+ for (const m of noComments.matchAll(/\b(?:aria-label|title|alt|placeholder|content)="([^"]*)"/g)) out.push(m[1]);
38
+ for (const m of noComments.replace(/<[^>]*>/g, '\n').split('\n')) if (m.trim()) out.push(m.trim());
39
+ return out;
40
+ }
41
+
42
+ // What status.js writes: its string literals, minus comments, minus any markup
43
+ // inside a template, minus the literals that are a selector, id or key.
44
+ function visibleJs(js) {
45
+ // line comments first: a `/*` inside one (a path like /public/*) would otherwise
46
+ // open a block comment that swallows the code after it
47
+ const code = js.replace(/(^|[^:\\'"`])\/\/.*$/gm, '$1').replace(/\/\*[\s\S]*?\*\//g, '');
48
+ const out = [];
49
+ for (const m of code.matchAll(/'((?:[^'\\\n]|\\.)*)'|"((?:[^"\\\n]|\\.)*)"|`((?:[^`\\]|\\.)*)`/g)) {
50
+ const lit = (m[1] ?? m[2] ?? m[3] ?? '').replace(/<[^>]*>/g, ' ').replace(/\$\{[^}]*\}/g, ' ').trim();
51
+ if (!lit) continue;
52
+ if (/^[a-z0-9_\-.#:[\]=" ]*$/.test(lit) && !/\s\S+\s/.test(lit)) continue; // a selector, id, class list or key
53
+ out.push(lit);
54
+ }
55
+ return out;
56
+ }
57
+
58
+ // The pages as the server serves them on the status host (stamped + branded), so
59
+ // the check reads what a visitor's browser receives, not the template.
60
+ function get(port, p) {
61
+ return new Promise((resolve, reject) => {
62
+ const req = http.request({ host: '127.0.0.1', port, path: p, headers: { Host: 'status.example.com' } }, (res) => {
63
+ let body = '';
64
+ res.setEncoding('utf8');
65
+ res.on('data', (c) => { body += c; });
66
+ res.on('end', () => resolve({ status: res.statusCode, body }));
67
+ });
68
+ req.on('error', reject);
69
+ req.end();
70
+ });
71
+ }
72
+ async function servedPages() {
73
+ const express = require('express');
74
+ const { mountInternalSurfaces } = require('../src/bongos/serve-internal.js');
75
+ const app = express();
76
+ mountInternalSurfaces(app);
77
+ const server = await new Promise((resolve) => { const s = app.listen(0, () => resolve(s)); });
78
+ try {
79
+ const port = server.address().port;
80
+ const out = {};
81
+ for (const f of fs.readdirSync(DIR).filter((n) => n.endsWith('.html'))) {
82
+ const r = await get(port, f === 'index.html' ? '/' : `/${f}`);
83
+ assert.equal(r.status, 200, f);
84
+ out[f] = r.body;
85
+ }
86
+ return out;
87
+ } finally {
88
+ await new Promise((r) => server.close(r));
89
+ }
90
+ }
91
+
92
+ test('no Greek-city word is left in what the status page shows', async () => {
93
+ const hits = [];
94
+ const pages = await servedPages();
95
+ assert.ok(Object.keys(pages).length >= 2);
96
+ for (const [f, html] of Object.entries(pages)) {
97
+ for (const line of visibleHtml(html)) if (CITY.test(line)) hits.push(`${f}: ${line.slice(0, 120)}`);
98
+ }
99
+ for (const f of ['status.js', 'explore.js']) {
100
+ for (const line of visibleJs(read(f))) if (CITY.test(line)) hits.push(`${f}: ${line.slice(0, 120)}`);
101
+ }
102
+ assert.deepEqual(hits, []);
103
+ });
104
+
105
+ test('the reader really reads the page (a guard that sees nothing passes everything)', async () => {
106
+ const html = visibleHtml((await servedPages())['index.html']);
107
+ assert.ok(html.includes('Recently shipped'), 'a heading is read');
108
+ assert.ok(html.some((l) => /^How .+ is being built, in the open/.test(l)), 'the meta description is read');
109
+ assert.ok(html.includes('Filter shipped tasks by date range'), 'an accessible name is read');
110
+ const js = visibleJs(read('status.js'));
111
+ assert.ok(js.includes('Nothing shipped in this window.'), 'a string status.js writes is read');
112
+ assert.ok(!js.includes('foundry__chart'), 'a class name is not');
113
+ // and the old lines would have been caught
114
+ for (const old of ['Architects of {{worldName}}', 'tablet last consulted at', 'works wrought, last 7 days', 'Citizens who have set the most stone.', '12 dr.']) {
115
+ assert.ok(CITY.test(old), `the guard catches "${old}"`);
116
+ }
117
+ });
118
+
119
+ test('money reads as dollars, not drachmas', () => {
120
+ // run the page's own two formatters
121
+ const js = read('status.js');
122
+ const fn = (name) => js.match(new RegExp(`function ${name}\\(n\\) \\{[\\s\\S]*?\\n\\}`))[0];
123
+ const ctx = vm.createContext({});
124
+ vm.runInContext(`${fn('fmtUsd')}\n${fn('fmtMoney')}\nthis.fmtMoney = fmtMoney;`, ctx);
125
+ assert.equal(ctx.fmtMoney(12.5), '$12.50');
126
+ assert.equal(ctx.fmtMoney(48297.4), `$${(48297).toLocaleString()}`);
127
+ assert.equal(ctx.fmtMoney(null), '—');
128
+ assert.doesNotMatch(js, /fmtDr|dr\.`/);
129
+ });