@bongos/core 1.19.489 → 1.19.491
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 +16 -16
- package/docs/copy-inventory.md +12 -11
- package/docs/copy-registry.json +23 -14
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/profile-charts.js +88 -25
- package/modules/hall-ui/public/profile.js +3 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/hall-preview/refresh-fixtures.js +6 -0
- package/scripts/hall-preview/server.js +3 -0
- package/src/module-api.js +1 -1
- package/tests/hall_profile_world.mjs +106 -11
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.491",
|
|
6
|
+
"core_contract": "1.19.491",
|
|
7
|
+
"source_commit": "7c6f9058ca961e49439235e4aec6dd39a3ffe68a",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-02T22:49:35.926Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 422,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2301,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "f8d28b162d783a50b0d62ba88bf1ac779f94f646c0b53e4d58b7553bc1385804",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -1707,12 +1707,12 @@
|
|
|
1707
1707
|
{
|
|
1708
1708
|
"path": "docs/copy-inventory.md",
|
|
1709
1709
|
"mode": "0000644",
|
|
1710
|
-
"sha256": "
|
|
1710
|
+
"sha256": "86eb1bbcd8457069f957c6d46244481450e177ead7dff06bfb4db0e81eab7ee3"
|
|
1711
1711
|
},
|
|
1712
1712
|
{
|
|
1713
1713
|
"path": "docs/copy-registry.json",
|
|
1714
1714
|
"mode": "0000644",
|
|
1715
|
-
"sha256": "
|
|
1715
|
+
"sha256": "0fe9dc2df8e22113b9d5ae7d18b762be3e0f05d546989905e6ba2cd37c72049b"
|
|
1716
1716
|
},
|
|
1717
1717
|
{
|
|
1718
1718
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2162,7 +2162,7 @@
|
|
|
2162
2162
|
{
|
|
2163
2163
|
"path": "docs/module-api-changelog.md",
|
|
2164
2164
|
"mode": "0000644",
|
|
2165
|
-
"sha256": "
|
|
2165
|
+
"sha256": "946a1c2234ae65a5474c6c60871c5c82e0ca9e3726cf02f099b7c43fa3c22c02"
|
|
2166
2166
|
},
|
|
2167
2167
|
{
|
|
2168
2168
|
"path": "docs/modules-contract.md",
|
|
@@ -4472,7 +4472,7 @@
|
|
|
4472
4472
|
{
|
|
4473
4473
|
"path": "modules/hall-ui/public/profile-charts.js",
|
|
4474
4474
|
"mode": "0000644",
|
|
4475
|
-
"sha256": "
|
|
4475
|
+
"sha256": "865df71a66dd944006b01dfb1317778a1d733d9320b4486efd8ee1ed3ea76f19"
|
|
4476
4476
|
},
|
|
4477
4477
|
{
|
|
4478
4478
|
"path": "modules/hall-ui/public/profile-connections.js",
|
|
@@ -4497,7 +4497,7 @@
|
|
|
4497
4497
|
{
|
|
4498
4498
|
"path": "modules/hall-ui/public/profile.js",
|
|
4499
4499
|
"mode": "0000644",
|
|
4500
|
-
"sha256": "
|
|
4500
|
+
"sha256": "15d0f56fa577161ed5ad78ae4117e6e8324f9b35cf7f2ffff51d8fc7bed4e382"
|
|
4501
4501
|
},
|
|
4502
4502
|
{
|
|
4503
4503
|
"path": "modules/hall-ui/public/profile.states.json",
|
|
@@ -6797,12 +6797,12 @@
|
|
|
6797
6797
|
{
|
|
6798
6798
|
"path": "package-lock.json",
|
|
6799
6799
|
"mode": "0000644",
|
|
6800
|
-
"sha256": "
|
|
6800
|
+
"sha256": "ca75c416a0104e2e57e5b9c45d2b74b1c6ddbf5de524ff487df095134634b53d"
|
|
6801
6801
|
},
|
|
6802
6802
|
{
|
|
6803
6803
|
"path": "package.json",
|
|
6804
6804
|
"mode": "0000644",
|
|
6805
|
-
"sha256": "
|
|
6805
|
+
"sha256": "91a2d397dca70394d2ca4d7f40e942f18672449781d8f08446ab8fdd8ddd9437"
|
|
6806
6806
|
},
|
|
6807
6807
|
{
|
|
6808
6808
|
"path": "public-docs/index.html",
|
|
@@ -8087,12 +8087,12 @@
|
|
|
8087
8087
|
{
|
|
8088
8088
|
"path": "scripts/hall-preview/refresh-fixtures.js",
|
|
8089
8089
|
"mode": "0000644",
|
|
8090
|
-
"sha256": "
|
|
8090
|
+
"sha256": "7f4c827ec125a719686f71b0d8abf5177062fef9d06b78fabe2993871f69c964"
|
|
8091
8091
|
},
|
|
8092
8092
|
{
|
|
8093
8093
|
"path": "scripts/hall-preview/server.js",
|
|
8094
8094
|
"mode": "0000644",
|
|
8095
|
-
"sha256": "
|
|
8095
|
+
"sha256": "8f2b9f348d7372db1a5c9be3cdd9c14d48f20884b3f816b664ff3e590fa3b951"
|
|
8096
8096
|
},
|
|
8097
8097
|
{
|
|
8098
8098
|
"path": "scripts/migrate.sh",
|
|
@@ -8462,7 +8462,7 @@
|
|
|
8462
8462
|
{
|
|
8463
8463
|
"path": "src/module-api.js",
|
|
8464
8464
|
"mode": "0000644",
|
|
8465
|
-
"sha256": "
|
|
8465
|
+
"sha256": "ad32edff429153aa8a1231a90be444f349bdbcd38b4940011a10f41c86b2e2fb"
|
|
8466
8466
|
},
|
|
8467
8467
|
{
|
|
8468
8468
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9887,7 +9887,7 @@
|
|
|
9887
9887
|
{
|
|
9888
9888
|
"path": "tests/hall_profile_world.mjs",
|
|
9889
9889
|
"mode": "0000644",
|
|
9890
|
-
"sha256": "
|
|
9890
|
+
"sha256": "9e5047b39d55875e167c992157c6c04a9a766ec331c1a3bf343fe7ea194a8309"
|
|
9891
9891
|
},
|
|
9892
9892
|
{
|
|
9893
9893
|
"path": "tests/hall_reading_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
|
+
**1590 strings** across 3 surfaces · 1381 distinct pieces of copy · 1590 certain, 0 heuristic · 150 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 | 1147 |
|
|
14
14
|
| `status-dashboard` | `modules/status-ui/public` | 4 | 98 |
|
|
15
15
|
| `landing` | `modules/public-landing/public` | 5 | 345 |
|
|
16
16
|
|
|
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
78
78
|
| `563d29ed86d8` | 2x | Bulk actions | modules/hall-ui/public/goals.html:172, modules/hall-ui/public/work.html:129 |
|
|
79
79
|
| … | | _110 more in the JSON registry_ | |
|
|
80
80
|
|
|
81
|
-
## `builders-hall` —
|
|
81
|
+
## `builders-hall` — 1147 strings
|
|
82
82
|
|
|
83
83
|
| id | conf | text | where |
|
|
84
84
|
|---|---|---|---|
|
|
@@ -284,7 +284,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
284
284
|
| `7775fec0fa85` | certain | Could not load sessions. | `modules/hall-ui/public/sessions.js:187` |
|
|
285
285
|
| `7775fec0fa85` | certain | Could not load sessions. | `modules/hall-ui/public/sessions.js:226` |
|
|
286
286
|
| `b2147cc90e36` | certain | Could not load the idea template. Reload, or file from the terminal with capture.js. | `modules/hall-ui/public/ideas.js:438` |
|
|
287
|
-
| `7604af13c40e` | certain | Could not load the profile. | `modules/hall-ui/public/profile.js:
|
|
287
|
+
| `7604af13c40e` | certain | Could not load the profile. | `modules/hall-ui/public/profile.js:759` |
|
|
288
288
|
| `8b69e445592a` | certain | Could not load the question labels; showing the answers as stored. | `modules/hall-ui/public/idea.js:244` |
|
|
289
289
|
| `edd5bafbe075` | certain | Could not load the queue just now. | `modules/hall-ui/public/ideas.js:387` |
|
|
290
290
|
| `02bb0570996a` | certain | Could not load the ranks. | `modules/hall-ui/public/ranks.js:197` |
|
|
@@ -533,7 +533,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
533
533
|
| `7a239a17afc8` | certain | Loading your preferences… | `modules/hall-ui/public/settings.html:32` |
|
|
534
534
|
| `04df1bd30129` | certain | Loading your rank… | `modules/hall-ui/public/ranks.html:30` |
|
|
535
535
|
| `c27711f9eb4d` | certain | Locked until you reach the end of the article above. | `modules/hall-ui/public/primer.html:103` |
|
|
536
|
-
| `4017ee030bc2` | certain | Lookback window | `modules/hall-ui/public/profile-charts.js:
|
|
536
|
+
| `4017ee030bc2` | certain | Lookback window | `modules/hall-ui/public/profile-charts.js:410` |
|
|
537
537
|
| `a93ca2841b96` | certain | Mac (Intel) | `modules/hall-ui/public/hall-render.js:744` |
|
|
538
538
|
| `06b4ede3fb58` | certain | Machine principals | `modules/hall-ui/public/government.html:124` |
|
|
539
539
|
| `6b3d660e0e31` | certain | Map view | `modules/hall-ui/public/atlas.html:46` |
|
|
@@ -1004,7 +1004,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1004
1004
|
| `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` |
|
|
1005
1005
|
| `bc960f460250` | certain | and install dependencies ( | `modules/hall-ui/public/hall-render.js:785` |
|
|
1006
1006
|
| `b867876e8b6c` | certain | and press Enter. | `modules/hall-ui/public/hall-render.js:744` |
|
|
1007
|
-
| `b4033000c537` | certain | as a table | `modules/hall-ui/public/profile-charts.js:
|
|
1007
|
+
| `b4033000c537` | certain | as a table | `modules/hall-ui/public/profile-charts.js:204` |
|
|
1008
1008
|
| `d7c511423bc0` | certain | as configuration: {…} | `modules/hall-ui/public/government.js:707` |
|
|
1009
1009
|
| `09db3fd4355b` | certain | asked by {…} | `modules/hall-ui/public/collab.js:69` |
|
|
1010
1010
|
| `63bd14f73960` | certain | author trusted | `modules/hall-ui/public/gate.js:141` |
|
|
@@ -1081,6 +1081,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1081
1081
|
| `3916a3dc8e98` | certain | not started | `modules/hall-ui/public/roadmap.js:108` |
|
|
1082
1082
|
| `3916a3dc8e98` | certain | not started | `modules/hall-ui/public/roadmap.js:263` |
|
|
1083
1083
|
| `b5567b42fa52` | certain | not yet | `modules/hall-ui/public/not-ready.html:25` |
|
|
1084
|
+
| `8ca0a499ec86` | certain | nothing recorded yet · {…} | `modules/hall-ui/public/profile-charts.js:222` |
|
|
1084
1085
|
| `e2c643fbf4d6` | certain | npm install | `modules/hall-ui/public/hall-render.js:785` |
|
|
1085
1086
|
| `e90b5601ce16` | certain | objected to | `modules/hall-ui/public/government.js:824` |
|
|
1086
1087
|
| `351d56b80a6c` | certain | of all in-flight tasks shipped | `modules/hall-ui/public/roadmap.js:273` |
|
|
@@ -1107,8 +1108,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1107
1108
|
| `9bec9eb4829e` | certain | scope: {…} | `modules/hall-ui/public/goals-render.js:774` |
|
|
1108
1109
|
| `a6f55d7aa6f7` | certain | shipped (live) | `modules/hall-ui/public/roadmap.js:273` |
|
|
1109
1110
|
| `026ba1289d4d` | certain | shipped · | `modules/hall-ui/public/goals-render.js:696` |
|
|
1110
|
-
| `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:
|
|
1111
|
-
| `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:
|
|
1111
|
+
| `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:472` |
|
|
1112
|
+
| `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:484` |
|
|
1112
1113
|
| `0215b2cdca8c` | certain | submit for review | `modules/hall-ui/public/modules.js:132` |
|
|
1113
1114
|
| `da30b952a60b` | certain | system / unattributed | `modules/hall-ui/public/goals-page.js:249` |
|
|
1114
1115
|
| `da30b952a60b` | certain | system / unattributed | `modules/hall-ui/public/work.js:498` |
|
|
@@ -1121,7 +1122,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1121
1122
|
| `bd3402efa59e` | certain | task{…} shipped | `modules/hall-ui/public/goals-render.js:737` |
|
|
1122
1123
|
| `95854f8315ca` | certain | the page everyone else reads | `modules/hall-ui/public/profile.html:32` |
|
|
1123
1124
|
| `632527db5bca` | certain | this session | `modules/hall-ui/public/settings-sessions.js:44` |
|
|
1124
|
-
| `130c3731ad83` | certain | this week | `modules/hall-ui/public/profile-charts.js:
|
|
1125
|
+
| `130c3731ad83` | certain | this week | `modules/hall-ui/public/profile-charts.js:298` |
|
|
1125
1126
|
| `ee431f143549` | certain | three tasks | `modules/hall-ui/public/not-ready.html:33` |
|
|
1126
1127
|
| `d70c287bcd7f` | certain | to authenticate this machine, then | `modules/hall-ui/public/hall-render.js:785` |
|
|
1127
1128
|
| `65702f92a8c8` | certain | to claim your first task. | `modules/hall-ui/public/hall-render.js:785` |
|
|
@@ -1190,8 +1191,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1190
1191
|
| `2a2728a4ec4f` | certain | {…}% of tasks complete | `modules/hall-ui/public/roadmap.js:70` |
|
|
1191
1192
|
| `15e545eb3ce2` | certain | {…}, no such task | `modules/hall-ui/public/task.js:505` |
|
|
1192
1193
|
| `c43050b62297` | certain | {…}/100 complete | `modules/hall-ui/public/idea.js:87` |
|
|
1193
|
-
| `afea9a3a8e9e` | certain | {…}: {…} as of today; arrow keys walk the days | `modules/hall-ui/public/profile-charts.js:
|
|
1194
|
-
| `b3d764eaf258` | certain | {…}: {…} in {…} weeks; arrow keys walk the weeks | `modules/hall-ui/public/profile-charts.js:
|
|
1194
|
+
| `afea9a3a8e9e` | certain | {…}: {…} as of today; arrow keys walk the days | `modules/hall-ui/public/profile-charts.js:248` |
|
|
1195
|
+
| `b3d764eaf258` | certain | {…}: {…} in {…} weeks; arrow keys walk the weeks | `modules/hall-ui/public/profile-charts.js:298` |
|
|
1195
1196
|
| `8e8c7da4f7f5` | certain | {…}criteria met · awaiting review | `modules/hall-ui/public/goals-render.js:155` |
|
|
1196
1197
|
| `1e275ee0299c` | certain | {…}s only | `modules/hall-ui/public/government.js:1121` |
|
|
1197
1198
|
| `499916045b19` | certain | {…}{…} asks to join | `modules/hall-ui/public/goal-inbox.js:69` |
|
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": 1590,
|
|
7
|
+
"unique_ids": 1381,
|
|
8
|
+
"certain": 1590,
|
|
9
9
|
"heuristic": 0,
|
|
10
10
|
"duplicate_ids": 150
|
|
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": 1147
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
19
19
|
"at": "modules/status-ui/public",
|
|
@@ -1443,8 +1443,8 @@
|
|
|
1443
1443
|
"text": "sign in",
|
|
1444
1444
|
"occurrences": 2,
|
|
1445
1445
|
"at": [
|
|
1446
|
-
"modules/hall-ui/public/profile-charts.js:
|
|
1447
|
-
"modules/hall-ui/public/profile-charts.js:
|
|
1446
|
+
"modules/hall-ui/public/profile-charts.js:472",
|
|
1447
|
+
"modules/hall-ui/public/profile-charts.js:484"
|
|
1448
1448
|
]
|
|
1449
1449
|
},
|
|
1450
1450
|
{
|
|
@@ -3422,7 +3422,7 @@
|
|
|
3422
3422
|
"surface": "builders-hall",
|
|
3423
3423
|
"text": "Could not load the profile.",
|
|
3424
3424
|
"file": "modules/hall-ui/public/profile.js",
|
|
3425
|
-
"line":
|
|
3425
|
+
"line": 759,
|
|
3426
3426
|
"origin": "js-notice",
|
|
3427
3427
|
"confidence": "certain"
|
|
3428
3428
|
},
|
|
@@ -5663,7 +5663,7 @@
|
|
|
5663
5663
|
"surface": "builders-hall",
|
|
5664
5664
|
"text": "Lookback window",
|
|
5665
5665
|
"file": "modules/hall-ui/public/profile-charts.js",
|
|
5666
|
-
"line":
|
|
5666
|
+
"line": 410,
|
|
5667
5667
|
"origin": "js-markup",
|
|
5668
5668
|
"confidence": "certain"
|
|
5669
5669
|
},
|
|
@@ -9902,7 +9902,7 @@
|
|
|
9902
9902
|
"surface": "builders-hall",
|
|
9903
9903
|
"text": "as a table",
|
|
9904
9904
|
"file": "modules/hall-ui/public/profile-charts.js",
|
|
9905
|
-
"line":
|
|
9905
|
+
"line": 204,
|
|
9906
9906
|
"origin": "js-markup",
|
|
9907
9907
|
"confidence": "certain"
|
|
9908
9908
|
},
|
|
@@ -10590,6 +10590,15 @@
|
|
|
10590
10590
|
"origin": "html-text",
|
|
10591
10591
|
"confidence": "certain"
|
|
10592
10592
|
},
|
|
10593
|
+
{
|
|
10594
|
+
"id": "8ca0a499ec86",
|
|
10595
|
+
"surface": "builders-hall",
|
|
10596
|
+
"text": "nothing recorded yet · {…}",
|
|
10597
|
+
"file": "modules/hall-ui/public/profile-charts.js",
|
|
10598
|
+
"line": 222,
|
|
10599
|
+
"origin": "js-markup",
|
|
10600
|
+
"confidence": "certain"
|
|
10601
|
+
},
|
|
10593
10602
|
{
|
|
10594
10603
|
"id": "e2c643fbf4d6",
|
|
10595
10604
|
"surface": "builders-hall",
|
|
@@ -10829,7 +10838,7 @@
|
|
|
10829
10838
|
"surface": "builders-hall",
|
|
10830
10839
|
"text": "sign in",
|
|
10831
10840
|
"file": "modules/hall-ui/public/profile-charts.js",
|
|
10832
|
-
"line":
|
|
10841
|
+
"line": 472,
|
|
10833
10842
|
"origin": "js-markup",
|
|
10834
10843
|
"confidence": "certain"
|
|
10835
10844
|
},
|
|
@@ -10838,7 +10847,7 @@
|
|
|
10838
10847
|
"surface": "builders-hall",
|
|
10839
10848
|
"text": "sign in",
|
|
10840
10849
|
"file": "modules/hall-ui/public/profile-charts.js",
|
|
10841
|
-
"line":
|
|
10850
|
+
"line": 484,
|
|
10842
10851
|
"origin": "js-markup",
|
|
10843
10852
|
"confidence": "certain"
|
|
10844
10853
|
},
|
|
@@ -10955,7 +10964,7 @@
|
|
|
10955
10964
|
"surface": "builders-hall",
|
|
10956
10965
|
"text": "this week",
|
|
10957
10966
|
"file": "modules/hall-ui/public/profile-charts.js",
|
|
10958
|
-
"line":
|
|
10967
|
+
"line": 298,
|
|
10959
10968
|
"origin": "js-markup",
|
|
10960
10969
|
"confidence": "certain"
|
|
10961
10970
|
},
|
|
@@ -11576,7 +11585,7 @@
|
|
|
11576
11585
|
"surface": "builders-hall",
|
|
11577
11586
|
"text": "{…}: {…} as of today; arrow keys walk the days",
|
|
11578
11587
|
"file": "modules/hall-ui/public/profile-charts.js",
|
|
11579
|
-
"line":
|
|
11588
|
+
"line": 248,
|
|
11580
11589
|
"origin": "js-markup",
|
|
11581
11590
|
"confidence": "certain"
|
|
11582
11591
|
},
|
|
@@ -11585,7 +11594,7 @@
|
|
|
11585
11594
|
"surface": "builders-hall",
|
|
11586
11595
|
"text": "{…}: {…} in {…} weeks; arrow keys walk the weeks",
|
|
11587
11596
|
"file": "modules/hall-ui/public/profile-charts.js",
|
|
11588
|
-
"line":
|
|
11597
|
+
"line": 298,
|
|
11589
11598
|
"origin": "js-markup",
|
|
11590
11599
|
"confidence": "certain"
|
|
11591
11600
|
},
|
|
@@ -1415,5 +1415,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1415
1415
|
landed since 1.19.487 with no explicit bump. run 33686951657. (task 1002620)
|
|
1416
1416
|
1.19.489 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1417
1417
|
landed since 1.19.488 with no explicit bump. run 33687461982. (task 1002620)
|
|
1418
|
+
1.19.490 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1419
|
+
landed since 1.19.489 with no explicit bump. run 33691767867. (task 1002620)
|
|
1420
|
+
1.19.491 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1421
|
+
landed since 1.19.490 with no explicit bump. run 33692191645. (task 1002620)
|
|
1418
1422
|
---------------------------------------------------------------------------
|
|
1419
1423
|
```
|
|
@@ -15,20 +15,30 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Data: GET /builders/:id/activity?section=tasks — any signed-in viewer, any
|
|
17
17
|
// builder; paged, followed to `total` up to MAX_ROWS. Its rows carry
|
|
18
|
-
// shipped_at
|
|
19
|
-
//
|
|
20
|
-
// GET /
|
|
21
|
-
//
|
|
18
|
+
// shipped_at and discipline, so the works and craft series derive client-side.
|
|
19
|
+
// The drachmae series is the CREDITS LEDGER (task 1003491): GET /me/credits on
|
|
20
|
+
// your own profile, GET /builders/:id/credits on another's (builder.roster.read,
|
|
21
|
+
// metic+), each row a `delta` at `recorded_at`, cumulated by day — the curve
|
|
22
|
+
// that ends at the headline balance. On a cost-plus instance (ADR 0146) the
|
|
23
|
+
// per-task award is structurally 0 and the pay is the session token reward,
|
|
24
|
+
// which only the ledger records; a series summed from per-task awards went
|
|
25
|
+
// flat the day per-task payout stopped. A viewer the ledger refuses (403)
|
|
26
|
+
// gets the activity rows' credits_reward ESTIMATE instead, cumulated and
|
|
27
|
+
// labelled so. The ship rate is GET /analytics/builder/:id, own-or-archon; it
|
|
28
|
+
// is asked for only when the viewer IS the builder and renders nothing
|
|
29
|
+
// elsewhere — never a 403 toast.
|
|
22
30
|
//
|
|
23
31
|
// Exposed as window.OTBProfileCharts: render({ api, API, builderId, selfId,
|
|
24
32
|
// signedIn, now? }) plus the pure series builders (cumulativeByDay, craftMix,
|
|
25
|
-
// weeklyBuckets,
|
|
33
|
+
// weeklyBuckets, estimateOf, clipToWindow) so tests can pin the arithmetic
|
|
26
34
|
// without a DOM.
|
|
27
35
|
(() => {
|
|
28
36
|
'use strict';
|
|
29
37
|
|
|
30
38
|
const MAX_ROWS = 500; // five pages: a prolific builder's whole record, bounded
|
|
31
39
|
const PAGE = 100;
|
|
40
|
+
const LEDGER_PAGE = 200; // the ledger route's MAX_LIMIT
|
|
41
|
+
const LEDGER_MAX_ROWS = 2000; // ten pages; the busiest builder's ledger is ~600 rows
|
|
32
42
|
const DAY = 86400000;
|
|
33
43
|
const WEEK = 7 * DAY;
|
|
34
44
|
const MIX_MAX = 7; // bars past this fold into "other" — never an eighth hue
|
|
@@ -72,24 +82,27 @@
|
|
|
72
82
|
|
|
73
83
|
// ---- series ---------------------------------------------------------------
|
|
74
84
|
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
// The ESTIMATE of what a shipped row pays — credits_reward, the figure the
|
|
86
|
+
// task carried before it shipped. The FALLBACK only: the drachmae series is
|
|
87
|
+
// the ledger wherever the viewer may read it, and this stands in, labelled
|
|
88
|
+
// "estimated" (ADR 0146), when the ledger answers 403. Never the per-task
|
|
89
|
+
// award column — on a cost-plus instance it is 0 for every task, and summing
|
|
90
|
+
// it is the flatline this file was fixed for.
|
|
91
|
+
function estimateOf(row) {
|
|
92
|
+
const e = Number(row && row.credits_reward);
|
|
93
|
+
return Number.isFinite(e) ? e : 0;
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
// rows → [{ day, value }] ascending by day, value cumulative over the WHOLE
|
|
86
|
-
// record.
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
97
|
+
// record. dateOf names the row's timestamp: shipped_at for activity rows (the
|
|
98
|
+
// default), recorded_at for ledger rows. Two entries on one day are one
|
|
99
|
+
// point; a row with no parsable date is skipped, not zeroed. The lookback
|
|
100
|
+
// clips this for drawing (clipToWindow) rather than re-accumulating, so the
|
|
101
|
+
// running total stays true.
|
|
102
|
+
function cumulativeByDay(rows, valueOf, dateOf = (r) => r && r.shipped_at) {
|
|
90
103
|
const byDay = new Map();
|
|
91
104
|
for (const r of rows || []) {
|
|
92
|
-
const t = Date.parse(r
|
|
105
|
+
const t = Date.parse(dateOf(r));
|
|
93
106
|
if (!Number.isFinite(t)) continue;
|
|
94
107
|
const d = midnight(t);
|
|
95
108
|
byDay.set(d, (byDay.get(d) || 0) + Number(valueOf(r)) || 0);
|
|
@@ -201,6 +214,18 @@
|
|
|
201
214
|
// then runs flat out to the window's end. The first drawn point enters at its
|
|
202
215
|
// running height (clipToWindow), the last value carries the only direct label.
|
|
203
216
|
function timeChart({ id, title, points, valueText, footer, now, windowStart }) {
|
|
217
|
+
// A series with nothing in it (a ledger no drachma has reached yet) is one
|
|
218
|
+
// honest line under the title, not a crash on points[0].
|
|
219
|
+
if (!points.length) {
|
|
220
|
+
return {
|
|
221
|
+
id,
|
|
222
|
+
html: `<figure class="pchart" id="${esc(id)}">
|
|
223
|
+
<figcaption class="pchart__title">${esc(title)}</figcaption>
|
|
224
|
+
<p class="pchart__foot">nothing recorded yet · ${esc(footer)}</p>
|
|
225
|
+
</figure>`,
|
|
226
|
+
wire() {},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
204
229
|
const today = midnight(now);
|
|
205
230
|
const x0 = windowStart != null ? windowStart : points[0].day;
|
|
206
231
|
const x1 = Math.max(today, x0 + WEEK);
|
|
@@ -335,6 +360,35 @@
|
|
|
335
360
|
return { signedOut: false, rows };
|
|
336
361
|
}
|
|
337
362
|
|
|
363
|
+
// The credits ledger, first page to `total`, bounded: /me/credits on your own
|
|
364
|
+
// profile, /builders/:id/credits on another's. The SERVER decides who may
|
|
365
|
+
// read another builder's ledger (builder.roster.read is a delegable atom, so
|
|
366
|
+
// no rank is hard-coded here): a refusal comes back as `denied` and the
|
|
367
|
+
// caller falls back to the estimate rather than the section failing. Any
|
|
368
|
+
// other failure reads the same way — a ledger the page could not read is
|
|
369
|
+
// still a ledger it must not pretend to have.
|
|
370
|
+
async function fetchLedger(api, API, builderId, isOwn) {
|
|
371
|
+
const base = isOwn
|
|
372
|
+
? `${API}/me/credits`
|
|
373
|
+
: `${API}/builders/${encodeURIComponent(builderId)}/credits`;
|
|
374
|
+
const rows = [];
|
|
375
|
+
let total = Infinity;
|
|
376
|
+
try {
|
|
377
|
+
while (rows.length < Math.min(total, LEDGER_MAX_ROWS)) {
|
|
378
|
+
const res = await api.request('GET', `${base}?limit=${LEDGER_PAGE}&offset=${rows.length}`);
|
|
379
|
+
if (!res.ok) return { denied: true, rows: [] };
|
|
380
|
+
const page = (res.data && res.data.rows) || [];
|
|
381
|
+
total = Number(res.data && res.data.page && res.data.page.total) || 0;
|
|
382
|
+
rows.push(...page);
|
|
383
|
+
if (!page.length) break;
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.error('[profile] credits ledger failed', err);
|
|
387
|
+
return { denied: true, rows: [] };
|
|
388
|
+
}
|
|
389
|
+
return { denied: false, rows };
|
|
390
|
+
}
|
|
391
|
+
|
|
338
392
|
async function fetchOwnRate(api, API, builderId, days) {
|
|
339
393
|
try {
|
|
340
394
|
const q = days ? `?days=${encodeURIComponent(days)}` : '';
|
|
@@ -360,7 +414,7 @@
|
|
|
360
414
|
|
|
361
415
|
// Draw every figure for one lookback window into #charts-grid. Split from
|
|
362
416
|
// render() so the range buttons can re-draw without re-fetching the record.
|
|
363
|
-
async function draw({ api, API, builderId, isOwn, rows, now, rangeId }) {
|
|
417
|
+
async function draw({ api, API, builderId, isOwn, rows, paidFull, estimated, now, rangeId }) {
|
|
364
418
|
const grid = $('#charts-grid');
|
|
365
419
|
if (!grid) return;
|
|
366
420
|
const range = RANGES.find((r) => r.id === rangeId) || RANGES[RANGES.length - 1];
|
|
@@ -369,8 +423,6 @@
|
|
|
369
423
|
const span = range.days == null ? 'all time' : `the last ${range.label}`;
|
|
370
424
|
|
|
371
425
|
const worksFull = cumulativeByDay(rows, () => 1);
|
|
372
|
-
let estimated = false;
|
|
373
|
-
const paidFull = cumulativeByDay(rows, (r) => { const c = creditsOf(r); estimated = estimated || c.estimated; return c.value; });
|
|
374
426
|
const works = clipToWindow(worksFull, windowStart);
|
|
375
427
|
const paid = clipToWindow(paidFull, windowStart);
|
|
376
428
|
const mix = craftMix(inWindow);
|
|
@@ -384,7 +436,9 @@
|
|
|
384
436
|
timeChart({
|
|
385
437
|
id: 'chart-drachmae', title: `${curLabel(true)} earned`, points: paid, now, windowStart,
|
|
386
438
|
valueText: (v) => `${fmtNum(v)} ${curLabel()}`,
|
|
387
|
-
footer:
|
|
439
|
+
footer: estimated
|
|
440
|
+
? `estimated from shipped work, not the ledger · ${span}`
|
|
441
|
+
: `${fmtNum(paidFull.length ? paidFull[paidFull.length - 1].value : 0)} paid, from the credits ledger · ${span}`,
|
|
388
442
|
}),
|
|
389
443
|
mixChart({
|
|
390
444
|
id: 'chart-mix', title: 'Works by craft', mix,
|
|
@@ -436,6 +490,15 @@
|
|
|
436
490
|
return;
|
|
437
491
|
}
|
|
438
492
|
|
|
493
|
+
// The drachmae series: the ledger wherever the viewer may read it, the
|
|
494
|
+
// estimate (labelled) where the server refuses. Fetched once, like the
|
|
495
|
+
// record; the lookback only clips it.
|
|
496
|
+
const ledger = await fetchLedger(api, API, builderId, isOwn);
|
|
497
|
+
const estimated = ledger.denied;
|
|
498
|
+
const paidFull = estimated
|
|
499
|
+
? cumulativeByDay(rows, estimateOf)
|
|
500
|
+
: cumulativeByDay(ledger.rows, (r) => r && r.delta, (r) => r && r.recorded_at);
|
|
501
|
+
|
|
439
502
|
// The lookback control. Its choice is remembered per viewer; changing it
|
|
440
503
|
// re-draws from the rows already in hand — the record is not re-fetched.
|
|
441
504
|
let rangeId = readRange();
|
|
@@ -451,11 +514,11 @@
|
|
|
451
514
|
b.classList.toggle('is-active', on);
|
|
452
515
|
b.setAttribute('aria-pressed', String(on));
|
|
453
516
|
});
|
|
454
|
-
draw({ api, API, builderId, isOwn, rows, now, rangeId });
|
|
517
|
+
draw({ api, API, builderId, isOwn, rows, paidFull, estimated, now, rangeId });
|
|
455
518
|
});
|
|
456
519
|
}
|
|
457
|
-
await draw({ api, API, builderId, isOwn, rows, now, rangeId });
|
|
520
|
+
await draw({ api, API, builderId, isOwn, rows, paidFull, estimated, now, rangeId });
|
|
458
521
|
}
|
|
459
522
|
|
|
460
|
-
window.OTBProfileCharts = { render, cumulativeByDay, craftMix, weeklyBuckets,
|
|
523
|
+
window.OTBProfileCharts = { render, cumulativeByDay, craftMix, weeklyBuckets, estimateOf, clipToWindow };
|
|
461
524
|
})();
|
|
@@ -653,8 +653,9 @@
|
|
|
653
653
|
showMessage(`${name} — ${rankLabel(normalizeRank(builder.rank))} · ${fmtNum(shipped)} work${shipped === 1 ? '' : 's'} shipped`);
|
|
654
654
|
renderHead(builder);
|
|
655
655
|
renderNumbers(data.shipped_count, data.achievements);
|
|
656
|
-
// The charts (task 1003489) read the activity route
|
|
657
|
-
//
|
|
656
|
+
// The charts (task 1003489) read the activity route and the credits ledger
|
|
657
|
+
// (task 1003491), so they need the same session the ledgers do; on the
|
|
658
|
+
// viewer's own profile they add the pace.
|
|
658
659
|
if (window.OTBProfileCharts) window.OTBProfileCharts.render({ api, API, builderId: builder.id, selfId, signedIn: selfId != null });
|
|
659
660
|
renderCrossProject(data, selfId);
|
|
660
661
|
renderAchievements(data.achievements);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.491",
|
|
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.491",
|
|
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.491",
|
|
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",
|
|
@@ -17,8 +17,14 @@ const FIXTURES = path.join(__dirname, 'fixtures');
|
|
|
17
17
|
|
|
18
18
|
// [api path (no /api prefix, no query), fixture name, optional trim fn]
|
|
19
19
|
const trimList = (key, n) => (d) => (Array.isArray(d?.[key]) ? { ...d, [key]: d[key].slice(0, n) } : d);
|
|
20
|
+
// A paged ledger is one page of a longer record, and the harness ignores query
|
|
21
|
+
// strings — so a fixture whose page.total exceeds its rows would send the
|
|
22
|
+
// profile charts' pager back for the same page until "total". Pin total to
|
|
23
|
+
// what the snapshot holds.
|
|
24
|
+
const selfPaged = (d) => (Array.isArray(d?.rows) && d.page ? { ...d, page: { ...d.page, total: d.rows.length } } : d);
|
|
20
25
|
const SNAPSHOTS = [
|
|
21
26
|
['me', 'me'],
|
|
27
|
+
['me/credits?limit=200', 'me__credits', selfPaged],
|
|
22
28
|
['me/recent-ships?limit=30', 'me__recent-ships'],
|
|
23
29
|
['achievements', 'achievements'],
|
|
24
30
|
['versions', 'versions'],
|
|
@@ -111,6 +111,9 @@ const FIXTURE_FALLBACKS = [
|
|
|
111
111
|
[/^analytics\/builder\/\d+$/, 'analytics__builder'],
|
|
112
112
|
[/^builders\/\d+\/active-claims$/, 'builders__active-claims'],
|
|
113
113
|
[/^builders\/\d+\/profile$/, 'builders__profile'],
|
|
114
|
+
// the profile's drachmae chart reads the credits ledger (task 1003491): one
|
|
115
|
+
// ledger fixture serves your own (/me/credits, exact match) and any other id.
|
|
116
|
+
[/^builders\/\d+\/credits$/, 'me__credits'],
|
|
114
117
|
// the profile's per-craft activity log (task 1003281). One fixture per SECTION
|
|
115
118
|
// rather than per id, because the section is what changes the row shape; the
|
|
116
119
|
// craft and the page window are query string, which fixture matching ignores.
|
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.491'; // 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');
|
|
@@ -17,10 +17,16 @@
|
|
|
17
17
|
// public latest-works strip and the cross-project record; the craft
|
|
18
18
|
// ledgers are one key over, so the first screen is a screen.
|
|
19
19
|
// * THE CHARTS ARE ARITHMETIC FIRST. cumulativeByDay folds two ships on one
|
|
20
|
-
// day into one point and accumulates
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
20
|
+
// day into one point and accumulates, over whichever timestamp the caller
|
|
21
|
+
// names; craftMix folds the tail past seven into "other" rather than
|
|
22
|
+
// minting an eighth hue; weeklyBuckets is a time axis (every week present,
|
|
23
|
+
// empty or not).
|
|
24
|
+
// * DRACHMAE COME FROM THE LEDGER (task 1003491). The earned line is the
|
|
25
|
+
// credits ledger — /me/credits on your own profile, /builders/:id/credits
|
|
26
|
+
// on another's — `delta` cumulated by `recorded_at`, so it ends at the
|
|
27
|
+
// balance. Per-task credits_awarded is 0 on a cost-plus instance and is no
|
|
28
|
+
// longer read at all (the owner's chart had flatlined since July on it).
|
|
29
|
+
// A viewer the ledger refuses gets the credits_reward estimate, labelled.
|
|
24
30
|
// * NO NUMBER IS PICTURE-ONLY. Every time chart carries a table twin with a
|
|
25
31
|
// row per plotted day; the readout is written with textContent.
|
|
26
32
|
// * OWN-ONLY MEANS OWN-ONLY. /analytics/builder/:id is asked for only when
|
|
@@ -163,14 +169,29 @@ test('cumulativeByDay folds a day\'s ships into one point and accumulates, skipp
|
|
|
163
169
|
{ day: day('2026-08-10'), value: 3 },
|
|
164
170
|
]);
|
|
165
171
|
assert.deepEqual(plain(C.cumulativeByDay([], () => 1)), []);
|
|
172
|
+
// the ledger's rows are stamped recorded_at, not shipped_at — the caller names the timestamp
|
|
173
|
+
const ledger = [
|
|
174
|
+
{ delta: 12, recorded_at: '2026-08-02T22:17:01Z' },
|
|
175
|
+
{ delta: 39, recorded_at: '2026-08-01T21:52:51Z' },
|
|
176
|
+
{ delta: 19, recorded_at: '2026-08-01T22:05:00Z' },
|
|
177
|
+
];
|
|
178
|
+
assert.deepEqual(plain(C.cumulativeByDay(ledger, (r) => r.delta, (r) => r.recorded_at)), [
|
|
179
|
+
{ day: day('2026-08-01'), value: 58 },
|
|
180
|
+
{ day: day('2026-08-02'), value: 70 },
|
|
181
|
+
], 'newest-first ledger rows still accumulate oldest-first, by their own timestamp');
|
|
166
182
|
});
|
|
167
183
|
|
|
168
|
-
test('
|
|
184
|
+
test('estimateOf is the credits_reward figure and nothing else — the per-task award column is not read anywhere', () => {
|
|
169
185
|
const C = bootCharts();
|
|
170
|
-
assert.
|
|
171
|
-
assert.
|
|
172
|
-
assert.
|
|
173
|
-
assert.
|
|
186
|
+
assert.equal(C.estimateOf({ credits_awarded: 58, credits_reward: 60 }), 60, 'the estimate ignores what was awarded');
|
|
187
|
+
assert.equal(C.estimateOf({ credits_awarded: 0, credits_reward: 45 }), 45, 'a cost-plus 0 award does not zero the estimate');
|
|
188
|
+
assert.equal(C.estimateOf({}), 0);
|
|
189
|
+
assert.equal(C.estimateOf(null), 0);
|
|
190
|
+
assert.equal(C.creditsOf, undefined, 'the old ledger-or-estimate picker is gone');
|
|
191
|
+
// The whole reason for task 1003491: on a cost-plus instance every task's
|
|
192
|
+
// credits_awarded is 0, so a drachmae series summed from it flatlines the day
|
|
193
|
+
// per-task payout stops. The column must not be read by the charts at all.
|
|
194
|
+
assert.ok(!chartsJs.includes('credits_awarded'), 'profile-charts.js never mentions credits_awarded');
|
|
174
195
|
});
|
|
175
196
|
|
|
176
197
|
test('craftMix sorts by count and folds the tail past seven into "other" — never an eighth hue', () => {
|
|
@@ -216,7 +237,10 @@ test('clipToWindow (the lookback) enters the window at the running height, not a
|
|
|
216
237
|
|
|
217
238
|
// ---- the charts: what render asks for and draws ---------------------------------
|
|
218
239
|
|
|
219
|
-
|
|
240
|
+
// `ledger` is what the credits routes answer: an array of rows (paged by the
|
|
241
|
+
// route's 200 in the real shape, { rows, page: { total } }), or 'denied' for the
|
|
242
|
+
// 403 a viewer without builder.roster.read gets on someone else's ledger.
|
|
243
|
+
function bootRender({ rows, status = 200, analytics = null, ledger = [] }) {
|
|
220
244
|
const requests = [];
|
|
221
245
|
const mount = { innerHTML: '', querySelector: () => null };
|
|
222
246
|
const controls = { innerHTML: '', addEventListener() {}, querySelectorAll: () => [] };
|
|
@@ -229,6 +253,11 @@ function bootRender({ rows, status = 200, analytics = null }) {
|
|
|
229
253
|
const offset = Number(new URL(`http://x${url}`).searchParams.get('offset')) || 0;
|
|
230
254
|
return { status: 200, ok: true, data: { total: rows.length, rows: rows.slice(offset, offset + 100) } };
|
|
231
255
|
}
|
|
256
|
+
if (url.includes('/credits')) {
|
|
257
|
+
if (ledger === 'denied') return { status: 403, ok: false, data: null };
|
|
258
|
+
const offset = Number(new URL(`http://x${url}`).searchParams.get('offset')) || 0;
|
|
259
|
+
return { status: 200, ok: true, data: { rows: ledger.slice(offset, offset + 200), page: { limit: 200, offset, total: ledger.length } } };
|
|
260
|
+
}
|
|
232
261
|
if (url.includes('/analytics/')) return analytics ? { status: 200, ok: true, data: analytics } : { status: 403, ok: false, data: null };
|
|
233
262
|
return { status: 404, ok: false, data: null };
|
|
234
263
|
},
|
|
@@ -253,12 +282,18 @@ function bootRender({ rows, status = 200, analytics = null }) {
|
|
|
253
282
|
}
|
|
254
283
|
|
|
255
284
|
const ship = (d, extra = {}) => ({ shipped_at: `${d}T10:00:00Z`, discipline: 'engineer', credits_awarded: 10, credits_reward: 12, ...extra });
|
|
285
|
+
// a credits-ledger row: what the instance actually paid, when
|
|
286
|
+
const pay = (d, delta, extra = {}) => ({ delta, reason: 'session.token_reward', recorded_at: `${d}T22:00:00Z`, ...extra });
|
|
256
287
|
|
|
257
288
|
test('render draws three public figures with a table twin per time chart, and no own-only one for another builder', async () => {
|
|
258
|
-
const b = bootRender({
|
|
289
|
+
const b = bootRender({
|
|
290
|
+
rows: [ship('2026-08-01'), ship('2026-08-01'), ship('2026-08-20', { discipline: 'ui' })],
|
|
291
|
+
ledger: [pay('2026-08-01', 20), pay('2026-08-20', 30)],
|
|
292
|
+
});
|
|
259
293
|
await b.C.render({ api: b.api, API: '/api/bongos', builderId: 95, selfId: 7, signedIn: true, now: NOW });
|
|
260
294
|
const out = b.mount.innerHTML;
|
|
261
295
|
assert.ok(out.includes('id="chart-works"') && out.includes('id="chart-drachmae"') && out.includes('id="chart-mix"'), 'the three public charts');
|
|
296
|
+
assert.ok(out.includes('<td>2026-08-20</td><td class="ledger__num">50 drachmae</td>'), 'the drachmae twin reads the ledger, not the 30 of per-task awards');
|
|
262
297
|
assert.ok(!out.includes('id="chart-rate"'), 'no pace chart on someone else\'s profile');
|
|
263
298
|
assert.ok(!b.requests.some((u) => u.includes('/analytics/')), 'and /analytics was never asked for');
|
|
264
299
|
assert.equal((out.match(/<details class="pchart__twin">/g) || []).length, 2, 'a table twin under each time chart');
|
|
@@ -272,6 +307,8 @@ test('render asks for the pace chart on the viewer\'s own profile only', async (
|
|
|
272
307
|
const own = bootRender({ rows: [ship('2026-08-01')], analytics });
|
|
273
308
|
await own.C.render({ api: own.api, API: '/api/bongos', builderId: 95, selfId: '95', signedIn: true, now: NOW });
|
|
274
309
|
assert.ok(own.requests.some((u) => u.includes('/analytics/builder/95')), 'own profile: the analytics read');
|
|
310
|
+
assert.ok(own.requests.some((u) => u.includes('/me/credits?')), 'own profile: your own ledger');
|
|
311
|
+
assert.ok(!own.requests.some((u) => u.includes('/builders/95/credits')), 'never through the roster route for yourself');
|
|
275
312
|
assert.ok(own.mount.innerHTML.includes('id="chart-rate"'), 'and the pace chart draws');
|
|
276
313
|
assert.ok(own.mount.innerHTML.includes('only you see this one'), 'saying plainly that it is private');
|
|
277
314
|
});
|
|
@@ -325,6 +362,64 @@ test('a 30-day window drops the older ships from the craft mix and clips the axi
|
|
|
325
362
|
assert.equal(C.craftMix(within).length, 1, 'only the recent craft survives a 30-day window');
|
|
326
363
|
});
|
|
327
364
|
|
|
365
|
+
test('the drachmae line is the credits ledger: /me/credits on your own profile, cumulated by recorded_at, ending at the balance', async () => {
|
|
366
|
+
const rows = [ship('2026-08-01'), ship('2026-08-20')]; // per-task awards would sum to 20
|
|
367
|
+
const ledger = [pay('2026-09-01', 12), pay('2026-08-20', 39), pay('2026-08-20', 19), pay('2026-07-05', 30)]; // balance 100
|
|
368
|
+
const b = bootRender({ rows, ledger });
|
|
369
|
+
await b.C.render({ api: b.api, API: '/api/bongos', builderId: 95, selfId: 95, signedIn: true, now: NOW });
|
|
370
|
+
assert.ok(b.requests.some((u) => u.includes('/me/credits?limit=200&offset=0')), 'own profile: /me/credits, paged');
|
|
371
|
+
assert.ok(!b.requests.some((u) => u.includes('/builders/95/credits')), 'not the roster route');
|
|
372
|
+
const out = b.mount.innerHTML;
|
|
373
|
+
const fig = out.slice(out.indexOf('id="chart-drachmae"'), out.indexOf('id="chart-mix"'));
|
|
374
|
+
assert.ok(fig.includes('>100 drachmae</text>'), 'the endpoint is the ledger balance, not the 20 of per-task awards');
|
|
375
|
+
assert.ok(fig.includes('<td>2026-07-05</td><td class="ledger__num">30 drachmae</td>'), 'a paid day with no ship is still a point');
|
|
376
|
+
assert.ok(fig.includes('<td>2026-08-20</td><td class="ledger__num">88 drachmae</td>'), 'two payments on one day fold into one point');
|
|
377
|
+
assert.match(fig, /100 paid, from the credits ledger/);
|
|
378
|
+
assert.doesNotMatch(fig, /estimated/);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test('another builder\'s ledger is read from /builders/:id/credits, paged by 200 to total', async () => {
|
|
382
|
+
const ledger = Array.from({ length: 450 }, (_, i) => pay(`2026-08-${String(1 + (i % 28)).padStart(2, '0')}`, 1));
|
|
383
|
+
const b = bootRender({ rows: [ship('2026-08-01')], ledger });
|
|
384
|
+
await b.C.render({ api: b.api, API: '/api/bongos', builderId: 95, selfId: 7, signedIn: true, now: NOW });
|
|
385
|
+
const pages = b.requests.filter((u) => u.includes('/builders/95/credits'));
|
|
386
|
+
assert.equal(pages.length, 3, 'three pages of 200 for 450 rows');
|
|
387
|
+
assert.ok(!b.requests.some((u) => u.includes('/me/credits')), 'never your own ledger for someone else\'s chart');
|
|
388
|
+
assert.ok(b.mount.innerHTML.includes('>450 drachmae</text>'), 'and every row counted once');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('a viewer the ledger refuses gets the credits_reward estimate, cumulated and labelled estimated', async () => {
|
|
392
|
+
// the cost-plus shape: every award 0, the estimate is all there is to show
|
|
393
|
+
const rows = [ship('2026-08-01', { credits_awarded: 0, credits_reward: 45 }), ship('2026-08-20', { credits_awarded: 0, credits_reward: 55 })];
|
|
394
|
+
const b = bootRender({ rows, ledger: 'denied' });
|
|
395
|
+
await b.C.render({ api: b.api, API: '/api/bongos', builderId: 95, selfId: 7, signedIn: true, now: NOW });
|
|
396
|
+
const out = b.mount.innerHTML;
|
|
397
|
+
const fig = out.slice(out.indexOf('id="chart-drachmae"'), out.indexOf('id="chart-mix"'));
|
|
398
|
+
assert.ok(fig.includes('>100 drachmae</text>'), 'the estimate climbs where the 0 awards would have flatlined');
|
|
399
|
+
assert.match(fig, /estimated from shipped work, not the ledger/);
|
|
400
|
+
assert.ok(!out.includes('id="chart-rate"'), 'the pace chart stays own-only');
|
|
401
|
+
assert.ok(!out.includes('Could not load'), 'a 403 on the ledger is a fallback, not a failure');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('a readable ledger with nothing in it draws one honest line, not a crash', async () => {
|
|
405
|
+
const b = bootRender({ rows: [ship('2026-08-01')], ledger: [] });
|
|
406
|
+
await b.C.render({ api: b.api, API: '/api/bongos', builderId: 95, selfId: 95, signedIn: true, now: NOW });
|
|
407
|
+
const out = b.mount.innerHTML;
|
|
408
|
+
const fig = out.slice(out.indexOf('id="chart-drachmae"'), out.indexOf('id="chart-mix"'));
|
|
409
|
+
assert.match(fig, /nothing recorded yet/);
|
|
410
|
+
assert.ok(out.includes('id="chart-works"') && out.includes('id="chart-mix"'), 'the other charts are untouched');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('the harness carries a credits-ledger fixture the chart can climb on, self-paged, serving both routes', () => {
|
|
414
|
+
const fx = JSON.parse(fs.readFileSync(path.join(ROOT, 'scripts', 'hall-preview', 'fixtures', 'me__credits.json'), 'utf8'));
|
|
415
|
+
assert.ok(Array.isArray(fx.rows) && fx.rows.length > 1, 'rows to draw');
|
|
416
|
+
assert.equal(fx.page.total, fx.rows.length, 'total pinned to the rows here, or the pager would loop on the same page');
|
|
417
|
+
assert.ok(fx.rows.every((r) => Number.isFinite(r.delta) && Number.isFinite(Date.parse(r.recorded_at))), 'every row is a delta at a time');
|
|
418
|
+
assert.ok(new Set(fx.rows.map((r) => r.recorded_at.slice(0, 10))).size > 1, 'across more than one day, so the line is not flat');
|
|
419
|
+
const server = fs.readFileSync(path.join(ROOT, 'scripts', 'hall-preview', 'server.js'), 'utf8');
|
|
420
|
+
assert.ok(server.includes("[/^builders\\/\\d+\\/credits$/, 'me__credits']"), 'the roster route falls back to the same ledger');
|
|
421
|
+
});
|
|
422
|
+
|
|
328
423
|
test('the readout is written with textContent — the hover layer never builds markup from data', () => {
|
|
329
424
|
const fn = chartsJs.slice(chartsJs.indexOf('function wireReadout'), chartsJs.indexOf('function twinHtml'));
|
|
330
425
|
assert.match(fn, /read\.textContent = /);
|