@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.
- package/.bongos-core.json +104 -89
- package/docs/copy-inventory.md +259 -277
- package/docs/copy-registry.json +461 -634
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +35 -2
- package/docs/page-readings.json +518 -547
- package/modules/hall-ui/public/ai-studio.html +1 -0
- package/modules/hall-ui/public/approval-queue.css +3 -11
- package/modules/hall-ui/public/approval-queue.js +13 -20
- package/modules/hall-ui/public/atlas.html +1 -0
- package/modules/hall-ui/public/blockers.html +1 -0
- package/modules/hall-ui/public/board-room.html +1 -0
- package/modules/hall-ui/public/bulk-toolbar.js +5 -1
- package/modules/hall-ui/public/city.html +1 -0
- package/modules/hall-ui/public/collab.html +1 -0
- package/modules/hall-ui/public/commands.html +1 -0
- package/modules/hall-ui/public/controls.css +100 -0
- package/modules/hall-ui/public/copy-desk.css +7 -11
- package/modules/hall-ui/public/copy-desk.html +17 -16
- package/modules/hall-ui/public/copy-desk.js +13 -9
- package/modules/hall-ui/public/deploy.html +1 -0
- package/modules/hall-ui/public/goal-inbox.js +32 -17
- package/modules/hall-ui/public/goal-map.html +1 -0
- package/modules/hall-ui/public/goals-page.js +7 -1
- package/modules/hall-ui/public/goals.html +1 -0
- package/modules/hall-ui/public/government.html +1 -0
- package/modules/hall-ui/public/government.js +10 -2
- package/modules/hall-ui/public/hall-dialog.js +255 -0
- package/modules/hall-ui/public/hall-kit.js +9 -2
- package/modules/hall-ui/public/idea.html +1 -0
- package/modules/hall-ui/public/ideas.html +1 -0
- package/modules/hall-ui/public/index.html +1 -0
- package/modules/hall-ui/public/market-watch-item.html +2 -1
- package/modules/hall-ui/public/market-watch-lib.js +16 -9
- package/modules/hall-ui/public/market-watch-rules.html +1 -0
- package/modules/hall-ui/public/market-watch.css +2 -3
- package/modules/hall-ui/public/market-watch.html +2 -1
- package/modules/hall-ui/public/modules.html +1 -0
- package/modules/hall-ui/public/money.html +1 -0
- package/modules/hall-ui/public/my-agents.html +1 -0
- package/modules/hall-ui/public/oversight.css +3 -33
- package/modules/hall-ui/public/primer.html +1 -0
- package/modules/hall-ui/public/profile.html +1 -0
- package/modules/hall-ui/public/project-settings.html +1 -0
- package/modules/hall-ui/public/roster.html +1 -0
- package/modules/hall-ui/public/settings-ai-keys.js +5 -1
- package/modules/hall-ui/public/settings-passkeys.js +5 -1
- package/modules/hall-ui/public/settings-sessions.js +10 -2
- package/modules/hall-ui/public/settings.html +1 -0
- package/modules/hall-ui/public/settings.js +5 -1
- package/modules/hall-ui/public/street.html +1 -0
- package/modules/hall-ui/public/studio.css +6 -32
- package/modules/hall-ui/public/studio.html +4 -3
- package/modules/hall-ui/public/studio.js +40 -35
- package/modules/hall-ui/public/style.css +2 -8
- package/modules/hall-ui/public/task.html +1 -0
- package/modules/hall-ui/public/task.js +26 -12
- package/modules/hall-ui/public/thinking.html +1 -0
- package/modules/hall-ui/public/tweak-editor.html +1 -0
- package/modules/hall-ui/public/watch.html +1 -0
- package/modules/hall-ui/public/work.html +1 -0
- package/modules/hall-ui/public/work.js +7 -1
- package/modules/status-ui/public/index.html +43 -43
- package/modules/status-ui/public/status.js +26 -27
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/module-api.js +1 -1
- package/tests/bulk_toolbar.mjs +1 -1
- package/tests/hall_approval_queue.mjs +8 -2
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_collab_world.mjs +9 -6
- package/tests/hall_dialog.mjs +401 -0
- package/tests/hall_error_envelope.mjs +1 -1
- package/tests/hall_oversight_world.mjs +9 -7
- package/tests/hall_record_world.mjs +1 -1
- package/tests/hall_street.mjs +1 -1
- package/tests/hall_studio_home.mjs +1 -1
- package/tests/home_attention_cards.mjs +2 -0
- package/tests/kind_axis_unset.mjs +1 -1
- package/tests/loading_mark.mjs +6 -6
- package/tests/market_watch_pages.mjs +3 -1
- package/tests/status_company_attribution.mjs +1 -1
- package/tests/status_page_wording.mjs +129 -0
|
@@ -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
|
+
});
|