@bongos/core 1.19.1013 → 1.19.1014

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.
Files changed (51) hide show
  1. package/.bongos-core.json +77 -52
  2. package/docs/copy-inventory.md +27 -16
  3. package/docs/copy-registry.json +153 -32
  4. package/docs/module-api-changelog.md +2 -0
  5. package/modules/hall-ui/public/atlas.html +1 -1
  6. package/modules/hall-ui/public/blockers.html +1 -1
  7. package/modules/hall-ui/public/board-room.html +1 -1
  8. package/modules/hall-ui/public/collab.html +1 -1
  9. package/modules/hall-ui/public/copy-desk.html +1 -1
  10. package/modules/hall-ui/public/deploy.html +1 -1
  11. package/modules/hall-ui/public/diagrams.html +1 -1
  12. package/modules/hall-ui/public/drachmae.html +1 -1
  13. package/modules/hall-ui/public/fleet-costs.js +80 -0
  14. package/modules/hall-ui/public/fleet.html +64 -0
  15. package/modules/hall-ui/public/fleet.js +45 -0
  16. package/modules/hall-ui/public/fleet.states.json +14 -0
  17. package/modules/hall-ui/public/gate.html +1 -1
  18. package/modules/hall-ui/public/goal-map.html +1 -1
  19. package/modules/hall-ui/public/goals.html +1 -1
  20. package/modules/hall-ui/public/government.html +1 -1
  21. package/modules/hall-ui/public/idea.html +1 -1
  22. package/modules/hall-ui/public/ideas.html +1 -1
  23. package/modules/hall-ui/public/index.html +1 -1
  24. package/modules/hall-ui/public/modules.html +1 -1
  25. package/modules/hall-ui/public/people.html +1 -1
  26. package/modules/hall-ui/public/primer.html +1 -1
  27. package/modules/hall-ui/public/profile.html +1 -1
  28. package/modules/hall-ui/public/project-settings.html +1 -1
  29. package/modules/hall-ui/public/ranks.html +1 -1
  30. package/modules/hall-ui/public/roadmap.html +1 -1
  31. package/modules/hall-ui/public/roster.html +1 -1
  32. package/modules/hall-ui/public/sessions.html +1 -1
  33. package/modules/hall-ui/public/settings.html +1 -1
  34. package/modules/hall-ui/public/shell.js +4 -0
  35. package/modules/hall-ui/public/studio.html +1 -1
  36. package/modules/hall-ui/public/task.html +1 -1
  37. package/modules/hall-ui/public/watch.html +1 -1
  38. package/modules/hall-ui/public/work.html +1 -1
  39. package/modules/provisioning/routes/provisioning.js +4 -4
  40. package/modules/public-landing/public/projects.html +7 -0
  41. package/modules/ui-design/kit/fixtures/provisioning-instances.json +1 -0
  42. package/package-lock.json +2 -2
  43. package/package.json +1 -1
  44. package/src/bongos/auth.js +8 -0
  45. package/src/bongos/serve-internal.js +5 -0
  46. package/src/module-api.js +1 -1
  47. package/tests/auth_resolve_failure.mjs +3 -2
  48. package/tests/fleet_cost_view.mjs +189 -0
  49. package/tests/hall_nav.mjs +3 -1
  50. package/tests/hall_page_gate_map.mjs +2 -0
  51. package/tests/nav_permission_atoms.mjs +1 -1
@@ -3,17 +3,17 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1995,
7
- "unique_ids": 1736,
8
- "certain": 1995,
6
+ "strings": 2006,
7
+ "unique_ids": 1743,
8
+ "certain": 2006,
9
9
  "heuristic": 0,
10
- "duplicate_ids": 179
10
+ "duplicate_ids": 181
11
11
  },
12
12
  "surfaces": {
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
- "files": 101,
16
- "strings": 1504
15
+ "files": 104,
16
+ "strings": 1515
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -101,6 +101,20 @@
101
101
  "modules/hall-ui/public/task-detail.js:226"
102
102
  ]
103
103
  },
