@bongos/core 1.19.516 → 1.19.517

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 CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.516",
6
- "core_contract": "1.19.516",
7
- "source_commit": "b92f5957bf6d2995db7633b965ea029192fb05df",
5
+ "core_version": "1.19.517",
6
+ "core_contract": "1.19.517",
7
+ "source_commit": "66eb690b847772bf8d674d1ccc615d19f39de601",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-03T19:52:58.155Z",
9
+ "built_at": "2026-09-03T20:01:20.384Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 431,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2421,
20
- "tree_sha256": "d9225673c69711642ce6ca8ae5ec201351c431974565132d756573f715a2c62e",
20
+ "tree_sha256": "1fbc6c8076d48aefdaba9be3f3d3d61162cf78dedb9bfd9fadaf1e31599834e2",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -1747,12 +1747,12 @@
1747
1747
  {
1748
1748
  "path": "docs/copy-inventory.md",
1749
1749
  "mode": "0000644",
1750
- "sha256": "e6aba956f5d6f056788f8d43f52dfcf4e242a9dff3bc348f312c875b4cd04be2"
1750
+ "sha256": "7fe7f7c6fd69f4345dda192ef8bc4c5f57b71337a43dcfacb502286bc3dabbbe"
1751
1751
  },
1752
1752
  {
1753
1753
  "path": "docs/copy-registry.json",
1754
1754
  "mode": "0000644",
1755
- "sha256": "5d0fca4e1e8b63e3679fe656ad773e914f944fa93fac66defeb46bb3642a6d74"
1755
+ "sha256": "e2c2aed12656d3395d2de2a8301dc895340d46876e0310acaa9ef0d83bb2f512"
1756
1756
  },
