@bongos/core 1.24.92 → 1.24.93
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 +35 -25
- package/docs/copy-inventory.md +1 -1
- package/docs/copy-registry.json +1 -1
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +4 -2
- package/docs/page-readings.json +4 -4
- package/modules/hall-ui/public/city.js +1 -1
- package/modules/hall-ui/public/goal-map.html +2 -0
- package/modules/hall-ui/public/sky-interaction.js +4 -9
- package/modules/hall-ui/public/sky-visor.js +96 -0
- package/modules/hall-ui/public/thinking.css +8 -0
- package/modules/hall-ui/public/thinking.html +2 -0
- package/modules/hall-ui/public/visor.js +2 -1
- package/modules/hall-ui/records/visor.md +11 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_sky_visor.mjs +150 -0
- package/tests/hall_visor.mjs +1 -1
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/bongos/package-core.js",
|
|
5
|
-
"core_version": "1.24.
|
|
6
|
-
"core_contract": "1.24.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.24.93",
|
|
6
|
+
"core_contract": "1.24.93",
|
|
7
|
+
"source_commit": "8071fd2502f713c6b549097cfd5d79d606f32867",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-11-12T20:
|
|
9
|
+
"built_at": "2026-11-12T20:21:01.886Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 650,
|
|
13
13
|
"agent_docs_stubbed": 32,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 3967,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 4650,
|
|
20
|
+
"tree_sha256": "14cce51acfe462e49a4caaa95f39316299e9da4b7eaad55df49bd1082d03adaf",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2517,12 +2517,12 @@
|
|
|
2517
2517
|
{
|
|
2518
2518
|
"path": "docs/copy-inventory.md",
|
|
2519
2519
|
"mode": "0000644",
|
|
2520
|
-
"sha256": "
|
|
2520
|
+
"sha256": "4f561c4c13b185d12c1cb4c2dce8d7f4415bf22fd3d9f6969556e21335a1f643"
|
|
2521
2521
|
},
|
|
2522
2522
|
{
|
|
2523
2523
|
"path": "docs/copy-registry.json",
|
|
2524
2524
|
"mode": "0000644",
|
|
2525
|
-
"sha256": "
|
|
2525
|
+
"sha256": "e2fd36ea4e5ba73c38737e07f3ced678d330356bd803cb52ecbb65063e85e457"
|
|
2526
2526
|
},
|
|
2527
2527
|
{
|
|
2528
2528
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -3037,7 +3037,7 @@
|
|
|
3037
3037
|
{
|
|
3038
3038
|
"path": "docs/module-api-changelog.md",
|
|
3039
3039
|
"mode": "0000644",
|
|
3040
|
-
"sha256": "
|
|
3040
|
+
"sha256": "bba6458b2c8742106eabe1e8c4646fb9a5ceea75fba0bb3a375b6ea7a986fc80"
|
|
3041
3041
|
},
|
|
3042
3042
|
{
|
|
3043
3043
|
"path": "docs/modules-contract.md",
|
|
@@ -3117,12 +3117,12 @@
|
|
|
3117
3117
|
{
|
|
3118
3118
|
"path": "docs/page-inventory.json",
|
|
3119
3119
|
"mode": "0000644",
|
|
3120
|
-
"sha256": "
|
|
3120
|
+
"sha256": "4b063630995768d45aa24d10d484607495ce6732fbf279488bbba89cccb1c17c"
|
|
3121
3121
|
},
|
|
3122
3122
|
{
|
|
3123
3123
|
"path": "docs/page-readings.json",
|
|
3124
3124
|
"mode": "0000644",
|
|
3125
|
-
"sha256": "
|
|
3125
|
+
"sha256": "a15df86b33d43c4b42bc9854319497d86b3671ffbda88f765bfe8ed3a9ed6fde"
|
|
3126
3126
|
},
|
|
3127
3127
|
{
|
|
3128
3128
|
"path": "docs/project-context.template.md",
|
|
@@ -6232,7 +6232,7 @@
|
|
|
6232
6232
|
{
|
|
6233
6233
|
"path": "modules/hall-ui/public/city.js",
|
|
6234
6234
|
"mode": "0000644",
|
|
6235
|
-
"sha256": "
|
|
6235
|
+
"sha256": "928ef8c8b5f567195c6892673d61e5a953c80f95f4524de1fe258a68fc926e81"
|
|
6236
6236
|
},
|
|
6237
6237
|
{
|
|
6238
6238
|
"path": "modules/hall-ui/public/city.states.json",
|
|
@@ -6517,7 +6517,7 @@
|
|
|
6517
6517
|
{
|
|
6518
6518
|
"path": "modules/hall-ui/public/goal-map.html",
|
|
6519
6519
|
"mode": "0000644",
|
|
6520
|
-
"sha256": "
|
|
6520
|
+
"sha256": "94be9df5343f593a296586185b4b544640063d8f8bd64c1c10d1061b275c37d0"
|
|
6521
6521
|
},
|
|
6522
6522
|
{
|
|
6523
6523
|
"path": "modules/hall-ui/public/goal-map.states.json",
|
|
@@ -7212,7 +7212,7 @@
|
|
|
7212
7212
|
{
|
|
7213
7213
|
"path": "modules/hall-ui/public/sky-interaction.js",
|
|
7214
7214
|
"mode": "0000644",
|
|
7215
|
-
"sha256": "
|
|
7215
|
+
"sha256": "2eae15cb9ec10e0317be9e92670bbaaf21e8157fa53234d1ca458c30c48eec6d"
|
|
7216
7216
|
},
|
|
7217
7217
|
{
|
|
7218
7218
|
"path": "modules/hall-ui/public/sky-layout.js",
|
|
@@ -7234,6 +7234,11 @@
|
|
|
7234
7234
|
"mode": "0000644",
|
|
7235
7235
|
"sha256": "8b9b9495eac0febcf56d4e049f1ceeb9cfdc6fc0031c93d99256dc628a95585a"
|
|
7236
7236
|
},
|
|
7237
|
+
{
|
|
7238
|
+
"path": "modules/hall-ui/public/sky-visor.js",
|
|
7239
|
+
"mode": "0000644",
|
|
7240
|
+
"sha256": "d3b594d333a427fbafb7584be6ceeb965f06d8363bfea322bab61dfbecf8fd3e"
|
|
7241
|
+
},
|
|
7237
7242
|
{
|
|
7238
7243
|
"path": "modules/hall-ui/public/speciality-tools.css",
|
|
7239
7244
|
"mode": "0000644",
|
|
@@ -7387,12 +7392,12 @@
|
|
|
7387
7392
|
{
|
|
7388
7393
|
"path": "modules/hall-ui/public/thinking.css",
|
|
7389
7394
|
"mode": "0000644",
|
|
7390
|
-
"sha256": "
|
|
7395
|
+
"sha256": "b0be3e10a3413a833043dc43d568071bc20040e593b2cad52016b5176be5269a"
|
|
7391
7396
|
},
|
|
7392
7397
|
{
|
|
7393
7398
|
"path": "modules/hall-ui/public/thinking.html",
|
|
7394
7399
|
"mode": "0000644",
|
|
7395
|
-
"sha256": "
|
|
7400
|
+
"sha256": "494e89a7a527be66b61d330047d0c425967f920140bcba6340fa25f51d53f185"
|
|
7396
7401
|
},
|
|
7397
7402
|
{
|
|
7398
7403
|
"path": "modules/hall-ui/public/thinking.states.json",
|
|
@@ -7442,7 +7447,7 @@
|
|
|
7442
7447
|
{
|
|
7443
7448
|
"path": "modules/hall-ui/public/visor.js",
|
|
7444
7449
|
"mode": "0000644",
|
|
7445
|
-
"sha256": "
|
|
7450
|
+
"sha256": "ce6b2a68e041a0e529fed8270405dcd15bb300e1ec5bf1d3c6afbf561ee53936"
|
|
7446
7451
|
},
|
|
7447
7452
|
{
|
|
7448
7453
|
"path": "modules/hall-ui/public/vote-control.js",
|
|
@@ -7752,7 +7757,7 @@
|
|
|
7752
7757
|
{
|
|
7753
7758
|
"path": "modules/hall-ui/records/visor.md",
|
|
7754
7759
|
"mode": "0000644",
|
|
7755
|
-
"sha256": "
|
|
7760
|
+
"sha256": "a1e1421463d9107782c46309339510d6e5c82a38f142ccf35f17fb53effb6c97"
|
|
7756
7761
|
},
|
|
7757
7762
|
{
|
|
7758
7763
|
"path": "modules/hall-ui/records/welcome-tower.md",
|
|
@@ -12102,12 +12107,12 @@
|
|
|
12102
12107
|
{
|
|
12103
12108
|
"path": "package-lock.json",
|
|
12104
12109
|
"mode": "0000644",
|
|
12105
|
-
"sha256": "
|
|
12110
|
+
"sha256": "aca584f965a5191a12f9c0547ed3bc0be72a13f8043b13a2d3a2ec65b08d8632"
|
|
12106
12111
|
},
|
|
12107
12112
|
{
|
|
12108
12113
|
"path": "package.json",
|
|
12109
12114
|
"mode": "0000644",
|
|
12110
|
-
"sha256": "
|
|
12115
|
+
"sha256": "c0cae817310174c3e98cef573263c33c9807c1c8086b050fc13f5f1a18001b4d"
|
|
12111
12116
|
},
|
|
12112
12117
|
{
|
|
12113
12118
|
"path": "public-docs/index.html",
|
|
@@ -12127,7 +12132,7 @@
|
|
|
12127
12132
|
{
|
|
12128
12133
|
"path": "release-notes.json",
|
|
12129
12134
|
"mode": "0000644",
|
|
12130
|
-
"sha256": "
|
|
12135
|
+
"sha256": "6aff28036a7071c6895abc1f682477f4cd41b1fe02bf96fad15a7f42c05de7d9"
|
|
12131
12136
|
},
|
|
12132
12137
|
{
|
|
12133
12138
|
"path": "scripts/bongos/CLAUDE.md",
|
|
@@ -16132,7 +16137,7 @@
|
|
|
16132
16137
|
{
|
|
16133
16138
|
"path": "src/module-api.js",
|
|
16134
16139
|
"mode": "0000644",
|
|
16135
|
-
"sha256": "
|
|
16140
|
+
"sha256": "a8399bb92d13d12c44c07cf942398f10b93590bbeb054610a5d9952cd9b044ab"
|
|
16136
16141
|
},
|
|
16137
16142
|
{
|
|
16138
16143
|
"path": "src/module-loader/catalog.js",
|
|
@@ -19032,7 +19037,7 @@
|
|
|
19032
19037
|
{
|
|
19033
19038
|
"path": "tests/hall_city.mjs",
|
|
19034
19039
|
"mode": "0000644",
|
|
19035
|
-
"sha256": "
|
|
19040
|
+
"sha256": "3505970e7551ed2d4ca8767ba0b3b63f564ffc6d76706a181db8826aeb4d8efd"
|
|
19036
19041
|
},
|
|
19037
19042
|
{
|
|
19038
19043
|
"path": "tests/hall_city_card_words.mjs",
|
|
@@ -19309,6 +19314,11 @@
|
|
|
19309
19314
|
"mode": "0000644",
|
|
19310
19315
|
"sha256": "77589e0ab28f723090eeb1863e1a431ed6fec9c761ece66239431d3116b1c63a"
|
|
19311
19316
|
},
|
|
19317
|
+
{
|
|
19318
|
+
"path": "tests/hall_sky_visor.mjs",
|
|
19319
|
+
"mode": "0000644",
|
|
19320
|
+
"sha256": "ffbf1958a0d19b498ebaa7d1b3d0865252c33bc3eb7ca9f58f10bb9861d05927"
|
|
19321
|
+
},
|
|
19312
19322
|
{
|
|
19313
19323
|
"path": "tests/hall_start_screen.mjs",
|
|
19314
19324
|
"mode": "0000644",
|
|
@@ -19392,7 +19402,7 @@
|
|
|
19392
19402
|
{
|
|
19393
19403
|
"path": "tests/hall_visor.mjs",
|
|
19394
19404
|
"mode": "0000644",
|
|
19395
|
-
"sha256": "
|
|
19405
|
+
"sha256": "7cebc92c9a8c34eb1d11ef0316767fd0ff3fc01dfe20917f7b4194f89174830c"
|
|
19396
19406
|
},
|
|
19397
19407
|
{
|
|
19398
19408
|
"path": "tests/hall_work_history_id.mjs",
|
package/docs/copy-inventory.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
| surface | lives at | files | strings |
|
|
12
12
|
|---|---|---|---|
|
|
13
|
-
| `builders-hall` | `modules/hall-ui/public` |
|
|
13
|
+
| `builders-hall` | `modules/hall-ui/public` | 180 | 2072 |
|
|
14
14
|
| `status-dashboard` | `modules/status-ui/public` | 4 | 89 |
|
|
15
15
|
| `landing` | `modules/public-landing/public` | 10 | 1023 |
|
|
16
16
|
|
package/docs/copy-registry.json
CHANGED
|
@@ -3432,5 +3432,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3432
3432
|
landed since 1.24.89 with no explicit bump. run 43217068783. (task 1002620)
|
|
3433
3433
|
1.24.92 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3434
3434
|
landed since 1.24.90 with no explicit bump. run 43296447627. (task 1002620)
|
|
3435
|
+
1.24.93 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3436
|
+
landed since 1.24.92 with no explicit bump. run 43299203682. (task 1002620)
|
|
3435
3437
|
---------------------------------------------------------------------------
|
|
3436
3438
|
```
|
package/docs/page-inventory.json
CHANGED
|
@@ -414,7 +414,8 @@
|
|
|
414
414
|
"modules/hall-ui/public/sky-interaction.js",
|
|
415
415
|
"modules/hall-ui/public/sky-panel.js",
|
|
416
416
|
"modules/hall-ui/public/sky-composer.js",
|
|
417
|
-
"modules/hall-ui/public/sky-tour.js"
|
|
417
|
+
"modules/hall-ui/public/sky-tour.js",
|
|
418
|
+
"modules/hall-ui/public/sky-visor.js"
|
|
418
419
|
],
|
|
419
420
|
"states_files": [
|
|
420
421
|
"modules/hall-ui/public/goal-map.states.json"
|
|
@@ -1324,7 +1325,8 @@
|
|
|
1324
1325
|
"modules/hall-ui/public/sky-interaction.js",
|
|
1325
1326
|
"modules/hall-ui/public/sky-panel.js",
|
|
1326
1327
|
"modules/hall-ui/public/sky-composer.js",
|
|
1327
|
-
"modules/hall-ui/public/sky-tour.js"
|
|
1328
|
+
"modules/hall-ui/public/sky-tour.js",
|
|
1329
|
+
"modules/hall-ui/public/sky-visor.js"
|
|
1328
1330
|
],
|
|
1329
1331
|
"states_files": [
|
|
1330
1332
|
"modules/hall-ui/public/thinking.states.json"
|
package/docs/page-readings.json
CHANGED
|
@@ -283,7 +283,7 @@
|
|
|
283
283
|
"surface": "builders",
|
|
284
284
|
"title": "The city",
|
|
285
285
|
"states_read": ["city","fullscreen","focus","pins","living","card","card-ask"],
|
|
286
|
-
"files_hash": "
|
|
286
|
+
"files_hash": "d9dd949934a5b719",
|
|
287
287
|
"reading_hash": "277096d903879a57",
|
|
288
288
|
"counts": {"lines":26,"placed":9,"shared":6,"unplaced":11},
|
|
289
289
|
"omitted": {"data":7},
|
|
@@ -613,7 +613,7 @@
|
|
|
613
613
|
"surface": "builders",
|
|
614
614
|
"title": "The sky",
|
|
615
615
|
"states_read": ["tour","sky","wells","ledger","full","full-back","out"],
|
|
616
|
-
"files_hash": "
|
|
616
|
+
"files_hash": "f54ebe767edaf0c8",
|
|
617
617
|
"reading_hash": "4a879397a84631e3",
|
|
618
618
|
"counts": {"lines":50,"placed":25,"shared":6,"unplaced":19},
|
|
619
619
|
"omitted": {"data":10},
|
|
@@ -954,7 +954,7 @@
|
|
|
954
954
|
"surface": "builders",
|
|
955
955
|
"title": "Builders' Hall",
|
|
956
956
|
"states_read": ["home","out","founding","genesis","decide","demo"],
|
|
957
|
-
"files_hash": "
|
|
957
|
+
"files_hash": "6fce98a5eabe1c82",
|
|
958
958
|
"reading_hash": "42901560779fc4d1",
|
|
959
959
|
"counts": {"lines":32,"placed":12,"shared":8,"unplaced":12},
|
|
960
960
|
"omitted": {"data":5},
|
|
@@ -3159,7 +3159,7 @@
|
|
|
3159
3159
|
"surface": "builders",
|
|
3160
3160
|
"title": "Your thinking",
|
|
3161
3161
|
"states_read": ["tour","sky","list","simple","full-idea","full","full-list","full-back","out"],
|
|
3162
|
-
"files_hash": "
|
|
3162
|
+
"files_hash": "3cce7079079c7782",
|
|
3163
3163
|
"reading_hash": "b73f848b46b0f9b8",
|
|
3164
3164
|
"counts": {"lines":50,"placed":25,"shared":6,"unplaced":19},
|
|
3165
3165
|
"omitted": {"data":10},
|
|
@@ -770,7 +770,7 @@
|
|
|
770
770
|
return {
|
|
771
771
|
name: 'city', place: 'Governor city',
|
|
772
772
|
actions: [
|
|
773
|
-
{ label: label
|
|
773
|
+
{ label: label, key: 'd', bind: true, lead: true, run: function () {
|
|
774
774
|
if (window.GovCityDecide && window.GovDeck) openDecide(null);
|
|
775
775
|
else if (window.location) window.location.hash = '#decide';
|
|
776
776
|
} },
|
|
@@ -158,5 +158,7 @@
|
|
|
158
158
|
<script src="/builders/sky-panel.js?v=2026-09-30-uat1"></script>
|
|
159
159
|
<script src="/builders/sky-composer.js?v=2026-09-27-split"></script>
|
|
160
160
|
<script src="/builders/sky-tour.js?v=2026-09-27-split"></script>
|
|
161
|
+
<!-- task 1005210: the sky as the visor's scene (ADR 0403), after every other sky file -->
|
|
162
|
+
<script src="/builders/sky-visor.js"></script>
|
|
161
163
|
</body>
|
|
162
164
|
</html>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// `filed_via` exists on at least one thought; until then it is hidden and
|
|
14
14
|
// every thought shows (the README's "must not ship" row).
|
|
15
15
|
//
|
|
16
|
-
// Keys: L the list, ? the tour, 0 fit, / the composer, Escape closes, arrows
|
|
16
|
+
// Keys: L the list, ? the tour, 0 fit, / the composer (J jumps, from the visor in game mode), Escape closes, arrows
|
|
17
17
|
// pan, + and - zoom.
|
|
18
18
|
|
|
19
19
|
(() => {
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
// An Escape the sky USED (the tour, the menu, a pick) calls preventDefault, so
|
|
76
76
|
// the shell's full-screen switch leaves that press alone (task 1004327).
|
|
77
77
|
addEventListener('keydown', (e) => {
|
|
78
|
-
if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')) { if (e.key === 'Escape') e.target.blur(); return; }
|
|
78
|
+
if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')) { if (e.key === 'Escape') { e.target.blur(); if (e.target.id === 'thought' || e.target.id === 'jump') { e.preventDefault(); if (SKY.closeField) SKY.closeField(e.target.id, true); } } return; }
|
|
79
79
|
const { cam } = S; const step = 60 / cam.k;
|
|
80
80
|
if (e.key === 'ArrowLeft') { cam.x -= step; S.dirty = true; } else if (e.key === 'ArrowRight') { cam.x += step; S.dirty = true; } else if (e.key === 'ArrowUp') { cam.y -= step; S.dirty = true; } else if (e.key === 'ArrowDown') { cam.y += step; S.dirty = true; }
|
|
81
81
|
else if (e.key === '+' || e.key === '=') { cam.k = clamp(cam.k * 1.3, .015, 9); S.dirty = true; } else if (e.key === '-') { cam.k = clamp(cam.k / 1.3, .015, 9); S.dirty = true; }
|
|
82
|
-
else if (e.key === '0') SKY.fitAll(); else if (e.key === 'Escape') { if (!$('tour').hidden) { e.preventDefault(); SKY.endTour(); } else if (!menu.hidden) { e.preventDefault(); hideMenu(); } else { if (S.picked || !$('panel').hidden) e.preventDefault(); pick(null); } } else if (e.key === '/') { e.preventDefault(); $('thought').focus(); }
|
|
82
|
+
else if (e.key === '0') SKY.fitAll(); else if (e.key === 'Escape') { if (!$('tour').hidden) { e.preventDefault(); SKY.endTour(); } else if (!menu.hidden) { e.preventDefault(); hideMenu(); } else { if (S.picked || !$('panel').hidden) e.preventDefault(); pick(null); } } else if (e.key === '/') { e.preventDefault(); if (SKY.openField) SKY.openField('thought'); else $('thought').focus(); }
|
|
83
83
|
else if (e.key === '?') SKY.startTour(); else if (e.key.toLowerCase() === 'l') SKY.toggleRoom(); else return;
|
|
84
84
|
});
|
|
85
85
|
function pick(h, fly) { if (h && h.kind === 'ltask') { SKY.renderPanel({ kind: 'ltask', ref: h.ref }); S.dirty = true; return; } S.picked = h; SKY.renderPanel(S.picked); SKY.renderRoom(); S.dirty = true; if (fly && S.picked && S.lens !== 'simple') { if (S.picked.kind === 'goal') SKY.fitGoal(S.picked.ref); else if (S.picked.kind === 'idea') SKY.fitThought(S.picked.ref); } }
|
|
@@ -148,10 +148,5 @@
|
|
|
148
148
|
|
|
149
149
|
Object.assign(SKY, { card, menu, glyphHtml, ideaTitle, sizeWords, showCard, showMenu, hideMenu, pick, setLens, togglePill, updateStats, altText, TAU, hash });
|
|
150
150
|
|
|
151
|
-
// task 1005148: the sky as the visor's scene
|
|
152
|
-
// "Back to centre" is the camera's own fitAll, and the keys listed are the ones bound above.
|
|
153
|
-
window.__visorScene = { name: 'sky', place: '', // the sky's own heading names the place
|
|
154
|
-
actions: [{ label: 'Back to centre', key: '0', run: () => SKY.fitAll() }],
|
|
155
|
-
keys: [{ label: 'Look around', key: 'drag' }, { label: 'List view', key: 'L' }, { label: 'Search', key: '/' }, { label: 'Take the tour', key: '?' }] };
|
|
156
|
-
if (window.OTBVisor) window.OTBVisor.scene(window.__visorScene);
|
|
151
|
+
// task 1005148 / 1005210: the sky as the visor's scene lives in sky-visor.js, after this file.
|
|
157
152
|
})();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// modules/hall-ui/public/sky-visor.js — THE SKY as the visor's scene (task 1005148; task
|
|
2
|
+
// 1005210, ADR 0403). Loaded after sky-interaction.js on /goal-map and /thinking.
|
|
3
|
+
//
|
|
4
|
+
// The visor owns no camera (ADR 0399 §2.2): every action here presses one of the sky's own
|
|
5
|
+
// controls or opens one of its own fields, so a pill does exactly what it did in classic mode.
|
|
6
|
+
(() => {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const SKY = window.OTBSky; const S = SKY.S;
|
|
10
|
+
const { $, on } = SKY;
|
|
11
|
+
|
|
12
|
+
// task 1005210 (ADR 0403): in game mode the visor is the control and the sky is the view. Its
|
|
13
|
+
// top row (the lens switch and every pill), the jump field and the thought composer leave the
|
|
14
|
+
// scene (thinking.css, html.visor-on) and come back as visor actions: writing a thought is the
|
|
15
|
+
// lead action, on the sky's own "/"; jumping is J; each pill is a row in the Actions sheet
|
|
16
|
+
// that presses the pill itself, so a pill does exactly what it did. The rows are read off the
|
|
17
|
+
// page's own buttons (their words, their pressed state, whether this lens shows them), and the
|
|
18
|
+
// scene registers again whenever one of those changes. Panning, zooming and picking a star
|
|
19
|
+
// stay in the sky. In classic mode nothing here shows or moves.
|
|
20
|
+
const FIELDS = ['thought', 'jump'];
|
|
21
|
+
function fieldWrap(id) { const el = $(id); return el && el.closest ? el.closest('.composer, .jump') : null; }
|
|
22
|
+
function openField(id) {
|
|
23
|
+
for (const other of FIELDS) if (other !== id) closeField(other, true);
|
|
24
|
+
const wrap = fieldWrap(id); if (wrap) wrap.classList.add('is-open');
|
|
25
|
+
$(id).focus();
|
|
26
|
+
}
|
|
27
|
+
// A field closes when it is left with nothing in it, or on Escape (its words are kept).
|
|
28
|
+
// The composer stays while a full idea is being asked or a receipt waits to be read.
|
|
29
|
+
function closeField(id, force) {
|
|
30
|
+
const wrap = fieldWrap(id); if (!wrap || !wrap.classList.contains('is-open')) return;
|
|
31
|
+
if (!force) {
|
|
32
|
+
if (wrap.contains(document.activeElement)) return;
|
|
33
|
+
if ($(id).value.trim()) return;
|
|
34
|
+
if (id === 'thought' && (!$('fullSteps').hidden || !$('receipt').hidden)) return;
|
|
35
|
+
}
|
|
36
|
+
wrap.classList.remove('is-open');
|
|
37
|
+
}
|
|
38
|
+
for (const id of FIELDS) {
|
|
39
|
+
const wrap = fieldWrap(id); if (!wrap) continue;
|
|
40
|
+
wrap.addEventListener('focusout', () => setTimeout(() => closeField(id), 0));
|
|
41
|
+
}
|
|
42
|
+
on('receiptX', 'click', () => setTimeout(() => closeField('thought'), 0));
|
|
43
|
+
// the sky binds these keys itself (sky-interaction.js); the rest are rows in the sheet
|
|
44
|
+
const OWN_KEYS = { roomBtn: 'L', tourBtn: '?', fitBtn: '0' };
|
|
45
|
+
function visible(el) { return !!el && !el.hidden && !(el.closest && el.closest('[hidden]')); }
|
|
46
|
+
function rowFor(b) {
|
|
47
|
+
const words = (b.textContent || '').trim();
|
|
48
|
+
const name = words.length > 1 ? words : (b.getAttribute('aria-label') || b.getAttribute('title') || '');
|
|
49
|
+
const pressed = b.getAttribute('aria-pressed');
|
|
50
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1) + (pressed == null ? '' : pressed === 'true' ? ' · on' : ' · off');
|
|
51
|
+
return { label, key: OWN_KEYS[b.id] || '', run: () => b.click() };
|
|
52
|
+
}
|
|
53
|
+
// The foot's clock and legend are the sky's too: in game mode each shows only while the visor
|
|
54
|
+
// has it open (thinking.css), so the scene carries none of their buttons until asked.
|
|
55
|
+
function footOpen(id) { const el = $(id); return !!el && el.classList.contains('is-open'); }
|
|
56
|
+
function footRow(id, name) {
|
|
57
|
+
return { label: name + (footOpen(id) ? ' · on' : ' · off'), key: '', run: () => {
|
|
58
|
+
const el = $(id); if (!el) return;
|
|
59
|
+
const open = !el.classList.contains('is-open');
|
|
60
|
+
el.classList.toggle('is-open', open);
|
|
61
|
+
// a clock classic mode was told to hide comes back through its own pill
|
|
62
|
+
if (open && el.hidden && id === 'time') $('clockBtn').click();
|
|
63
|
+
} };
|
|
64
|
+
}
|
|
65
|
+
function skyScene() {
|
|
66
|
+
const actions = [{ label: 'Write a thought', key: '/', lead: true, run: () => openField('thought') }];
|
|
67
|
+
if (visible(fieldWrap('jump'))) actions.push({ label: 'Jump to a goal or a thought', key: 'j', bind: true, run: () => openField('jump') });
|
|
68
|
+
document.querySelectorAll('#lensGroup button, #tools button').forEach((b) => {
|
|
69
|
+
if (!visible(b)) return;
|
|
70
|
+
// the clock pill keeps classic mode's remembered choice; in game mode the clock opens from here
|
|
71
|
+
if (b.id === 'clockBtn') { actions.push(footRow('time', 'Clock')); return; }
|
|
72
|
+
actions.push(rowFor(b));
|
|
73
|
+
});
|
|
74
|
+
if (visible($('legend'))) actions.push(footRow('legend', 'Legend'));
|
|
75
|
+
return { name: 'sky', place: '', // the sky's own heading names the place
|
|
76
|
+
actions, keys: [{ label: 'Look around', key: 'drag' }, { label: 'Zoom', key: '+ −' }] };
|
|
77
|
+
}
|
|
78
|
+
let sceneSig = null;
|
|
79
|
+
function registerScene() {
|
|
80
|
+
const next = skyScene();
|
|
81
|
+
const sig = next.actions.map((a) => a.label + '|' + a.key).join('\n');
|
|
82
|
+
if (sig === sceneSig) return;
|
|
83
|
+
sceneSig = sig;
|
|
84
|
+
window.__visorScene = next;
|
|
85
|
+
if (window.OTBVisor) window.OTBVisor.scene(next);
|
|
86
|
+
}
|
|
87
|
+
// a pill's pressed state or a lens's set of pills changed: the sheet follows
|
|
88
|
+
if (typeof MutationObserver === 'function') {
|
|
89
|
+
const watch = new MutationObserver(registerScene);
|
|
90
|
+
for (const id of ['tools', 'lensGroup']) { const el = $(id); if (el) watch.observe(el, { subtree: true, attributes: true, attributeFilter: ['aria-pressed', 'hidden'] }); }
|
|
91
|
+
const jump = fieldWrap('jump'); if (jump) watch.observe(jump, { attributes: true, attributeFilter: ['hidden'] });
|
|
92
|
+
for (const id of ['time', 'legend']) { const el = $(id); if (el) watch.observe(el, { attributes: true, attributeFilter: ['class', 'hidden'] }); }
|
|
93
|
+
}
|
|
94
|
+
Object.assign(SKY, { openField, closeField, skyScene });
|
|
95
|
+
registerScene();
|
|
96
|
+
})();
|
|
@@ -315,3 +315,11 @@ html.visor-on .composer, html.visor-on .jump { top: calc(140px + env(safe-area-i
|
|
|
315
315
|
html.visor-on .panel, html.visor-on .room { top: calc(204px + env(safe-area-inset-top, 0px)); bottom: calc(200px + env(safe-area-inset-bottom, 0px)); }
|
|
316
316
|
html.visor-on .foot { padding-bottom: calc(132px + env(safe-area-inset-bottom, 0px)); }
|
|
317
317
|
html.visor-on .tour, html.visor-on .start { bottom: calc(210px + env(safe-area-inset-bottom, 0px)); }
|
|
318
|
+
/* task 1005210 (ADR 0403): with the visor up the sky carries no control of its own. The top
|
|
319
|
+
row's lens switch and pills are rows in the visor's Actions sheet; the composer and the jump
|
|
320
|
+
field open from the visor ("Write a thought" on /, "Jump" on J) and close again when left
|
|
321
|
+
empty (sky-interaction.js openField / closeField). The brand and its figures stay, as words. */
|
|
322
|
+
html.visor-on .top .tools, html.visor-on .top .lens { display: none; }
|
|
323
|
+
html.visor-on .composer:not(.is-open), html.visor-on .jump:not(.is-open) { display: none; }
|
|
324
|
+
html.visor-on .jump.is-open:not([hidden]) { display: block; }
|
|
325
|
+
html.visor-on .time:not(.is-open), html.visor-on .legend:not(.is-open) { display: none; }
|
|
@@ -159,5 +159,7 @@
|
|
|
159
159
|
<script src="/builders/sky-panel.js?v=2026-09-30-uat1"></script>
|
|
160
160
|
<script src="/builders/sky-composer.js?v=2026-09-27-split"></script>
|
|
161
161
|
<script src="/builders/sky-tour.js?v=2026-09-27-split"></script>
|
|
162
|
+
<!-- task 1005210: the sky as the visor's scene (ADR 0403), after every other sky file -->
|
|
163
|
+
<script src="/builders/sky-visor.js"></script>
|
|
162
164
|
</body>
|
|
163
165
|
</html>
|
|
@@ -274,7 +274,8 @@
|
|
|
274
274
|
sheet.setAttribute('role', 'dialog');
|
|
275
275
|
sheet.setAttribute('aria-labelledby', 'visor-sheet-title');
|
|
276
276
|
sheet.innerHTML = sheetHtml(id);
|
|
277
|
-
sheet
|
|
277
|
+
// no listener of its own: the sheet sits inside #visor, whose click listener already
|
|
278
|
+
// sees its rows (a second one ran every row twice, so a toggle undid itself; task 1005210)
|
|
278
279
|
root.appendChild(sheet);
|
|
279
280
|
var btn = root.querySelector('[data-visor-sheet]');
|
|
280
281
|
if (btn) { btn.setAttribute('aria-expanded', 'true'); btn.hidden = true; }
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
# The visor — the first-person HUD over every scene (task 1005148, BONGOS-V3 R08; ADR 0399)
|
|
2
2
|
|
|
3
|
-
Covers: `visor.js` + `visor.css`, loaded on Home, `/city`, `/goal-map` and `/thinking`, and the three scene registrations (`sky-
|
|
3
|
+
Covers: `visor.js` + `visor.css`, loaded on Home, `/city`, `/goal-map` and `/thinking`, and the three scene registrations (`sky-visor.js`, `city.js`, `home-city.js`). Spec: `docs/specs/bongos-v3-game-conversion.md` C2 `you-are-a-character-in-first-person`. Game mode itself is in `game-mode.md`.
|
|
4
4
|
|
|
5
5
|
## The rule every game page keeps (ADR 0403, owner 2026-11-11)
|
|
6
6
|
|
|
7
|
-
The visor is the control and the scene is the view: every main call to action in game mode is on the glass (the accent button, the key hints, the Actions sheet), and a scene carries none of its own. A scene that has something to do registers it with `OTBVisor.scene({ actions, keys })`. ADR 0403 §3 lists the scenes that do not keep it yet; the Governor City keeps it since task 1005209 (below).
|
|
7
|
+
The visor is the control and the scene is the view: every main call to action in game mode is on the glass (the accent button, the key hints, the Actions sheet), and a scene carries none of its own. A scene that has something to do registers it with `OTBVisor.scene({ actions, keys })`. ADR 0403 §3 lists the scenes that do not keep it yet; the Governor City keeps it since task 1005209 and the sky since task 1005210 (below).
|
|
8
8
|
|
|
9
9
|
## A place's own buttons on the glass (task 1005209, the Governor City first)
|
|
10
10
|
|
|
11
11
|
- **The scene contract grew two flags.** A scene action's `key` is still a key the scene binds itself, shown beside it in the sheet. With `bind: true` the visor binds that key instead, after the character's keys (a clash goes to the character), for a scene button that had no key of its own. With `lead: true` the action is also the first quiet key hint on the glass (`.visor__hint--place`). Every scene action is still a row under "This place" in the sheet.
|
|
12
12
|
- **The glass binds keys in game mode only.** `onKey` now returns unless `html[data-game="on"]`: the glass was never drawn outside game mode, so its keys no longer act there either (before, a held character's letter keys still ran on a classic page).
|
|
13
|
-
- **The Governor City keeps ADR 0403.** In game mode (`html.visor-on`) the door's button (`#cityStart`) and the archived Street links leave the scene (`city.css`). The door is the lead action, on D, in the door's own words ("
|
|
13
|
+
- **The Governor City keeps ADR 0403.** In game mode (`html.visor-on`) the door's button (`#cityStart`) and the archived Street links leave the scene (`city.css`). The door is the lead action, on D, in the door's own words and case ("decide 37 in a row", or "start governing"; lowercase is the door's, task 1005210); it re-registers only when those words change. The archived links are rows in the sheet ("Archived: Street", "Archived: Building Level Views"), read off the page's own archived line so the words and the addresses cannot drift; reached by A and the keyboard. The city line stays on the scene as words, raised to `bottom: 224px` because the extra hint wraps the hints onto a second line at a desk width and the centred accent button rises with it. A building's card is a pop-up, not the scene, and keeps its own buttons and keys. Classic mode is as it was.
|
|
14
|
+
|
|
15
|
+
## The sky on the glass (task 1005210)
|
|
16
|
+
|
|
17
|
+
- **What left the sky in game mode** (`thinking.css`, under `html.visor-on`): the top row's lens switch and every pill, the jump field, the thought composer, and the foot's clock and legend. The brand line and its figures stay, as words. A building-like pop-up (the panel, the list, the start dialog, the tour) is not the scene and keeps its buttons.
|
|
18
|
+
- **Where it went** (`sky-visor.js`, loaded after every other sky file; the registration moved out of `sky-interaction.js`). "Write a thought" is the lead action, on the sky's own `/`; "Jump to a goal or a thought" is J (bound by the visor). Each visible pill is a row in the sheet, in its own words with "· on" / "· off" for a toggle, and pressing the row presses the pill, so it does exactly what it did; List, the tour and Fit keep the sky's own L, ? and 0. "Clock" and "Legend" open the foot's two parts. The rows are read off the page's buttons and the scene registers again when a pill's pressed state, a pill's presence (the lens decides it) or the foot changes.
|
|
19
|
+
- **The fields open and close.** A field opens with `is-open` (one at a time) and closes when focus leaves it empty, or on Escape, which keeps its words and is spent there (no options menu). The composer stays open while a full idea is being asked or a receipt waits.
|
|
20
|
+
- **A visor fix that came with it.** The sheet had a click listener of its own inside `#visor`'s, so every row ran twice; a toggle row undid itself. The sheet's listener is gone.
|
|
21
|
+
- **Not done:** the tour still spots the pills it explains, which are off the glass in game mode. Tests: `tests/hall_sky_visor.mjs` (both pages' own markup, the real visor and sheets, in a real browser).
|
|
14
22
|
|
|
15
23
|
## The minimal visor (Helm look 3, task 1005195)
|
|
16
24
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.93",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.93",
|
|
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.24.
|
|
3
|
+
"version": "1.24.93",
|
|
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/release-notes.json
CHANGED
|
@@ -11657,5 +11657,11 @@
|
|
|
11657
11657
|
"id": "1005206",
|
|
11658
11658
|
"text": "The screen you land on after signing in, where you pick Game or Classic, now looks like the V3 game start menu: your usual choice is the bright salmon bar with its Enter key, the other a quiet row."
|
|
11659
11659
|
}
|
|
11660
|
+
],
|
|
11661
|
+
"1.24.93": [
|
|
11662
|
+
{
|
|
11663
|
+
"id": "1005210",
|
|
11664
|
+
"text": "In game mode the sky (Goals and Thinking) no longer has buttons of its own: \"Write a thought\" is on the visor (press /), jumping is J, and the lens switch, the pills, the clock and the legend are rows in the visor's Actions li"
|
|
11665
|
+
}
|
|
11660
11666
|
]
|
|
11661
11667
|
}
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.93'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/hall_city.mjs
CHANGED
|
@@ -514,7 +514,7 @@ test("game mode: the door is the visor's lead action on D, and the archived link
|
|
|
514
514
|
const last = scenes[scenes.length - 1];
|
|
515
515
|
assert.equal(last.place, 'Governor city');
|
|
516
516
|
const [lead, street, views] = last.actions;
|
|
517
|
-
assert.equal(lead.label, '
|
|
517
|
+
assert.equal(lead.label, 'start governing', 'what the door says, before the living city reads who it waits on (hall_city_living pins the count)');
|
|
518
518
|
assert.deepEqual([lead.key, lead.bind, lead.lead], ['d', true, true], 'it is on the glass, and D (which nothing else binds) presses it');
|
|
519
519
|
assert.deepEqual([street.label, views.label], ['Archived: Street', 'Archived: Building Level Views'], "the line's own words, read off the page");
|
|
520
520
|
assert.ok(!street.lead && !views.lead && !street.bind && !views.bind, 'the archived links are rows in the sheet, reached by A then the keyboard');
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// tests/hall_sky_visor.mjs — the sky as the visor's scene in game mode (task 1005210; ADR 0403).
|
|
2
|
+
//
|
|
3
|
+
// In game mode the visor is the control and the sky is the view: the sky's top row, its jump
|
|
4
|
+
// field, its thought composer and its foot's clock and legend leave the scene and come back as
|
|
5
|
+
// visor actions (sky-visor.js, thinking.css). Driven in a real browser over the page's OWN
|
|
6
|
+
// markup (goal-map.html's and thinking.html's <main>, word for word) with the real visor and
|
|
7
|
+
// the hall's real sheets. The pills are wired here the way sky-interaction.js wires them (a
|
|
8
|
+
// press flips aria-pressed), because what is under test is the visor's side: that each pill is
|
|
9
|
+
// a row, that a row presses its pill exactly once, and that the rows follow the pills.
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { test, after } from 'node:test';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
|
+
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
20
|
+
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
21
|
+
|
|
22
|
+
test('source: both sky pages load sky-visor.js after every other sky file', () => {
|
|
23
|
+
for (const p of ['goal-map.html', 'thinking.html']) {
|
|
24
|
+
const html = read(p);
|
|
25
|
+
const at = html.indexOf('/builders/sky-visor.js');
|
|
26
|
+
assert.ok(at > 0, `${p} loads it`);
|
|
27
|
+
for (const f of ['sky-interaction.js', 'sky-panel.js', 'sky-composer.js', 'sky-tour.js']) assert.ok(html.indexOf('/builders/' + f) < at, `${p}: after ${f}`);
|
|
28
|
+
}
|
|
29
|
+
assert.doesNotMatch(read('sky-interaction.js'), /window\.__visorScene = /, 'one registration, in sky-visor.js');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('source: the sky keeps its controls in classic mode; in game mode they open from the visor', () => {
|
|
33
|
+
const css = read('thinking.css');
|
|
34
|
+
for (const rule of [
|
|
35
|
+
'html.visor-on .top .tools, html.visor-on .top .lens { display: none; }',
|
|
36
|
+
'html.visor-on .composer:not(.is-open), html.visor-on .jump:not(.is-open) { display: none; }',
|
|
37
|
+
'html.visor-on .time:not(.is-open), html.visor-on .legend:not(.is-open) { display: none; }',
|
|
38
|
+
]) assert.ok(css.includes('\n' + rule), rule);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ---------- in a browser ----------
|
|
42
|
+
const lib = require(path.join(ROOT, 'modules', 'ui-design', 'kit', 'lib.js'));
|
|
43
|
+
let browser = null;
|
|
44
|
+
try { browser = await lib.launchBrowser(); } catch (e) { if (!(e instanceof lib.NoBrowserError)) throw e; }
|
|
45
|
+
after(() => browser && browser.close());
|
|
46
|
+
const skip = !browser && 'no system Edge or Chrome';
|
|
47
|
+
|
|
48
|
+
const ORIGIN = 'http://hall.test';
|
|
49
|
+
const TYPES = { '.js': 'text/javascript', '.css': 'text/css', '.png': 'image/png' };
|
|
50
|
+
const mainOf = (page) => { const html = read(page); return html.slice(html.indexOf('<main'), html.indexOf('</main>') + 7); };
|
|
51
|
+
// The sky's own wiring, as far as the visor needs it: SKY.$ / on / S, and a press that flips.
|
|
52
|
+
const STUB = `<script>
|
|
53
|
+
window.OTB = { escapeHtml: (s) => String(s) };
|
|
54
|
+
window.OTBCraftMode = { character() { return 'artist'; }, current() { return null; }, me() { return { builder: { total_credits: 10 } }; }, spriteSrc() { return ''; }, openTool() {} };
|
|
55
|
+
const $ = (id) => document.getElementById(id);
|
|
56
|
+
window.OTBSky = { $, on: (id, ev, fn) => { const el = $(id); if (el) el.addEventListener(ev, fn); }, S: { clockHidden: false } };
|
|
57
|
+
window.presses = {};
|
|
58
|
+
document.querySelectorAll('#tools button, #lensGroup button').forEach((b) => b.addEventListener('click', () => {
|
|
59
|
+
window.presses[b.id] = (window.presses[b.id] || 0) + 1;
|
|
60
|
+
if (b.hasAttribute('aria-pressed')) b.setAttribute('aria-pressed', String(b.getAttribute('aria-pressed') !== 'true'));
|
|
61
|
+
}));
|
|
62
|
+
</script>`;
|
|
63
|
+
const PAGE = (page, game) => `<!doctype html><html data-theme="dark" data-faces="helm"${game ? ' data-game="on"' : ''}><head><meta charset="utf-8">
|
|
64
|
+
<link rel="stylesheet" href="/builders/style.css"><link rel="stylesheet" href="/builders/visor.css"><link rel="stylesheet" href="/builders/thinking.css"></head>
|
|
65
|
+
<body data-page="${page.replace('.html', '')}">${mainOf(page)}${STUB}
|
|
66
|
+
<script src="/builders/shell-game.js"></script><script src="/builders/visor.js"></script><script src="/builders/sky-visor.js"></script></body></html>`;
|
|
67
|
+
|
|
68
|
+
async function open(page, { game = true } = {}) {
|
|
69
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
70
|
+
await ctx.route(ORIGIN + '/**', (route) => {
|
|
71
|
+
const p = new URL(route.request().url()).pathname;
|
|
72
|
+
if (p === '/') return route.fulfill({ contentType: 'text/html', body: PAGE(page, game) });
|
|
73
|
+
const f = path.join(HALL, p.replace(/^\/builders\//, ''));
|
|
74
|
+
if (p.startsWith('/builders/') && TYPES[path.extname(p)] && fs.existsSync(f)) return route.fulfill({ contentType: TYPES[path.extname(p)], body: fs.readFileSync(f) });
|
|
75
|
+
return route.fulfill({ status: 404, body: '' });
|
|
76
|
+
});
|
|
77
|
+
const pg = await ctx.newPage();
|
|
78
|
+
const errors = [];
|
|
79
|
+
pg.on('pageerror', (e) => errors.push(String(e.message)));
|
|
80
|
+
await pg.addInitScript((g) => { try { localStorage.setItem('hall-game', g ? 'on' : 'off'); } catch (_) { /* none */ } }, game);
|
|
81
|
+
await pg.goto(ORIGIN + '/');
|
|
82
|
+
await pg.waitForFunction(() => !!window.OTBVisor && !!window.__visorScene);
|
|
83
|
+
return { ctx, page: pg, errors };
|
|
84
|
+
}
|
|
85
|
+
// every button and field on the sky a person can see (dialogs and side panels are pop-ups)
|
|
86
|
+
const sceneControls = () => [...document.querySelectorAll('.sky-stage button, .sky-stage input')]
|
|
87
|
+
.filter((e) => e.getClientRects().length && !e.closest('#start, #tour, #panel, #room, #menu')).map((e) => e.id || e.className);
|
|
88
|
+
const labels = () => window.__visorScene.actions.map((a) => a.label + (a.key ? ' [' + a.key + ']' : ''));
|
|
89
|
+
|
|
90
|
+
test('game mode, the project sky: no control of its own, and every one is a visor action', { skip }, async () => {
|
|
91
|
+
const { ctx, page, errors } = await open('goal-map.html');
|
|
92
|
+
assert.deepEqual(await page.evaluate(sceneControls), [], 'the sky shows no button or field');
|
|
93
|
+
assert.deepEqual(await page.evaluate(labels), [
|
|
94
|
+
'Write a thought [/]', 'Jump to a goal or a thought [j]', 'List · off [L]', 'Explore · on', 'My ideas · off',
|
|
95
|
+
'Clock · off', 'How to read the sky [?]', 'Fit the whole sky [0]', 'Legend · off',
|
|
96
|
+
]);
|
|
97
|
+
assert.equal(await page.textContent('.visor__hint--place'), '/Write a thought', 'writing a thought is the lead, on the glass');
|
|
98
|
+
assert.deepEqual(errors, []);
|
|
99
|
+
await ctx.close();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('game mode, your sky: the lens switch and its own pills are rows too', { skip }, async () => {
|
|
103
|
+
const { ctx, page } = await open('thinking.html');
|
|
104
|
+
assert.deepEqual(await page.evaluate(sceneControls), []);
|
|
105
|
+
const got = await page.evaluate(labels);
|
|
106
|
+
for (const want of ['Simple · off', 'Your thinking · on', 'My influence · off', 'Claude layer · off', 'What if · off']) assert.ok(got.includes(want), want);
|
|
107
|
+
await ctx.close();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('a row presses its pill once, and the row follows the pill', { skip }, async () => {
|
|
111
|
+
const { ctx, page } = await open('goal-map.html');
|
|
112
|
+
await page.keyboard.press('a');
|
|
113
|
+
await page.click('.visor__sheet [data-visor-scene] >> text=My ideas · off');
|
|
114
|
+
assert.deepEqual(await page.evaluate(() => [window.presses.ideasBtn, document.getElementById('ideasBtn').getAttribute('aria-pressed')]), [1, 'true'], 'one press, not two');
|
|
115
|
+
await page.waitForFunction(() => window.__visorScene.actions.some((a) => a.label === 'My ideas · on'));
|
|
116
|
+
await ctx.close();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('J opens the jump field and / is the composer; each closes again when left empty', { skip }, async () => {
|
|
120
|
+
const { ctx, page } = await open('goal-map.html');
|
|
121
|
+
await page.keyboard.press('j');
|
|
122
|
+
assert.deepEqual(await page.evaluate(() => [getComputedStyle(document.querySelector('.jump')).display, document.activeElement.id]), ['block', 'jump']);
|
|
123
|
+
await page.evaluate(() => window.__visorScene.actions[0].run());
|
|
124
|
+
assert.deepEqual(await page.evaluate(() => [getComputedStyle(document.querySelector('.jump')).display, getComputedStyle(document.getElementById('composerWrap')).display, document.activeElement.id]), ['none', 'flex', 'thought'], 'one field at a time');
|
|
125
|
+
await page.keyboard.type('half a thought');
|
|
126
|
+
await page.evaluate(() => document.activeElement.blur());
|
|
127
|
+
await page.waitForTimeout(20);
|
|
128
|
+
assert.equal(await page.evaluate(() => getComputedStyle(document.getElementById('composerWrap')).display), 'flex', 'words in it: it stays');
|
|
129
|
+
await page.evaluate(() => { document.getElementById('thought').value = ''; document.getElementById('thought').focus(); document.activeElement.blur(); });
|
|
130
|
+
await page.waitForTimeout(20);
|
|
131
|
+
assert.equal(await page.evaluate(() => getComputedStyle(document.getElementById('composerWrap')).display), 'none', 'left empty: it closes');
|
|
132
|
+
await ctx.close();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('the clock and the legend open from the visor', { skip }, async () => {
|
|
136
|
+
const { ctx, page } = await open('goal-map.html');
|
|
137
|
+
const shown = () => page.evaluate(() => ['time', 'legend'].map((id) => getComputedStyle(document.getElementById(id)).display));
|
|
138
|
+
assert.deepEqual(await shown(), ['none', 'none']);
|
|
139
|
+
await page.evaluate(() => { for (const a of window.__visorScene.actions) if (/^(Clock|Legend)/.test(a.label)) a.run(); });
|
|
140
|
+
assert.deepEqual(await shown(), ['flex', 'flex']);
|
|
141
|
+
await page.waitForFunction(() => window.__visorScene.actions.some((a) => a.label === 'Clock · on'));
|
|
142
|
+
await ctx.close();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('classic mode: the sky keeps its own top row, fields and foot', { skip }, async () => {
|
|
146
|
+
const { ctx, page } = await open('goal-map.html', { game: false });
|
|
147
|
+
const ids = await page.evaluate(sceneControls);
|
|
148
|
+
for (const id of ['roomBtn', 'exploreBtn', 'ideasBtn', 'jump', 'thought', 'fileGo', 'play', 'clock']) assert.ok(ids.includes(id), id);
|
|
149
|
+
await ctx.close();
|
|
150
|
+
});
|
package/tests/hall_visor.mjs
CHANGED
|
@@ -120,7 +120,7 @@ test('source: the visor loads after the shell on every scene page, with its shee
|
|
|
120
120
|
assert.ok(at('/builders/visor.js') > at('/builders/shell.js?') && at('/builders/shell.js?') > at('/builders/shell-game.js'), `${f}: shell-game, shell, then the visor`);
|
|
121
121
|
assert.match(html, /<link rel="stylesheet" href="\/builders\/visor\.css/, `${f}: links visor.css`);
|
|
122
122
|
}
|
|
123
|
-
for (const f of ['sky-
|
|
123
|
+
for (const f of ['sky-visor.js', 'city.js', 'home-city.js']) assert.match(read(f), /window\.__visorScene = /, `${f} registers its scene`);
|
|
124
124
|
assert.doesNotMatch(VISOR, /cam\.(x|y|k)\s*=/, 'the visor writes no camera (ADR 0399 §2.2)');
|
|
125
125
|
assert.doesNotMatch(VISOR, /setAttribute\('hidden'|\.hidden = true;[^\n]*nav/, 'it hides nothing but itself');
|
|
126
126
|
assert.match(read('visor.css'), /html\[data-game="on"\] \.visor/, 'game mode only');
|