104
+ {
105
+ "id": "9a685b01e2c6",
106
+ "surface": "builders-hall",
107
+ "text": "This page opens at a higher rank.",
108
+ "occurrences": 6,
109
+ "at": [
110
+ "modules/hall-ui/public/deploy.html:31",
111
+ "modules/hall-ui/public/fleet.html:31",
112
+ "modules/hall-ui/public/gate.html:37",
113
+ "modules/hall-ui/public/government.html:37",
114
+ "modules/hall-ui/public/project-settings.html:36",
115
+ "modules/hall-ui/public/watch.html:40"
116
+ ]
117
+ },
104
118
  {
105
119
  "id": "4d8ee7d68974",
106
120
  "surface": "builders-hall",
@@ -178,20 +192,7 @@
178
192
  "modules/hall-ui/public/hall-render.js:367",
179
193
  "modules/hall-ui/public/hall-render.js:391",
180
194
  "modules/hall-ui/public/hall-render.js:461",
181
- "modules/hall-ui/public/shell.js:441"
182
- ]
183
- },
184
- {
185
- "id": "9a685b01e2c6",
186
- "surface": "builders-hall",
187
- "text": "This page opens at a higher rank.",
188
- "occurrences": 5,
189
- "at": [
190
- "modules/hall-ui/public/deploy.html:31",
191
- "modules/hall-ui/public/gate.html:37",
192
- "modules/hall-ui/public/government.html:37",
193
- "modules/hall-ui/public/project-settings.html:36",
194
- "modules/hall-ui/public/watch.html:40"
195
+ "modules/hall-ui/public/shell.js:445"
195
196
  ]
196
197
  },
197
198
  {
@@ -356,6 +357,17 @@
356
357
  "modules/hall-ui/public/drachmae.html:37"
357
358
  ]
358
359
  },
360
+ {
361
+ "id": "e0e2606e1104",
362
+ "surface": "builders-hall",
363
+ "text": "Fleet costs",
364
+ "occurrences": 3,
365
+ "at": [
366
+ "modules/hall-ui/public/fleet.html:15",
367
+ "modules/hall-ui/public/fleet.html:15",
368
+ "modules/hall-ui/public/fleet.html:24"
369
+ ]
370
+ },
359
371
  {
360
372
  "id": "3807521c5db7",
361
373
  "surface": "builders-hall",
@@ -868,6 +880,16 @@
868
880
  "modules/hall-ui/public/diagram-viewer.js:100"
869
881
  ]
870
882
  },
883
+ {
884
+ "id": "9b0efcaac184",
885
+ "surface": "builders-hall",
886
+ "text": "Fleet costs — {{worldName}}",
887
+ "occurrences": 2,
888
+ "at": [
889
+ "modules/hall-ui/public/fleet.html:6",
890
+ "modules/hall-ui/public/fleet.html:6"
891
+ ]
892
+ },
871
893
  {
872
894
  "id": "aab2b51efeab",
873
895
  "surface": "landing",
@@ -1424,8 +1446,8 @@
1424
1446
  "text": "Sign out",
1425
1447
  "occurrences": 2,
1426
1448
  "at": [
1427
- "modules/hall-ui/public/shell.js:459",
1428
- "modules/hall-ui/public/shell.js:692"
1449
+ "modules/hall-ui/public/shell.js:463",
1450
+ "modules/hall-ui/public/shell.js:696"
1429
1451
  ]
1430
1452
  },
1431
1453
  {
@@ -3872,6 +3894,15 @@
3872
3894
  "origin": "js-markup",
3873
3895
  "confidence": "certain"
3874
3896
  },
3897
+ {
3898
+ "id": "301afa9a94b9",
3899
+ "surface": "builders-hall",
3900
+ "text": "Costs us / month",
3901
+ "file": "modules/hall-ui/public/fleet-costs.js",
3902
+ "line": 70,
3903
+ "origin": "js-markup",
3904
+ "confidence": "certain"
3905
+ },
3875
3906
  {
3876
3907
  "id": "e269ff7b3144",
3877
3908
  "surface": "builders-hall",
@@ -5429,6 +5460,51 @@
5429
5460
  "origin": "js-notice",
5430
5461
  "confidence": "certain"
5431
5462
  },
