@bongos/core 1.24.35 → 1.24.37
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 +128 -168
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/docs/adr/0228-the-reading-room-owes-a-reference-content-rules-not-a-second-archetype.md +1 -1
- package/docs/copy-inventory.md +164 -219
- package/docs/copy-registry.json +323 -892
- package/docs/module-api-changelog.md +4 -0
- package/docs/onboarding/diagrams/README.md +1 -2
- package/docs/onboarding/primer.md +66 -22
- package/docs/page-inventory.json +39 -133
- package/docs/page-readings.json +882 -1346
- package/docs/recipes/ui-look-before-you-ship.md +2 -2
- package/modules/design-styles/skills/style/SKILL.md +2 -2
- package/modules/hall-ui/public/ai-studio.html +2 -2
- package/modules/hall-ui/public/atlas.html +2 -2
- package/modules/hall-ui/public/blockers.html +2 -2
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/builders.js +8 -6
- package/modules/hall-ui/public/city.html +2 -2
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/craft-mode.js +2 -2
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/diagram-viewer.css +1 -1
- package/modules/hall-ui/public/diagram-viewer.js +5 -5
- package/modules/hall-ui/public/fleet-costs.js +3 -2
- package/modules/hall-ui/public/founding-mark.js +3 -2
- package/modules/hall-ui/public/gate.html +2 -2
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/hall-render.js +62 -1
- package/modules/hall-ui/public/hall-widgets.js +11 -1
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +4 -4
- package/modules/hall-ui/public/market-watch-item.html +2 -2
- package/modules/hall-ui/public/market-watch-rules.html +2 -2
- package/modules/hall-ui/public/market-watch.html +2 -2
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/money.html +92 -0
- package/modules/hall-ui/public/money.js +302 -0
- package/modules/hall-ui/public/money.states.json +15 -0
- package/modules/hall-ui/public/my-agents.html +2 -2
- package/modules/hall-ui/public/palette.js +22 -0
- package/modules/hall-ui/public/primer-live.js +101 -0
- package/modules/hall-ui/public/primer.html +10 -6
- package/modules/hall-ui/public/primer.js +93 -23
- package/modules/hall-ui/public/primer.states.json +54 -7
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.css +2 -1
- package/modules/hall-ui/public/project-settings.html +6 -16
- package/modules/hall-ui/public/project-settings.js +2 -142
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/room.css +23 -121
- package/modules/hall-ui/public/roster.html +2 -2
- package/modules/hall-ui/public/sessions.html +2 -2
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +21 -26
- package/modules/hall-ui/public/street.html +2 -2
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/style.css +8 -55
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/watch.html +2 -2
- package/modules/hall-ui/public/work.html +2 -2
- package/modules/hall-ui/records/founding-mark.md +4 -4
- package/modules/hall-ui/records/reading-rooms.md +2 -0
- package/modules/hall-ui/records/reference-room.md +2 -0
- package/modules/onboarding/founding-mode.js +6 -2
- package/modules/ui-design/kit/render.js +1 -1
- package/modules/ui-design/styles/README.md +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/gen-hall-sitemap.js +3 -0
- package/scripts/hall-preview/server.js +6 -11
- package/src/bongos/auth.js +0 -8
- package/src/bongos/serve-internal.js +43 -43
- package/src/module-api.js +1 -1
- package/tests/auth_resolve_failure.mjs +5 -4
- package/tests/craft_mode_home.mjs +6 -2
- package/tests/fleet_cost_view.mjs +2 -18
- package/tests/founding_builders.mjs +6 -5
- package/tests/founding_mode.mjs +7 -4
- package/tests/hall_board_world.mjs +44 -1
- package/tests/hall_fullscreen.mjs +1 -1
- package/tests/hall_loading.mjs +1 -1
- package/tests/hall_money_page.mjs +272 -0
- package/tests/hall_nav.mjs +29 -11
- package/tests/hall_oversight_world.mjs +1 -1
- package/tests/hall_page_gate_map.mjs +17 -7
- package/tests/hall_reading_room.mjs +385 -0
- package/tests/hall_reading_world.mjs +36 -30
- package/tests/nav_permission_atoms.mjs +3 -2
- package/tests/rank_tier_single_source.mjs +0 -1
- package/tests/ui_design_kit.mjs +1 -1
- package/modules/hall-ui/public/diagrams.css +0 -45
- package/modules/hall-ui/public/diagrams.html +0 -106
- package/modules/hall-ui/public/diagrams.states.json +0 -28
- package/modules/hall-ui/public/drachmae.html +0 -110
- package/modules/hall-ui/public/drachmae.js +0 -431
- package/modules/hall-ui/public/drachmae.states.json +0 -58
- package/modules/hall-ui/public/fleet.html +0 -69
- package/modules/hall-ui/public/fleet.js +0 -41
- package/modules/hall-ui/public/fleet.states.json +0 -14
- package/modules/hall-ui/public/ranks-founding.states.json +0 -12
- package/modules/hall-ui/public/ranks.html +0 -83
- package/modules/hall-ui/public/ranks.js +0 -229
- package/modules/hall-ui/public/ranks.states.json +0 -15
- package/tests/hall_reference_world.mjs +0 -352
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// tests/hall_reading_room.mjs — ONE reading room (task 1005046, owner decision
|
|
2
|
+
// 2026-11-03, idea 1001311).
|
|
3
|
+
//
|
|
4
|
+
// Four Docs pages explained the same three topics — the task lifecycle, the rank
|
|
5
|
+
// ladder, and credits and karma — so their copies drifted. Ranks, Economy
|
|
6
|
+
// (/drachmae) and Diagrams became chapters of the Primer, each with its diagram
|
|
7
|
+
// beside it. This file holds what makes that true and keeps it true:
|
|
8
|
+
//
|
|
9
|
+
// * EVERY OLD URL LANDS ON ITS CHAPTER. /ranks, /drachmae and /diagrams 302 to
|
|
10
|
+
// the Primer chapter that replaced each — run against the real middleware,
|
|
11
|
+
// on both host shapes — and the diagram IMAGES under /builders/diagrams/*
|
|
12
|
+
// still serve (behind sign-in) rather than being swallowed by the redirect.
|
|
13
|
+
// * THE CHAPTERS EXIST, AND EACH CARRIES ITS DIAGRAM. Every id primer.js
|
|
14
|
+
// inlines a diagram at, every redirect target and every palette jump is a
|
|
15
|
+
// real h2/h3 slug of docs/onboarding/primer.md, computed by the SAME slugify
|
|
16
|
+
// the page runs — so retitling a chapter fails here, not silently in a
|
|
17
|
+
// browser (the credits diagram was lost that way once).
|
|
18
|
+
// * THE ECONOMY PAGE'S LIVE SECTION MOVED WHOLE. The bounty rate card
|
|
19
|
+
// (primer-live.js, from drachmae.js, task 2174) is RUN in a vm against the
|
|
20
|
+
// checked-in fixture: a Loading… line first, the real numbers after, and
|
|
21
|
+
// NOTHING — not an empty panel — when the feed fails. (The treasury beside
|
|
22
|
+
// it went to the money page in task 1005047: tests/hall_money_page.mjs.)
|
|
23
|
+
// * THE SHARED READER the economy doc leaned on (h4, linked headings, nested
|
|
24
|
+
// emphasis) stays pinned: drachmae-and-karma.md is still the full reference.
|
|
25
|
+
//
|
|
26
|
+
// Moved here from tests/hall_reference_world.mjs, whose reference-room rules
|
|
27
|
+
// (room--reference, the h3 outline, the heading anchors) went with the page.
|
|
28
|
+
|
|
29
|
+
import { test } from 'node:test';
|
|
30
|
+
import assert from 'node:assert/strict';
|
|
31
|
+
import fs from 'node:fs';
|
|
32
|
+
import http from 'node:http';
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
import vm from 'node:vm';
|
|
35
|
+
import { createRequire } from 'node:module';
|
|
36
|
+
import { fileURLToPath } from 'node:url';
|
|
37
|
+
import { realOTB } from './helpers.mjs';
|
|
38
|
+
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
41
|
+
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
42
|
+
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
43
|
+
|
|
44
|
+
const roomCss = read('room.css');
|
|
45
|
+
const primerHtml = read('primer.html');
|
|
46
|
+
const primerJs = read('primer.js');
|
|
47
|
+
const liveJs = read('primer-live.js');
|
|
48
|
+
const PRIMER_MD = fs.readFileSync(path.join(ROOT, 'docs', 'onboarding', 'primer.md'), 'utf8');
|
|
49
|
+
|
|
50
|
+
const decomment = (css) => css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
51
|
+
const rule = (css, selector) => {
|
|
52
|
+
const esc = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
53
|
+
const m = decomment(css).match(new RegExp(`(^|[},])\\s*${esc}\\s*\\{([^}]*)\\}`, 'm'));
|
|
54
|
+
return m ? m[2] : null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function loadReader() {
|
|
58
|
+
const sandbox = { window: { OTB: realOTB() }, location: { hostname: 'builders.example.com', href: 'https://builders.example.com/primer' } };
|
|
59
|
+
vm.createContext(sandbox);
|
|
60
|
+
vm.runInContext(read('md-reader.js'), sandbox, { filename: 'md-reader.js' });
|
|
61
|
+
return sandbox.window.OTBMdReader;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The primer's headings exactly as the page renders them: parsed by the shared
|
|
65
|
+
// reader, ids from its slugify.
|
|
66
|
+
function primerHeadings() {
|
|
67
|
+
return loadReader().parseMarkdown(PRIMER_MD, (h) => h).headings;
|
|
68
|
+
}
|
|
69
|
+
const h2Ids = () => primerHeadings().filter((h) => h.level === 2).map((h) => h.id);
|
|
70
|
+
|
|
71
|
+
// ---- 1. every old URL lands on its chapter ---------------------------------
|
|
72
|
+
|
|
73
|
+
// The real middleware, mounted on a bare app. Nothing here needs a database:
|
|
74
|
+
// the redirect runs before any gate, and the gate it hands an image to only has
|
|
75
|
+
// to redirect an anonymous request into sign-in.
|
|
76
|
+
async function withHall(fn) {
|
|
77
|
+
const express = require('express');
|
|
78
|
+
const { mountInternalSurfaces } = require('../src/bongos/serve-internal.js');
|
|
79
|
+
const app = express();
|
|
80
|
+
mountInternalSurfaces(app);
|
|
81
|
+
const server = app.listen(0, '127.0.0.1');
|
|
82
|
+
await new Promise((r) => server.once('listening', r));
|
|
83
|
+
const { port } = server.address();
|
|
84
|
+
const get = (p, host) => new Promise((resolve, reject) => {
|
|
85
|
+
http.get({ host: '127.0.0.1', port, path: p, headers: { host } }, (res) => {
|
|
86
|
+
res.resume();
|
|
87
|
+
res.on('end', () => resolve({ status: res.statusCode, location: res.headers.location || '' }));
|
|
88
|
+
}).on('error', reject);
|
|
89
|
+
});
|
|
90
|
+
try { await fn(get); } finally { server.close(); }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const CHAPTER_OF = {
|
|
94
|
+
ranks: 'the-rank-ladder',
|
|
95
|
+
drachmae: 'credits-and-karma',
|
|
96
|
+
diagrams: 'the-claim-ship-lifecycle',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
test('RUN: /ranks, /drachmae and /diagrams each 302 to the Primer chapter that replaced them', async () => {
|
|
100
|
+
await withHall(async (get) => {
|
|
101
|
+
for (const [page, chapter] of Object.entries(CHAPTER_OF)) {
|
|
102
|
+
for (const form of [`/${page}`, `/${page}/`, `/${page}.html`]) {
|
|
103
|
+
const r = await get(form, 'builders.127.0.0.1');
|
|
104
|
+
assert.equal(r.status, 302, `${form} is a temporary redirect, so a later re-point is never cached past`);
|
|
105
|
+
assert.equal(r.location, `/primer#${chapter}`, `${form} lands on #${chapter}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// A query string rides along; the chapter is the fragment after it.
|
|
109
|
+
const q = await get('/drachmae?from=mail', 'builders.127.0.0.1');
|
|
110
|
+
assert.equal(q.location, '/primer?from=mail#credits-and-karma');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('RUN: the diagram images are NOT swallowed by the redirect, and stay behind sign-in', async () => {
|
|
115
|
+
await withHall(async (get) => {
|
|
116
|
+
const img = await get('/diagrams/01-task-lifecycle.png', 'builders.127.0.0.1');
|
|
117
|
+
assert.equal(img.status, 302);
|
|
118
|
+
assert.match(img.location, /\/auth\/web\/start\?return=/, 'an anonymous image request goes to sign-in, not to the Primer');
|
|
119
|
+
const primer = await get('/primer', 'builders.127.0.0.1');
|
|
120
|
+
assert.match(primer.location, /\/auth\/web\/start\?return=/, 'and the Primer itself keeps its sign-in gate');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('every redirect target is a real chapter of the Primer', () => {
|
|
125
|
+
const ids = h2Ids();
|
|
126
|
+
for (const chapter of Object.values(CHAPTER_OF)) assert.ok(ids.includes(chapter), `primer.md has an h2 slugged ${chapter}`);
|
|
127
|
+
const src = fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'serve-internal.js'), 'utf8');
|
|
128
|
+
const map = src.slice(src.indexOf('const RETIRED_DOC_CHAPTER'), src.indexOf('const RETIRED_DOC_PAGE_RE'));
|
|
129
|
+
for (const [page, chapter] of Object.entries(CHAPTER_OF)) {
|
|
130
|
+
assert.match(map, new RegExp(`${page}: '${chapter}'`), `serve-internal.js sends /${page} to #${chapter}`);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ---- 2. the chapters, each with its diagram --------------------------------
|
|
135
|
+
|
|
136
|
+
const diagramTable = () => [...primerJs.matchAll(/\{ id: '([a-z0-9-]+)',\s*src: '\/builders\/diagrams\/([\w.-]+)'/g)]
|
|
137
|
+
.map((m) => ({ id: m[1], file: m[2] }));
|
|
138
|
+
|
|
139
|
+
test('all five diagrams are inlined, each at a chapter that exists', () => {
|
|
140
|
+
const table = diagramTable();
|
|
141
|
+
assert.deepEqual(table.map((d) => d.file.replace(/\.png$/, '')).sort(),
|
|
142
|
+
fs.readdirSync(path.join(ROOT, 'docs', 'onboarding', 'diagrams')).filter((f) => f.endsWith('.mmd')).map((f) => f.replace(/\.mmd$/, '')).sort(),
|
|
143
|
+
'one figure per diagram source, none left without a chapter');
|
|
144
|
+
const ids = h2Ids();
|
|
145
|
+
for (const d of table) assert.ok(ids.includes(d.id), `${d.file} is inlined at #${d.id}, which primer.md must have`);
|
|
146
|
+
assert.equal(new Set(table.map((d) => d.id)).size, table.length, 'and no chapter carries two');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('the lifecycle, the rank ladder and credits and karma are each explained ONCE', () => {
|
|
150
|
+
const ids = h2Ids();
|
|
151
|
+
for (const chapter of ['the-claim-ship-lifecycle', 'the-rank-ladder', 'credits-and-karma']) {
|
|
152
|
+
assert.equal(ids.filter((id) => id === chapter).length, 1, `one #${chapter} chapter`);
|
|
153
|
+
}
|
|
154
|
+
// The ladder chapter carries what the Ranks page said: every live rank, and
|
|
155
|
+
// how each promotion happens.
|
|
156
|
+
const ladder = PRIMER_MD.slice(PRIMER_MD.indexOf('## The rank ladder'), PRIMER_MD.indexOf('## Credits and karma'));
|
|
157
|
+
for (const rank of ['Xenos', 'Thetes', 'Metic', 'Archon']) assert.match(ladder, new RegExp(`\\*\\*${rank}\\*\\*`));
|
|
158
|
+
assert.match(ladder, /### How you advance/);
|
|
159
|
+
assert.match(ladder, /\/builders#accomplishments-progress/, 'and it points the reader at their own standing');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('the two stale primer lines are gone', () => {
|
|
163
|
+
assert.doesNotMatch(PRIMER_MD, /touches the game/, 'the core has no game');
|
|
164
|
+
assert.doesNotMatch(PRIMER_MD, /Entry to the project is \*\*approval-gated\*\*/);
|
|
165
|
+
assert.match(PRIMER_MD, /\*\*depends on the project\*\*/, 'entry depends on the project');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('the primer keeps a hall address in the hall, and sends repo paths to GitHub', () => {
|
|
169
|
+
const sandbox = { window: { OTB: realOTB() }, document: { readyState: 'complete', querySelector: () => null } };
|
|
170
|
+
vm.createContext(sandbox);
|
|
171
|
+
vm.runInContext(read('md-reader.js'), sandbox, { filename: 'md-reader.js' });
|
|
172
|
+
// Lift the resolver out of primer.js without booting the page.
|
|
173
|
+
const src = primerJs.slice(primerJs.indexOf(' const resolveLinkHref = (() => {'), primerJs.indexOf(' function toast('));
|
|
174
|
+
const fn = vm.runInContext(`(() => { const MD = window.OTBMdReader; const PRIMER_DIR = 'docs/onboarding/'; let repoBlobBase = 'https://gh/blob/main'; let repoTreeBase = 'https://gh/tree/main';\n${src}\nreturn resolveLinkHref; })()`, sandbox);
|
|
175
|
+
assert.equal(fn('/builders#accomplishments-progress'), '/builders#accomplishments-progress');
|
|
176
|
+
assert.equal(fn('drachmae-and-karma.md'), 'https://gh/blob/main/docs/onboarding/drachmae-and-karma.md',
|
|
177
|
+
'the full reference is a repo doc now that its hall page is a chapter');
|
|
178
|
+
assert.equal(fn('../adr/'), 'https://gh/tree/main/docs/adr');
|
|
179
|
+
assert.equal(fn('/buildersx/'), 'https://gh/tree/main/buildersx', 'only a real /builders address is kept');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Lift insertLiveSections out of primer.js (with the two consts it reads) and run
|
|
183
|
+
// it against a fake article, a fake OTBPrimerLive and a controllable observer.
|
|
184
|
+
function liftInsertLive({ hash = '', withObserver = true }) {
|
|
185
|
+
const src = primerJs.slice(primerJs.indexOf(' const LIVE_HASHES'), primerJs.indexOf(' // A deep link — /primer#the-rank-ladder'));
|
|
186
|
+
const fills = [];
|
|
187
|
+
const observers = [];
|
|
188
|
+
const window = {
|
|
189
|
+
location: { hash },
|
|
190
|
+
OTBPrimerLive: {
|
|
191
|
+
placeholder: (kind) => ({ kind }),
|
|
192
|
+
fillBounty: async (s) => { fills.push(s.kind); return true; },
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
if (withObserver) {
|
|
196
|
+
window.IntersectionObserver = class {
|
|
197
|
+
constructor(cb) { this.cb = cb; this.targets = []; observers.push(this); }
|
|
198
|
+
observe(t) { this.targets.push(t); }
|
|
199
|
+
disconnect() { this.gone = true; }
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const next = { tagName: 'H2' };
|
|
203
|
+
const h = { tagName: 'H2', nextElementSibling: { tagName: 'P', nextElementSibling: next } };
|
|
204
|
+
const placed = [];
|
|
205
|
+
const article = {
|
|
206
|
+
querySelector: (sel) => (sel === 'h2[id="credits-and-karma"]' ? h : null),
|
|
207
|
+
insertBefore: (el, ref) => placed.push([el.kind, ref === next]),
|
|
208
|
+
appendChild: (el) => placed.push([el.kind, false]),
|
|
209
|
+
};
|
|
210
|
+
const ctx = { window, Promise };
|
|
211
|
+
vm.createContext(ctx);
|
|
212
|
+
const fn = vm.runInContext(`(() => { const LIVE_CHAPTER_ID = 'credits-and-karma';\n${src}\nreturn insertLiveSections; })()`, ctx);
|
|
213
|
+
return { run: () => fn(article), fills, observers, placed };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
test('RUN: the live reads wait until the reader comes near, so an ordinary Primer visit costs nothing', async () => {
|
|
217
|
+
const t = liftInsertLive({ hash: '#the-rank-ladder' });
|
|
218
|
+
await t.run();
|
|
219
|
+
assert.deepEqual(t.placed, [['bounty', true]], 'placed at the end of the credits chapter, ahead of the next one (the treasury is on /money since task 1005047)');
|
|
220
|
+
assert.deepEqual(t.fills, [], 'no fetch on load');
|
|
221
|
+
assert.equal(t.observers.length, 1);
|
|
222
|
+
t.observers[0].cb([{ isIntersecting: false }]);
|
|
223
|
+
assert.deepEqual(t.fills, [], 'nor while they are still far away');
|
|
224
|
+
t.observers[0].cb([{ isIntersecting: true }]);
|
|
225
|
+
t.observers[0].cb([{ isIntersecting: true }]);
|
|
226
|
+
await Promise.resolve();
|
|
227
|
+
assert.deepEqual(t.fills, ['bounty'], 'one read, once, when the reader approaches');
|
|
228
|
+
assert.equal(t.observers[0].gone, true, 'and the watch is dropped');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('RUN: an old /drachmae link (or a section address) fills at once, so the landing is on the final layout', async () => {
|
|
232
|
+
for (const hash of ['#credits-and-karma', '#security-bounty-rates']) {
|
|
233
|
+
const t = liftInsertLive({ hash });
|
|
234
|
+
await t.run();
|
|
235
|
+
assert.deepEqual(t.fills, ['bounty'], `${hash} reads it before landing`);
|
|
236
|
+
assert.equal(t.observers.length, 0);
|
|
237
|
+
}
|
|
238
|
+
const old = liftInsertLive({ withObserver: false });
|
|
239
|
+
await old.run();
|
|
240
|
+
assert.deepEqual(old.fills, ['bounty'], 'a browser with no observer still gets the section');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('a deep link lands after the article, its live sections and the fonts', () => {
|
|
244
|
+
const boot = primerJs.slice(primerJs.indexOf('async function boot()'));
|
|
245
|
+
assert.match(boot, /const live = insertLiveSections\(article\);/);
|
|
246
|
+
assert.match(boot, /live\.then\(\(\) => \(document\.fonts \? document\.fonts\.ready : null\)\)\.then\(landOnHash\);/);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('the states file shoots a redirected deep link, not just the top of the page', () => {
|
|
250
|
+
const states = JSON.parse(read('primer.states.json'));
|
|
251
|
+
assert.match(states.states.jump.url, /#credits-and-karma$/);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ---- 3. the live section, RUN ---------------------------------------------
|
|
255
|
+
|
|
256
|
+
const bountyFixture = JSON.parse(
|
|
257
|
+
fs.readFileSync(path.join(ROOT, 'scripts', 'hall-preview', 'fixtures', 'public__bounty-table.json'), 'utf8'),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Just enough of an element for primer-live.js: it only ever writes a section's
|
|
261
|
+
// innerHTML, its data-live and aria-busy, and removes it.
|
|
262
|
+
function fakeSection() {
|
|
263
|
+
return {
|
|
264
|
+
className: '', dataset: {}, attrs: {}, innerHTML: '', removed: false,
|
|
265
|
+
setAttribute(k, v) { this.attrs[k] = String(v); },
|
|
266
|
+
removeAttribute(k) { delete this.attrs[k]; },
|
|
267
|
+
remove() { this.removed = true; },
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function loadLive({ responses, branding = { currency: { label: 'drachmae' } } }) {
|
|
272
|
+
const warnings = [];
|
|
273
|
+
const sandbox = {
|
|
274
|
+
window: { OTB: realOTB(), __BRANDING__: branding },
|
|
275
|
+
document: { createElement: () => fakeSection() },
|
|
276
|
+
console: { warn: (...a) => warnings.push(a.join(' ')) },
|
|
277
|
+
fetch: async (url) => {
|
|
278
|
+
const r = responses[url];
|
|
279
|
+
if (r instanceof Error) throw r;
|
|
280
|
+
if (!r) return { ok: false, status: 404, json: async () => ({}) };
|
|
281
|
+
return { ok: true, status: 200, json: async () => r };
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
vm.createContext(sandbox);
|
|
285
|
+
vm.runInContext(liveJs, sandbox, { filename: 'primer-live.js' });
|
|
286
|
+
return { live: sandbox.window.OTBPrimerLive, warnings };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const BOUNTY = '/api/bongos/public/bounty-table';
|
|
290
|
+
|
|
291
|
+
test('RUN: a section says Loading… the moment it is placed, under the heading a deep link needs', async () => {
|
|
292
|
+
const { live } = await loadLive({ responses: {} });
|
|
293
|
+
for (const [kind, id] of [['bounty', 'security-bounty-rates']]) {
|
|
294
|
+
const s = live.placeholder(kind);
|
|
295
|
+
assert.match(s.innerHTML, new RegExp(`^<h3 id="${id}">`), `${kind} carries its address from the start`);
|
|
296
|
+
assert.match(s.innerHTML, /<p class="hall-loading" role="status">Loading…<\/p>/);
|
|
297
|
+
assert.equal(s.attrs['aria-busy'], 'true');
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('RUN: the bounty rate card renders the live table, tuned tiers marked', async () => {
|
|
302
|
+
const tuned = { ...bountyFixture, table: bountyFixture.table.map((r, i) => (i === 0 ? { ...r, overridden: true } : r)) };
|
|
303
|
+
const { live } = await loadLive({ responses: { [BOUNTY]: tuned }, branding: {} });
|
|
304
|
+
const s = live.placeholder('bounty');
|
|
305
|
+
assert.equal(await live.fillBounty(s), true);
|
|
306
|
+
assert.match(s.innerHTML, /^<h3 id="security-bounty-rates">Security bounty rates<\/h3>/);
|
|
307
|
+
assert.equal((s.innerHTML.match(/<tr><td><strong>/g) || []).length, bountyFixture.table.length, 'one row per severity');
|
|
308
|
+
assert.match(s.innerHTML, /<em>\(tuned\)<\/em>/);
|
|
309
|
+
assert.match(s.innerHTML, /<th>Credits<\/th>/, 'the neutral currency word when the pack names none');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('RUN: a bounty feed that fails leaves no section', async () => {
|
|
313
|
+
for (const responses of [{}, { [BOUNTY]: { table: [] } }]) {
|
|
314
|
+
const { live } = await loadLive({ responses });
|
|
315
|
+
const s = live.placeholder('bounty');
|
|
316
|
+
assert.equal(await live.fillBounty(s), false);
|
|
317
|
+
assert.equal(s.removed, true);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('the live section reads an endpoint that already existed, and the treasury is gone to /money', () => {
|
|
322
|
+
assert.match(liveJs, /BOUNTY_URL = '\/api\/bongos\/public\/bounty-table'/);
|
|
323
|
+
assert.doesNotMatch(liveJs, /cost-summary|fillTreasury/, 'task 1005047: a money figure is drawn on the money page alone');
|
|
324
|
+
assert.doesNotMatch(liveJs, /<(?:path|polyline|rect)\b[^>]*\b(?:d|points)="\$\{/, 'no hand-built SVG geometry');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('the page loads the live module, then the page script', () => {
|
|
328
|
+
const loaded = [...primerHtml.matchAll(/<script[^>]+src="([^"?]+)/g)].map((m) => m[1].split('/').pop());
|
|
329
|
+
const at = (f) => loaded.indexOf(f);
|
|
330
|
+
assert.ok(at('primer-live.js') > -1 && at('primer.js') > -1);
|
|
331
|
+
assert.ok(at('primer-live.js') < at('primer.js'));
|
|
332
|
+
// task 1005047: the chart kit drew the treasury, which is on the money page now.
|
|
333
|
+
assert.equal(at('hall-kit.js'), -1, 'nothing on the Primer draws a chart');
|
|
334
|
+
// The figure chrome is shared (style.css), so the Primer needs no profile sheet.
|
|
335
|
+
assert.match(primerHtml, /style\.css/);
|
|
336
|
+
assert.doesNotMatch(primerHtml, /profile\.css/);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ---- 4. the shared reader, actually run -------------------------------------
|
|
340
|
+
|
|
341
|
+
test('the shared parser reaches h4 — the reference doc\'s earning sources are ####', () => {
|
|
342
|
+
const MD = loadReader();
|
|
343
|
+
const { html, headings } = MD.parseMarkdown('#### 4. Achievements\n\nUnlocking one pays its bonus.\n', (h) => h);
|
|
344
|
+
assert.equal(headings.length, 1);
|
|
345
|
+
assert.equal(headings[0].level, 4);
|
|
346
|
+
assert.match(html, /<h4 id="4-achievements">4\. Achievements<\/h4>/);
|
|
347
|
+
assert.doesNotMatch(html, /####/);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('a paragraph breaks before an h4 the way it breaks before an h3', () => {
|
|
351
|
+
const MD = loadReader();
|
|
352
|
+
const { html } = MD.parseMarkdown('a line\n#### A head\n', (h) => h);
|
|
353
|
+
assert.match(html, /<p>a line<\/p>\s*<h4/);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test('a heading that carries a link slugs by its text, so the copied address stays readable', () => {
|
|
357
|
+
const MD = loadReader();
|
|
358
|
+
const withLink = '2. Per-task credits, adjusted by *kind* ([ADR 0023](../adr/0023-kind-multipliers-and-idea-bonus.md))';
|
|
359
|
+
assert.equal(MD.slugify(withLink), MD.slugify('2. Per-task credits, adjusted by *kind* (ADR 0023)'));
|
|
360
|
+
assert.doesNotMatch(MD.slugify(withLink), /adr0023|md$/, 'the target path is not part of the address');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test('emphasis inside a bold run renders — the API list-price line showed its asterisks', () => {
|
|
364
|
+
const MD = loadReader();
|
|
365
|
+
const { html } = MD.parseMarkdown('- **It is the API *list* price, deliberately** and not the rest\n', (h) => h);
|
|
366
|
+
assert.match(html, /<strong>It is the API <em>list<\/em> price, deliberately<\/strong> and not the rest/);
|
|
367
|
+
const two = MD.parseMarkdown('**a** and **b**\n', (h) => h).html;
|
|
368
|
+
assert.match(two, /<strong>a<\/strong> and <strong>b<\/strong>/, 'and two bold runs on one line stay two');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test('the room gives the fourth level a rule, the measure and the anchor offset', () => {
|
|
372
|
+
const h4 = rule(roomCss, '.primer-article h4');
|
|
373
|
+
assert.ok(h4, 'room.css styles h4 (the credits reference doc uses them)');
|
|
374
|
+
assert.match(h4, /scroll-margin-top/, 'a deep link to an h4 clears the top bar');
|
|
375
|
+
const measured = decomment(roomCss).match(/\.primer-article\s*>\s*p,[\s\S]*?\{([^}]*)\}/);
|
|
376
|
+
assert.ok(measured, 'the measured-children list exists');
|
|
377
|
+
assert.match(measured[0], /\.primer-article\s*>\s*h4/, 'and the h4 is measured with the prose');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('the reference rules left with the reference page', () => {
|
|
381
|
+
assert.doesNotMatch(decomment(roomCss), /room--reference/, 'no rule keyed on a body class no page wears');
|
|
382
|
+
for (const f of fs.readdirSync(HALL).filter((n) => n.endsWith('.html'))) {
|
|
383
|
+
assert.doesNotMatch(read(f), /room--reference/, `${f} wears no reference modifier`);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
@@ -68,16 +68,13 @@ const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
|
68
68
|
const styleCss = read('style.css');
|
|
69
69
|
const roomCss = read('room.css');
|
|
70
70
|
const primerCss = read('primer.css');
|
|
71
|
-
const diagramsCss = read('diagrams.css');
|
|
72
71
|
const roadmapCss = read('roadmap.css');
|
|
73
72
|
const viewerCss = read('diagram-viewer.css');
|
|
74
73
|
const primerJs = read('primer.js');
|
|
75
74
|
const viewerJs = read('diagram-viewer.js');
|
|
76
75
|
const mdReaderJs = read('md-reader.js');
|
|
77
76
|
const primerHtml = read('primer.html');
|
|
78
|
-
const diagramsHtml = read('diagrams.html');
|
|
79
77
|
const roadmapHtml = read('roadmap.html');
|
|
80
|
-
const ranksHtml = read('ranks.html');
|
|
81
78
|
|
|
82
79
|
const decomment = (css) => css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
83
80
|
// The body of the first rule matching `selector {`, comments stripped. Naive on
|
|
@@ -94,7 +91,6 @@ const rule = (css, selector) => {
|
|
|
94
91
|
const RESKINNED = [
|
|
95
92
|
['room.css', roomCss],
|
|
96
93
|
['primer.css', primerCss],
|
|
97
|
-
['diagrams.css', diagramsCss],
|
|
98
94
|
['roadmap.css', roadmapCss],
|
|
99
95
|
['diagram-viewer.css', viewerCss],
|
|
100
96
|
];
|
|
@@ -143,14 +139,18 @@ test('no re-skinned reading-room sheet spends a compat alias', () => {
|
|
|
143
139
|
'the aliases exist for pages nobody has re-skinned; spending one here quietly opts the sheet back out of the world');
|
|
144
140
|
});
|
|
145
141
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
// task 1005046: the Ranks page folded into the Primer; its ladder cards went with
|
|
143
|
+
// it and "Where you stand" (Accomplishments) kept the standing rules, which hold
|
|
144
|
+
// the same line.
|
|
145
|
+
test('the "Where you stand" section of style.css spends the real names too', () => {
|
|
146
|
+
for (const sel of ['.ranks-standing', '.ranks-standing__blurb', '.ranks-standing__next', '.ranks-rise__body']) {
|
|
147
|
+
const body = rule(styleCss, sel);
|
|
148
|
+
assert.ok(body, `${sel} has a rule`);
|
|
149
|
+
for (const alias of ALIASES) {
|
|
150
|
+
assert.doesNotMatch(body, new RegExp(`var\\(\\s*${alias}\\s*[,)]`), `${sel} spends no ${alias}`);
|
|
151
|
+
}
|
|
151
152
|
}
|
|
152
|
-
assert.
|
|
153
|
-
'the edge is reserved as transparent so a modifier can draw one — collab.css\'s .btn--quiet is the precedent');
|
|
153
|
+
assert.equal(rule(styleCss, '.rank-card'), null, 'the ladder cards left with the page');
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
test('the alias guard would CATCH --surface without matching --surface-2', () => {
|
|
@@ -223,8 +223,11 @@ test('primer.html loads the reader BEFORE the page script', () => {
|
|
|
223
223
|
});
|
|
224
224
|
|
|
225
225
|
test('the primer keeps the one rewrite that is its own', () => {
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
// task 1005046: the economy doc's in-hall page became a Primer chapter, so the
|
|
227
|
+
// rewrite that kept a link to it in the hall is gone; what is the primer's own
|
|
228
|
+
// now is a /builders address (run in tests/hall_reading_room.mjs).
|
|
229
|
+
assert.doesNotMatch(primerJs, /'\/builders\/drachmae'/, 'no link is sent to the retired Economy page');
|
|
230
|
+
assert.match(primerJs, /\/\^\\\/builders/, 'a hall address stays in the hall');
|
|
228
231
|
assert.match(primerJs, /baseDir:\s*PRIMER_DIR/, 'and resolves its relative links against docs/onboarding/');
|
|
229
232
|
});
|
|
230
233
|
|
|
@@ -237,7 +240,7 @@ test('the room styles what the shared parser emits that the old copy never did',
|
|
|
237
240
|
|
|
238
241
|
test('primer.js still injects the diagrams — the one parser-adjacent job that is the primer\'s', () => {
|
|
239
242
|
assert.match(primerJs, /function inlineDiagrams/);
|
|
240
|
-
assert.match(primerJs, /
|
|
243
|
+
assert.match(primerJs, /DIAGRAM_AT_CHAPTER/, 'keyed by chapter id (tests/hall_reading_room.mjs checks each id exists)');
|
|
241
244
|
assert.match(primerJs, /fig\.className = 'primer-figure'/,
|
|
242
245
|
'the injected figure takes the class diagram-viewer.js upgrades and room.css frames');
|
|
243
246
|
});
|
|
@@ -285,17 +288,19 @@ test('the fullscreen scroll lock is on the root, where the shell already locks',
|
|
|
285
288
|
assert.match(decomment(styleCss), /html\.drawer-open/, 'and that is the convention it is matching');
|
|
286
289
|
});
|
|
287
290
|
|
|
288
|
-
test('the room frames
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
assert.
|
|
293
|
-
|
|
291
|
+
test('the room frames the figure the viewer upgrades', () => {
|
|
292
|
+
// task 1005046: the diagrams index's `figure.diagram` was the second class; with
|
|
293
|
+
// that page folded into the primer, the primer's figure is the only one.
|
|
294
|
+
assert.match(viewerJs, /SELECTOR = 'figure\.primer-figure img'/, 'the viewer upgrades the primer\'s figures');
|
|
295
|
+
assert.doesNotMatch(decomment(roomCss), /figure\.diagram/, 'and the room keeps no rule for a class no page draws');
|
|
296
|
+
const frame = rule(roomCss, '.primer-figure');
|
|
297
|
+
assert.ok(frame, 'the room frames the figure');
|
|
298
|
+
assert.match(frame, /var\(--glass\)/);
|
|
294
299
|
// the canvas is inserted INSIDE the figure, so exactly one of the two draws
|
|
295
300
|
// the frame or the diagram lands in nested boxes
|
|
296
|
-
const inner =
|
|
301
|
+
const inner = rule(roomCss, '.primer-figure .dgv');
|
|
297
302
|
assert.ok(inner, 'the inserted canvas gives up its own frame');
|
|
298
|
-
assert.match(inner
|
|
303
|
+
assert.match(inner, /border:\s*0/);
|
|
299
304
|
});
|
|
300
305
|
|
|
301
306
|
// ---- 7b. the figure follows the theme (task 1003639) ----------------------
|
|
@@ -326,8 +331,8 @@ test('dark turns the canvas figure, in both twins, byte-identical', () => {
|
|
|
326
331
|
assert.doesNotMatch(rule(viewerCss, '.dgv__pan svg'), /filter/, 'light keeps the render exactly as mermaid drew it');
|
|
327
332
|
});
|
|
328
333
|
|
|
329
|
-
test('the fallback PNG turns with it, on
|
|
330
|
-
const both = (pre) => `${pre} .primer-figure img
|
|
334
|
+
test('the fallback PNG turns with it, on the figure the viewer upgrades', () => {
|
|
335
|
+
const both = (pre) => `${pre} .primer-figure img`;
|
|
331
336
|
const sw = rule(roomCss, both('html[data-theme="dark"]'));
|
|
332
337
|
const media = mediaRule(roomCss, both('html:not([data-theme])'));
|
|
333
338
|
assert.ok(sw && media, 'the PNG shows before the SVG arrives and whenever it cannot — a dark reader must not get a white flash');
|
|
@@ -344,7 +349,8 @@ test('the viewer drops the white ground mermaid bakes into the SVG', () => {
|
|
|
344
349
|
|
|
345
350
|
// ---- 8. every room has exactly one h1 -------------------------------------
|
|
346
351
|
|
|
347
|
-
|
|
352
|
+
// task 1005046: diagrams.html and ranks.html folded into the primer's chapters.
|
|
353
|
+
const ROOMS = [['primer.html', primerHtml], ['roadmap.html', roadmapHtml]];
|
|
348
354
|
|
|
349
355
|
// An HTML comment that mentions <h1> is prose, not markup — and these pages each
|
|
350
356
|
// carry one explaining why the heading is there at all.
|
|
@@ -358,8 +364,8 @@ test('every reading room renders exactly one h1, and it is the page head\'s', ()
|
|
|
358
364
|
}
|
|
359
365
|
});
|
|
360
366
|
|
|
361
|
-
test('the
|
|
362
|
-
for (const [name, html] of [['primer.html', primerHtml]
|
|
367
|
+
test('the DOCS room takes the room voice; the deck takes the board\'s', () => {
|
|
368
|
+
for (const [name, html] of [['primer.html', primerHtml]]) {
|
|
363
369
|
assert.match(html, /class="page-head page-head--room"/, `${name} is a reading room`);
|
|
364
370
|
}
|
|
365
371
|
assert.match(roadmapHtml, /class="page-head page-head--board"/,
|
|
@@ -375,7 +381,7 @@ test('the document\'s own h1 is suppressed, or the article would render a second
|
|
|
375
381
|
// ---- 9. the archetype's shape ---------------------------------------------
|
|
376
382
|
|
|
377
383
|
test('the room opts in from its markup, and the archetype sheet is what carries it', () => {
|
|
378
|
-
for (const [name, html] of [['primer.html', primerHtml]
|
|
384
|
+
for (const [name, html] of [['primer.html', primerHtml]]) {
|
|
379
385
|
assert.match(html, /<body class="room[^"]*"/, `${name} opts into the room`);
|
|
380
386
|
assert.match(html, /href="\/builders\/room\.css"/, `${name} loads the archetype`);
|
|
381
387
|
}
|
|
@@ -724,13 +730,13 @@ test('the timeline module is a pure builder, so it is testable and cannot boot i
|
|
|
724
730
|
// reader sees, and a script writing into one is a message nobody gets.
|
|
725
731
|
test('every Docs page head holds only its h1, and no script writes into it', () => {
|
|
726
732
|
const pub = path.join(ROOT, 'modules/hall-ui/public');
|
|
727
|
-
for (const page of ['primer', '
|
|
733
|
+
for (const page of ['primer', 'commands', 'atlas', 'modules']) {
|
|
728
734
|
const html = dehtmlcomment(fs.readFileSync(path.join(pub, `${page}.html`), 'utf8'));
|
|
729
735
|
const head = html.match(/<div class="page-head[^"]*">([\s\S]*?)<\/div>/);
|
|
730
736
|
assert.ok(head, `${page}.html keeps its head for the h1`);
|
|
731
737
|
assert.doesNotMatch(head[1], /page-head__eyebrow|page-head__sub/, `${page}.html's hidden head holds no eyebrow or sub-line`);
|
|
732
738
|
}
|
|
733
|
-
for (const [file, id] of [['
|
|
739
|
+
for (const [file, id] of [['modules.js', 'modules-sub']]) {
|
|
734
740
|
assert.ok(!fs.readFileSync(path.join(pub, file), 'utf8').includes(id), `${file} no longer writes into #${id}`);
|
|
735
741
|
}
|
|
736
742
|
});
|
|
@@ -94,8 +94,9 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
94
94
|
// "Upgraded to core X" notice after a move; task 1004826: Standing is Accomplishments;
|
|
95
95
|
// task 1004846: the new logo (its bump landed late, in task 1004852); task 1004938: the
|
|
96
96
|
// Market Watch settings nav item and its fuel glyph; task 1005041: the Update pill replaced the
|
|
97
|
-
// core-update banner; task 1005045: the Market Watch settings item left (one sidebar item)
|
|
98
|
-
|
|
97
|
+
// core-update banner; task 1005045: the Market Watch settings item left (one sidebar item);
|
|
98
|
+
// task 1005046: Ranks, Economy and Diagrams left Docs (one reading room, the Primer).
|
|
99
|
+
const SHELL_SHA = '662e036a340ad33f';
|
|
99
100
|
|
|
100
101
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
101
102
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|
|
@@ -92,7 +92,6 @@ const EXACT_LADDER_RE = /\[\s*'xenos',\s*'thetes',\s*'metic',\s*'archon'\s*\]/;
|
|
|
92
92
|
const STATIC_LADDER_FILES = [
|
|
93
93
|
'modules/discord/discord-channels.js',
|
|
94
94
|
'modules/memory/search.js',
|
|
95
|
-
'modules/hall-ui/public/ranks.js',
|
|
96
95
|
'modules/hall-ui/public/profile.js',
|
|
97
96
|
'modules/hall-ui/public/watch.js',
|
|
98
97
|
// task 1003094: the Constitution panel and the Board Room's sitting card both
|
package/tests/ui_design_kit.mjs
CHANGED
|
@@ -34,7 +34,7 @@ test('the platform floors are a pinned list — a floor cannot vanish quietly',
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
test('the shipped states files validate and sit beside the page they name', () => {
|
|
37
|
-
for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/projects-empty.states.json', 'modules/public-landing/public/projects-dns-pending.states.json', 'modules/public-landing/public/projects-hall-live.states.json','modules/public-landing/public/index.states.json', 'modules/public-landing/public/index-cli.states.json', 'modules/public-landing/public/account.states.json', 'modules/public-landing/public/account-private.states.json', 'modules/public-landing/public/guild.states.json', 'modules/public-landing/public/invitations.states.json', 'modules/public-landing/public/invitations-empty.states.json', 'modules/public-landing/public/invitations-picker.states.json', 'modules/hall-ui/public/
|
|
37
|
+
for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/projects-empty.states.json', 'modules/public-landing/public/projects-dns-pending.states.json', 'modules/public-landing/public/projects-hall-live.states.json','modules/public-landing/public/index.states.json', 'modules/public-landing/public/index-cli.states.json', 'modules/public-landing/public/account.states.json', 'modules/public-landing/public/account-private.states.json', 'modules/public-landing/public/guild.states.json', 'modules/public-landing/public/invitations.states.json', 'modules/public-landing/public/invitations-empty.states.json', 'modules/public-landing/public/invitations-picker.states.json', 'modules/hall-ui/public/primer.states.json']) {
|
|
38
38
|
const json = readJson(rel);
|
|
39
39
|
const v = lib.validateStatesFile(json);
|
|
40
40
|
assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/* diagrams.css — what the DIAGRAMS index has that the other reading rooms do
|
|
2
|
-
not (task 1003306, goal 1000081).
|
|
3
|
-
|
|
4
|
-
The room — the framed figure, the reading measure, the prose rhythm — is the
|
|
5
|
-
shared archetype in room.css, which this page loads first. What is left here
|
|
6
|
-
is the legend strip: the three-state key (live / planned / reserved) that
|
|
7
|
-
tells a reader how to read the five generated diagrams. It is genuinely this
|
|
8
|
-
page's alone, so it is this page's sheet.
|
|
9
|
-
|
|
10
|
-
Promoted out of a 16-line inline <style> block, which sat one line over the
|
|
11
|
-
hall's default budget with its own tests/hall_audit.mjs exception. The
|
|
12
|
-
exception retires with it, and the hall now carries no substantive inline
|
|
13
|
-
style at all.
|
|
14
|
-
|
|
15
|
-
The diagrams themselves are GENERATED (scripts/bongos/gen-diagrams.js writes the
|
|
16
|
-
.svg/.png pair and assertions.json in lockstep) — never hand-edit a .mmd. */
|
|
17
|
-
|
|
18
|
-
/* The key sits above the figures on the ground of its own panel, wrapping to a
|
|
19
|
-
column on a phone rather than scrolling sideways. */
|
|
20
|
-
.legend {
|
|
21
|
-
display: flex;
|
|
22
|
-
flex-wrap: wrap;
|
|
23
|
-
gap: var(--sp-xs) var(--sp-md);
|
|
24
|
-
margin: 0;
|
|
25
|
-
}
|
|
26
|
-
.legend__item {
|
|
27
|
-
display: flex;
|
|
28
|
-
align-items: center;
|
|
29
|
-
gap: 8px;
|
|
30
|
-
min-height: 24px;
|
|
31
|
-
font-size: 13.5px;
|
|
32
|
-
color: var(--ink-soft);
|
|
33
|
-
}
|
|
34
|
-
/* The three states are the hall's fixed status vocabulary, not accent-derived:
|
|
35
|
-
a diagram's "planned" must not be the same salmon as a claim button. */
|
|
36
|
-
.legend__swatch {
|
|
37
|
-
font-family: var(--label-face);
|
|
38
|
-
font-size: var(--label-size);
|
|
39
|
-
letter-spacing: var(--label-space);
|
|
40
|
-
font-weight: 400;
|
|
41
|
-
white-space: nowrap;
|
|
42
|
-
}
|
|
43
|
-
.legend__swatch--live { color: var(--ok); }
|
|
44
|
-
.legend__swatch--planned { color: var(--warn); }
|
|
45
|
-
.legend__swatch--reserved { color: var(--ink-faint); }
|