1757
1757
  {
1758
1758
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2597,7 +2597,7 @@
2597
2597
  {
2598
2598
  "path": "docs/module-api-changelog.md",
2599
2599
  "mode": "0000644",
2600
- "sha256": "98e8b564cdaccd042553e8337d0d67222911bdf7958f02e168f1b200da2183a9"
2600
+ "sha256": "5ed5e8440137014a32ea6d15e4d3d9fad357301373a34a8d2ca27d337cf0a0e9"
2601
2601
  },
2602
2602
  {
2603
2603
  "path": "docs/modules-contract.md",
@@ -4622,12 +4622,12 @@
4622
4622
  {
4623
4623
  "path": "modules/hall-ui/public/drachmae.html",
4624
4624
  "mode": "0000644",
4625
- "sha256": "25cf34cf723e8ca2fb492f589fb3415e51aab0b2ab187bad77f2c270248c61b8"
4625
+ "sha256": "89bd28f00b4f4849b8789f8d5b4d5eb50705fddeb65f1675bb0d88fc43fd1859"
4626
4626
  },
4627
4627
  {
4628
4628
  "path": "modules/hall-ui/public/drachmae.js",
4629
4629
  "mode": "0000644",
4630
- "sha256": "7da97f075f12cb479491d43d5501b9e39aecfd36a250c066917c7381db292872"
4630
+ "sha256": "fbcc696c9d32c6ebd616bcff3c32b777086bc3bb3da22ea06770aadf27a4f33c"
4631
4631
  },
4632
4632
  {
4633
4633
  "path": "modules/hall-ui/public/drachmae.states.json",
@@ -4722,7 +4722,7 @@
4722
4722
  {
4723
4723
  "path": "modules/hall-ui/public/hall-kit.js",
4724
4724
  "mode": "0000644",
4725
- "sha256": "9fdd4fa659a6ff1f36159a89a5bab9d12009b1624f7d72f8d1296d1dffff0d3f"
4725
+ "sha256": "cf6cf21ae8263ddcd2ea5b95bc467646987b91e36998588cc80c5279ff997926"
4726
4726
  },
4727
4727
  {
4728
4728
  "path": "modules/hall-ui/public/hall-render.js",
@@ -4922,7 +4922,7 @@
4922
4922
  {
4923
4923
  "path": "modules/hall-ui/public/profile-charts.js",
4924
4924
  "mode": "0000644",
4925
- "sha256": "6eb791bb5d73e869f1af1dbabe6fbd04eccf970b54696a89a4cd1d28e6e705df"
4925
+ "sha256": "83cbe72b266177d407095b16775be52de633f79220d4bbcc5700dbf0e67b29a2"
4926
4926
  },
4927
4927
  {
4928
4928
  "path": "modules/hall-ui/public/profile-connections.js",
@@ -4937,7 +4937,7 @@
4937
4937
  {
4938
4938
  "path": "modules/hall-ui/public/profile.css",
4939
4939
  "mode": "0000644",
4940
- "sha256": "18d65cb2bab78487a4cbf2447154a69674291972d15256d338123c2a437abc87"
4940
+ "sha256": "2d3b4596ce8cbafcaef71d2882351c764a030bdbfeee51192136ec7f17c86475"
4941
4941
  },
4942
4942
  {
4943
4943
  "path": "modules/hall-ui/public/profile.html",
@@ -5072,7 +5072,7 @@
5072
5072
  {
5073
5073
  "path": "modules/hall-ui/public/style.css",
5074
5074
  "mode": "0000644",
5075
- "sha256": "801d467a63d65c24877cac2741308590b9cd66530231f77bdf7230d1c4ebe513"
5075
+ "sha256": "25e2ade6a51158e430fb0e49d1c50f654b13e5f272544fc4b592dc022a4ddd60"
5076
5076
  },
5077
5077
  {
5078
5078
  "path": "modules/hall-ui/public/task-detail.js",
@@ -7292,12 +7292,12 @@
7292
7292
  {
7293
7293
  "path": "package-lock.json",
7294
7294
  "mode": "0000644",
7295
- "sha256": "d137b5fec77d066d3e93b56a0af042e80252513dfa759872ae352a99b92170f6"
7295
+ "sha256": "58709e2e4ec44db5a5364697716bf3b6627d7bde82bb2dec1b421137b2ce3d7a"
7296
7296
  },
7297
7297
  {
7298
7298
  "path": "package.json",
7299
7299
  "mode": "0000644",
7300
- "sha256": "a212fc90527071642d6693ca0ea6dae91c8ad9c9452a8c19d550f2bf452d1e92"
7300
+ "sha256": "9ecf6e4f8dc7db037c8504b47ce30d11720c9f8f4ee8b5f78296b340908df1cb"
7301
7301
  },
7302
7302
  {
7303
7303
  "path": "public-docs/index.html",
@@ -8967,7 +8967,7 @@
8967
8967
  {
8968
8968
  "path": "src/module-api.js",
8969
8969
  "mode": "0000644",
8970
- "sha256": "ec17c796e1498bc12b3ecce4d194f7a4026081373d7d209fbfb49343368b5498"
8970
+ "sha256": "83620eba55f459b95f6716241fd425bd40bad06cf10a8fb98a9d5545b0c71139"
8971
8971
  },
8972
8972
  {
8973
8973
  "path": "src/module-loader/catalog.js",
@@ -10372,7 +10372,7 @@
10372
10372
  {
10373
10373
  "path": "tests/hall_kit_charts.mjs",
10374
10374
  "mode": "0000644",
10375
- "sha256": "475c712af2b45fed7863b486c3d2ed3b96e955dccb9acc5909999db3990843d5"
10375
+ "sha256": "388bd57df9e060401af618acc9787b3f9c70b64790a3385832a6bf79a642b9e7"
10376
10376
  },
10377
10377
  {
10378
10378
  "path": "tests/hall_nav.mjs",
@@ -10417,7 +10417,7 @@
10417
10417
  {
10418
10418
  "path": "tests/hall_reference_world.mjs",
10419
10419
  "mode": "0000644",
10420
- "sha256": "efa4896db5902cdad99cbaa82787a39394a41753e0ff086d4e6582514178b1c0"
10420
+ "sha256": "fac14ddf85824d34c0bb2d5b81417c981611d6257b92bc801ee6003b6975683a"
10421
10421
  },
10422
10422
  {
10423
10423
  "path": "tests/hall_roster_world.mjs",
@@ -4,13 +4,13 @@
4
4
  > Deterministic on a given tree — regenerate and commit rather than hand-editing.
5
5
  > Machine-readable twin: [`copy-registry.json`](copy-registry.json).
6
6
 
7
- **1639 strings** across 3 surfaces · 1421 distinct pieces of copy · 1639 certain, 0 heuristic · 154 shown in more than one place.
7
+ **1641 strings** across 3 surfaces · 1423 distinct pieces of copy · 1641 certain, 0 heuristic · 154 shown in more than one place.
8
8
 
9
9
  ## Surfaces
10
10
 
11
11
  | surface | lives at | files | strings |
12
12
  |---|---|---|---|
13
- | `builders-hall` | `modules/hall-ui/public` | 73 | 1181 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 73 | 1183 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 98 |
15
15
  | `landing` | `modules/public-landing/public` | 5 | 360 |
16
16
 
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
78
78
  | `60b248791dc3` | 2x | Builders — {{worldName}} | modules/hall-ui/public/roster.html:6, modules/hall-ui/public/roster.html:6 |
79
79
  | … | | _114 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 1181 strings
81
+ ## `builders-hall` — 1183 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -92,7 +92,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
92
92
  | `87fa099f5068` | certain | '; } html += ' | `modules/hall-ui/public/md-reader.js:224` |
93
93
  | `cb327c50b079` | certain | 's dev box (state: {…}). Any uncommitted work on that box is lost. The box is destroyed; the builder can open | `modules/hall-ui/public/harbor.js:125` |
94
94
  | `544794f1ec8f` | certain | (later in | `modules/hall-ui/public/index.html:9` |
95
- | `3ac2eb36ba97` | certain | (no sections) | `modules/hall-ui/public/drachmae.js:128` |
95
+ | `3ac2eb36ba97` | certain | (no sections) | `modules/hall-ui/public/drachmae.js:260` |
96
96
  | `a4614c0b8eb5` | certain | (or use the Claude Code CLI) and open the project. | `modules/hall-ui/public/hall-render.js:785` |
97
97
  | `c72946be7ac0` | certain | (spin-up {…} · compute {…} · storage {…}) | `modules/hall-ui/public/box-panel.js:236` |
98
98
  | `1eee272d5ff4` | certain | (the droplet) is both the authority and production; | `modules/hall-ui/public/diagrams.html:74` |
@@ -517,7 +517,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
517
517
  | `19ad2430937d` | certain | Learning capture | `modules/hall-ui/public/ideas.html:78` |
518
518
  | `7c9b7a89353b` | certain | Leave goal | `modules/hall-ui/public/goals-render.js:851` |
519
519
  | `f192ff6563f9` | certain | Leave your GitHub username and an Archon will let you in. No coding needed to start — once you're approved, si | `modules/hall-ui/public/hall-render.js:278` |
520
- | `43660902bf79` | certain | Link copied. | `modules/hall-ui/public/drachmae.js:200` |
520
+ | `43660902bf79` | certain | Link copied. | `modules/hall-ui/public/drachmae.js:332` |
521
521
  | `a53e0cde7ff7` | certain | Link first — your rank role is set the moment you join. | `modules/hall-ui/public/settings.js:928` |
522
522
  | `64e85f1691f9` | certain | Link your Discord account and you're added to the {{worldName}} server with your rank role already set — no in | `modules/hall-ui/public/settings.html:61` |
523
523
  | `9a31cf5fe05b` | certain | Linking opens Discord's consent screen; unlinking applies at once. | `modules/hall-ui/public/settings.html:66` |
@@ -544,7 +544,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
544
544
  | `7a239a17afc8` | certain | Loading your preferences… | `modules/hall-ui/public/settings.html:32` |
545
545
  | `04df1bd30129` | certain | Loading your rank… | `modules/hall-ui/public/ranks.html:30` |
546
546
  | `c27711f9eb4d` | certain | Locked until you reach the end of the article above. | `modules/hall-ui/public/primer.html:103` |
547
- | `4017ee030bc2` | certain | Lookback window | `modules/hall-ui/public/profile-charts.js:403` |
547
+ | `4017ee030bc2` | certain | Lookback window | `modules/hall-ui/public/profile-charts.js:396` |
548
548
  | `a93ca2841b96` | certain | Mac (Intel) | `modules/hall-ui/public/hall-render.js:744` |
549
549
  | `06b4ede3fb58` | certain | Machine principals | `modules/hall-ui/public/government.html:124` |
550
550
  | `6b3d660e0e31` | certain | Map view | `modules/hall-ui/public/atlas.html:46` |
@@ -694,6 +694,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
694
694
  | `e60039795803` | certain | Re-issue CLI token | `modules/hall-ui/public/settings.html:151` |
695
695
  | `101a51902f53` | certain | Re-issue failed: {…} | `modules/hall-ui/public/settings.js:1001` |
696
696
  | `fae2f90927bd` | certain | Read an idea, then decide. An objection needs a reason and the section it faults. | `modules/hall-ui/public/government.html:66` |
697
+ | `b03e17693dcd` | certain | Read it as a table | `modules/hall-ui/public/drachmae.js:148` |
697
698
  | `b6fcfdd652a7` | certain | Read it here | `modules/hall-ui/public/work.html:45` |
698
699
  | `c5f19a888a15` | certain | Read the handoff notes | `modules/hall-ui/public/task.js:441` |
699
700
  | `f9e998f4af3d` | certain | Read to the end and the button below unlocks. It is an honour system — nothing here checks your comprehension. | `modules/hall-ui/public/primer.html:100` |
@@ -948,7 +949,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
948
949
  | `0658f0358ffd` | certain | Welcome back, {…}. | `modules/hall-ui/public/hall-render.js:526` |
949
950
  | `04ef2f1bc4c4` | certain | Welcome, {…}. Claim and release tasks and follow work in progress — no terminal needed. | `modules/hall-ui/public/work.js:1145` |
950
951
  | `d6bae87dc29b` | certain | What builders will see | `modules/hall-ui/public/government.html:88` |
951
- | `f3d93a872a38` | certain | What counts | `modules/hall-ui/public/drachmae.js:98` |
952
+ | `f3d93a872a38` | certain | What counts | `modules/hall-ui/public/drachmae.js:107` |
952
953
  | `4a33938611be` | certain | What each builder's dev box has cost — spin-up, compute, and storage. Tracked for oversight; not charged in th | `modules/hall-ui/public/watch.html:95` |
953
954
  | `0874800c694a` | certain | What is being built right now. See | `modules/hall-ui/public/work.html:84` |
954
955
  | `70dc106494cf` | certain | What is wrong with it, and what would better look like? | `modules/hall-ui/public/copy-desk.html:101` |
@@ -961,6 +962,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
961
962
  | `3dade88f0584` | certain | What your box would cost: | `modules/hall-ui/public/box-panel.js:235` |
962
963
  | `b8743d16eab8` | certain | What {…} unlocks | `modules/hall-ui/public/hall-render.js:82` |
963
964
  | `ec1a438eee3f` | certain | Where each comes from and where each goes. {{Currency}} = credits; karma is a separate peer-trust signal, not | `modules/hall-ui/public/diagrams.html:68` |
965
+ | `c44892e8870d` | certain | Where it went | `modules/hall-ui/public/drachmae.js:218` |
964
966
  | `4052378c3e04` | certain | Where the words stand | `modules/hall-ui/public/copy-desk.html:38` |
965
967
  | `158499e38ceb` | certain | Where you stand | `modules/hall-ui/public/ranks.html:36` |
966
968
  | `b216e7af8140` | certain | Where your work runs and how it reaches production, in one diagram. Your | `modules/hall-ui/public/diagrams.html:74` |
@@ -1035,7 +1037,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1035
1037
  | `181d339c9318` | certain | and build right here in the browser — no remote control needed. Either way, reopening the online terminal late | `modules/hall-ui/public/hall-render.js:744` |
1036
1038
  | `bc960f460250` | certain | and install dependencies ( | `modules/hall-ui/public/hall-render.js:785` |
1037
1039
  | `b867876e8b6c` | certain | and press Enter. | `modules/hall-ui/public/hall-render.js:744` |
1038
- | `b4033000c537` | certain | as a table | `modules/hall-ui/public/profile-charts.js:209` |
1040
+ | `b4033000c537` | certain | as a table | `modules/hall-ui/public/profile-charts.js:202` |
1039
1041
  | `d7c511423bc0` | certain | as configuration: {…} | `modules/hall-ui/public/government.js:707` |
1040
1042
  | `09db3fd4355b` | certain | asked by {…} | `modules/hall-ui/public/collab.js:69` |
1041
1043
  | `63bd14f73960` | certain | author trusted | `modules/hall-ui/public/gate.js:141` |
@@ -1103,7 +1105,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1103
1105
  | `fdfb8b2ce948` | certain | new identity | `modules/hall-ui/public/box-panel.js:339` |
1104
1106
  | `86f235ddfe01` | certain | new thread | `modules/hall-ui/public/settings.html:260` |
1105
1107
  | `b9f2e0d43c94` | certain | no clock | `modules/hall-ui/public/government.js:602` |
1106
- | `e02979266baf` | certain | no data | `modules/hall-ui/public/hall-kit.js:695` |
1108
+ | `e02979266baf` | certain | no data | `modules/hall-ui/public/hall-kit.js:724` |
1107
1109
  | `33ca71bef112` | certain | no tasks yet | `modules/hall-ui/public/goals-render.js:163` |
1108
1110
  | `739a4bd6bd83` | certain | node scripts/setup-builder.js | `modules/hall-ui/public/hall-render.js:811` |
1109
1111
  | `61f0f4b2f4ab` | certain | not categorised yet | `modules/hall-ui/public/goals-page.js:277` |
@@ -1112,7 +1114,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1112
1114
  | `3916a3dc8e98` | certain | not started | `modules/hall-ui/public/roadmap.js:108` |
1113
1115
  | `3916a3dc8e98` | certain | not started | `modules/hall-ui/public/roadmap.js:263` |
1114
1116
  | `b5567b42fa52` | certain | not yet | `modules/hall-ui/public/not-ready.html:25` |
1115
- | `8ca0a499ec86` | certain | nothing recorded yet · {…} | `modules/hall-ui/public/profile-charts.js:227` |
1117
+ | `8ca0a499ec86` | certain | nothing recorded yet · {…} | `modules/hall-ui/public/profile-charts.js:220` |
1116
1118
  | `e2c643fbf4d6` | certain | npm install | `modules/hall-ui/public/hall-render.js:785` |
1117
1119
  | `e90b5601ce16` | certain | objected to | `modules/hall-ui/public/government.js:824` |
1118
1120
  | `351d56b80a6c` | certain | of all in-flight tasks shipped | `modules/hall-ui/public/roadmap.js:273` |
@@ -1140,8 +1142,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1140
1142
  | `9bec9eb4829e` | certain | scope: {…} | `modules/hall-ui/public/goals-render.js:785` |
1141
1143
  | `a6f55d7aa6f7` | certain | shipped (live) | `modules/hall-ui/public/roadmap.js:273` |
1142
1144
  | `026ba1289d4d` | certain | shipped · | `modules/hall-ui/public/goals-render.js:707` |
1143
- | `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:465` |
1144
- | `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:477` |
1145
+ | `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:458` |
1146
+ | `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:470` |
1145
1147
  | `0215b2cdca8c` | certain | submit for review | `modules/hall-ui/public/modules.js:132` |
1146
1148
  | `da30b952a60b` | certain | system / unattributed | `modules/hall-ui/public/goals-page.js:249` |
1147
1149
  | `da30b952a60b` | certain | system / unattributed | `modules/hall-ui/public/work.js:498` |
@@ -3,9 +3,9 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1639,
7
- "unique_ids": 1421,
8
- "certain": 1639,
6
+ "strings": 1641,
7
+ "unique_ids": 1423,
8
+ "certain": 1641,
9
9
  "heuristic": 0,
10
10
  "duplicate_ids": 154
11
11
  },
@@ -13,7 +13,7 @@
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
15
  "files": 73,
16
- "strings": 1181
16
+ "strings": 1183
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -1478,8 +1478,8 @@
1478
1478
  "text": "sign in",
1479
1479
  "occurrences": 2,
1480
1480
  "at": [
1481
- "modules/hall-ui/public/profile-charts.js:465",
1482
- "modules/hall-ui/public/profile-charts.js:477"
1481
+ "modules/hall-ui/public/profile-charts.js:458",
1482
+ "modules/hall-ui/public/profile-charts.js:470"
1483
1483
  ]
1484
1484
  },
1485
1485
  {
@@ -1739,7 +1739,7 @@
1739
1739
  "surface": "builders-hall",
1740
1740
  "text": "(no sections)",
1741
1741
  "file": "modules/hall-ui/public/drachmae.js",
1742
- "line": 128,
1742
+ "line": 260,
1743
1743
  "origin": "js-markup",
1744
1744
  "confidence": "certain"
1745
1745
  },
@@ -5564,7 +5564,7 @@
5564
5564
  "surface": "builders-hall",
5565
5565
  "text": "Link copied.",
5566
5566
  "file": "modules/hall-ui/public/drachmae.js",
5567
- "line": 200,
5567
+ "line": 332,
5568
5568
  "origin": "js-notice",
5569
5569
  "confidence": "certain"
5570
5570
  },
@@ -5807,7 +5807,7 @@
5807
5807
  "surface": "builders-hall",
5808
5808
  "text": "Lookback window",
5809
5809
  "file": "modules/hall-ui/public/profile-charts.js",
5810
- "line": 403,
5810
+ "line": 396,
5811
5811
  "origin": "js-markup",
5812
5812
  "confidence": "certain"
5813
5813
  },
@@ -7152,6 +7152,15 @@
7152
7152
  "origin": "html-text",
7153
7153
  "confidence": "certain"
7154
7154
  },
7155
+ {
7156
+ "id": "b03e17693dcd",
7157
+ "surface": "builders-hall",
7158
+ "text": "Read it as a table",
7159
+ "file": "modules/hall-ui/public/drachmae.js",
7160
+ "line": 148,
7161
+ "origin": "js-markup",
7162
+ "confidence": "certain"
7163
+ },
7155
7164
  {
7156
7165
  "id": "b6fcfdd652a7",
7157
7166
  "surface": "builders-hall",
@@ -9443,7 +9452,7 @@
9443
9452
  "surface": "builders-hall",
9444
9453
  "text": "What counts",
9445
9454
  "file": "modules/hall-ui/public/drachmae.js",
9446
- "line": 98,
9455
+ "line": 107,
9447
9456
  "origin": "js-markup",
9448
9457
  "confidence": "certain"
9449
9458
  },
@@ -9555,6 +9564,15 @@
9555
9564
  "origin": "html-text",
9556
9565
  "confidence": "certain"
9557
9566
  },
9567
+ {
9568
+ "id": "c44892e8870d",
9569
+ "surface": "builders-hall",
9570
+ "text": "Where it went",
9571
+ "file": "modules/hall-ui/public/drachmae.js",
9572
+ "line": 218,
9573
+ "origin": "js-markup",
9574
+ "confidence": "certain"
9575
+ },
9558
9576
  {
9559
9577
  "id": "4052378c3e04",
9560
9578
  "surface": "builders-hall",
@@ -10226,7 +10244,7 @@
10226
10244
  "surface": "builders-hall",
10227
10245
  "text": "as a table",
10228
10246
  "file": "modules/hall-ui/public/profile-charts.js",
10229
- "line": 209,
10247
+ "line": 202,
10230
10248
  "origin": "js-markup",
10231
10249
  "confidence": "certain"
10232
10250
  },
@@ -10838,7 +10856,7 @@
10838
10856
  "surface": "builders-hall",
10839
10857
  "text": "no data",
10840
10858
  "file": "modules/hall-ui/public/hall-kit.js",
10841
- "line": 695,
10859
+ "line": 724,
10842
10860
  "origin": "js-markup",
10843
10861
  "confidence": "certain"
10844
10862
  },
@@ -10919,7 +10937,7 @@
10919
10937
  "surface": "builders-hall",
10920
10938
  "text": "nothing recorded yet · {…}",
10921
10939
  "file": "modules/hall-ui/public/profile-charts.js",
10922
- "line": 227,
10940
+ "line": 220,
10923
10941
  "origin": "js-markup",
10924
10942
  "confidence": "certain"
10925
10943
  },
@@ -11171,7 +11189,7 @@
11171
11189
  "surface": "builders-hall",
11172
11190
  "text": "sign in",
11173
11191
  "file": "modules/hall-ui/public/profile-charts.js",
11174
- "line": 465,
11192
+ "line": 458,
11175
11193
  "origin": "js-markup",
11176
11194
  "confidence": "certain"
11177
11195
  },
@@ -11180,7 +11198,7 @@
11180
11198
  "surface": "builders-hall",
11181
11199
  "text": "sign in",
11182
11200
  "file": "modules/hall-ui/public/profile-charts.js",
11183
- "line": 477,
11201
+ "line": 470,
11184
11202
  "origin": "js-markup",
11185
11203
  "confidence": "certain"
11186
11204
  },
@@ -1469,5 +1469,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1469
1469
  landed since 1.19.514 with no explicit bump. run 33797825811. (task 1002620)
1470
1470
  1.19.516 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1471
1471
  landed since 1.19.515 with no explicit bump. run 33798954769. (task 1002620)
1472
+ 1.19.517 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1473
+ landed since 1.19.516 with no explicit bump. run 33799755382. (task 1002620)
1472
1474
  ---------------------------------------------------------------------------
1473
1475
  ```
@@ -102,6 +102,9 @@
102
102
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
103
103
  <script src="/builders/palette.js" defer></script>
104
104
  <script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
105
+ <!-- The chart primitive, for the treasury section (task 1003446). Before
106
+ drachmae.js, which reads window.OTBKit when it inserts that section. -->
107
+ <script src="/builders/hall-kit.js" defer></script>
105
108
  <script src="/builders/drachmae.js?v=2026-09-02-drachmae-2" defer></script>
106
109
  <script src="/builders/copy-on-click.js?v=2026-06-01-copy-1" defer></script>
107
110
  </body>
@@ -31,6 +31,15 @@
31
31
  // The doc lives at docs/onboarding/, so relative links resolve against it.
32
32
  const DOC_DIR = 'docs/onboarding/';
33
33
  const BOUNTY_URL = '/api/gds/public/bounty-table';
34
+ // task 1003446: the treasury feed. NOT a new endpoint — this is the same
35
+ // /public/cost-summary the signed-out status page already draws as a
36
+ // cumulative line and the Work board already fetches to read ONE scalar off
37
+ // (`total_value_credits`). Its own route comment calls `daily[]` "the
38
+ // treasury chart's cumulative line"; the series was in the payload the whole
39
+ // time, and the stranger got the chart while the builder got the number.
40
+ const TREASURY_URL = '/api/gds/public/cost-summary';
41
+ const TREASURY_SECTION_ID = 'the-treasury';
42
+ const TREASURY_SECTION_TITLE = 'The treasury, so far';
34
43
  const BOUNTY_SECTION_ID = 'security-bounty-rates';
35
44
  const BOUNTY_SECTION_TITLE = 'Security bounty rates';
36
45
  // The section the live table is inserted AHEAD of: the doc's closing links.
@@ -109,6 +118,129 @@
109
118
  return true;
110
119
  }
111
120
 
121
+ // ---- the treasury (task 1003446) -----------------------------------------
122
+
123
+ // One cumulative figure: the plot, the number it ends on, and the table twin
124
+ // that keeps it out of picture-only territory. `fmt` renders a value for a
125
+ // human; `rows` is the twin's own text, so the money and the credits can read
126
+ // differently without the chart knowing about either.
127
+ function treasuryFigure({ id, title, points, fmt, foot }) {
128
+ const kit = window.OTBKit;
129
+ const esc = window.OTBMdReader.escapeHtml;
130
+ if (!points.length) return '';
131
+ const last = points[points.length - 1];
132
+ const series = kit.timeStepSeriesHtml(points, {
133
+ x0: points[0].x,
134
+ x1: last.x,
135
+ yMax: last.value,
136
+ });
137
+ const { endX, endY } = series.geometry;
138
+ const day = (t) => new Date(t).toISOString().slice(0, 10);
139
+ // Every 7th day plus the last: a twin with one row per calendar day since
140
+ // May would be ~120 rows behind a disclosure nobody finishes reading.
141
+ const twinRows = points.filter((p, i) => i % 7 === 0 || i === points.length - 1);
142
+ return `<figure class="pchart" id="${esc(id)}">
143
+ <figcaption class="pchart__title">${esc(title)}</figcaption>
144
+ ${kit.plotSvgHtml({
145
+ series: series.body,
146
+ // No readout is wired here, so no focus stop and no dead crosshair.
147
+ interactive: false,
148
+ ariaLabel: `${title}: ${fmt(last.value)} as of ${day(last.x)}`,
149
+ label: kit.plotValueLabelHtml(endX, endY - 8, fmt(last.value), 'end'),
150
+ leftTick: day(points[0].x),
151
+ rightTick: day(last.x),
152
+ })}
153
+ <details class="pchart__twin">
154
+ <summary>Read it as a table</summary>
155
+ <table class="ledger pchart__table"><thead><tr><th>Day</th><th>${esc(title)}</th></tr></thead><tbody>
156
+ ${twinRows.map((p) => `<tr><td>${esc(day(p.x))}</td><td>${esc(fmt(p.value))}</td></tr>`).join('')}
157
+ </tbody></table>
158
+ </details>
159
+ <p class="pchart__foot">${esc(foot)}</p>
160
+ </figure>`;
161
+ }
162
+
163
+ // The live treasury as a real section of the article, ahead of the rate card.
164
+ // Returns false when the summary can't be read: the prose stays and no empty
165
+ // panel or outline entry that leads nowhere is rendered — the same contract
166
+ // insertBountySection keeps.
167
+ async function insertTreasurySection(article) {
168
+ if (!window.OTBKit) {
169
+ console.warn('[drachmae] OTBKit missing — the treasury section needs the chart primitive');
170
+ return false;
171
+ }
172
+ let payload;
173
+ try {
174
+ const r = await fetch(TREASURY_URL, { credentials: 'same-origin' });
175
+ if (!r.ok) throw new Error(`${TREASURY_URL} → ${r.status}`);
176
+ payload = await r.json();
177
+ } catch (err) {
178
+ console.warn('[drachmae] cost summary unavailable:', err.message);
179
+ return false;
180
+ }
181
+ const esc = window.OTBMdReader.escapeHtml;
182
+ const kit = window.OTBKit;
183
+ const currency = (window.__BRANDING__ && window.__BRANDING__.currency
184
+ && window.__BRANDING__.currency.label) || 'credits';
185
+ const Currency = currency.charAt(0).toUpperCase() + currency.slice(1);
186
+ const usd = (n) => `$${Number(n || 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}`;
187
+ const num = (n) => Number(n || 0).toLocaleString();
188
+
189
+ // The server already ran generate_series, so every calendar day is present
190
+ // and a flat run is a real flat run rather than a missing sample.
191
+ const spend = kit.cumulateByDay(payload.daily, { valueOf: (r) => r.amount_usd });
192
+ const earned = kit.cumulateByDay(payload.daily_credits, { valueOf: (r) => r.credits });
193
+ // Both series empty is a treasury with nothing in it yet — one honest line,
194
+ // not two empty plots.
195
+ if (!spend.length && !earned.length) return false;
196
+
197
+ const figures = [
198
+ treasuryFigure({
199
+ id: 'treasury-spend',
200
+ title: 'Spent, cumulative',
201
+ points: spend,
202
+ fmt: usd,
203
+ foot: 'Every recorded cost — API, infra, compute, art — added up day by day.',
204
+ }),
205
+ treasuryFigure({
206
+ id: 'treasury-earned',
207
+ title: `${Currency} awarded, cumulative`,
208
+ points: earned,
209
+ fmt: num,
210
+ foot: `What the project has paid builders, in ${esc(currency)}, day by day.`,
211
+ }),
212
+ ].filter(Boolean).join('');
213
+
214
+ // Where the money went, as the kit's bar series rather than a fourth
215
+ // hand-rolled one. Categories arrive sorted biggest-first.
216
+ const byCategory = (payload.by_category || []).slice(0, 8);
217
+ const categories = byCategory.length
218
+ ? `<h3 id="treasury-by-category">Where it went</h3>`
219
+ + kit.barSeriesHtml(
220
+ byCategory.map((c) => ({ label: c.category, value: c.total_usd, valueLabel: usd(c.total_usd) })),
221
+ { list: true, ariaLabel: 'Spend by category', valueWidth: '84px' },
222
+ )
223
+ : '';
224
+
225
+ const section = document.createElement('section');
226
+ section.innerHTML =
227
+ `<h2 id="${esc(TREASURY_SECTION_ID)}">${esc(TREASURY_SECTION_TITLE)}</h2>`
228
+ + `<p>These are this project's real numbers, read from the server when you `
229
+ + `opened this page. It has spent <strong>${esc(usd(payload.total_usd))}</strong> `
230
+ + `and awarded <strong>${esc(num(payload.total_value_credits))} ${esc(currency)}</strong> `
231
+ + `for the work that money bought.</p>`
232
+ + `<div class="pcharts">${figures}</div>`
233
+ + categories;
234
+
235
+ const bounty = article.querySelector(`#${BOUNTY_SECTION_ID}`);
236
+ const closing = [...article.querySelectorAll('h2')].find((h) => CLOSING_H2.test(h.textContent.trim()));
237
+ // Ahead of the rate card if it landed first, else ahead of the closing
238
+ // links, else appended: the doc keeps ending on "dig deeper" either way.
239
+ const before = (bounty && bounty.parentElement === article ? bounty : null) || closing;
240
+ if (before) article.insertBefore(section, before); else article.appendChild(section);
241
+ return true;
242
+ }
243
+
112
244
  // The outline is read from the RENDERED headings rather than the parser's raw
113
245
  // text, so `*not*` and a [link](…) in a heading arrive as plain words, and so
114
246
  // the live section inserted above is simply one more heading in order.
@@ -276,9 +408,13 @@
276
408
  article.innerHTML = parsed.html.replace(/^\s*<h1\b[^>]*>[\s\S]*?<\/h1>\s*/, '');
277
409
  article.removeAttribute('aria-busy');
278
410
  show('article');
279
- // Awaited before the outline is built so the rate card gets its entry and a
280
- // scrollspy target; the article itself is already on screen by now.
411
+ // Awaited before the outline is built so both live sections get their
412
+ // entries and scrollspy targets; the article itself is already on screen by
413
+ // now. The rate card goes in FIRST so the treasury can land ahead of it by
414
+ // id — the doc then reads prose, then what actually happened, then the
415
+ // published rates, then "dig deeper".
281
416
  await insertBountySection(article);
417
+ await insertTreasurySection(article);
282
418
  addAnchors(article);
283
419
  setupTocToggle();
284
420
  setupScrollspy(renderToc(collectHeadings(article)));
@@ -663,6 +663,35 @@
663
663
  return chartNums(values).map((v) => (run += v));
664
664
  }
665
665
 
666
+ // Timestamped rows → [{ x, value }] ascending, one point per calendar day
667
+ // that HAS a row, value cumulative. The third consumer of this shape put it
668
+ // here (task 1003446): the profile's works and credits lines, and
669
+ // /drachmae's treasury. Two callers is a coincidence; three is a primitive.
670
+ //
671
+ // `dateOf`/`valueOf` are the caller's, because only the caller knows which
672
+ // column is the date — `shipped_at` on an activity row, `recorded_at` on a
673
+ // credits row, `day` on a gap-filled daily feed. A row whose date will not
674
+ // parse is DROPPED rather than counted at the epoch, where it would stretch
675
+ // the axis back to 1970 and flatten every real point against the floor.
676
+ function cumulateByDay(rows, opts = {}) {
677
+ const dateOf = opts.dateOf || ((r) => r && r.day);
678
+ const valueOf = opts.valueOf || ((r) => r && r.value);
679
+ const byDay = new Map();
680
+ for (const r of rows || []) {
681
+ const t = Date.parse(dateOf(r));
682
+ if (!Number.isFinite(t)) continue;
683
+ // The epoch-day floor — UTC midnight, so two rows on one day land on one
684
+ // point. Same value as setUTCHours(0,0,0,0) without a Date per row, and
685
+ // the same expression profile-charts.js's own `midnight` used before this
686
+ // moved, so no series shifts by a day.
687
+ const d = Math.floor(t / 86400000) * 86400000;
688
+ byDay.set(d, (byDay.get(d) || 0) + (Number(valueOf(r)) || 0));
689
+ }
690
+ const days = [...byDay.keys()].sort((a, b) => a - b);
691
+ const totals = cumulate(days.map((d) => byDay.get(d)));
692
+ return days.map((d, i) => ({ x: d, value: totals[i] }));
693
+ }
694
+
666
695
  // The line geometry, separated from the markup so a chart that needs the
667
696
  // maths without this SVG can take just this. Returns { points, x, y, nums }.
668
697
  //
@@ -867,21 +896,30 @@
867
896
  return { body, xs, base, py, max };
868
897
  }
869
898
 
870
- // The plot itself: the series a geometry built, on the axis, with the hidden
871
- // crosshair a caller's readout moves and the two end ticks. `tabindex="0"` is
872
- // deliberate and paired with the readout — the chart is walkable by arrow
873
- // key, so it has to be reachable by tab.
899
+ // The plot itself: the series a geometry built, on the axis, with the two end
900
+ // ticks — and, when the caller wires a readout, the hidden crosshair it moves
901
+ // plus the `tabindex` that makes an arrow-key-walkable chart reachable by tab.
902
+ //
903
+ // `interactive: false` (task 1003446) drops BOTH of those, and the pairing is
904
+ // the point: a `role="img"` with a `tabindex="0"` is a focus stop a keyboard
905
+ // reader lands on with nothing to do there, and a crosshair nothing moves is
906
+ // markup that only looks like a feature. A plot whose whole reading is its
907
+ // aria-label and its table twin says so.
874
908
  function plotSvgHtml(opts = {}) {
875
909
  const { width, height, pad, base } = plotBox(opts);
910
+ const interactive = opts.interactive !== false;
876
911
  const tick = (x, text, anchor) => (text
877
912
  ? `<text class="kit-plot__tick" x="${x}" y="${height - 6}"`
878
913
  + `${anchor ? ` text-anchor="${anchor}"` : ''}>${esc(text)}</text>`
879
914
  : '');
880
- return `<svg class="kit-plot" viewBox="0 0 ${width} ${height}" role="img" tabindex="0"`
915
+ return `<svg class="kit-plot" viewBox="0 0 ${width} ${height}" role="img"`
916
+ + (interactive ? ' tabindex="0"' : '')
881
917
  + ` aria-label="${esc(opts.ariaLabel || '')}">`
882
918
  + (opts.series || '')
883
919
  + `<line class="kit-plot__axis" x1="${pad.l}" y1="${base}" x2="${width - pad.r}" y2="${base}"></line>`
884
- + `<line class="kit-plot__hair" x1="0" y1="${pad.t}" x2="0" y2="${base}" style="visibility:hidden"></line>`
920
+ + (interactive
921
+ ? `<line class="kit-plot__hair" x1="0" y1="${pad.t}" x2="0" y2="${base}" style="visibility:hidden"></line>`
922
+ : '')
885
923
  + (opts.label || '')
886
924
  + tick(pad.l, opts.leftTick)
887
925
  + tick(width - pad.r, opts.rightTick, 'end')
@@ -918,7 +956,7 @@
918
956
  // charts (task 1003445) — the ONE place the hall draws a series.
919
957
  // linePoints/cumulate are the maths on their own, for a chart that wants
920
958
  // the geometry without this SVG (the profile's timeChart).
921
- sparklineHtml, barSeriesHtml, chartCardHtml, linePoints, cumulate,
959
+ sparklineHtml, barSeriesHtml, chartCardHtml, linePoints, cumulate, cumulateByDay,
922
960
  // plotted charts: the axis frame + the two series shapes that ride it
923
961
  plotSvgHtml, plotValueLabelHtml, timeStepSeriesHtml, timeStepGeometry, columnSeriesHtml,
924
962
  // plotBox is exported so a caller mapping pointer x back into viewBox
@@ -101,20 +101,13 @@
101
101
  // point; a row with no parsable date is skipped, not zeroed. The lookback
102
102
  // clips this for drawing (clipToWindow) rather than re-accumulating, so the
103
103
  // running total stays true.
104
+ // The grouping AND the running total are the kit's since task 1003446, when
105
+ // /drachmae's treasury became the third caller of this shape. This wrapper
106
+ // survives only to keep the `{ day, value }` key this page's callers and
107
+ // clipToWindow already read; the arithmetic has one home.
104
108
  function cumulativeByDay(rows, valueOf, dateOf = (r) => r && r.shipped_at) {
105
- const byDay = new Map();
106
- for (const r of rows || []) {
107
- const t = Date.parse(dateOf(r));
108
- if (!Number.isFinite(t)) continue;
109
- const d = midnight(t);
110
- byDay.set(d, (byDay.get(d) || 0) + Number(valueOf(r)) || 0);
111
- }
112
- // The grouping is this function's; the running total is the kit's
113
- // (task 1003445), so the hall has ONE accumulator rather than two that
114
- // could disagree about what a hole in the series means.
115
- const days = [...byDay.keys()].sort((a, b) => a - b);
116
- const totals = window.OTBKit.cumulate(days.map((d) => byDay.get(d)));
117
- return days.map((d, i) => ({ day: d, value: totals[i] }));
109
+ return window.OTBKit.cumulateByDay(rows, { dateOf, valueOf })
110
+ .map((p) => ({ day: p.x, value: p.value }));
118
111
  }
119
112
 
120
113
  // Clip a cumulative series to [windowStart, ∞) for drawing. The line must
@@ -119,41 +119,12 @@
119
119
  (glass on the card, like settings' quote), the series in the accent and every
120
120
  word in ink. The SVG scales with its card; the axis band is inside the
121
121
  viewBox, so the card grows with its content and never scrolls. */
122
- .pcharts {
123
- display: grid;
124
- grid-template-columns: repeat(2, minmax(0, 1fr));
125
- gap: var(--sp-sm);
126
- }
127
- .pchart {
128
- margin: 0;
129
- padding: var(--sp-xs) var(--sp-sm) var(--sp-xs);
130
- background: var(--glass);
131
- border: var(--line) solid var(--glass-edge);
132
- border-radius: var(--radius-sm);
133
- min-width: 0;
134
- }
135
- .pchart__title { margin: 0; font-size: 13.5px; font-weight: 500; color: var(--ink); }
136
- .pchart__read { margin: 2px 0 var(--sp-hair); font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-soft); min-height: 1.4em; }
137
- /* The plot itself — the svg, the line, the columns, the axis, the crosshair,
138
- the ticks — is OTBKit's `.kit-plot*` in style.css (task 1003445). It was
139
- written twice in this file's own script, and the goal's next charts want it
140
- again. The dataviz spec moved verbatim; what stays below is this page's
141
- FIGURE: the caption, the live readout, the table twin and the foot. */
142
- .pchart__twin { margin-top: var(--sp-hair); }
143
- .pchart__twin > summary { display: inline-flex; align-items: center; min-height: 24px; padding: 0 4px; margin: 0 -4px; font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-soft); cursor: pointer; border-radius: var(--radius-sm); }
144
- .pchart__twin > summary:hover { color: var(--ink); background: var(--glass-strong); }
145
- .pchart__table { margin-top: var(--sp-hair); font-size: 12.5px; }
146
- .pchart__table td, .pchart__table th { padding: 3px 6px; }
147
- .pchart__foot { margin: var(--sp-hair) 0 0; font-size: 12px; color: var(--ink-faint); }
148
- .pchart__note { margin: 0; font-size: 14px; color: var(--ink-soft); }
149
- /* the craft mix: label, bar, count — a row is its own table row */
150
- /* The craft mix is OTBKit.barSeriesHtml (task 1003445) — the .pmix family was
151
- the hall's third copy of one horizontal bar. Only the vertical air around it
152
- is this page's; the row, the track and the 24px floor are the kit's. */
153
- .pchart .kit-bars { margin: var(--sp-hair) 0; }
154
- @media (max-width: 720px) {
155
- .pcharts { grid-template-columns: minmax(0, 1fr); }
156
- }
122
+ /* The plot is OTBKit's `.kit-plot*` (task 1003445) and the FIGURE around it —
123
+ `.pcharts` / `.pchart` / `__title` / `__read` / `__twin` / `__table` /
124
+ `__foot` — moved to style.css beside it in task 1003446, when /drachmae's
125
+ treasury became its second consumer. The names did not change: they are
126
+ spelled into profile.js AND drachmae.js. What stays in this sheet is the one
127
+ thing only this page has, the lookback control below. */
157
128
 
158
129
  /* the lookback control (task 1003489, owner request) — one segmented row above
159
130
  the grid, scoping every chart. The pressed side takes the accent TINT the
@@ -2047,6 +2047,41 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
2047
2047
  .kit-plot__end { font-family: var(--font-body); font-size: 11.5px; font-weight: 500; fill: var(--ink); }
2048
2048
  .kit-plot__tick { font-family: var(--label-face); font-size: 10px; letter-spacing: var(--label-space); text-transform: var(--label-case); fill: var(--ink-faint); }
2049
2049
 
2050
+ /* The FIGURE a plot sits in — a card one tonal step down from the panel (glass
2051
+ on the card, like settings' quote), its caption, its live readout, its
2052
+ <details> table twin and its foot. Moved here from profile.css in task
2053
+ 1003446, when /drachmae's treasury became the second consumer; the `.pchart`
2054
+ names are unchanged because they are spelled into profile.js and drachmae.js.
2055
+ The kit does NOT emit these — a page composes its own figure and the kit
2056
+ draws the series inside it. */
2057
+ .pcharts {
2058
+ display: grid;
2059
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2060
+ gap: var(--sp-sm);
2061
+ }
2062
+ .pchart {
2063
+ margin: 0;
2064
+ padding: var(--sp-xs) var(--sp-sm) var(--sp-xs);
2065
+ background: var(--glass);
2066
+ border: var(--line) solid var(--glass-edge);
2067
+ border-radius: var(--radius-sm);
2068
+ min-width: 0;
2069
+ }
2070
+ .pchart__title { margin: 0; font-size: 13.5px; font-weight: 500; color: var(--ink); }
2071
+ .pchart__read { margin: 2px 0 var(--sp-hair); font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-soft); min-height: 1.4em; }
2072
+ .pchart__twin { margin-top: var(--sp-hair); }
2073
+ .pchart__twin > summary { display: inline-flex; align-items: center; min-height: 24px; padding: 0 4px; margin: 0 -4px; font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-soft); cursor: pointer; border-radius: var(--radius-sm); }
2074
+ .pchart__twin > summary:hover { color: var(--ink); background: var(--glass-strong); }
2075
+ .pchart__table { margin-top: var(--sp-hair); font-size: 12.5px; }
2076
+ .pchart__table td, .pchart__table th { padding: 3px 6px; }
2077
+ .pchart__foot { margin: var(--sp-hair) 0 0; font-size: 12px; color: var(--ink-faint); }
2078
+ .pchart__note { margin: 0; font-size: 14px; color: var(--ink-soft); }
2079
+ /* The bar series inside a figure keeps the figure's own vertical air. */
2080
+ .pchart .kit-bars { margin: var(--sp-hair) 0; }
2081
+ @media (max-width: 720px) {
2082
+ .pcharts { grid-template-columns: minmax(0, 1fr); }
2083
+ }
2084
+
2050
2085
  .curve__kinds { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; }