5463
+ {
5464
+ "id": "e0e2606e1104",
5465
+ "surface": "builders-hall",
5466
+ "text": "Fleet costs",
5467
+ "file": "modules/hall-ui/public/fleet.html",
5468
+ "line": 15,
5469
+ "origin": "html-attr:title",
5470
+ "confidence": "certain"
5471
+ },
5472
+ {
5473
+ "id": "e0e2606e1104",
5474
+ "surface": "builders-hall",
5475
+ "text": "Fleet costs",
5476
+ "file": "modules/hall-ui/public/fleet.html",
5477
+ "line": 15,
5478
+ "origin": "html-attr:data-page-title",
5479
+ "confidence": "certain"
5480
+ },
5481
+ {
5482
+ "id": "e0e2606e1104",
5483
+ "surface": "builders-hall",
5484
+ "text": "Fleet costs",
5485
+ "file": "modules/hall-ui/public/fleet.html",
5486
+ "line": 24,
5487
+ "origin": "html-text",
5488
+ "confidence": "certain"
5489
+ },
5490
+ {
5491
+ "id": "9b0efcaac184",
5492
+ "surface": "builders-hall",
5493
+ "text": "Fleet costs — {{worldName}}",
5494
+ "file": "modules/hall-ui/public/fleet.html",
5495
+ "line": 6,
5496
+ "origin": "html-text",
5497
+ "confidence": "certain"
5498
+ },
5499
+ {
5500
+ "id": "9b0efcaac184",
5501
+ "surface": "builders-hall",
5502
+ "text": "Fleet costs — {{worldName}}",
5503
+ "file": "modules/hall-ui/public/fleet.html",
5504
+ "line": 6,
5505
+ "origin": "html-title",
5506
+ "confidence": "certain"
5507
+ },
5432
5508
  {
5433
5509
  "id": "617f13fa3fdb",
5434
5510
  "surface": "builders-hall",
@@ -6451,7 +6527,7 @@
6451
6527
  "surface": "builders-hall",
6452
6528
  "text": "Jump to a page or record",
6453
6529
  "file": "modules/hall-ui/public/shell.js",
6454
- "line": 480,
6530
+ "line": 484,
6455
6531
  "origin": "js-markup",
6456
6532
  "confidence": "certain"
6457
6533
  },
@@ -6460,7 +6536,7 @@
6460
6536
  "surface": "builders-hall",
6461
6537
  "text": "Jump to…",
6462
6538
  "file": "modules/hall-ui/public/shell.js",
6463
- "line": 482,
6539
+ "line": 486,
6464
6540
  "origin": "js-markup",
6465
6541
  "confidence": "certain"
6466
6542
  },
@@ -7427,6 +7503,15 @@
7427
7503
  "origin": "js-markup",
7428
7504
  "confidence": "certain"
7429
7505
  },
7506
+ {
7507
+ "id": "1a46c31343cb",
7508
+ "surface": "builders-hall",
7509
+ "text": "No projects are hosted here yet.",
7510
+ "file": "modules/hall-ui/public/fleet-costs.js",
7511
+ "line": 68,
7512
+ "origin": "js-markup",
7513
+ "confidence": "certain"
7514
+ },
7430
7515
  {
7431
7516
  "id": "9f73b3f3dad0",
7432
7517
  "surface": "builders-hall",
@@ -8026,7 +8111,7 @@
8026
8111
  "surface": "builders-hall",
8027
8112
  "text": "Open navigation",
8028
8113
  "file": "modules/hall-ui/public/shell.js",
8029
- "line": 471,
8114
+ "line": 475,
8030
8115
  "origin": "js-markup",
8031
8116
  "confidence": "certain"
8032
8117
  },
@@ -8192,6 +8277,15 @@
8192
8277
  "origin": "html-text",
8193
8278
  "confidence": "certain"
8194
8279
  },
8280
+ {
8281
+ "id": "0046e08db06a",
8282
+ "surface": "builders-hall",
8283
+ "text": "Owner’s bill / month (est.)",
8284
+ "file": "modules/hall-ui/public/fleet-costs.js",
8285
+ "line": 70,
8286
+ "origin": "js-markup",
8287
+ "confidence": "certain"
8288
+ },
8195
8289
  {
8196
8290
  "id": "15a49bf15007",
8197
8291
  "surface": "builders-hall",
@@ -9853,7 +9947,7 @@
9853
9947
  "surface": "builders-hall",
9854
9948
  "text": "Sign in with GitHub",
9855
9949
  "file": "modules/hall-ui/public/shell.js",
9856
- "line": 441,
9950
+ "line": 445,
9857
9951
  "origin": "js-markup",
9858
9952
  "confidence": "certain"
9859
9953
  },
@@ -9862,7 +9956,7 @@
9862
9956
  "surface": "builders-hall",
9863
9957
  "text": "Sign out",
9864
9958
  "file": "modules/hall-ui/public/shell.js",
9865
- "line": 459,
9959
+ "line": 463,
9866
9960
  "origin": "js-markup",
