@bongos/core 1.24.35 → 1.24.36
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 +101 -146
- 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 +136 -192
- package/docs/copy-registry.json +186 -752
- package/docs/module-api-changelog.md +2 -0
- package/docs/onboarding/diagrams/README.md +1 -2
- package/docs/onboarding/primer.md +64 -22
- package/docs/page-inventory.json +7 -101
- package/docs/page-readings.json +947 -1415
- 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 +1 -1
- 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.html +2 -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/my-agents.html +2 -2
- package/modules/hall-ui/public/palette.js +15 -0
- package/modules/hall-ui/public/primer-live.js +188 -0
- package/modules/hall-ui/public/primer.html +11 -6
- package/modules/hall-ui/public/primer.js +95 -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.html +2 -2
- 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 +14 -21
- 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 +6 -0
- package/scripts/bongos/gen-hall-sitemap.js +3 -0
- package/scripts/hall-preview/server.js +6 -11
- package/src/bongos/serve-internal.js +28 -37
- package/src/module-api.js +1 -1
- package/tests/craft_mode_home.mjs +6 -2
- 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_nav.mjs +24 -7
- package/tests/hall_page_gate_map.mjs +14 -5
- package/tests/hall_reading_room.mjs +426 -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/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,188 @@
|
|
|
1
|
+
// primer-live.js — the two LIVE sections of the Primer's "Credits and karma"
|
|
2
|
+
// chapter: the project's treasury (what it has spent, and what it has awarded
|
|
3
|
+
// builders for that spend) and the published security bounty rates.
|
|
4
|
+
//
|
|
5
|
+
// Task 1005046 (owner decision 2026-11-03, idea 1001311): the Economy page
|
|
6
|
+
// (/drachmae) folded into the Primer so credits and karma are explained once.
|
|
7
|
+
// Its prose became the chapter; these two sections are the part that was never
|
|
8
|
+
// prose — numbers read from the server when the reader opens the page — so they
|
|
9
|
+
// moved here whole, from drachmae.js (tasks 2174 and 1003446), as a small
|
|
10
|
+
// module primer.js calls rather than more lines in primer.js.
|
|
11
|
+
//
|
|
12
|
+
// The contract each section keeps, unchanged from the Economy page:
|
|
13
|
+
// * it reads an endpoint that already existed — /public/cost-summary (the
|
|
14
|
+
// status page's treasury chart) and /public/bounty-table — no new route;
|
|
15
|
+
// * every mark is drawn by the shared chart kit (hall-kit.js, window.OTBKit);
|
|
16
|
+
// * the currency word is the branding pack's, never a hardcoded unit;
|
|
17
|
+
// * a feed that cannot be read degrades to NOTHING — the section is removed,
|
|
18
|
+
// never left as an empty panel or an outline entry that leads nowhere.
|
|
19
|
+
// What is new: each section is placed the moment the article renders, with a
|
|
20
|
+
// Loading… line, so the reader sees that something is on its way.
|
|
21
|
+
|
|
22
|
+
(() => {
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const BOUNTY_URL = '/api/bongos/public/bounty-table';
|
|
26
|
+
// NOT a new endpoint: the same /public/cost-summary the signed-out status page
|
|
27
|
+
// draws as a cumulative line. Its `daily[]` is "the treasury chart's
|
|
28
|
+
// cumulative line"; the series was in the payload all along.
|
|
29
|
+
const TREASURY_URL = '/api/bongos/public/cost-summary';
|
|
30
|
+
|
|
31
|
+
// The two sections' addresses. Kept from the Economy page, so a link someone
|
|
32
|
+
// copied there (/drachmae#the-treasury) names the same section here.
|
|
33
|
+
const SECTIONS = {
|
|
34
|
+
treasury: { id: 'the-treasury', title: 'The treasury, so far' },
|
|
35
|
+
bounty: { id: 'security-bounty-rates', title: 'Security bounty rates' },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const esc = (s) => window.OTB.escapeHtml(s);
|
|
39
|
+
const currencyLabel = () => (window.__BRANDING__ && window.__BRANDING__.currency
|
|
40
|
+
&& window.__BRANDING__.currency.label) || 'credits';
|
|
41
|
+
const caps = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
42
|
+
|
|
43
|
+
// A section with its heading and a Loading… line, ready to be placed. The h3
|
|
44
|
+
// carries the address from the start, so a deep link has somewhere to land
|
|
45
|
+
// even before the numbers arrive.
|
|
46
|
+
function headingHtml(kind) {
|
|
47
|
+
const def = SECTIONS[kind];
|
|
48
|
+
return `<h3 id="${esc(def.id)}">${esc(def.title)}</h3>`;
|
|
49
|
+
}
|
|
50
|
+
function placeholder(kind) {
|
|
51
|
+
const section = document.createElement('section');
|
|
52
|
+
section.className = 'primer-live';
|
|
53
|
+
section.dataset.live = kind;
|
|
54
|
+
section.setAttribute('aria-busy', 'true');
|
|
55
|
+
section.innerHTML = headingHtml(kind) + '<p class="hall-loading" role="status">Loading…</p>';
|
|
56
|
+
return section;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Replace the Loading… line with the real body, or take the section away.
|
|
60
|
+
function settle(section, bodyHtml) {
|
|
61
|
+
if (!bodyHtml) { section.remove(); return false; }
|
|
62
|
+
section.innerHTML = headingHtml(section.dataset.live) + bodyHtml;
|
|
63
|
+
section.removeAttribute('aria-busy');
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function readJson(url) {
|
|
68
|
+
try {
|
|
69
|
+
const r = await fetch(url, { credentials: 'same-origin' });
|
|
70
|
+
if (!r.ok) throw new Error(`${url} → ${r.status}`);
|
|
71
|
+
return await r.json();
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.warn('[primer] live section unavailable:', err.message);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---- the security bounty rate card (task 2174) ---------------------------
|
|
79
|
+
|
|
80
|
+
function bountyHtml(payload) {
|
|
81
|
+
const rows = Array.isArray(payload && payload.table) ? payload.table : [];
|
|
82
|
+
if (!rows.length) return '';
|
|
83
|
+
const currency = payload.currency || currencyLabel();
|
|
84
|
+
return `<p>Find a flaw in the system’s own walls and report it: rewards are paid in `
|
|
85
|
+
+ `${esc(currency)} by severity. These are the live numbers, read from the server `
|
|
86
|
+
+ 'when you opened this page, so a tuned rate shows up here immediately.</p>'
|
|
87
|
+
+ '<table><thead><tr><th>Severity</th>'
|
|
88
|
+
+ `<th>${esc(caps(currency))}</th>`
|
|
89
|
+
+ '<th>What counts</th></tr></thead><tbody>'
|
|
90
|
+
+ rows.map((r) => {
|
|
91
|
+
// `overridden` means this project tuned the tier off its default.
|
|
92
|
+
const tuned = r.overridden ? ' <em>(tuned)</em>' : '';
|
|
93
|
+
return `<tr><td><strong>${esc(r.severity || '')}</strong></td>`
|
|
94
|
+
+ `<td>${esc(String(r.drachmae ?? ''))}${tuned}</td>`
|
|
95
|
+
+ `<td>${esc(r.blurb || '')}</td></tr>`;
|
|
96
|
+
}).join('')
|
|
97
|
+
+ '</tbody></table>';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function fillBounty(section) {
|
|
101
|
+
return settle(section, bountyHtml(await readJson(BOUNTY_URL)));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- the treasury (task 1003446) -----------------------------------------
|
|
105
|
+
|
|
106
|
+
// One cumulative figure: the plot, the number it ends on, and the table twin
|
|
107
|
+
// that keeps it out of picture-only territory.
|
|
108
|
+
function treasuryFigure({ id, title, points, fmt, foot }) {
|
|
109
|
+
const kit = window.OTBKit;
|
|
110
|
+
if (!points.length) return '';
|
|
111
|
+
const last = points[points.length - 1];
|
|
112
|
+
const series = kit.timeStepSeriesHtml(points, { x0: points[0].x, x1: last.x, yMax: last.value });
|
|
113
|
+
const { endX, endY } = series.geometry;
|
|
114
|
+
const day = (t) => new Date(t).toISOString().slice(0, 10);
|
|
115
|
+
// Every 7th day plus the last: one row per calendar day would be a table
|
|
116
|
+
// nobody finishes reading.
|
|
117
|
+
const twinRows = points.filter((p, i) => i % 7 === 0 || i === points.length - 1);
|
|
118
|
+
return `<figure class="pchart" id="${esc(id)}">
|
|
119
|
+
<figcaption class="pchart__title">${esc(title)}</figcaption>
|
|
120
|
+
${kit.plotSvgHtml({
|
|
121
|
+
series: series.body,
|
|
122
|
+
// No readout is wired here, so no focus stop and no dead crosshair.
|
|
123
|
+
interactive: false,
|
|
124
|
+
ariaLabel: `${title}: ${fmt(last.value)} as of ${day(last.x)}`,
|
|
125
|
+
label: kit.plotValueLabelHtml(endX, endY - 8, fmt(last.value), 'end'),
|
|
126
|
+
leftTick: day(points[0].x),
|
|
127
|
+
rightTick: day(last.x),
|
|
128
|
+
})}
|
|
129
|
+
<details class="pchart__twin">
|
|
130
|
+
<summary>Read it as a table</summary>
|
|
131
|
+
<table class="ledger pchart__table"><thead><tr><th>Day</th><th>${esc(title)}</th></tr></thead><tbody>
|
|
132
|
+
${twinRows.map((p) => `<tr><td>${esc(day(p.x))}</td><td>${esc(fmt(p.value))}</td></tr>`).join('')}
|
|
133
|
+
</tbody></table>
|
|
134
|
+
</details>
|
|
135
|
+
<p class="pchart__foot">${esc(foot)}</p>
|
|
136
|
+
</figure>`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function treasuryHtml(payload) {
|
|
140
|
+
const kit = window.OTBKit;
|
|
141
|
+
if (!payload || !kit) return '';
|
|
142
|
+
const currency = currencyLabel();
|
|
143
|
+
const usd = (n) => `$${Number(n || 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}`;
|
|
144
|
+
const num = (n) => Number(n || 0).toLocaleString();
|
|
145
|
+
// The server already ran generate_series, so every calendar day is present
|
|
146
|
+
// and a flat run is a real flat run rather than a missing sample.
|
|
147
|
+
const spend = kit.cumulateByDay(payload.daily, { valueOf: (r) => r.amount_usd });
|
|
148
|
+
const earned = kit.cumulateByDay(payload.daily_credits, { valueOf: (r) => r.credits });
|
|
149
|
+
// Both series empty is a treasury with nothing in it yet: no section at all.
|
|
150
|
+
if (!spend.length && !earned.length) return '';
|
|
151
|
+
const figures = [
|
|
152
|
+
treasuryFigure({
|
|
153
|
+
id: 'treasury-spend', title: 'Spent, cumulative', points: spend, fmt: usd,
|
|
154
|
+
foot: 'Every recorded cost — API, infra, compute, art — added up day by day.',
|
|
155
|
+
}),
|
|
156
|
+
treasuryFigure({
|
|
157
|
+
id: 'treasury-earned', title: `${caps(currency)} awarded, cumulative`, points: earned, fmt: num,
|
|
158
|
+
foot: `What the project has awarded builders, in ${currency}, day by day.`,
|
|
159
|
+
}),
|
|
160
|
+
].filter(Boolean).join('');
|
|
161
|
+
// Where the money went, as the kit's bar series. Categories arrive sorted
|
|
162
|
+
// biggest-first.
|
|
163
|
+
const byCategory = (payload.by_category || []).slice(0, 8);
|
|
164
|
+
const categories = byCategory.length
|
|
165
|
+
? '<h4 id="treasury-by-category">Where it went</h4>'
|
|
166
|
+
+ kit.barSeriesHtml(
|
|
167
|
+
byCategory.map((c) => ({ label: c.category, value: c.total_usd, valueLabel: usd(c.total_usd) })),
|
|
168
|
+
{ list: true, ariaLabel: 'Spend by category', valueWidth: '84px' },
|
|
169
|
+
)
|
|
170
|
+
: '';
|
|
171
|
+
return '<p>These are this project’s real numbers, read from the server when you '
|
|
172
|
+
+ `opened this page. It has spent <strong>${esc(usd(payload.total_usd))}</strong> `
|
|
173
|
+
+ `and awarded <strong>${esc(num(payload.total_value_credits))} ${esc(currency)}</strong> `
|
|
174
|
+
+ 'for the work that money bought.</p>'
|
|
175
|
+
+ `<div class="pcharts">${figures}</div>`
|
|
176
|
+
+ categories;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function fillTreasury(section) {
|
|
180
|
+
if (!window.OTBKit) {
|
|
181
|
+
console.warn('[primer] OTBKit missing — the treasury section needs the chart primitive');
|
|
182
|
+
return settle(section, '');
|
|
183
|
+
}
|
|
184
|
+
return settle(section, treasuryHtml(await readJson(TREASURY_URL)));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
window.OTBPrimerLive = { SECTIONS, placeholder, fillBounty, fillTreasury, bountyHtml, treasuryHtml };
|
|
188
|
+
})();
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Primer — {{worldName}}</title>
|
|
7
|
-
<meta name="description" content="The guide a new builder reads on day one. What {{productName}} is, how the build system works, the claim-to-ship lifecycle, the rank ladder,
|
|
7
|
+
<meta name="description" content="The guide a new builder reads on day one. What {{productName}} is, how the build system works, the claim-to-ship lifecycle, the rank ladder, {{currency}} and karma, and how versions organise the work, each with its diagram.">
|
|
8
8
|
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
11
|
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
12
12
|
<link rel="stylesheet" href="/builders/room.css">
|
|
13
13
|
<link rel="stylesheet" href="/builders/primer.css?v=2026-07-02-shell1">
|
|
14
|
-
<link rel="stylesheet" href="/builders/diagram-viewer.css?v=2026-
|
|
14
|
+
<link rel="stylesheet" href="/builders/diagram-viewer.css?v=2026-11-04-reading-room">
|
|
15
15
|
</head>
|
|
16
16
|
<!-- `room` opts this page into the reading-room archetype's composition tokens
|
|
17
17
|
(room.css). They sit on the body, never in a page :root — the branding
|
|
@@ -112,15 +112,20 @@
|
|
|
112
112
|
|
|
113
113
|
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
114
114
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
115
|
-
<script src="/builders/craft-mode.js?v=2026-
|
|
116
|
-
<script src="/builders/shell.js?v=2026-11-04-
|
|
115
|
+
<script src="/builders/craft-mode.js?v=2026-11-04-reading-room"></script>
|
|
116
|
+
<script src="/builders/shell.js?v=2026-11-04-reading-room"></script>
|
|
117
117
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
118
118
|
<script src="/builders/palette.js" defer></script>
|
|
119
119
|
<!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
|
|
120
120
|
it rather than carrying a second copy of the grammar. -->
|
|
121
121
|
<script src="/builders/md-reader.js" defer></script>
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
<!-- The credits chapter's live sections (task 1005046, from the retired Economy
|
|
123
|
+
page): the chart kit first, then the module that draws with it, both before
|
|
124
|
+
primer.js, which places them once the article renders. -->
|
|
125
|
+
<script src="/builders/hall-kit.js" defer></script>
|
|
126
|
+
<script src="/builders/primer-live.js?v=2026-11-04-reading-room" defer></script>
|
|
127
|
+
<script src="/builders/primer.js?v=2026-11-04-reading-room-lazy" defer></script>
|
|
128
|
+
<script src="/builders/diagram-viewer.js?v=2026-11-04-reading-room" defer></script>
|
|
124
129
|
<script src="/builders/copy-on-click.js?v=2026-06-01-copy-1" defer></script>
|
|
125
130
|
</body>
|
|
126
131
|
</html>
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Fetches /builders/primer.md (canonical markdown from docs/onboarding/),
|
|
4
4
|
// renders it through the SHARED reader (md-reader.js / window.OTBMdReader),
|
|
5
|
-
// inlines the five onboarding diagrams
|
|
5
|
+
// inlines the five onboarding diagrams in their chapters, adds the credits
|
|
6
|
+
// chapter's two live sections (primer-live.js), lands a deep link once all of
|
|
7
|
+
// that is in (task 1005046: the hall's one reading room), builds a TOC from
|
|
6
8
|
// h2s with a scrollspy, drives a reading-progress hairline, and soft-gates the
|
|
7
9
|
// footer ack button until the reader has scrolled past ~90% of the article.
|
|
8
10
|
// Acking POSTs to /api/bongos/builders/me/onboarding/primer-read (same endpoint
|
|
@@ -37,17 +39,29 @@
|
|
|
37
39
|
let repoBlobBase = null;
|
|
38
40
|
let repoTreeBase = null;
|
|
39
41
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
// The five onboarding diagrams, each inlined in the chapter it illustrates
|
|
43
|
+
// (task 1005046: the Diagrams page folded into this one, so this table is now
|
|
44
|
+
// the ONLY place a diagram is shown in the hall). Keyed by the chapter's h2 id
|
|
45
|
+
// — the slug md-reader.js gives it — not by a substring of its words, so a
|
|
46
|
+
// retitled chapter fails loudly in tests/hall_reading_world.mjs instead of
|
|
47
|
+
// silently losing its picture (the credits diagram did exactly that when the
|
|
48
|
+
// chapter's heading stopped matching 'drachmae and karma'). The image paths
|
|
49
|
+
// resolve at /builders/diagrams/* (serve-internal.js mounts the directory).
|
|
50
|
+
const DIAGRAM_AT_CHAPTER = [
|
|
51
|
+
{ id: 'the-claim-ship-lifecycle', src: '/builders/diagrams/01-task-lifecycle.png', alt: 'The task lifecycle from backlog to shipped, with the step where credits land' },
|
|
52
|
+
{ id: 'the-rank-ladder', src: '/builders/diagrams/02-rank-ladder.png', alt: 'The rank ladder: which ranks are live today and what each may do' },
|
|
53
|
+
{ id: 'credits-and-karma', src: '/builders/diagrams/03-drachmae-karma.png', alt: 'Where credits and karma come from, and where each shows up' },
|
|
54
|
+
{ id: 'how-versions-organise-the-work', src: '/builders/diagrams/05-versioning.png', alt: 'How a planning session seeds a version\'s task pool, and how a version closes' },
|
|
55
|
+
{ id: 'get-set-up-on-your-machine', src: '/builders/diagrams/04-architecture.png', alt: 'The complete architecture: your machine holds the worker, the code and your local memory; the server is the authority and production; GitHub holds the repo — and the path a change takes to production' },
|
|
49
56
|
];
|
|
50
57
|
|
|
58
|
+
// The chapter the two live sections close (task 1005046, moved from the
|
|
59
|
+
// retired /drachmae page): what the treasury has actually spent and paid, and
|
|
60
|
+
// the published security bounty rates. They go at the end of this chapter,
|
|
61
|
+
// ahead of the next one, as h3s — so the table of contents stays one line per
|
|
62
|
+
// chapter and a deep link (#the-treasury, #security-bounty-rates) still works.
|
|
63
|
+
const LIVE_CHAPTER_ID = 'credits-and-karma';
|
|
64
|
+
|
|
51
65
|
// -----------------------------------------------------------------------
|
|
52
66
|
// Tiny utilities
|
|
53
67
|
// -----------------------------------------------------------------------
|
|
@@ -57,13 +71,14 @@
|
|
|
57
71
|
const MD = window.OTBMdReader;
|
|
58
72
|
const escapeHtml = (s) => window.OTB.escapeHtml(s); // task 1004876: the old fallback escaped nothing
|
|
59
73
|
|
|
60
|
-
// The shared resolver, plus the rewrites that are the primer's alone:
|
|
61
|
-
//
|
|
62
|
-
//
|
|
74
|
+
// The shared resolver, plus the rewrites that are the primer's alone: a link
|
|
75
|
+
// written as a hall address (/builders/…) is a page in this hall, so it stays
|
|
76
|
+
// here instead of being dressed as a repo path on GitHub (task 1005046: the
|
|
77
|
+
// rank ladder sends the reader to their own standing). And a link to a hall
|
|
63
78
|
// page's own file (../../modules/hall-ui/public/<page>.html) opens that page
|
|
64
79
|
// here (task 1005069); on GitHub the same link shows the file.
|
|
65
|
-
const HALL_PAGE = /^(?:\.\.\/)+modules\/hall-ui\/public\/([a-z0-9-]+)\.html$/;
|
|
66
80
|
const resolveLinkHref = (() => {
|
|
81
|
+
const HALL_PAGE = /^(?:\.\.\/)+modules\/hall-ui\/public\/([a-z0-9-]+)\.html$/;
|
|
67
82
|
if (!MD) return (h) => h;
|
|
68
83
|
const base = MD.makeResolveLinkHref({
|
|
69
84
|
baseDir: PRIMER_DIR,
|
|
@@ -71,7 +86,7 @@
|
|
|
71
86
|
getTreeBase: () => repoTreeBase,
|
|
72
87
|
});
|
|
73
88
|
return (href) => {
|
|
74
|
-
if (href
|
|
89
|
+
if (/^\/builders(?:[/#?]|$)/.test(String(href || ''))) return href;
|
|
75
90
|
const page = HALL_PAGE.exec(href || '');
|
|
76
91
|
return page ? '/builders/' + page[1] : base(href);
|
|
77
92
|
};
|
|
@@ -97,19 +112,17 @@
|
|
|
97
112
|
// -----------------------------------------------------------------------
|
|
98
113
|
|
|
99
114
|
function inlineDiagrams(articleEl) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const match = DIAGRAM_AT_HEADING.find((d) => txt.includes(d.match));
|
|
104
|
-
if (!match) continue;
|
|
115
|
+
for (const d of DIAGRAM_AT_CHAPTER) {
|
|
116
|
+
const h = articleEl.querySelector(`h2[id="${d.id}"]`);
|
|
117
|
+
if (!h) continue;
|
|
105
118
|
const fig = document.createElement('figure');
|
|
106
119
|
fig.className = 'primer-figure';
|
|
107
120
|
const img = document.createElement('img');
|
|
108
|
-
img.src =
|
|
109
|
-
img.alt =
|
|
121
|
+
img.src = d.src;
|
|
122
|
+
img.alt = d.alt;
|
|
110
123
|
img.loading = 'lazy';
|
|
111
124
|
const cap = document.createElement('figcaption');
|
|
112
|
-
cap.textContent =
|
|
125
|
+
cap.textContent = d.alt;
|
|
113
126
|
fig.appendChild(img);
|
|
114
127
|
fig.appendChild(cap);
|
|
115
128
|
// Place the figure AFTER the h2's first paragraph so it sits inside
|
|
@@ -124,6 +137,63 @@
|
|
|
124
137
|
}
|
|
125
138
|
}
|
|
126
139
|
|
|
140
|
+
// The live sections of the credits chapter (primer-live.js). Placed at the
|
|
141
|
+
// END of that chapter — ahead of the next h2 — each with a Loading… line, and
|
|
142
|
+
// removed outright if its feed cannot be read, so the chapter never shows an
|
|
143
|
+
// empty panel or an outline entry that leads nowhere.
|
|
144
|
+
//
|
|
145
|
+
// FETCHED ON APPROACH, not on load. The Primer is the hall's most-read page
|
|
146
|
+
// and three old URLs now land on it, while most readers never scroll to the
|
|
147
|
+
// treasury; firing /public/cost-summary (a day-by-day cumulative query) and
|
|
148
|
+
// /public/bounty-table on every open would spend that on readers who never
|
|
149
|
+
// see it. So the two reads start when the reader comes within a screen of
|
|
150
|
+
// the sections — or at once when the address points at the credits chapter
|
|
151
|
+
// or either section (an old /drachmae link), so a deep link lands on their
|
|
152
|
+
// final layout. Resolves when there is nothing left to wait for before
|
|
153
|
+
// landing: after the fills on a deep link, at once otherwise.
|
|
154
|
+
const LIVE_HASHES = new Set([LIVE_CHAPTER_ID, 'the-treasury', 'security-bounty-rates']);
|
|
155
|
+
function insertLiveSections(articleEl) {
|
|
156
|
+
const live = window.OTBPrimerLive;
|
|
157
|
+
const h = articleEl.querySelector(`h2[id="${LIVE_CHAPTER_ID}"]`);
|
|
158
|
+
if (!live || !h) return Promise.resolve();
|
|
159
|
+
let next = h.nextElementSibling;
|
|
160
|
+
while (next && next.tagName !== 'H2') next = next.nextElementSibling;
|
|
161
|
+
const place = (el) => { if (next) articleEl.insertBefore(el, next); else articleEl.appendChild(el); };
|
|
162
|
+
const treasury = live.placeholder('treasury');
|
|
163
|
+
const bounty = live.placeholder('bounty');
|
|
164
|
+
place(treasury);
|
|
165
|
+
place(bounty);
|
|
166
|
+
let started = null;
|
|
167
|
+
const fill = () => {
|
|
168
|
+
if (!started) started = Promise.allSettled([live.fillTreasury(treasury), live.fillBounty(bounty)]);
|
|
169
|
+
return started;
|
|
170
|
+
};
|
|
171
|
+
let hash = '';
|
|
172
|
+
try { hash = decodeURIComponent(window.location.hash.slice(1)); } catch (_) { hash = ''; }
|
|
173
|
+
if (LIVE_HASHES.has(hash) || typeof window.IntersectionObserver !== 'function') return fill();
|
|
174
|
+
const io = new window.IntersectionObserver((entries) => {
|
|
175
|
+
if (!entries.some((e) => e.isIntersecting)) return;
|
|
176
|
+
io.disconnect();
|
|
177
|
+
fill();
|
|
178
|
+
}, { rootMargin: '100% 0px' });
|
|
179
|
+
io.observe(treasury);
|
|
180
|
+
io.observe(bounty);
|
|
181
|
+
return Promise.resolve();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// A deep link — /primer#the-rank-ladder, or an old /ranks, /drachmae or
|
|
185
|
+
// /diagrams URL that serve-internal.js 302s here — arrives before the article
|
|
186
|
+
// exists, so the browser's own jump found nothing. Once the document and its
|
|
187
|
+
// live sections are in, scroll the target into view. After the fonts: a swap
|
|
188
|
+
// after the jump moves everything above the target.
|
|
189
|
+
function landOnHash() {
|
|
190
|
+
let id = '';
|
|
191
|
+
// a malformed percent-escape in the fragment throws; there is nothing to land on
|
|
192
|
+
try { id = decodeURIComponent(window.location.hash.slice(1)); } catch (_) { return; }
|
|
193
|
+
const el = id && document.getElementById(id);
|
|
194
|
+
if (el) el.scrollIntoView({ block: 'start' });
|
|
195
|
+
}
|
|
196
|
+
|
|
127
197
|
// -----------------------------------------------------------------------
|
|
128
198
|
// TOC — built from h2 headings. The numeral is drawn by room.css's counter,
|
|
129
199
|
// so the markup carries only the title.
|
|
@@ -430,12 +500,14 @@
|
|
|
430
500
|
article.removeAttribute('aria-busy');
|
|
431
501
|
show('article');
|
|
432
502
|
inlineDiagrams(article);
|
|
503
|
+
const live = insertLiveSections(article);
|
|
433
504
|
const tocSections = renderToc(headings);
|
|
434
505
|
setupTocToggle();
|
|
435
506
|
setupScrollspy(tocSections);
|
|
436
507
|
setupProgressBar(article, onArticleProgress);
|
|
437
508
|
setupAck();
|
|
438
509
|
checkAlreadyAcked();
|
|
510
|
+
live.then(() => (document.fonts ? document.fonts.ready : null)).then(landOnHash);
|
|
439
511
|
}
|
|
440
512
|
|
|
441
513
|
if (document.readyState === 'loading') {
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
"_": "The hall's primer through modules/ui-design/kit/render.js (task 1003306). Written with the reading-room re-skin so the room's failure state and its table of contents are MEASURED at 1440/390/320 in both modes, not just looked at.",
|
|
3
3
|
"page": "/builders/primer",
|
|
4
4
|
"surface": "hall-ui",
|
|
5
|
-
"stub": {
|
|
5
|
+
"stub": {
|
|
6
|
+
"prefix": "/builders/"
|
|
7
|
+
},
|
|
6
8
|
"modeQuery": false,
|
|
7
9
|
"_feeds": "The kit's stub carries the public-landing fixtures, not the hall's, and it serves no /builders/primer.md at all — so the room renders its SKELETON and then its FAILURE note here, which is the point: the two states before the document are the ones nobody looks at, and they have to hold the floors too. The populated document is the hall-preview harness's job: node scripts/hall-preview/server.js --port 4612 --fixture-me, then this same file with --base-in/--base-out pointed at it.",
|
|
8
10
|
"ignoreRequests": [
|
|
@@ -11,7 +13,10 @@
|
|
|
11
13
|
"/api/(gds|bongos)/goals",
|
|
12
14
|
"/api/(gds|bongos)/inbox/",
|
|
13
15
|
"/api/(gds|bongos)/public/repo-info",
|
|
14
|
-
"/builders/primer.md"
|
|
16
|
+
"/builders/primer.md",
|
|
17
|
+
"/api/(gds|bongos)/public/cost-summary",
|
|
18
|
+
"/api/(gds|bongos)/public/bounty-table",
|
|
19
|
+
"/builders/diagrams/"
|
|
15
20
|
],
|
|
16
21
|
"ignoreConsole": [
|
|
17
22
|
"/api/(gds|bongos)/",
|
|
@@ -25,14 +30,56 @@
|
|
|
25
30
|
],
|
|
26
31
|
"_expect": "The expectations name only what is true under BOTH bases. Under the kit stub there is no primer.md, so the failure note renders; under the harness the real document does. Pinning either one would make this file pass in one place and fail in the other, which is how a states file stops being run against the harness at all.",
|
|
27
32
|
"states": {
|
|
28
|
-
"in": {
|
|
33
|
+
"in": {
|
|
34
|
+
"auth": true,
|
|
35
|
+
"expect": {
|
|
36
|
+
"visible": [
|
|
37
|
+
"#hall-main",
|
|
38
|
+
"#primer-toc"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
29
42
|
"toc-open": {
|
|
30
43
|
"auth": true,
|
|
31
|
-
"actions": [
|
|
44
|
+
"actions": [
|
|
45
|
+
[
|
|
46
|
+
"clickIfVisible",
|
|
47
|
+
"#primer-toc-toggle"
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"wait",
|
|
51
|
+
250
|
|
52
|
+
]
|
|
53
|
+
],
|
|
32
54
|
"_": "the room's one disclosure. Below 760px the contents collapse behind this toggle; above it the toggle is not rendered at all, so clickIfVisible is the right verb and the state is a no-op at 1440.",
|
|
33
|
-
"expect": {
|
|
55
|
+
"expect": {
|
|
56
|
+
"visible": [
|
|
57
|
+
"#primer-toc"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"jump": {
|
|
62
|
+
"auth": true,
|
|
63
|
+
"url": "/builders/primer#credits-and-karma",
|
|
64
|
+
"actions": [
|
|
65
|
+
[
|
|
66
|
+
"wait",
|
|
67
|
+
800
|
|
68
|
+
]
|
|
69
|
+
],
|
|
70
|
+
"_": "where an old /drachmae link lands (task 1005046: it 302s here). Under the harness the credits chapter is in view with its diagram and the two live sections below; under the kit stub it is the failure note again, which the floors must hold in too.",
|
|
71
|
+
"expect": {
|
|
72
|
+
"visible": [
|
|
73
|
+
"#hall-main",
|
|
74
|
+
"#primer-toc"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
34
77
|
},
|
|
35
|
-
"out": {
|
|
78
|
+
"out": {
|
|
79
|
+
"auth": false
|
|
80
|
+
}
|
|
36
81
|
},
|
|
37
|
-
"reducedMotion": {
|
|
82
|
+
"reducedMotion": {
|
|
83
|
+
"auth": true
|
|
84
|
+
}
|
|
38
85
|
}
|
|
@@ -207,8 +207,8 @@
|
|
|
207
207
|
chrome exists before the page script runs. -->
|
|
208
208
|
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
209
209
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
210
|
-
<script src="/builders/craft-mode.js?v=2026-
|
|
211
|
-
<script src="/builders/shell.js?v=2026-11-04-
|
|
210
|
+
<script src="/builders/craft-mode.js?v=2026-11-04-reading-room"></script>
|
|
211
|
+
<script src="/builders/shell.js?v=2026-11-04-reading-room"></script>
|
|
212
212
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
213
213
|
<script src="/builders/palette.js" defer></script>
|
|
214
214
|
<!-- task 1004727: the ONE karma vote control every task/idea list renders -->
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
|
|
87
87
|
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
88
88
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
89
|
-
<script src="/builders/craft-mode.js?v=2026-
|
|
90
|
-
<script src="/builders/shell.js?v=2026-11-04-
|
|
89
|
+
<script src="/builders/craft-mode.js?v=2026-11-04-reading-room"></script>
|
|
90
|
+
<script src="/builders/shell.js?v=2026-11-04-reading-room"></script>
|
|
91
91
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
92
92
|
<script src="/builders/palette.js" defer></script>
|
|
93
93
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
|
|
44
44
|
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
45
45
|
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
46
|
-
<script src="/builders/craft-mode.js?v=2026-
|
|
47
|
-
<script src="/builders/shell.js?v=2026-11-04-
|
|
46
|
+
<script src="/builders/craft-mode.js?v=2026-11-04-reading-room"></script>
|
|
47
|
+
<script src="/builders/shell.js?v=2026-11-04-reading-room"></script>
|
|
48
48
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
49
49
|
<script src="/builders/palette.js" defer></script>
|
|
50
50
|
<!-- The chart kit and the timeline's series builders (task 1003447). Both MUST
|