@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 +19 -19
- package/docs/copy-inventory.md +14 -12
- package/docs/copy-registry.json +33 -15
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/drachmae.html +3 -0
- package/modules/hall-ui/public/drachmae.js +138 -2
- package/modules/hall-ui/public/hall-kit.js +45 -7
- package/modules/hall-ui/public/profile-charts.js +6 -13
- package/modules/hall-ui/public/profile.css +6 -35
- package/modules/hall-ui/public/style.css +35 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/hall_kit_charts.mjs +49 -0
- package/tests/hall_reference_world.mjs +129 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
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-
|
|
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": "
|
|
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": "
|
|
1750
|
+
"sha256": "7fe7f7c6fd69f4345dda192ef8bc4c5f57b71337a43dcfacb502286bc3dabbbe"
|
|
1751
1751
|
},
|
|
1752
1752
|
{
|
|
1753
1753
|
"path": "docs/copy-registry.json",
|
|
1754
1754
|
"mode": "0000644",
|
|
1755
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
4625
|
+
"sha256": "89bd28f00b4f4849b8789f8d5b4d5eb50705fddeb65f1675bb0d88fc43fd1859"
|
|
4626
4626
|
},
|
|
4627
4627
|
{
|
|
4628
4628
|
"path": "modules/hall-ui/public/drachmae.js",
|
|
4629
4629
|
"mode": "0000644",
|
|
4630
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
7295
|
+
"sha256": "58709e2e4ec44db5a5364697716bf3b6627d7bde82bb2dec1b421137b2ce3d7a"
|
|
7296
7296
|
},
|
|
7297
7297
|
{
|
|
7298
7298
|
"path": "package.json",
|
|
7299
7299
|
"mode": "0000644",
|
|
7300
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
10420
|
+
"sha256": "fac14ddf85824d34c0bb2d5b81417c981611d6257b92bc801ee6003b6975683a"
|
|
10421
10421
|
},
|
|
10422
10422
|
{
|
|
10423
10423
|
"path": "tests/hall_roster_world.mjs",
|
package/docs/copy-inventory.md
CHANGED
|
@@ -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
|
-
**
|
|
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 |
|
|
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` —
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1144
|
-
| `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:
|
|
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` |
|
package/docs/copy-registry.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"generator": "scripts/gds/copy-inventory.js",
|
|
4
4
|
"goal": 1000074,
|
|
5
5
|
"counts": {
|
|
6
|
-
"strings":
|
|
7
|
-
"unique_ids":
|
|
8
|
-
"certain":
|
|
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":
|
|
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:
|
|
1482
|
-
"modules/hall-ui/public/profile-charts.js:
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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
|
|
280
|
-
// scrollspy
|
|
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
|
|
871
|
-
//
|
|
872
|
-
//
|
|
873
|
-
//
|
|
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"
|
|
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
|
-
+
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
});
|