9867
9961
  "confidence": "certain"
9868
9962
  },
@@ -9871,7 +9965,7 @@
9871
9965
  "surface": "builders-hall",
9872
9966
  "text": "Sign out",
9873
9967
  "file": "modules/hall-ui/public/shell.js",
9874
- "line": 692,
9968
+ "line": 696,
9875
9969
  "origin": "js-markup",
9876
9970
  "confidence": "certain"
9877
9971
  },
@@ -9898,7 +9992,7 @@
9898
9992
  "surface": "builders-hall",
9899
9993
  "text": "Skip to content",
9900
9994
  "file": "modules/hall-ui/public/shell.js",
9901
- "line": 683,
9995
+ "line": 687,
9902
9996
  "origin": "js-text-assign",
9903
9997
  "confidence": "certain"
9904
9998
  },
@@ -10141,7 +10235,7 @@
10141
10235
  "surface": "builders-hall",
10142
10236
  "text": "Switch theme",
10143
10237
  "file": "modules/hall-ui/public/shell.js",
10144
- "line": 486,
10238
+ "line": 490,
10145
10239
  "origin": "js-markup",
10146
10240
  "confidence": "certain"
10147
10241
  },
@@ -10330,7 +10424,7 @@
10330
10424
  "surface": "builders-hall",
10331
10425
  "text": "Text size",
10332
10426
  "file": "modules/hall-ui/public/shell.js",
10333
- "line": 485,
10427
+ "line": 489,
10334
10428
  "origin": "js-markup",
10335
10429
  "confidence": "certain"
10336
10430
  },
@@ -11225,6 +11319,15 @@
11225
11319
  "origin": "html-text",
11226
11320
  "confidence": "certain"
11227
11321
  },
11322
+ {
11323
+ "id": "9a685b01e2c6",
11324
+ "surface": "builders-hall",
11325
+ "text": "This page opens at a higher rank.",
11326
+ "file": "modules/hall-ui/public/fleet.html",
11327
+ "line": 31,
11328
+ "origin": "html-text",
11329
+ "confidence": "certain"
11330
+ },
11228
11331
  {
11229
11332
  "id": "9a685b01e2c6",
11230
11333
  "surface": "builders-hall",
@@ -11428,7 +11531,7 @@
11428
11531
  "surface": "builders-hall",
11429
11532
  "text": "Use the control below to take it.",
11430
11533
  "file": "modules/hall-ui/public/shell.js",
11431
- "line": 650,
11534
+ "line": 654,
11432
11535
  "origin": "js-markup",
11433
11536
  "confidence": "certain"
11434
11537
  },
@@ -11639,6 +11742,15 @@
11639
11742
  "origin": "js-markup",
11640
11743
  "confidence": "certain"
11641
11744
  },
11745
+ {
11746
+ "id": "6bf049a0c943",
11747
+ "surface": "builders-hall",
11748
+ "text": "What each project costs",
11749
+ "file": "modules/hall-ui/public/fleet.html",
11750
+ "line": 35,
11751
+ "origin": "html-text",
11752
+ "confidence": "certain"
11753
+ },
11642
11754
  {
11643
11755
  "id": "21b7bd90ce2a",
11644
11756
  "surface": "builders-hall",
@@ -14726,6 +14838,15 @@
14726
14838
  "origin": "js-markup",
14727
14839
  "confidence": "certain"
14728
14840
  },