2051
2086
 
2052
2087
  /* ---------- onboarding checklist ---------- */
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.516",
3
+ "version": "1.19.517",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.516",
9
+ "version": "1.19.517",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.516",
3
+ "version": "1.19.517",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
package/src/module-api.js CHANGED
@@ -45,7 +45,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
45
45
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
46
46
  // the entry to that file. Look for a version's history there, not here.
47
47
  // ---------------------------------------------------------------------------
48
- const CORE_VERSION = '1.19.516'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
48
+ const CORE_VERSION = '1.19.517'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
49
49
 
50
50
  // A namespaced logger so a module's log lines are attributable + consistent.
51
51
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -76,6 +76,55 @@ test('cumulate is a running total that survives holes', () => {
76
76
  assert.deepEqual(kit.cumulate([]), []);
77
77
  });
78
78
 
79
+ test('cumulateByDay groups a day, cumulates it, and drops what will not parse', () => {
80
+ // The third caller of this shape put it in the kit (task 1003446): the
81
+ // profile's two lines and /drachmae's treasury.
82
+ const pts = kit.cumulateByDay([
83
+ { day: '2026-05-02T10:00:00Z', v: 1 },
84
+ { day: '2026-05-02T22:00:00Z', v: 2 }, // same day → one point
85
+ { day: '2026-05-04T00:00:00Z', v: 4 },
86
+ ], { valueOf: (r) => r.v });
87
+ assert.deepEqual(pts.map((p) => p.value), [3, 7], 'summed within a day, cumulative across');
88
+ assert.equal(pts.length, 2, 'a day with no row is not invented');
89
+ assert.ok(pts[0].x < pts[1].x, 'ascending');
90
+ });
91
+
92
+ test('an unparsable date is dropped, not counted at the epoch', () => {
93
+ // Counted at 0 it would stretch the axis back to 1970 and flatten every real
94
+ // point onto the floor — the failure mode is a chart, not an exception.
95
+ const pts = kit.cumulateByDay([
96
+ { day: 'not a date', v: 99 },
97
+ { day: '2026-05-02T10:00:00Z', v: 5 },
98
+ ], { valueOf: (r) => r.v });
99
+ assert.deepEqual(pts.map((p) => p.value), [5]);
100
+ assert.ok(pts[0].x > Date.parse('2020-01-01'), 'and the axis starts in this decade');
101
+ });
102
+
103
+ test('cumulateByDay reads the date column the caller names', () => {
104
+ // shipped_at on an activity row, recorded_at on a credits row, day on a
105
+ // gap-filled feed — only the caller knows which.
106
+ const pts = kit.cumulateByDay(
107
+ [{ recorded_at: '2026-05-02T10:00:00Z', delta: 8 }],
108
+ { dateOf: (r) => r.recorded_at, valueOf: (r) => r.delta },
109
+ );
110
+ assert.deepEqual(pts.map((p) => p.value), [8]);
111
+ assert.deepEqual(kit.cumulateByDay(null), [], 'and no rows is no points');
112
+ });
113
+
114
+ test('a plot with no readout ships no focus stop and no dead crosshair', () => {
115
+ // A role="img" with tabindex="0" is a focus stop a keyboard reader lands on
116
+ // with nothing to do; a crosshair nothing moves only looks like a feature.
117
+ const inert = kit.plotSvgHtml({ series: '<path/>', ariaLabel: 'Spent', interactive: false });
118
+ assert.doesNotMatch(inert, /tabindex/);
119
+ assert.doesNotMatch(inert, /kit-plot__hair/);
120
+ assert.match(inert, /aria-label="Spent"/, 'the label is still the reading');
121
+ assert.match(inert, /kit-plot__axis/, 'and the axis is still drawn');
122
+ // The default stays interactive, because the profile wires a readout.
123
+ const live = kit.plotSvgHtml({ series: '<path/>' });
124
+ assert.match(live, /tabindex="0"/);
125
+ assert.match(live, /kit-plot__hair/);
126
+ });
127
+
79
128
  test('the line is scaled to its own min and max, and a flat series sits mid-box', () => {
80
129
  const g = kit.linePoints([0, 10], { width: 100, height: 50, pad: 0 });
81
130
  const ys = g.points.split(' ').map((p) => Number(p.split(',')[1]));
@@ -220,3 +220,132 @@ test('the reference renders exactly one h1, the page head\'s', () => {
220
220
  assert.equal([...html.matchAll(/<h1\b/g)].length, 1);
221
221
  assert.match(html, /<h1 class="page-head__h">/);
222
222
  });
223
+
224
+ // ---- 9. the treasury section (task 1003446) --------------------------------
225
+ //
226
+ // The sharpest version of goal 1000072's thesis. /drachmae is labelled Economy
227
+ // in the nav and was a markdown reader with ZERO live numbers, while the
228
+ // signed-out status page already drew this very endpoint's cumulative line and
229
+ // the Work board fetched the same payload to read ONE scalar off it. The
230
+ // stranger got the chart; the builder got the number.
231
+ //
232
+ // The section's ARITHMETIC is not asserted here — it is the kit's
233
+ // (`cumulateByDay`, `timeStepSeriesHtml`, `barSeriesHtml`) and
234
+ // tests/hall_kit_charts.mjs runs the real kit against it. What these hold is
235
+ // the page's half of the contract, the half a kit test cannot see.
236
+
237
+ const costSummaryFixture = JSON.parse(
238
+ fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'fixtures', 'cost-summary.json'), 'utf8'),
239
+ );
240
+
241
+ test('the treasury reads the endpoint that already carried the series — no new route', () => {
242
+ // The whole point of the task: the payload had both series all along, and the
243
+ // route's own comment calls daily[] "the treasury chart's cumulative line".
244
+ assert.match(drachmaeJs, /TREASURY_URL = '\/api\/gds\/public\/cost-summary'/);
245
+ assert.ok(costSummaryFixture.daily.length, 'the payload really carries daily[]');
246
+ assert.ok(costSummaryFixture.daily_credits.length, 'and daily_credits[]');
247
+ assert.ok(Number(costSummaryFixture.total_usd) > 0, 'and the totals the prose states');
248
+ assert.ok(Number(costSummaryFixture.total_value_credits) > 0);
249
+ });
250
+
251
+ test('RUN: the cumulative line ends exactly on the total the prose states', async () => {
252
+ // The invariant that matters, and the one a source-text assertion cannot
253
+ // reach: the chart and the sentence above it are derived from the SAME
254
+ // payload, so they can never disagree. Run against the real kit and the real
255
+ // checked-in summary, which is where a units or gap-fill mistake would show
256
+ // up as a line ending somewhere other than the headline number.
257
+ const kitPath = path.join(HALL, 'hall-kit.js');
258
+ const kit = await import(`file://${kitPath.replace(/\\/g, '/')}`).then((m) => m.default);
259
+ const spend = kit.cumulateByDay(costSummaryFixture.daily, { valueOf: (r) => r.amount_usd });
260
+ const earned = kit.cumulateByDay(costSummaryFixture.daily_credits, { valueOf: (r) => r.credits });
261
+ assert.ok(spend.length > 1 && earned.length > 1, 'both series have depth');
262
+ assert.ok(Math.abs(spend[spend.length - 1].value - Number(costSummaryFixture.total_usd)) < 1,
263
+ 'the spend line ends on total_usd');
264
+ assert.equal(earned[earned.length - 1].value, Number(costSummaryFixture.total_value_credits),
265
+ 'and the awarded line ends on total_value_credits');
266
+ // Whatever the plot draws from that must be finite — a NaN in a path `d`
267
+ // renders as an invisible chart with no error anywhere.
268
+ const series = kit.timeStepSeriesHtml(spend, {
269
+ x0: spend[0].x, x1: spend[spend.length - 1].x, yMax: spend[spend.length - 1].value,
270
+ });
271
+ assert.doesNotMatch(series.body, /NaN|Infinity/);
272
+ assert.doesNotMatch(
273
+ kit.barSeriesHtml(costSummaryFixture.by_category.map((c) => ({ label: c.category, value: c.total_usd }))),
274
+ /NaN|Infinity/,
275
+ );
276
+ });
277
+
278
+ test('the page loads the chart primitive BEFORE the script that reads it', () => {
279
+ // window.OTBKit read at insert time with the kit loaded after it is an empty
280
+ // section in production and a green unit suite.
281
+ const loaded = [...drachmaeHtml.matchAll(/<script[^>]+src="([^"?]+)/g)].map((m) => m[1].split('/').pop());
282
+ const kitAt = loaded.indexOf('hall-kit.js');
283
+ const pageAt = loaded.indexOf('drachmae.js');
284
+ assert.ok(kitAt > -1, 'drachmae.html must load hall-kit.js');
285
+ assert.ok(pageAt > -1, 'and drachmae.js');
286
+ assert.ok(kitAt < pageAt, 'kit first');
287
+ });
288
+
289
+ test('every mark on the page is drawn by the kit, none by hand', () => {
290
+ const js = dejscomment(drachmaeJs);
291
+ for (const fn of ['cumulateByDay', 'timeStepSeriesHtml', 'plotSvgHtml', 'plotValueLabelHtml', 'barSeriesHtml']) {
292
+ assert.match(js, new RegExp(`(kit|window\\.OTBKit)\\.${fn}\\(`), `the treasury draws through ${fn}`);
293
+ }
294
+ // The §6b audit rule holds this globally; asserted here too because THIS is
295
+ // the page that had no chart at all and so the easiest place to hand-roll one.
296
+ assert.doesNotMatch(js, /<(?:path|polyline|rect)\b[^>]*\b(?:d|points)="\$\{/,
297
+ 'no hand-built SVG geometry on the reference page');
298
+ });
299
+
300
+ test('the section degrades to NOTHING rather than an empty panel', () => {
301
+ const js = dejscomment(drachmaeJs);
302
+ // The bounty section's contract, kept: a failed read leaves the prose alone
303
+ // and adds no outline entry that leads nowhere.
304
+ const fn = js.slice(js.indexOf('async function insertTreasurySection'));
305
+ const body = fn.slice(0, fn.indexOf('\n }\n'));
306
+ assert.match(body, /console\.warn\([^)]*cost summary unavailable/);
307
+ assert.equal((body.match(/return false;/g) || []).length, 3,
308
+ 'three honest exits: no kit, an unreadable summary, and a treasury with nothing in it');
309
+ assert.match(body, /if \(!spend\.length && !earned\.length\) return false;/,
310
+ 'both series empty is one honest nothing, not two empty plots');
311
+ });
312
+
313
+ test('the plots ship no focus stop, because nothing here wires a readout', () => {
314
+ // A role="img" with tabindex="0" is a focus stop a keyboard reader lands on
315
+ // with nothing to do, and a crosshair nothing moves only looks like a
316
+ // feature. The kit half is asserted in tests/hall_kit_charts.mjs.
317
+ assert.match(dejscomment(drachmaeJs), /interactive: false/);
318
+ });
319
+
320
+ test('the currency is the branding pack\'s, never a hardcoded word', () => {
321
+ const js = dejscomment(drachmaeJs);
322
+ assert.match(js, /__BRANDING__ && window\.__BRANDING__\.currency/);
323
+ assert.match(js, /\|\| 'credits'/, 'with a neutral fallback, not a world-specific one');
324
+ assert.doesNotMatch(js.replace(/\|\| 'credits'/g, ''), /drachmae'/i,
325
+ 'the instance\'s own currency name is not spelled into the core page');
326
+ });
327
+
328
+ test('the treasury lands ahead of the rate card, so the doc still ends on "dig deeper"', () => {
329
+ const js = dejscomment(drachmaeJs);
330
+ // The bounty section goes in first and the treasury anchors on its id, so the
331
+ // order is prose → what happened → the published rates → the closing links.
332
+ const boot = js.slice(js.indexOf('async function boot()'));
333
+ assert.ok(boot.indexOf('insertBountySection(article)') < boot.indexOf('insertTreasurySection(article)'),
334
+ 'the rate card is inserted first, so the treasury can anchor ahead of it');
335
+ assert.match(js, /article\.querySelector\(`#\$\{BOUNTY_SECTION_ID\}`\)/);
336
+ assert.match(js, /CLOSING_H2\.test/, 'and it falls back to the closing links, then to appending');
337
+ });
338
+
339
+ test('the figure chrome is shared, because a second page now draws figures', () => {
340
+ // .pchart* moved from profile.css to style.css with this task. drachmae.html
341
+ // loads style.css and NOT profile.css, so a copy left behind would render
342
+ // this page's figures unstyled.
343
+ const style = decomment(read('style.css'));
344
+ const profile = decomment(read('profile.css'));
345
+ for (const sel of ['.pcharts', '.pchart ', '.pchart__title', '.pchart__twin', '.pchart__foot']) {
346
+ assert.ok(style.includes(sel), `${sel} is in the shared sheet`);
347
+ }
348
+ assert.ok(!/^\.pcharts\s*\{/m.test(profile), 'and not left duplicated in profile.css');
349
+ assert.match(drachmaeHtml, /style\.css/);
350
+ assert.doesNotMatch(drachmaeHtml, /profile\.css/, 'the reference page must not need the profile sheet');
351
+ });