@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
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
// tests/hall_reference_world.mjs — the REFERENCE room in the chrome world:
|
|
2
|
-
// drachmae, and what the reading-room archetype owes a second document
|
|
3
|
-
// (task 1003473, goal 1000081).
|
|
4
|
-
//
|
|
5
|
-
// tests/hall_reading_world.mjs pins the reading rooms (the primer, the diagrams
|
|
6
|
-
// index, the deck, the ladder); this is the same job for the one document
|
|
7
|
-
// people SCAN rather than read. Everything here is a fact that is silent in a
|
|
8
|
-
// browser: the page still renders, it just renders the primer's instruments on
|
|
9
|
-
// a document that has no end to reach, or an outline that stops one level
|
|
10
|
-
// short of the questions people came with.
|
|
11
|
-
//
|
|
12
|
-
// What it locks:
|
|
13
|
-
// * ONE ROOM, ONE MODIFIER. drachmae opts into `room room--reference` and
|
|
14
|
-
// loads room.css alone. primer.css holds only the acknowledge gate and
|
|
15
|
-
// was loaded here for nothing; the reference rules live in room.css §8, so
|
|
16
|
-
// the next scanned document takes them by one body class.
|
|
17
|
-
// * THE LINEAR-READ INSTRUMENTS ARE OFF. No progress hairline, no section
|
|
18
|
-
// counters, no "About a five-minute read" — and the primer keeps all three.
|
|
19
|
-
// * THE OUTLINE GOES TO h3 DEPTH and lights the entry AND its part.
|
|
20
|
-
// * EVERY HEADING IS AN ADDRESS. `.room-anchor` on h2/h3/h4, a 24px box of
|
|
21
|
-
// its own, copying the absolute link on click; a deep link lands after the
|
|
22
|
-
// async render, or the browser's own jump finds nothing.
|
|
23
|
-
// * THE PARSER REACHES h4, and a heading that carries a link slugs by its
|
|
24
|
-
// text. The economy doc's six earning sources are `####`, and the shared
|
|
25
|
-
// reader stopped at `###`, so they rendered as paragraphs with a literal
|
|
26
|
-
// "####" — the largest defect on the page, and invisible to every floor.
|
|
27
|
-
// * NESTED EMPHASIS RENDERS. "**It's the API *list* price**" showed its
|
|
28
|
-
// asterisks because the bold rule refused an inner `*`.
|
|
29
|
-
// * THE LIVE TABLE IS A SECTION AHEAD OF THE CLOSING LINKS, not an appendix
|
|
30
|
-
// after "dig deeper".
|
|
31
|
-
//
|
|
32
|
-
// Reproduce the renders these came from (the populated document needs the
|
|
33
|
-
// fixture harness; the kit stub serves neither the markdown nor the bounty feed):
|
|
34
|
-
// node scripts/hall-preview/server.js --port 4620 --fixture-me
|
|
35
|
-
// node modules/ui-design/kit/render.js --states modules/hall-ui/public/drachmae.states.json \
|
|
36
|
-
// --base-in http://builders.localhost:4620 --base-out http://builders.localhost:4620
|
|
37
|
-
|
|
38
|
-
import { test } from 'node:test';
|
|
39
|
-
import assert from 'node:assert/strict';
|
|
40
|
-
import fs from 'node:fs';
|
|
41
|
-
import path from 'node:path';
|
|
42
|
-
import vm from 'node:vm';
|
|
43
|
-
import { fileURLToPath } from 'node:url';
|
|
44
|
-
import { realOTB } from './helpers.mjs';
|
|
45
|
-
|
|
46
|
-
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
47
|
-
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
48
|
-
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
49
|
-
|
|
50
|
-
const roomCss = read('room.css');
|
|
51
|
-
const mdReaderJs = read('md-reader.js');
|
|
52
|
-
const drachmaeHtml = read('drachmae.html');
|
|
53
|
-
const drachmaeJs = read('drachmae.js');
|
|
54
|
-
const drachmaeStates = JSON.parse(read('drachmae.states.json'));
|
|
55
|
-
const primerHtml = read('primer.html');
|
|
56
|
-
const primerJs = read('primer.js');
|
|
57
|
-
|
|
58
|
-
const decomment = (css) => css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
59
|
-
const dehtmlcomment = (html) => html.replace(/<!--[\s\S]*?-->/g, '');
|
|
60
|
-
const dejscomment = (js) => js.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
61
|
-
// The body of the first rule matching `selector {`, comments stripped. Takes a
|
|
62
|
-
// PLAIN selector and escapes it here.
|
|
63
|
-
const rule = (css, selector) => {
|
|
64
|
-
const esc = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
65
|
-
const m = decomment(css).match(new RegExp(`(^|[},])\\s*${esc}\\s*\\{([^}]*)\\}`, 'm'));
|
|
66
|
-
return m ? m[2] : null;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// md-reader.js is a browser IIFE that only touches `window` and `location`.
|
|
70
|
-
function loadReader() {
|
|
71
|
-
const sandbox = { window: { OTB: realOTB() }, location: { hostname: 'builders.example.com', href: 'https://builders.example.com/drachmae' } };
|
|
72
|
-
vm.createContext(sandbox);
|
|
73
|
-
vm.runInContext(mdReaderJs, sandbox, { filename: 'md-reader.js' });
|
|
74
|
-
return sandbox.window.OTBMdReader;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ---- 1. one room, one modifier ----------------------------------------------
|
|
78
|
-
|
|
79
|
-
test('drachmae opts into the room and its reference rules, and loads the archetype sheet alone', () => {
|
|
80
|
-
assert.match(drachmaeHtml, /<body class="room room--reference"/, 'the reference is the room with one modifier, not a second archetype');
|
|
81
|
-
assert.match(drachmaeHtml, /href="\/builders\/room\.css"/, 'the archetype is loaded');
|
|
82
|
-
assert.doesNotMatch(dehtmlcomment(drachmaeHtml), /primer\.css/,
|
|
83
|
-
'primer.css holds only the acknowledge gate, which a reference has no use for');
|
|
84
|
-
assert.match(drachmaeHtml, /class="page-head page-head--room"/, 'the head keeps the room voice');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('the reference rules live in room.css, keyed on the body modifier', () => {
|
|
88
|
-
const css = decomment(roomCss);
|
|
89
|
-
assert.match(css, /\.room--reference \.primer-article h2::before,\s*\.room--reference \.primer-toc__list a::before\s*\{\s*content:\s*none/,
|
|
90
|
-
'the counters are off on a reference');
|
|
91
|
-
assert.ok(rule(roomCss, '.room--reference .primer-toc__sub'), 'the nested contents have a rule');
|
|
92
|
-
assert.ok(rule(roomCss, '.room--reference .primer-toc__list a.is-trail'), 'the part of the lit entry is the trail');
|
|
93
|
-
assert.match(rule(roomCss, '.primer-article h2::before') || '', /counter-increment/,
|
|
94
|
-
'and the primer keeps its counter — the modifier turns it off, the archetype still draws it');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// ---- 2. the linear-read instruments are off ---------------------------------
|
|
98
|
-
|
|
99
|
-
test('the reference carries no progress hairline, no acknowledge gate and no read time', () => {
|
|
100
|
-
const html = dehtmlcomment(drachmaeHtml);
|
|
101
|
-
assert.doesNotMatch(html, /primer-progress/, 'no progress hairline: a reference has no end to reach');
|
|
102
|
-
assert.doesNotMatch(dejscomment(drachmaeJs), /primer-progress|setupProgressBar/, 'and the script drives none');
|
|
103
|
-
assert.doesNotMatch(html, /primer-ack/, 'no acknowledge gate');
|
|
104
|
-
assert.doesNotMatch(html, /minute read/i, 'the sub line says what kind of document this is, not how long it takes');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('the primer keeps all three — the modifier is drachmae\'s, not the room\'s', () => {
|
|
108
|
-
assert.doesNotMatch(primerHtml, /room--reference/);
|
|
109
|
-
assert.match(primerHtml, /primer-progress__bar/);
|
|
110
|
-
assert.match(primerJs, /function setupProgressBar/, 'the primer\'s gate is fed by its progress');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// ---- 3. the outline -----------------------------------------------------------
|
|
114
|
-
|
|
115
|
-
test('the outline is read from the rendered h2s AND h3s and nests them', () => {
|
|
116
|
-
assert.match(drachmaeJs, /querySelectorAll\('h2\[id\], h3\[id\]'\)/, 'the contents are collected from the DOM, to h3 depth');
|
|
117
|
-
assert.match(drachmaeJs, /class="primer-toc__sub"/, 'h3s nest under their part');
|
|
118
|
-
assert.match(drachmaeJs, /classList\.toggle\('is-trail'/, 'the spy marks the lit entry\'s part');
|
|
119
|
-
assert.match(drachmaeHtml, /id="primer-toc-current"/, 'the phone toggle has a middle that names the part');
|
|
120
|
-
assert.match(drachmaeJs, /current\.textContent = active \? active\.part : ''/, 'and the spy writes it');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('the toggle\'s chevron is drawn, and the contents are named for a reference', () => {
|
|
124
|
-
assert.doesNotMatch(dehtmlcomment(drachmaeHtml), /▾/, 'no glyph standing in for an icon');
|
|
125
|
-
assert.match(drachmaeHtml, /<svg class="primer-toc__chevron"/);
|
|
126
|
-
assert.match(drachmaeHtml, /id="primer-toc-heading">On this page</, 'a reference is "on this page", not "in this guide"');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// ---- 4. every heading is an address -------------------------------------------
|
|
130
|
-
|
|
131
|
-
test('every h2, h3 and h4 gets an anchor that is a real link and copies the address', () => {
|
|
132
|
-
assert.match(drachmaeJs, /querySelectorAll\('h2\[id\], h3\[id\], h4\[id\]'\)/);
|
|
133
|
-
assert.match(drachmaeJs, /className = 'room-anchor'/);
|
|
134
|
-
assert.match(drachmaeJs, /a\.href = `#\$\{h\.id\}`/, 'the anchor is the heading\'s own id, so it works with no script');
|
|
135
|
-
assert.match(drachmaeJs, /navigator\.clipboard\.writeText\(url\)/, 'the click copies the absolute address');
|
|
136
|
-
const anchor = rule(roomCss, '.room--reference .primer-article .room-anchor');
|
|
137
|
-
assert.ok(anchor, 'the anchor has its own rule, scoped to the reference, at higher specificity than the prose link');
|
|
138
|
-
assert.match(anchor, /width:\s*24px/); assert.match(anchor, /height:\s*24px/);
|
|
139
|
-
assert.match(anchor, /text-decoration:\s*none/);
|
|
140
|
-
assert.ok(rule(roomCss, '.room--reference .primer-article :is(:target, .is-target) > .room-anchor'),
|
|
141
|
-
'the one the reader arrived at takes the accent — by :target, or by the mark the landing sets when :target resolved against nothing');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test('a deep link lands after the async render, the live section and the fonts', () => {
|
|
145
|
-
assert.match(drachmaeJs, /function landOnHash\(\)[\s\S]*classList\.add\('is-target'\)[\s\S]*scrollIntoView/);
|
|
146
|
-
const src = dejscomment(drachmaeJs);
|
|
147
|
-
const inserted = src.indexOf('await insertBountySection(article)');
|
|
148
|
-
const landed = src.indexOf('.then(landOnHash)');
|
|
149
|
-
assert.ok(inserted > -1 && landed > inserted, 'landOnHash runs once the rate card has taken its place, or the offsets it lands on are wrong');
|
|
150
|
-
assert.match(src, /document\.fonts\.ready[\s\S]*\.then\(landOnHash\)/, 'and after the fonts: a swap after the jump moves everything above the target');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('on a phone the collapsed outline rides under the top bar, so the toggle can name the part from anywhere', () => {
|
|
154
|
-
const m = decomment(roomCss).match(/@media \(max-width: 760px\)\s*\{\s*\.room--reference \.primer-toc\s*\{([^}]*)\}/);
|
|
155
|
-
assert.ok(m, 'the reference re-sticks the contents at phone width (the archetype makes them static there)');
|
|
156
|
-
assert.match(m[1], /position:\s*sticky/);
|
|
157
|
-
assert.match(m[1], /z-index:\s*var\(--z-progress\)/, 'below the top bar in the ladder');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// ---- 5. the live table --------------------------------------------------------
|
|
161
|
-
|
|
162
|
-
test('the live rate card is a section ahead of the closing links, not an appendix after them', () => {
|
|
163
|
-
assert.match(drachmaeJs, /CLOSING_H2 = \/\^see it\/i/);
|
|
164
|
-
assert.match(drachmaeJs, /article\.insertBefore\(section, closing\)/);
|
|
165
|
-
assert.match(drachmaeJs, /else article\.appendChild\(section\)/, 'a retitled footer falls back to appending');
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// ---- 6. the parser, actually run ----------------------------------------------
|
|
169
|
-
|
|
170
|
-
test('the shared parser reaches h4 — the six earning sources rendered as paragraphs with a literal ####', () => {
|
|
171
|
-
const MD = loadReader();
|
|
172
|
-
const { html, headings } = MD.parseMarkdown('#### 4. Achievements\n\nUnlocking one pays its bonus.\n', (h) => h);
|
|
173
|
-
assert.equal(headings.length, 1);
|
|
174
|
-
assert.equal(headings[0].level, 4);
|
|
175
|
-
assert.match(html, /<h4 id="4-achievements">4\. Achievements<\/h4>/);
|
|
176
|
-
assert.doesNotMatch(html, /####/);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('a paragraph breaks before an h4 the way it breaks before an h3', () => {
|
|
180
|
-
const MD = loadReader();
|
|
181
|
-
const { html } = MD.parseMarkdown('a line\n#### A head\n', (h) => h);
|
|
182
|
-
assert.match(html, /<p>a line<\/p>\s*<h4/);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('a heading that carries a link slugs by its text, so the copied address stays readable', () => {
|
|
186
|
-
const MD = loadReader();
|
|
187
|
-
const withLink = '2. Per-task credits, adjusted by *kind* ([ADR 0023](../adr/0023-kind-multipliers-and-idea-bonus.md))';
|
|
188
|
-
assert.equal(MD.slugify(withLink), MD.slugify('2. Per-task credits, adjusted by *kind* (ADR 0023)'));
|
|
189
|
-
assert.doesNotMatch(MD.slugify(withLink), /adr0023|md$/, 'the target path is not part of the address');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test('emphasis inside a bold run renders — the API list-price line showed its asterisks', () => {
|
|
193
|
-
const MD = loadReader();
|
|
194
|
-
const { html } = MD.parseMarkdown('- **It is the API *list* price, deliberately** and not the rest\n', (h) => h);
|
|
195
|
-
assert.match(html, /<strong>It is the API <em>list<\/em> price, deliberately<\/strong> and not the rest/);
|
|
196
|
-
const two = MD.parseMarkdown('**a** and **b**\n', (h) => h).html;
|
|
197
|
-
assert.match(two, /<strong>a<\/strong> and <strong>b<\/strong>/, 'and two bold runs on one line stay two');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// ---- 7. the room's h4 -----------------------------------------------------------
|
|
201
|
-
|
|
202
|
-
test('the room gives the fourth level a rule, the measure and the anchor offset', () => {
|
|
203
|
-
const h4 = rule(roomCss, '.primer-article h4');
|
|
204
|
-
assert.ok(h4, 'room.css styles h4 now that the parser emits it');
|
|
205
|
-
assert.match(h4, /scroll-margin-top/, 'a deep link to an h4 clears the top bar');
|
|
206
|
-
const measured = decomment(roomCss).match(/\.primer-article\s*>\s*p,[\s\S]*?\{([^}]*)\}/);
|
|
207
|
-
assert.ok(measured, 'the measured-children list exists');
|
|
208
|
-
assert.match(measured[0], /\.primer-article\s*>\s*h4/, 'and the h4 is measured with the prose');
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// ---- 8. the states file and the one h1 ------------------------------------------
|
|
212
|
-
|
|
213
|
-
test('the states file measures the reference\'s own states, including the deep link', () => {
|
|
214
|
-
for (const name of ['read', 'toc-open', 'jump', 'out']) assert.ok(drachmaeStates.states[name], `state ${name}`);
|
|
215
|
-
assert.match(drachmaeStates.states.jump.url, /#how-karma-is-earned-and-lost$/, 'the jump lands on an h3, so the trail is what the shot proves');
|
|
216
|
-
assert.ok(drachmaeStates.reducedMotion, 'the kill switch is exercised');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('the reference renders exactly one h1, the page head\'s', () => {
|
|
220
|
-
const html = dehtmlcomment(drachmaeHtml);
|
|
221
|
-
assert.equal([...html.matchAll(/<h1\b/g)].length, 1);
|
|
222
|
-
assert.match(html, /<h1 class="page-head__h">/);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// ---- 9. the treasury section (task 1003446) --------------------------------
|
|
226
|
-
//
|
|
227
|
-
// The sharpest version of goal 1000072's thesis. /drachmae is labelled Economy
|
|
228
|
-
// in the nav and was a markdown reader with ZERO live numbers, while the
|
|
229
|
-
// signed-out status page already drew this very endpoint's cumulative line and
|
|
230
|
-
// the Work board fetched the same payload to read ONE scalar off it. The
|
|
231
|
-
// stranger got the chart; the builder got the number.
|
|
232
|
-
//
|
|
233
|
-
// The section's ARITHMETIC is not asserted here — it is the kit's
|
|
234
|
-
// (`cumulateByDay`, `timeStepSeriesHtml`, `barSeriesHtml`) and
|
|
235
|
-
// tests/hall_kit_charts.mjs runs the real kit against it. What these hold is
|
|
236
|
-
// the page's half of the contract, the half a kit test cannot see.
|
|
237
|
-
|
|
238
|
-
const costSummaryFixture = JSON.parse(
|
|
239
|
-
fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'fixtures', 'cost-summary.json'), 'utf8'),
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
test('the treasury reads the endpoint that already carried the series — no new route', () => {
|
|
243
|
-
// The whole point of the task: the payload had both series all along, and the
|
|
244
|
-
// route's own comment calls daily[] "the treasury chart's cumulative line".
|
|
245
|
-
assert.match(drachmaeJs, /TREASURY_URL = '\/api\/bongos\/public\/cost-summary'/);
|
|
246
|
-
assert.ok(costSummaryFixture.daily.length, 'the payload really carries daily[]');
|
|
247
|
-
assert.ok(costSummaryFixture.daily_credits.length, 'and daily_credits[]');
|
|
248
|
-
assert.ok(Number(costSummaryFixture.total_usd) > 0, 'and the totals the prose states');
|
|
249
|
-
assert.ok(Number(costSummaryFixture.total_value_credits) > 0);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test('RUN: the cumulative line ends exactly on the total the prose states', async () => {
|
|
253
|
-
// The invariant that matters, and the one a source-text assertion cannot
|
|
254
|
-
// reach: the chart and the sentence above it are derived from the SAME
|
|
255
|
-
// payload, so they can never disagree. Run against the real kit and the real
|
|
256
|
-
// checked-in summary, which is where a units or gap-fill mistake would show
|
|
257
|
-
// up as a line ending somewhere other than the headline number.
|
|
258
|
-
const kitPath = path.join(HALL, 'hall-kit.js');
|
|
259
|
-
const kit = await import(`file://${kitPath.replace(/\\/g, '/')}`).then((m) => m.default);
|
|
260
|
-
const spend = kit.cumulateByDay(costSummaryFixture.daily, { valueOf: (r) => r.amount_usd });
|
|
261
|
-
const earned = kit.cumulateByDay(costSummaryFixture.daily_credits, { valueOf: (r) => r.credits });
|
|
262
|
-
assert.ok(spend.length > 1 && earned.length > 1, 'both series have depth');
|
|
263
|
-
assert.ok(Math.abs(spend[spend.length - 1].value - Number(costSummaryFixture.total_usd)) < 1,
|
|
264
|
-
'the spend line ends on total_usd');
|
|
265
|
-
assert.equal(earned[earned.length - 1].value, Number(costSummaryFixture.total_value_credits),
|
|
266
|
-
'and the awarded line ends on total_value_credits');
|
|
267
|
-
// Whatever the plot draws from that must be finite — a NaN in a path `d`
|
|
268
|
-
// renders as an invisible chart with no error anywhere.
|
|
269
|
-
const series = kit.timeStepSeriesHtml(spend, {
|
|
270
|
-
x0: spend[0].x, x1: spend[spend.length - 1].x, yMax: spend[spend.length - 1].value,
|
|
271
|
-
});
|
|
272
|
-
assert.doesNotMatch(series.body, /NaN|Infinity/);
|
|
273
|
-
assert.doesNotMatch(
|
|
274
|
-
kit.barSeriesHtml(costSummaryFixture.by_category.map((c) => ({ label: c.category, value: c.total_usd }))),
|
|
275
|
-
/NaN|Infinity/,
|
|
276
|
-
);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test('the page loads the chart primitive BEFORE the script that reads it', () => {
|
|
280
|
-
// window.OTBKit read at insert time with the kit loaded after it is an empty
|
|
281
|
-
// section in production and a green unit suite.
|
|
282
|
-
const loaded = [...drachmaeHtml.matchAll(/<script[^>]+src="([^"?]+)/g)].map((m) => m[1].split('/').pop());
|
|
283
|
-
const kitAt = loaded.indexOf('hall-kit.js');
|
|
284
|
-
const pageAt = loaded.indexOf('drachmae.js');
|
|
285
|
-
assert.ok(kitAt > -1, 'drachmae.html must load hall-kit.js');
|
|
286
|
-
assert.ok(pageAt > -1, 'and drachmae.js');
|
|
287
|
-
assert.ok(kitAt < pageAt, 'kit first');
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
test('every mark on the page is drawn by the kit, none by hand', () => {
|
|
291
|
-
const js = dejscomment(drachmaeJs);
|
|
292
|
-
for (const fn of ['cumulateByDay', 'timeStepSeriesHtml', 'plotSvgHtml', 'plotValueLabelHtml', 'barSeriesHtml']) {
|
|
293
|
-
assert.match(js, new RegExp(`(kit|window\\.OTBKit)\\.${fn}\\(`), `the treasury draws through ${fn}`);
|
|
294
|
-
}
|
|
295
|
-
// The §6b audit rule holds this globally; asserted here too because THIS is
|
|
296
|
-
// the page that had no chart at all and so the easiest place to hand-roll one.
|
|
297
|
-
assert.doesNotMatch(js, /<(?:path|polyline|rect)\b[^>]*\b(?:d|points)="\$\{/,
|
|
298
|
-
'no hand-built SVG geometry on the reference page');
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test('the section degrades to NOTHING rather than an empty panel', () => {
|
|
302
|
-
const js = dejscomment(drachmaeJs);
|
|
303
|
-
// The bounty section's contract, kept: a failed read leaves the prose alone
|
|
304
|
-
// and adds no outline entry that leads nowhere.
|
|
305
|
-
const fn = js.slice(js.indexOf('async function insertTreasurySection'));
|
|
306
|
-
const body = fn.slice(0, fn.indexOf('\n }\n'));
|
|
307
|
-
assert.match(body, /console\.warn\([^)]*cost summary unavailable/);
|
|
308
|
-
assert.equal((body.match(/return false;/g) || []).length, 3,
|
|
309
|
-
'three honest exits: no kit, an unreadable summary, and a treasury with nothing in it');
|
|
310
|
-
assert.match(body, /if \(!spend\.length && !earned\.length\) return false;/,
|
|
311
|
-
'both series empty is one honest nothing, not two empty plots');
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
test('the plots ship no focus stop, because nothing here wires a readout', () => {
|
|
315
|
-
// A role="img" with tabindex="0" is a focus stop a keyboard reader lands on
|
|
316
|
-
// with nothing to do, and a crosshair nothing moves only looks like a
|
|
317
|
-
// feature. The kit half is asserted in tests/hall_kit_charts.mjs.
|
|
318
|
-
assert.match(dejscomment(drachmaeJs), /interactive: false/);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
test('the currency is the branding pack\'s, never a hardcoded word', () => {
|
|
322
|
-
const js = dejscomment(drachmaeJs);
|
|
323
|
-
assert.match(js, /__BRANDING__ && window\.__BRANDING__\.currency/);
|
|
324
|
-
assert.match(js, /\|\| 'credits'/, 'with a neutral fallback, not a world-specific one');
|
|
325
|
-
assert.doesNotMatch(js.replace(/\|\| 'credits'/g, ''), /drachmae'/i,
|
|
326
|
-
'the instance\'s own currency name is not spelled into the core page');
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
test('the treasury lands ahead of the rate card, so the doc still ends on "dig deeper"', () => {
|
|
330
|
-
const js = dejscomment(drachmaeJs);
|
|
331
|
-
// The bounty section goes in first and the treasury anchors on its id, so the
|
|
332
|
-
// order is prose → what happened → the published rates → the closing links.
|
|
333
|
-
const boot = js.slice(js.indexOf('async function boot()'));
|
|
334
|
-
assert.ok(boot.indexOf('insertBountySection(article)') < boot.indexOf('insertTreasurySection(article)'),
|
|
335
|
-
'the rate card is inserted first, so the treasury can anchor ahead of it');
|
|
336
|
-
assert.match(js, /article\.querySelector\(`#\$\{BOUNTY_SECTION_ID\}`\)/);
|
|
337
|
-
assert.match(js, /CLOSING_H2\.test/, 'and it falls back to the closing links, then to appending');
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test('the figure chrome is shared, because a second page now draws figures', () => {
|
|
341
|
-
// .pchart* moved from profile.css to style.css with this task. drachmae.html
|
|
342
|
-
// loads style.css and NOT profile.css, so a copy left behind would render
|
|
343
|
-
// this page's figures unstyled.
|
|
344
|
-
const style = decomment(read('style.css'));
|
|
345
|
-
const profile = decomment(read('profile.css'));
|
|
346
|
-
for (const sel of ['.pcharts', '.pchart ', '.pchart__title', '.pchart__twin', '.pchart__foot']) {
|
|
347
|
-
assert.ok(style.includes(sel), `${sel} is in the shared sheet`);
|
|
348
|
-
}
|
|
349
|
-
assert.ok(!/^\.pcharts\s*\{/m.test(profile), 'and not left duplicated in profile.css');
|
|
350
|
-
assert.match(drachmaeHtml, /style\.css/);
|
|
351
|
-
assert.doesNotMatch(drachmaeHtml, /profile\.css/, 'the reference page must not need the profile sheet');
|
|
352
|
-
});
|