14841
+ {
14842
+ "id": "771bef3d31b5",
14843
+ "surface": "builders-hall",
14844
+ "text": "{…} project{…}",
14845
+ "file": "modules/hall-ui/public/fleet.js",
14846
+ "line": 39,
14847
+ "origin": "js-text-assign",
14848
+ "confidence": "certain"
14849
+ },
14729
14850
  {
14730
14851
  "id": "154bc518bab4",
14731
14852
  "surface": "builders-hall",
@@ -2500,5 +2500,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2500
2500
  landed since 1.19.1011 with no explicit bump. run 36067980398. (task 1002620)
2501
2501
  1.19.1013 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2502
2502
  landed since 1.19.1012 with no explicit bump. run 36072935774. (task 1002620)
2503
+ 1.19.1014 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2504
+ landed since 1.19.1013 with no explicit bump. run 36076310606. (task 1002620)
2503
2505
  ---------------------------------------------------------------------------
2504
2506
  ```
@@ -91,7 +91,7 @@
91
91
 
92
92
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
93
93
 
94
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
94
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
95
95
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
96
96
  <script src="/builders/palette.js" defer></script>
97
97
  <script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
@@ -50,7 +50,7 @@
50
50
 
51
51
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
52
52
  chrome exists before the page script runs. -->
53
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
53
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
54
54
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
55
55
  <script src="/builders/palette.js" defer></script>
56
56
  <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
@@ -60,7 +60,7 @@
60
60
 
61
61
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
62
62
 
63
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
63
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
64
64
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
65
65
  <script src="/builders/palette.js" defer></script>
66
66
  <script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
@@ -148,7 +148,7 @@
148
148
 
149
149
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
150
150
 
151
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
151
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
152
152
  <script src="/builders/palette.js" defer></script>
153
153
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
154
154
  <script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
@@ -177,7 +177,7 @@
177
177
 
178
178
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
179
179
 
180
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
180
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
181
181
  <script src="/builders/palette.js" defer></script>
182
182
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
183
183
  <script src="/builders/hall-kit.js"></script>
@@ -60,7 +60,7 @@
60
60
 
61
61
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
62
62
 
63
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
63
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
64
64
  <script src="/builders/palette.js" defer></script>
65
65
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
66
66
  <script src="/builders/hall-kit.js"></script>
@@ -97,7 +97,7 @@
97
97
 
98
98
  <!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
99
99
  time so the chrome exists before the deferred page scripts execute. -->
100
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
100
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
101
101
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
102
102
  <script src="/builders/palette.js" defer></script>
103
103
  <script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
@@ -98,7 +98,7 @@
98
98
 
99
99
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
100
100
 
101
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
101
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
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>
@@ -0,0 +1,80 @@
1
+ // fleet-costs.js — the fleet cost page's builders (task 1001949, ADR 0327).
2
+ //
3
+ // WHY. GET /provisioning/fleet/cost-ledger answers two questions per project, one
4
+ // column each: what the project costs US, and an ESTIMATE of its owner's own hosting
5
+ // bill. null in either means UNPRICED — the row names no plan anything can price.
6
+ // What this file exists to hold: an unpriced cost never renders as $0 and never moves
7
+ // a total. A $0 there would be a price nobody quoted.
8
+ //
9
+ // Pure, so a test runs the real rendering over the real fixture instead of a copy of
10
+ // it. Exposed as window.OTBFleetCosts; node-require-safe (the roadmap-timeline.js
11
+ // pattern — assignment is the only eval-time side effect). fleet.js is the chrome.
12
+ (() => {
13
+ 'use strict';
14
+
15
+ const UNPRICED = 'not priced yet';
16
+ const SHAPE = {
17
+ 'cloud-host': 'Cloud host (owner’s account)',
18
+ 'byo-host': 'Self-hosted',
19
+ 'control-plane': 'This platform’s server',
20
+ 'co-tenant': 'Shared server (retired)',
21
+ };
22
+
23
+ const ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
24
+ const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ESC[c]);
25
+ const isPriced = (v) => typeof v === 'number' && Number.isFinite(v);
26
+ const quiet = (text) => `<span class="ledger__est">${text}</span>`;
27
+
28
+ function money(v) {
29
+ if (!isPriced(v)) return null;
30
+ return `$${Number.isInteger(v) ? v : v.toFixed(2)}`;
31
+ }
32
+
33
+ // A self-hosted project runs on its owner's own machine, so its bill is not ours to
34
+ // see: a different absence from a plan nobody has recorded yet.
35
+ function cellHtml(v, shape, column) {
36
+ if (isPriced(v)) return esc(money(v));
37
+ return quiet(column === 'owner' && shape === 'byo-host' ? 'not visible to us' : UNPRICED);
38
+ }
39
+
40
+ // The server's total sums only priced rows and counts the rest as one number; the
41
+ // self-hosted share of that count is split back out so the total says what its cells
42
+ // say. A column with NOTHING priced gets no figure at all: the sum of no prices is not $0.
43
+ function totalHtml(total, rows, column) {
44
+ const unpriced = Number(total && total.unpriced) || 0;
45
+ const hidden = column === 'owner'
46
+ ? rows.filter((r) => r.hosting_shape === 'byo-host' && !isPriced(r.owner_monthly_usd)).length
47
+ : 0;
48
+ const notPriced = unpriced - hidden;
49
+ if (rows.length - unpriced <= 0) return quiet(notPriced > 0 ? UNPRICED : 'not visible to us');
50
+ const notes = [notPriced > 0 && `${notPriced} not priced`, hidden > 0 && `${hidden} not visible`].filter(Boolean);
51
+ const figure = esc(money(Number(total && total.monthly_usd) || 0));
52
+ return notes.length ? `${figure} ${quiet(`+ ${notes.join(', ')}`)}` : figure;
53
+ }
54
+
55
+ function rowHtml(r) {
56
+ return `<tr>
57
+ <td>${esc(r.slug)}</td>
58
+ <td>${esc(r.owner)}</td>
59
+ <td>${esc(SHAPE[r.hosting_shape] || r.hosting_shape)}</td>
60
+ <td>${r.tier ? esc(r.tier) : quiet('none recorded')}</td>
61
+ <td class="ledger__num">${cellHtml(r.platform_monthly_usd, r.hosting_shape, 'platform')}</td>
62
+ <td class="ledger__num">${cellHtml(r.owner_monthly_usd, r.hosting_shape, 'owner')}</td>
63
+ </tr>`;
64
+ }
65
+
66
+ function ledgerHtml(ledger) {
67
+ const rows = ledger && Array.isArray(ledger.instances) ? ledger.instances : [];
68
+ if (!rows.length) return '<p class="ledger__note">No projects are hosted here yet.</p>';
69
+ const totals = (ledger && ledger.totals) || {};
70
+ return `<table class="ledger" id="fl-table">
71
+ <thead><tr><th>Project</th><th>Owner</th><th>Hosting</th><th>Plan</th><th>Costs us / month</th><th>Owner’s bill / month (est.)</th></tr></thead>
72
+ <tbody>${rows.map(rowHtml).join('')}</tbody>
73
+ <tfoot><tr><th scope="row" colspan="4">Total</th><td class="ledger__num">${totalHtml(totals.platform, rows, 'platform')}</td><td class="ledger__num">${totalHtml(totals.owner, rows, 'owner')}</td></tr></tfoot>
74
+ </table>`;
75
+ }
76
+
77
+ const api = { money, ledgerHtml };
78
+ if (typeof window !== 'undefined') window.OTBFleetCosts = api;
79
+ if (typeof module !== 'undefined' && module.exports) module.exports = api;
80
+ })();
@@ -0,0 +1,64 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Fleet costs — {{worldName}}</title>
7
+ <meta name="description" content="What each hosted project costs this platform, and an estimate of what its owner pays for their own hosting.">
8
+ <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-09-23-changelist1">
12
+ <link rel="stylesheet" href="/builders/oversight.css">
13
+ <link rel="stylesheet" href="/builders/project-settings.css">
14
+ </head>
15
+ <body data-page="fleet" data-page-title="Fleet costs">
16
+
17
+ <div class="app">
18
+ <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
19
+ <div class="app-scrim" id="app-scrim" hidden></div>
20
+ <div class="app-body">
21
+ <header class="app-topbar" id="app-topbar"></header>
22
+ <div class="app-content">
23
+ <div class="page-head page-head--board">
24
+ <h1 class="page-head__h">Fleet costs</h1>
25
+ <p class="page-head__sub" id="fl-sub">Loading…</p>
26
+ </div>
27
+
28
+ <main id="hall-main">
29
+ <section class="scroll" id="fl-sealed" aria-labelledby="fl-sealed-h" hidden>
30
+ <h2 class="scroll__h" id="fl-sealed-h">Restricted</h2>
31
+ <p class="scroll__lede">This page opens at a higher rank.</p>
32
+ </section>
33
+
34
+ <section class="scroll" id="fl-body" aria-labelledby="fl-body-h" hidden>
35
+ <h2 class="scroll__h" id="fl-body-h">What each project costs</h2>
36
+ <!-- Two different bills, one column each (ADR 0327): a cloud host runs on its
37
+ OWNER's account, so it costs this platform nothing and its owner pays their
38
+ hosting provider directly. Nothing on this page is charged to anyone. -->
39
+ <p class="scroll__lede">
40
+ Two different bills, one column each. The first is what a project costs this
41
+ platform. The second is an estimate of what its owner pays their own hosting
42
+ provider, which bills them directly. A project with no recorded plan reads
43
+ "not priced yet", never $0, and is left out of the total. A self-hosted
44
+ project runs on its owner's own machine, so its bill is not visible to us.
45
+ </p>
46
+ <div id="fl-panel"><span class="kit-skel"></span></div>
47
+ </section>
48
+ </main>
49
+
50
+ <footer class="app-foot" id="app-foot"></footer>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
56
+
57
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
58
+ <script src="/builders/palette.js" defer></script>
59
+ <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
60
+ <script src="/builders/hall-kit.js"></script>
61
+ <script src="/builders/fleet-costs.js"></script>
62
+ <script src="/builders/fleet.js"></script>
63
+ </body>
64
+ </html>
@@ -0,0 +1,45 @@
1
+ // fleet.js — the fleet cost page (task 1001949, ADR 0327): the chrome around
2
+ // fleet-costs.js, which does all the drawing.
3
+ //
4
+ // One read, GET /provisioning/fleet/cost-ledger, gated on `provisioning.fleet.manage`
5
+ // exactly as this page is. A 403 shows the sealed state rather than an empty table,
6
+ // so a refusal is never mistaken for a fleet with nothing in it.
7
+ (() => {
8
+ 'use strict';
9
+
10
+ const API = '/api/bongos';
11
+ const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
12
+ const { escapeHtml } = window.OTB;
13
+ const $ = (id) => document.getElementById(id);
14
+
15
+ function fail(message) {
16
+ $('fl-body').hidden = false;
17
+ $('fl-sub').textContent = 'Unavailable';
18
+ $('fl-panel').innerHTML = `<p class="ps-invalid">${escapeHtml(message)}</p>`;
19
+ }
20
+
21
+ async function load() {
22
+ let res;
23
+ try {
24
+ res = await api.request('GET', `${API}/provisioning/fleet/cost-ledger`);
25
+ } catch (e) {
26
+ return fail('Could not reach the server.');
27
+ }
28
+ if (res.status === 401) { window.location.assign(`${API}/auth/web/start`); return; }
29
+ if (res.status === 403) {
30
+ $('fl-sealed').hidden = false;
31
+ $('fl-sub').textContent = 'Restricted';
32
+ return;
33
+ }
34
+ if (!res.ok) {
35
+ const err = (res.data && res.data.error) || {};
36
+ return fail(err.message && err.message !== err.code ? err.message : 'Could not read the fleet costs.');
37
+ }
38
+ const n = ((res.data && res.data.instances) || []).length;
39
+ $('fl-sub').textContent = `${n} project${n === 1 ? '' : 's'}`;
40
+ $('fl-body').hidden = false;
41
+ $('fl-panel').innerHTML = window.OTBFleetCosts.ledgerHtml(res.data);
42
+ }
43
+
44
+ document.addEventListener('DOMContentLoaded', load);
45
+ })();
@@ -0,0 +1,14 @@
1
+ {
2
+ "_": "FLEET COSTS (task 1001949, ADR 0327) through modules/ui-design/kit/render.js. The page reads ONE authenticated route, GET /provisioning/fleet/cost-ledger, gated on provisioning.fleet.manage, so against the kit's own stub it renders its failed-read state — a state the floors must hold in. To look at the populated ledger run it against the hall-preview harness, whose provisioning__fleet__cost-ledger.json fixture carries two cloud hosts with no recorded plan (every owner cost unpriced, the state live is in today):\n\n node scripts/hall-preview/server.js --port 4620 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/fleet.states.json --base-in http://localhost:4620 --base-out http://localhost:4620",
3
+ "page": "/builders/fleet",
4
+ "surface": "hall-ui",
5
+ "stub": { "prefix": "/builders/" },
6
+ "modeQuery": false,
7
+ "ignoreRequests": ["/api/(gds|bongos)/"],
8
+ "ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b403\\b", "\\b404\\b", "Unauthorized", "Forbidden", "Not Found", "load failed"],
9
+ "states": {
10
+ "ledger": { "auth": true, "actions": [["wait", 800]], "expect": { "visible": ["#fl-body"] } },
11
+ "out": { "auth": false, "actions": [["wait", 600]] }
12
+ },
13
+ "reducedMotion": { "auth": true, "actions": [["wait", 800]] }
14
+ }
@@ -76,7 +76,7 @@
76
76
  <div id="gate-modal-root"></div>
77
77
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
78
78
 
79
- <script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
79
+ <script src="/builders/shell.js?v=2026-09-24-fleetcosts1"></script>
80
80
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
81
81
  <script src="/builders/palette.js" defer></script>
82
82
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>