@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
|
@@ -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 live section closes (task 1005046, moved from the retired
|
|
59
|
+
// /drachmae page): the published security bounty rates. It goes at the end of
|
|
60
|
+
// this chapter, ahead of the next one, as an h3 — so the table of contents stays
|
|
61
|
+
// one line per chapter and a deep link (#security-bounty-rates) still works.
|
|
62
|
+
// (The treasury it once sat beside is on the money page since task 1005047.)
|
|
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,61 @@
|
|
|
124
137
|
}
|
|
125
138
|
}
|
|
126
139
|
|
|
140
|
+
// The live section of the credits chapter (primer-live.js). Placed at the
|
|
141
|
+
// END of that chapter — ahead of the next h2 — 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
|
+
// rate card; firing /public/bounty-table on every open would spend that on
|
|
148
|
+
// readers who never see it. So the read starts when the reader comes within a
|
|
149
|
+
// screen of the section — or at once when the address points at the credits
|
|
150
|
+
// chapter or the section itself (an old /drachmae link), so a deep link lands
|
|
151
|
+
// on its final layout. Resolves when there is nothing left to wait for before
|
|
152
|
+
// landing: after the fills on a deep link, at once otherwise.
|
|
153
|
+
const LIVE_HASHES = new Set([LIVE_CHAPTER_ID, 'security-bounty-rates']);
|
|
154
|
+
function insertLiveSections(articleEl) {
|
|
155
|
+
const live = window.OTBPrimerLive;
|
|
156
|
+
const h = articleEl.querySelector(`h2[id="${LIVE_CHAPTER_ID}"]`);
|
|
157
|
+
if (!live || !h) return Promise.resolve();
|
|
158
|
+
let next = h.nextElementSibling;
|
|
159
|
+
while (next && next.tagName !== 'H2') next = next.nextElementSibling;
|
|
160
|
+
const place = (el) => { if (next) articleEl.insertBefore(el, next); else articleEl.appendChild(el); };
|
|
161
|
+
// task 1005047: the treasury left for the money page (money.js); the chapter's
|
|
162
|
+
// prose links there, so the bounty rate card is the one live section.
|
|
163
|
+
const bounty = live.placeholder('bounty');
|
|
164
|
+
place(bounty);
|
|
165
|
+
let started = null;
|
|
166
|
+
const fill = () => {
|
|
167
|
+
if (!started) started = Promise.allSettled([live.fillBounty(bounty)]);
|
|
168
|
+
return started;
|
|
169
|
+
};
|
|
170
|
+
let hash = '';
|
|
171
|
+
try { hash = decodeURIComponent(window.location.hash.slice(1)); } catch (_) { hash = ''; }
|
|
172
|
+
if (LIVE_HASHES.has(hash) || typeof window.IntersectionObserver !== 'function') return fill();
|
|
173
|
+
const io = new window.IntersectionObserver((entries) => {
|
|
174
|
+
if (!entries.some((e) => e.isIntersecting)) return;
|
|
175
|
+
io.disconnect();
|
|
176
|
+
fill();
|
|
177
|
+
}, { rootMargin: '100% 0px' });
|
|
178
|
+
io.observe(bounty);
|
|
179
|
+
return Promise.resolve();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// A deep link — /primer#the-rank-ladder, or an old /ranks, /drachmae or
|
|
183
|
+
// /diagrams URL that serve-internal.js 302s here — arrives before the article
|
|
184
|
+
// exists, so the browser's own jump found nothing. Once the document and its
|
|
185
|
+
// live sections are in, scroll the target into view. After the fonts: a swap
|
|
186
|
+
// after the jump moves everything above the target.
|
|
187
|
+
function landOnHash() {
|
|
188
|
+
let id = '';
|
|
189
|
+
// a malformed percent-escape in the fragment throws; there is nothing to land on
|
|
190
|
+
try { id = decodeURIComponent(window.location.hash.slice(1)); } catch (_) { return; }
|
|
191
|
+
const el = id && document.getElementById(id);
|
|
192
|
+
if (el) el.scrollIntoView({ block: 'start' });
|
|
193
|
+
}
|
|
194
|
+
|
|
127
195
|
// -----------------------------------------------------------------------
|
|
128
196
|
// TOC — built from h2 headings. The numeral is drawn by room.css's counter,
|
|
129
197
|
// so the markup carries only the title.
|
|
@@ -430,12 +498,14 @@
|
|
|
430
498
|
article.removeAttribute('aria-busy');
|
|
431
499
|
show('article');
|
|
432
500
|
inlineDiagrams(article);
|
|
501
|
+
const live = insertLiveSections(article);
|
|
433
502
|
const tocSections = renderToc(headings);
|
|
434
503
|
setupTocToggle();
|
|
435
504
|
setupScrollspy(tocSections);
|
|
436
505
|
setupProgressBar(article, onArticleProgress);
|
|
437
506
|
setupAck();
|
|
438
507
|
checkAlreadyAcked();
|
|
508
|
+
live.then(() => (document.fonts ? document.fonts.ready : null)).then(landOnHash);
|
|
439
509
|
}
|
|
440
510
|
|
|
441
511
|
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-money"></script>
|
|
211
|
+
<script src="/builders/shell.js?v=2026-11-04-money"></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 -->
|
|
@@ -247,7 +247,8 @@
|
|
|
247
247
|
}
|
|
248
248
|
.ps-ship__choice input { margin-top: 4px; flex: 0 0 auto; }
|
|
249
249
|
|
|
250
|
-
/* Where the money went (task 1003184)
|
|
250
|
+
/* Where the money went (task 1003184), drawn on money.html since task 1005047, which
|
|
251
|
+
loads this sheet for it. Four lead figures that wrap two-by-two on a
|
|
251
252
|
phone; the tables scroll inside their own strip, never the page (hall-ui/CLAUDE.md). */
|
|
252
253
|
.ps-money__figs {
|
|
253
254
|
display: grid;
|
|
@@ -49,19 +49,9 @@
|
|
|
49
49
|
server resolved it through, and markup that could drift from that is the
|
|
50
50
|
thing this page exists to end. -->
|
|
51
51
|
<div id="ps-rows"><span class="kit-skel"></span></div>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
read (payout.approve, the owner), so every other reader of this page never
|
|
56
|
-
sees an empty panel. Written by project-settings.js; every figure is the
|
|
57
|
-
server's, summed from records that already exist. -->
|
|
58
|
-
<section class="scroll" id="ps-money" aria-labelledby="ps-money-h" hidden>
|
|
59
|
-
<h2 class="scroll__h" id="ps-money-h">Where the money went</h2>
|
|
60
|
-
<p class="scroll__lede">
|
|
61
|
-
What this project has spent, what each builder has been paid, and what it has
|
|
62
|
-
promised but not paid yet. A balance only means something after those promises.
|
|
63
|
-
</p>
|
|
64
|
-
<div id="ps-money-body"></div>
|
|
52
|
+
<!-- task 1005047: the money figures this page drew ("Where the money went",
|
|
53
|
+
task 1003184) moved to the one money page; this page keeps its knobs. -->
|
|
54
|
+
<p class="ps-prov" id="ps-money-link">What this project has spent and paid out, its treasury and its hosting bills are on the <a href="/builders/money">Money</a> page.</p>
|
|
65
55
|
</section>
|
|
66
56
|
<!-- Danger zone (task 1004570): where this project's OWNER takes it offline or
|
|
67
57
|
disconnects it. Both controls live on the platform that hosts the project,
|
|
@@ -86,11 +76,11 @@
|
|
|
86
76
|
|
|
87
77
|
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
88
78
|
<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-
|
|
79
|
+
<script src="/builders/craft-mode.js?v=2026-11-04-money"></script>
|
|
80
|
+
<script src="/builders/shell.js?v=2026-11-04-money"></script>
|
|
91
81
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
92
82
|
<script src="/builders/palette.js" defer></script>
|
|
93
83
|
<script src="/builders/hall-kit.js"></script>
|
|
94
|
-
<script src="/builders/project-settings.js?v=2026-11-
|
|
84
|
+
<script src="/builders/project-settings.js?v=2026-11-04-money"></script>
|
|
95
85
|
</body>
|
|
96
86
|
</html>
|
|
@@ -417,148 +417,8 @@
|
|
|
417
417
|
$('ps-danger').hidden = false;
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
// The owner's outflow view. The server sums every figure (modules/economy/outflow.js);
|
|
423
|
-
// this only draws it. It leads with what is left AFTER what is owed, because a balance
|
|
424
|
-
// that looks healthy while it owes three payouts is the trap the view exists to show.
|
|
425
|
-
// Any outcome but a 200 leaves the section hidden: the read is the payout authority's,
|
|
426
|
-
// and everyone else on this page has nothing to see here, not an error.
|
|
427
|
-
|
|
428
|
-
const money = (micro) => {
|
|
429
|
-
const usd = Number(micro || 0) / 1e6;
|
|
430
|
-
const abs = Math.abs(usd).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
431
|
-
return `${usd < 0 ? '−' : ''}$${abs}`;
|
|
432
|
-
};
|
|
433
|
-
const monthLabel = (ym) => {
|
|
434
|
-
const [y, m] = String(ym).split('-').map(Number);
|
|
435
|
-
return new Date(Date.UTC(y, m - 1, 1)).toLocaleDateString(undefined, { month: 'short', year: 'numeric', timeZone: 'UTC' });
|
|
436
|
-
};
|
|
437
|
-
const dayLabel = (iso) => (iso ? new Date(iso).toLocaleDateString() : '');
|
|
438
|
-
|
|
439
|
-
function moneyFiguresHtml(out) {
|
|
440
|
-
const c = out.committed;
|
|
441
|
-
const after = out.balance.after_commitments_micro;
|
|
442
|
-
const fig = (label, value, note, tone) => `<div class="ps-money__fig${tone ? ` ps-money__fig--${tone}` : ''}">
|
|
443
|
-
<span class="ps-money__label">${escapeHtml(label)}</span>
|
|
444
|
-
<strong class="ps-money__value">${escapeHtml(value)}</strong>
|
|
445
|
-
<span class="ps-money__note">${escapeHtml(note)}</span>
|
|
446
|
-
</div>`;
|
|
447
|
-
const owedNote = c.owed_count === 1 ? '1 payment' : `${c.owed_count} payments`;
|
|
448
|
-
const apprNote = c.approved_count === 1 ? '1 approval' : `${c.approved_count} approvals`;
|
|
449
|
-
return `<div class="ps-money__figs">
|
|
450
|
-
${fig('Balance', money(out.balance.available_micro), c.held_micro ? `after ${money(c.held_micro)} held` : 'on our record')}
|
|
451
|
-
${fig('Owed but not paid', money(c.owed_micro), owedNote, c.owed_micro ? 'warn' : '')}
|
|
452
|
-
${fig('Approved, not started', money(c.approved_micro), apprNote, c.approved_micro ? 'warn' : '')}
|
|
453
|
-
${fig('Left after all of that', money(after), after < 0 ? 'the project owes more than it holds' : 'what is really free', after < 0 ? 'bad' : '')}
|
|
454
|
-
</div>${c.other_currency_count ? `<p class="ps-prov">${c.other_currency_count} more in a currency other than US dollars, not added in.</p>` : ''}`;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// The payout's latest try in words. A reason is either the processor's own sentence, shown
|
|
458
|
-
// as it is, or one of core's held codes (builder-payouts.js HELD_WORDS). The codes an owner
|
|
459
|
-
// can act on get a short phrase here; any other code is left out rather than shown as a slug.
|
|
460
|
-
const TRY_WORDS = { held: 'Waiting', failed: 'Did not go through' };
|
|
461
|
-
const REASON_WORDS = {
|
|
462
|
-
no_processor: 'no payment processor connected yet',
|
|
463
|
-
recipient_gated: 'this person cannot be paid right now',
|
|
464
|
-
guardian_cosign_missing: 'a guardian has not co-signed yet',
|
|
465
|
-
currency_not_supported: 'not in US dollars',
|
|
466
|
-
};
|
|
467
|
-
function lastTryWords(r) {
|
|
468
|
-
if (!r.last_outcome) return 'Not tried yet';
|
|
469
|
-
const reason = String(r.last_reason || '');
|
|
470
|
-
const why = REASON_WORDS[reason] || (/^[a-z_]+$/.test(reason) ? '' : reason);
|
|
471
|
-
return `${TRY_WORDS[r.last_outcome] || r.last_outcome}${why ? ` — ${why}` : ''}`;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function owedHtml(out) {
|
|
475
|
-
const rows = out.committed.owed;
|
|
476
|
-
if (!rows.length) return '';
|
|
477
|
-
return `<h3 class="ps-money__h">Owed but not paid</h3>
|
|
478
|
-
<div class="ps-money__wrap"><table class="ledger">
|
|
479
|
-
<thead><tr><th>Builder</th><th>Task</th><th>Amount</th><th>Owed since</th><th>Last try</th></tr></thead>
|
|
480
|
-
<tbody>${rows.map((r) => `<tr>
|
|
481
|
-
<td>${escapeHtml(r.builder_name || `builder ${r.builder_id}`)}</td>
|
|
482
|
-
<td><a href="/builders/task/${encodeURIComponent(r.task_id)}">${escapeHtml(r.task_title || `task ${r.task_id}`)}</a></td>
|
|
483
|
-
<td>${escapeHtml(money(r.amount_micro))}</td>
|
|
484
|
-
<td>${escapeHtml(dayLabel(r.since))}</td>
|
|
485
|
-
<td>${escapeHtml(lastTryWords(r))}</td>
|
|
486
|
-
</tr>`).join('')}</tbody>
|
|
487
|
-
</table></div>`;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function buildersHtml(out) {
|
|
491
|
-
const rows = out.by_builder;
|
|
492
|
-
if (!rows.length) return '';
|
|
493
|
-
return `<h3 class="ps-money__h">By builder</h3>
|
|
494
|
-
<div class="ps-money__wrap"><table class="ledger">
|
|
495
|
-
<thead><tr><th>Builder</th><th>Spent</th><th>Paid</th><th>Owed</th><th>Approved</th></tr></thead>
|
|
496
|
-
<tbody>${rows.map((b) => `<tr>
|
|
497
|
-
<td>${escapeHtml(b.builder_name || `builder ${b.builder_id}`)}</td>
|
|
498
|
-
<td>${escapeHtml(money(b.spend_micro))}</td>
|
|
499
|
-
<td>${escapeHtml(money(b.paid_micro))}</td>
|
|
500
|
-
<td>${escapeHtml(money(b.owed_micro))}</td>
|
|
501
|
-
<td>${escapeHtml(money(b.approved_micro))}</td>
|
|
502
|
-
</tr>`).join('')}</tbody>
|
|
503
|
-
</table></div>
|
|
504
|
-
<p class="ps-prov">Spent is the recorded cost of each builder’s sessions (API, compute). Paid, owed and approved are payouts.</p>`;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
function burnHtml(out) {
|
|
508
|
-
const months = out.burn.months;
|
|
509
|
-
if (!months.some((m) => m.total_micro > 0)) return '';
|
|
510
|
-
const bars = window.OTBKit.barSeriesHtml(
|
|
511
|
-
months.map((m) => ({ label: monthLabel(m.month), value: m.total_micro, valueLabel: money(m.total_micro) })),
|
|
512
|
-
{ list: true, ariaLabel: 'Money out by month', valueWidth: '96px' });
|
|
513
|
-
return `<h3 class="ps-money__h">Money out, by month</h3>${bars}
|
|
514
|
-
<p class="ps-prov">Each month is recorded spend (${escapeHtml(money(months.reduce((s, m) => s + m.spend_micro, 0)))} over these ${months.length} months) plus payouts and fees out of the project account. Read from the cost log and the account’s own record.</p>`;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// The daily check of our record against the processor's (task 1004912). The newest check
|
|
518
|
-
// in words, and on a difference each one with both amounts. The blocker it raises is public
|
|
519
|
-
// and carries no numbers, so this is where the owner reads them.
|
|
520
|
-
const CHECK_WORDS = {
|
|
521
|
-
clean: 'Our records and the payment processor’s agree.',
|
|
522
|
-
drift: 'Our records and the payment processor’s differ.',
|
|
523
|
-
unchecked: 'Not checked: no payment processor is connected yet, so there is nothing to compare with.',
|
|
524
|
-
error: 'Could not check: the payment processor could not be read.',
|
|
525
|
-
};
|
|
526
|
-
function ledgerCheckHtml(out) {
|
|
527
|
-
const c = out && out.checks && out.checks[0];
|
|
528
|
-
if (!c) return '';
|
|
529
|
-
const words = out.kind_words || {};
|
|
530
|
-
const amt = (v) => (v === null || v === undefined ? '—' : money(v));
|
|
531
|
-
const rows = (c.findings || []).map((f) => `<tr>
|
|
532
|
-
<td>${escapeHtml(words[f.kind] || f.kind)}</td>
|
|
533
|
-
<td>${escapeHtml(f.ref || 'the project balance')}</td>
|
|
534
|
-
<td>${escapeHtml(amt(f.ours_micro))}</td>
|
|
535
|
-
<td>${escapeHtml(amt(f.theirs_micro))}</td>
|
|
536
|
-
</tr>`).join('');
|
|
537
|
-
return `<h3 class="ps-money__h">Daily check against the payment processor</h3>
|
|
538
|
-
<p class="ps-prov">${escapeHtml(dayLabel(c.created_at))}: ${escapeHtml(CHECK_WORDS[c.outcome] || c.outcome)}${c.outcome === 'clean' || c.outcome === 'drift' ? ` ${c.refs_compared} ${c.refs_compared === 1 ? 'movement' : 'movements'} compared.` : ''}</p>
|
|
539
|
-
${rows ? `<div class="ps-money__wrap"><table class="ledger">
|
|
540
|
-
<thead><tr><th>Difference</th><th>Reference</th><th>Our record</th><th>Processor</th></tr></thead>
|
|
541
|
-
<tbody>${rows}</tbody>
|
|
542
|
-
</table></div>` : ''}`;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
async function loadMoney() {
|
|
546
|
-
let out = null;
|
|
547
|
-
try {
|
|
548
|
-
const res = await api.request('GET', `${API}/project-account/outflow`);
|
|
549
|
-
out = res.ok ? res.data : null;
|
|
550
|
-
} catch (_) { out = null; }
|
|
551
|
-
if (!out || !out.balance || !out.committed) return;
|
|
552
|
-
let checks = null;
|
|
553
|
-
try {
|
|
554
|
-
const res = await api.request('GET', `${API}/project-account/ledger-checks?limit=1`);
|
|
555
|
-
checks = res.ok ? res.data : null;
|
|
556
|
-
} catch (_) { checks = null; }
|
|
557
|
-
$('ps-money-body').innerHTML = moneyFiguresHtml(out) + ledgerCheckHtml(checks) + owedHtml(out) + buildersHtml(out) + burnHtml(out);
|
|
558
|
-
$('ps-money').hidden = false;
|
|
559
|
-
}
|
|
560
|
-
|
|
420
|
+
// task 1005047: "Where the money went" (task 1003184) moved to money.js, the one
|
|
421
|
+
// money page; the section's link is in project-settings.html.
|
|
561
422
|
load();
|
|
562
|
-
loadMoney();
|
|
563
423
|
loadDanger();
|
|
564
424
|
})();
|
|
@@ -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-money"></script>
|
|
47
|
+
<script src="/builders/shell.js?v=2026-11-04-money"></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
|