@bongos/core 1.24.88 → 1.24.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,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.88",
6
- "core_contract": "1.24.88",
7
- "source_commit": "9cae78916698991df76bd7767758abec0b14492b",
5
+ "core_version": "1.24.90",
6
+ "core_contract": "1.24.90",
7
+ "source_commit": "8b084c190e038c91f0cd34788d09ef2aaaf11a22",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-11-12T11:35:17.869Z",
9
+ "built_at": "2026-11-12T12:22:11.337Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 649,
12
+ "docs_redacted": 650,
13
13
  "agent_docs_stubbed": 32,
14
- "functional_verbatim": 3963,
14
+ "functional_verbatim": 3964,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 4645,
20
- "tree_sha256": "f48750290cca4d1cd80ef79b32ed0dfb13062b4da5e2dca2e23fb747fb892e61",
19
+ "file_count": 4647,
20
+ "tree_sha256": "904d7883071b44b177146ffb3f7ac3b50033d6f3a86d5b162f91c48e1f5e397b",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2474,10 +2474,15 @@
2474
2474
  "mode": "0000644",
2475
2475
  "sha256": "2b1d542cca6f0766d214c9b42b38094fa5aeeb3926f66f1cd88ae62bcb3d06e4"
2476
2476
  },
2477
+ {
2478
+ "path": "docs/adr/0403-in-game-mode-the-visor-is-the-control-and-the-scene-is-the-view.md",
2479
+ "mode": "0000644",
2480
+ "sha256": "d98e0ea9a8a9b541bb58361bb124a4884c8165758aa8e3243e743368467e79ed"
2481
+ },
2477
2482
  {
2478
2483
  "path": "docs/adr/README.md",
2479
2484
  "mode": "0000644",
2480
- "sha256": "6ec9829802a80562a413b4dc571552c84bb52ab96a0dc155cbdce8233fc1fe49"
2485
+ "sha256": "ca848f718bbc048e492417b7b92b00359d691473891950302d5d7d232da2a1ad"
2481
2486
  },
2482
2487
  {
2483
2488
  "path": "docs/api-reference.md",
@@ -2512,12 +2517,12 @@
2512
2517
  {
2513
2518
  "path": "docs/copy-inventory.md",
2514
2519
  "mode": "0000644",
2515
- "sha256": "349a712a81588492c8694f0c7c5ea267414d892c53a076a6c838fd0ef8b6c23d"
2520
+ "sha256": "c37bd90219c6e760347c3cc48f3647a0f255b4ae084edbb010ec5a33f7ea04dc"
2516
2521
  },
2517
2522
  {
2518
2523
  "path": "docs/copy-registry.json",
2519
2524
  "mode": "0000644",
2520
- "sha256": "1f076a4c1e82f2ba9b6b27b050d5da9f8df6c14ad91f0cfe19d7041c3f3a6630"
2525
+ "sha256": "f493610562231d36902d6f4b8c0842574ac19f960a58a59812cec7bd04048073"
2521
2526
  },
2522
2527
  {
2523
2528
  "path": "docs/design/apex-pass-2-direction.md",
@@ -3032,7 +3037,7 @@
3032
3037
  {
3033
3038
  "path": "docs/module-api-changelog.md",
3034
3039
  "mode": "0000644",
3035
- "sha256": "a12a9637942f1c88b7f388c2c27860eeefda541a8688233f4fdc0df15db11d9c"
3040
+ "sha256": "40c2f5de03f98c8bcbf0f169f0bb1d6066803f6ab96171ddf29b9cf4491abad6"
3036
3041
  },
3037
3042
  {
3038
3043
  "path": "docs/modules-contract.md",
@@ -3117,7 +3122,7 @@
3117
3122
  {
3118
3123
  "path": "docs/page-readings.json",
3119
3124
  "mode": "0000644",
3120
- "sha256": "d35610b6f68ee39d58c74078a17b6ac637c07a293438ba494caf7075ab593503"
3125
+ "sha256": "10f19956b0f3c1b7e14f5433b40f417ce54a8f4291f4194fb98163522f9a83fd"
3121
3126
  },
3122
3127
  {
3123
3128
  "path": "docs/project-context.template.md",
@@ -6327,7 +6332,7 @@
6327
6332
  {
6328
6333
  "path": "modules/hall-ui/public/craft-mode.js",
6329
6334
  "mode": "0000644",
6330
- "sha256": "743c515009a46b46833807b9ddb4e5d9304cb3107c9e62a7b87bd7cffe29c3c6"
6335
+ "sha256": "15dc97554ec17cde2938078650290333d5d15a847654688381d5a9b218d90b9f"
6331
6336
  },
6332
6337
  {
6333
6338
  "path": "modules/hall-ui/public/craft-review.css",
@@ -6917,12 +6922,12 @@
6917
6922
  {
6918
6923
  "path": "modules/hall-ui/public/page-notes.css",
6919
6924
  "mode": "0000644",
6920
- "sha256": "fc40a2d1f548d5ed15583d5d7fb26a2f7926fc2f10e9612fb525a936353ea26a"
6925
+ "sha256": "cd73ad050eaa76bbe04033f41cc7069bf126d8678ff567ab0f40409cb70a6af5"
6921
6926
  },
6922
6927
  {
6923
6928
  "path": "modules/hall-ui/public/page-notes.js",
6924
6929
  "mode": "0000644",
6925
- "sha256": "eef2a37a877a38801cce227786ac53623e89d8b1e07521beefb4c21fb503f935"
6930
+ "sha256": "b8ee9540a09ccc9c46b4d8ad7a5cc916405c1b7b24ca73d45dda078145613666"
6926
6931
  },
6927
6932
  {
6928
6933
  "path": "modules/hall-ui/public/paintbrush.js",
@@ -7342,7 +7347,7 @@
7342
7347
  {
7343
7348
  "path": "modules/hall-ui/public/style.css",
7344
7349
  "mode": "0000644",
7345
- "sha256": "0912992b2572931af010bca4c8141619b9c8671c3aed55d7aec0243479bb916c"
7350
+ "sha256": "d42939b4e5777cd304c272caa057f6723b8cff8a372b4275a7caa6dade7200b4"
7346
7351
  },
7347
7352
  {
7348
7353
  "path": "modules/hall-ui/public/task-detail.js",
@@ -7382,7 +7387,7 @@
7382
7387
  {
7383
7388
  "path": "modules/hall-ui/public/thinking.css",
7384
7389
  "mode": "0000644",
7385
- "sha256": "c6a055278702bb005b65202ef8a0b367283f13092c3e99faf91d39025b3019df"
7390
+ "sha256": "99df2ce6bb017b78d0fd4f3f4f4607cecfea3e3a9275aaded09c4a66bdfe5a9f"
7386
7391
  },
7387
7392
  {
7388
7393
  "path": "modules/hall-ui/public/thinking.html",
@@ -7432,12 +7437,12 @@
7432
7437
  {
7433
7438
  "path": "modules/hall-ui/public/visor.css",
7434
7439
  "mode": "0000644",
7435
- "sha256": "8f5750cba2621d04e0dbff0efe4bff29c9b071ef84de342e0b93e2ab32b03a07"
7440
+ "sha256": "8c7c3088fb756d2e6fddc6d9d9df81fb67ada498628dc3a9ff67a8cbb97ab3e6"
7436
7441
  },
7437
7442
  {
7438
7443
  "path": "modules/hall-ui/public/visor.js",
7439
7444
  "mode": "0000644",
7440
- "sha256": "92a14b637f97f86ae3661a26a22b78cce3e098c4c85e6c1d672eb3b4a05e6dc5"
7445
+ "sha256": "7c3dec2c81435516e15a35d4245557215c84d809f76ac9b49f95529a935518ec"
7441
7446
  },
7442
7447
  {
7443
7448
  "path": "modules/hall-ui/public/vote-control.js",
@@ -7582,7 +7587,7 @@
7582
7587
  {
7583
7588
  "path": "modules/hall-ui/records/game-mode.md",
7584
7589
  "mode": "0000644",
7585
- "sha256": "0c9b326ed5c49a7157a4f7eac4207229e006b0bf5b4205d3a6f2b2b5d21176b0"
7590
+ "sha256": "bcf581da178354e3c366668b2b6b3641181ea6691bba754ad43876f0cacee0a0"
7586
7591
  },
7587
7592
  {
7588
7593
  "path": "modules/hall-ui/records/genesis-home.md",
@@ -7747,7 +7752,7 @@
7747
7752
  {
7748
7753
  "path": "modules/hall-ui/records/visor.md",
7749
7754
  "mode": "0000644",
7750
- "sha256": "563d2c2291aed7ca650c977900fe54eac57350d56ca520585de953a28be764be"
7755
+ "sha256": "515c6e31880265d11477e723fd0307d49409b6c04d5e53aa6579ef809c88269b"
7751
7756
  },
7752
7757
  {
7753
7758
  "path": "modules/hall-ui/records/welcome-tower.md",
@@ -12097,12 +12102,12 @@
12097
12102
  {
12098
12103
  "path": "package-lock.json",
12099
12104
  "mode": "0000644",
12100
- "sha256": "f1d1734b747e9b279bf00ec7f8d782362aef5a0793dd8d3563ab746de80412e7"
12105
+ "sha256": "7cd685f627a16c373b7c02c103aa377c1978526abdd4551c1dfe2b4e73b7eed4"
12101
12106
  },
12102
12107
  {
12103
12108
  "path": "package.json",
12104
12109
  "mode": "0000644",
12105
- "sha256": "15d8be448f60457c3fe8bb3246555e17d813dc97c55e59ca10ec821e28bd6813"
12110
+ "sha256": "b1a500b3e515779422782bf3f0e36f3c33ec5616170ceecdcb1b58650ab43aee"
12106
12111
  },
12107
12112
  {
12108
12113
  "path": "public-docs/index.html",
@@ -12122,7 +12127,7 @@
12122
12127
  {
12123
12128
  "path": "release-notes.json",
12124
12129
  "mode": "0000644",
12125
- "sha256": "ff81223c009e439843f7b19017c9c4d2e3fed034b8487bf801a0818aff14f066"
12130
+ "sha256": "9c99264a25217b3386e20b6a4151551200345eb1b7d3d9bb55a03dd0c259bef7"
12126
12131
  },
12127
12132
  {
12128
12133
  "path": "scripts/bongos/CLAUDE.md",
@@ -16127,7 +16132,7 @@
16127
16132
  {
16128
16133
  "path": "src/module-api.js",
16129
16134
  "mode": "0000644",
16130
- "sha256": "d18338a71f96f04825f9fcc95d8567c380149ebc18879fb161ff28263315e04b"
16135
+ "sha256": "3f89e6652915fa721adbcd8cdb5d45d62aa319df8928f03f8684d01c303a3064"
16131
16136
  },
16132
16137
  {
16133
16138
  "path": "src/module-loader/catalog.js",
@@ -19079,6 +19084,11 @@
19079
19084
  "mode": "0000644",
19080
19085
  "sha256": "c7219ac3c13252fe64fb35a7d1dfa297af70ba25d8834a91fa7775feb468cdd3"
19081
19086
  },
19087
+ {
19088
+ "path": "tests/hall_game_review_tab.mjs",
19089
+ "mode": "0000644",
19090
+ "sha256": "472c1b226c80b3da8881c4d4abdf0ab7abbeefd24930ddc478671e06dcae9784"
19091
+ },
19082
19092
  {
19083
19093
  "path": "tests/hall_goals_band.mjs",
19084
19094
  "mode": "0000644",
@@ -19377,7 +19387,7 @@
19377
19387
  {
19378
19388
  "path": "tests/hall_visor.mjs",
19379
19389
  "mode": "0000644",
19380
- "sha256": "fc78f108f610fa15299b0a73f3ce1a69fa138e950353a1d7d99044e1b70b4e5d"
19390
+ "sha256": "055de4ac3afe4e7883be6cb462e00879bafc4c7593186ae7d728dbd5ee13f6f6"
19381
19391
  },
19382
19392
  {
19383
19393
  "path": "tests/hall_work_history_id.mjs",
@@ -20822,7 +20832,7 @@
20822
20832
  {
20823
20833
  "path": "tests/page_notes_panel.mjs",
20824
20834
  "mode": "0000644",
20825
- "sha256": "98e1382b0bf02f19209ed68ebf43408eea81f0732692a4c28363bfbc9d80a741"
20835
+ "sha256": "69d321a78de7347b1129499c0798e6c0f0c9621bf2c7870fb0fbd227c26e4ac9"
20826
20836
  },
20827
20837
  {
20828
20838
  "path": "tests/page_reader.mjs",
@@ -0,0 +1,37 @@
1
+ # 0403 — In game mode the visor is the control and the scene is the view
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-11-11
5
+ - **Decided by:** the owner, while reviewing [task 1005203](https://cloudbongos.com/builders#/task/1005203) (the visor's progress segments).
6
+ - **Goal:** [goal 1000142](https://cloudbongos.com/builders#/goal/1000142) — the V3 game conversion.
7
+ - **Builds on:** [ADR 0399](0399-the-visor-is-glass-over-the-scene-and-owns-no-camera.md) (the visor is glass over the scene), [ADR 0402](0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md) (the Helm look).
8
+
9
+ ## 1. The decision
10
+
11
+ In game mode the screen has two jobs, and each has one place:
12
+
13
+ - **The visor is the control.** Every main call to action you can see (a button that starts something: claim, decide, file, review, open a place) lives on the visor's glass: the one accent button, the key hints, or the Actions sheet.
14
+ - **The scene is the view.** The city, the sky and Home show the project. They can be looked at and moved through (pan, zoom, pick a star or a building to see it), but they carry no call-to-action buttons of their own.
15
+
16
+ A scene that has something to be done offers it to the visor: it registers the action with `OTBVisor.scene({ actions, keys })` (ADR 0399 §2.2), and the visor draws it. What you picked in the scene can change what the visor offers, but the button is still on the glass.
17
+
18
+ This holds for every game place (`shell-game.js` `PLACES`) and every scene built later. Classic mode is unchanged.
19
+
20
+ ## 2. Why
21
+
22
+ One place for every control means a player always knows where to look to act, and the world stays a picture of the project rather than a page of buttons. It is the reason the visor exists.
23
+
24
+ ## 3. What does not yet keep it (found 2026-11-11)
25
+
26
+ Each is its own task; none is changed by this record.
27
+
28
+ - **The Governor City:** the "decide N in a row" button and the city door sit in the scene. ([task 1005209](https://cloudbongos.com/builders#/task/1005209))
29
+ - **The sky:** the thought composer and its file button, the list / explore / my ideas buttons and the jump field are the sky's own top row. ([task 1005210](https://cloudbongos.com/builders#/task/1005210))
30
+ - **Home:** the gallery's tabs and links (Most popular, Latest, The full gallery, Your studio). ([task 1005211](https://cloudbongos.com/builders#/task/1005211))
31
+ - **The Artist's feedback tab** (`page-notes.js` `.pn__tab`) stays parked on the right edge; a session is already hiding it in game mode.
32
+
33
+ ## 4. Consequences
34
+
35
+ - A new game scene is reviewed against this first: a button in the scene is a defect, not a style choice.
36
+ - The visor will need room for more than one main action per place (a scene's actions already go in the sheet's "This place" list); how a scene's lead action reaches the accent button is the visor's next design question.
37
+ - Records: `modules/hall-ui/records/game-mode.md` and `records/visor.md` carry the rule for anyone editing a game page.
@@ -518,3 +518,4 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
518
518
  | 0401 | [**One options menu opens every settings screen in a frame** ([task 1005144](https://cloudbongos.com/builders#/task/1005144), goal 1000141). `/settings` is the options menu: a search box over a grouped list. Settings' own panels stay native; My agents, Project settings, Modules, Permissions and Market Watch's settings and rules open inside it in an `<iframe>` of the old address with `?embed=1`, which `shell-game.js` reads only inside a frame to drop the page's chrome, send an unused Escape to the menu and open links to other pages in the whole window. A plain visit to an old address 302s to `/settings?open=<entry>` (the old #anchor rides along); the framed request keeps its own page gate and routes. In game mode the last Escape opens the menu and closes it again. Rejected: merging seven pages' scripts into one, a second /options page, links out of the menu.](0401-one-options-menu-opens-every-settings-screen-in-a-frame.md) | hall / game frame |
519
519
  | 0400 | [**A chain reserves work; each sub-quest is still claimed on its own** ([task 1005158](https://cloudbongos.com/builders#/task/1005158), spike V3G.R18, goal 1000144). C5 of the game conversion: work is claimed as a chain, not picked from a flat table. Owner ruling (interview answer 3): a chain RESERVES, tasks are still claimed one at a time. **Decision:** three new tables (`chains` inside one goal, `chain_tasks` with a task in at most one open chain, `chain_party`), none of them a claim — no `claims` row, no session binding, no worktree — so `gateOneActiveClaimPerSession` (ADR 0097) and its unique index never see a chain. **Order is derived, never stored** (ADR 0153's rejection holds): the topological order of `task_dependencies` over the chain's members, ties by priority/estimate/id — `sequence.js`'s `topoOrder`, moved into one shared module. Every sub-quest claims through the unchanged `POST /claims` ladder. **The one addition** is a narrowing-only `CHAIN_RESERVED` check placed AFTER the last existing rung on both insert sites + the dry run + the feed: an outsider can't claim a reserved chain's sub-quest, while every existing refusal keeps its code, message and precedence. Party size (raid quorum vs routine) is refused at the chain's own join door, never in the claim ladder; character counts seats and is never read by a claim rung. A reservation lapses after days with no claim or ship, reported before it acts (ADR 0226). Rejected: a chain as N claims, loosening the session guard, chain = goal, a stored order, an early-ladder check (would mask rank/deps refusals), a feed-only reservation.](0400-a-chain-reserves-work-and-each-sub-quest-is-still-claimed-alone.md) | lifecycle / claims / game conversion |
520
520
  | 0402 | [**Helm is the hall's look, and the hall is dark only** ([task 1005192](https://cloudbongos.com/builders#/task/1005192)). The game (the hall, everything after sign-in) wears Helm; the landing, the hub and the status page keep the chrome world. The kit: two voices (Barlow Condensed in wide capitals names a thing, Barlow says everything else), four colours with one job each (Night the ground, Ice what you read, White names and numbers, the accent only what you can act on), three shapes (the cut corner for anything pressable, the segment for progress, the arc for the visor). The values live in the hall's token block; `src/hall-helm.js` recognises a hall page by the sheet it links, pins it dark and hands it the Helm faces unless its pack chose its own. The light/dark switch is gone; the light tier stays until its own removal. The shared button spends every Helm token with the old pill as the fallback, so the status page does not move.](0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md) | design system / hall |
521
+ | 0403 | [**In game mode the visor is the control and the scene is the view** ([task 1005203](https://cloudbongos.com/builders#/task/1005203), goal 1000142; owner 2026-11-11). Every main call to action you can see in game mode lives on the visor (the accent button, the key hints, the Actions sheet); the city, the sky and Home only show the project and can be moved through, never carry buttons of their own. A scene offers its actions to the visor through `OTBVisor.scene`. Found not yet keeping it: the city's decide button and door, the sky's top row and composer, Home's gallery links, the Artist's feedback tab.](0403-in-game-mode-the-visor-is-the-control-and-the-scene-is-the-view.md) | hall / game frame |
@@ -316,7 +316,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
316
316
  | `4df46e2be815` | certain | Clear the key? | `modules/hall-ui/public/market-watch-settings.js:218` |
317
317
  | `593dda3178eb` | certain | Click to copy | `modules/hall-ui/public/copy-on-click.js:137` |
318
318
  | `34624d39ff29` | certain | Clone the project to your machine and run | `modules/hall-ui/public/setup-gate.js:34` |
319
- | `3a426bad2c49` | certain | Close actions | `modules/hall-ui/public/visor.js:178` |
319
+ | `3a426bad2c49` | certain | Close actions | `modules/hall-ui/public/visor.js:222` |
320
320
  | `70f6a704d504` | certain | Close it | `modules/hall-ui/public/copy-desk.html:140` |
321
321
  | `053e576ba9a3` | certain | Close options | `modules/hall-ui/public/shell-game.js:453` |
322
322
  | `d12a2fb5730d` | certain | Close this flag | `modules/hall-ui/public/copy-desk.html:116` |
@@ -1486,7 +1486,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1486
1486
  | `9a685b01e2c6` | certain | This page opens at a higher rank. | `modules/hall-ui/public/watch.html:237` |
1487
1487
  | `e7a289134dc3` | certain | This page shows no words the reader could find. | `modules/hall-ui/public/tweak-editor.js:246` |
1488
1488
  | `d4b3b5556986` | certain | This page's reference docs | `modules/hall-ui/public/page-notes.js:166` |
1489
- | `79e4a934b434` | certain | This place | `modules/hall-ui/public/visor.js:180` |
1489
+ | `79e4a934b434` | certain | This place | `modules/hall-ui/public/visor.js:224` |
1490
1490
  | `5bdcbec770dd` | certain | This project has no slash commands yet. | `modules/hall-ui/public/commands.js:37` |
1491
1491
  | `f53b83bfbe54` | certain | This project has no watch rules yet. | `modules/hall-ui/public/market-watch-rules.js:62` |
1492
1492
  | `e97b2d549f10` | certain | This project is hosted on {…}, and that is where it is taken offline or removed. Both links below open this pr | `modules/hall-ui/public/project-settings.js:412` |
@@ -5208,7 +5208,7 @@
5208
5208
  "surface": "builders-hall",
5209
5209
  "text": "Close actions",
5210
5210
  "file": "modules/hall-ui/public/visor.js",
5211
- "line": 178,
5211
+ "line": 222,
5212
5212
  "origin": "js-markup",
5213
5213
  "confidence": "certain"
5214
5214
  },
@@ -15738,7 +15738,7 @@
15738
15738
  "surface": "builders-hall",
15739
15739
  "text": "This place",
15740
15740
  "file": "modules/hall-ui/public/visor.js",
15741
- "line": 180,
15741
+ "line": 224,
15742
15742
  "origin": "js-markup",
15743
15743
  "confidence": "certain"
15744
15744
  },
@@ -3426,5 +3426,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
3426
3426
  landed since 1.24.86 with no explicit bump. run 43147929315. (task 1002620)
3427
3427
  1.24.88 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3428
3428
  landed since 1.24.87 with no explicit bump. run 43210223245. (task 1002620)
3429
+ 1.24.89 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3430
+ landed since 1.24.88 with no explicit bump. run 43213545598. (task 1002620)
3431
+ 1.24.90 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3432
+ landed since 1.24.89 with no explicit bump. run 43217068783. (task 1002620)
3429
3433
  ---------------------------------------------------------------------------
3430
3434
  ```
@@ -12,7 +12,7 @@
12
12
  "surface": "builders",
13
13
  "title": "Studio",
14
14
  "states_read": ["pick","describe","make","waiting","result","result-own","failed","balance","balance-all","out"],
15
- "files_hash": "c6fe46384d8bff8c",
15
+ "files_hash": "23f9e59301672d58",
16
16
  "reading_hash": "d9e0e85bcee4aff4",
17
17
  "counts": {"lines":106,"placed":41,"shared":7,"unplaced":58},
18
18
  "omitted": {"data":17},
@@ -131,7 +131,7 @@
131
131
  "surface": "builders",
132
132
  "title": "Repo Atlas",
133
133
  "states_read": ["map","truth","detail","index","out"],
134
- "files_hash": "4b34e7e147b15e04",
134
+ "files_hash": "79e0951cb18465bb",
135
135
  "reading_hash": "6934dead335b2507",
136
136
  "counts": {"lines":25,"placed":14,"shared":7,"unplaced":4},
137
137
  "omitted": {"data":5},
@@ -169,7 +169,7 @@
169
169
  "surface": "builders",
170
170
  "title": "Blockers",
171
171
  "states_read": ["default"],
172
- "files_hash": "4550334cb3ae91c4",
172
+ "files_hash": "19846a8c445f37ea",
173
173
  "reading_hash": "60d73d854ced958a",
174
174
  "counts": {"lines":36,"placed":17,"shared":7,"unplaced":12},
175
175
  "omitted": {"data":51},
@@ -218,7 +218,7 @@
218
218
  "surface": "builders",
219
219
  "title": "Board Room",
220
220
  "states_read": ["room","out","sitting-meta","objection-form"],
221
- "files_hash": "ec52d3968ea2f2fc",
221
+ "files_hash": "291a782052c75cdf",
222
222
  "reading_hash": "0260b797b8dc1526",
223
223
  "counts": {"lines":52,"placed":21,"shared":7,"unplaced":24},
224
224
  "omitted": {"data":17},
@@ -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": "d0715a562faaa050",
286
+ "files_hash": "909fdbd80b138fd3",
287
287
  "reading_hash": "277096d903879a57",
288
288
  "counts": {"lines":26,"placed":9,"shared":6,"unplaced":11},
289
289
  "omitted": {"data":7},
@@ -322,7 +322,7 @@
322
322
  "surface": "builders",
323
323
  "title": "Collab",
324
324
  "states_read": ["board","out"],
325
- "files_hash": "216de45c5d2118a5",
325
+ "files_hash": "e2742d78f9cccca8",
326
326
  "reading_hash": "e6452bf92231656f",
327
327
  "counts": {"lines":82,"placed":36,"shared":6,"unplaced":40},
328
328
  "omitted": {"data":23},
@@ -417,7 +417,7 @@
417
417
  "surface": "builders",
418
418
  "title": "Commands",
419
419
  "states_read": ["in","out"],
420
- "files_hash": "7e077f709d50d716",
420
+ "files_hash": "40d23c5b0a81a29a",
421
421
  "reading_hash": "ecc452689a8ee9d7",
422
422
  "counts": {"lines":71,"placed":49,"shared":7,"unplaced":15},
423
423
  "omitted": {"data":177},
@@ -501,7 +501,7 @@
501
501
  "surface": "builders",
502
502
  "title": "Copy desk",
503
503
  "states_read": ["queue","out","search","composer","closer","proposer"],
504
- "files_hash": "b0a766d3737c46fb",
504
+ "files_hash": "f3cf83236fe02237",
505
505
  "reading_hash": "3819ef6fa8f6fb7c",
506
506
  "counts": {"lines":72,"placed":36,"shared":7,"unplaced":29},
507
507
  "omitted": {"data":94},
@@ -586,7 +586,7 @@
586
586
  "surface": "builders",
587
587
  "title": "Deploy",
588
588
  "states_read": ["work","out"],
589
- "files_hash": "543e811b353093c2",
589
+ "files_hash": "fd49d5d95913e541",
590
590
  "reading_hash": "3770e8b2fa56bee1",
591
591
  "counts": {"lines":14,"placed":5,"shared":7,"unplaced":2},
592
592
  "omitted": {"data":5},
@@ -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": "597d96868864c1ce",
616
+ "files_hash": "48204568b1300984",
617
617
  "reading_hash": "4a879397a84631e3",
618
618
  "counts": {"lines":50,"placed":25,"shared":6,"unplaced":19},
619
619
  "omitted": {"data":10},
@@ -676,7 +676,7 @@
676
676
  "surface": "builders",
677
677
  "title": "Goals",
678
678
  "states_read": ["list","filters","cards","detail","dependencies","out","uat","uat-panel","author"],
679
- "files_hash": "aa6b14fc4d9c3833",
679
+ "files_hash": "5bf411f2510edcc9",
680
680
  "reading_hash": "13a5d07b3804b627",
681
681
  "counts": {"lines":187,"placed":61,"shared":7,"unplaced":119},
682
682
  "omitted": {"data":315},
@@ -876,7 +876,7 @@
876
876
  "surface": "builders",
877
877
  "title": "Government",
878
878
  "states_read": ["constitution","out","permissions","board","constitution-tab"],
879
- "files_hash": "1da7a621974799ea",
879
+ "files_hash": "7de9a6b413426928",
880
880
  "reading_hash": "37c6878d60447fd6",
881
881
  "counts": {"lines":65,"placed":27,"shared":7,"unplaced":31},
882
882
  "omitted": {"data":276},
@@ -954,8 +954,8 @@
954
954
  "surface": "builders",
955
955
  "title": "Builders' Hall",
956
956
  "states_read": ["home","out","founding","genesis","decide","demo"],
957
- "files_hash": "a872d5c364c55966",
958
- "reading_hash": "21e68b6037037652",
957
+ "files_hash": "72688333d07fc19e",
958
+ "reading_hash": "583ba51e45463d65",
959
959
  "counts": {"lines":32,"placed":12,"shared":8,"unplaced":12},
960
960
  "omitted": {"data":5},
961
961
  "notes": ["founding: an action failed (page.focus: Timeout 6000ms exceeded.); read as it stood"],
@@ -977,7 +977,7 @@
977
977
  {"key":"L0015","section":"Needs your attention13","text":"Ideas awaiting triage","placement":"placed","file":"modules/hall-ui/public/builders.js","line":320,"string_id":"e181637c5a5e"},
978
978
  {"key":"L0016","section":"Needs your attention13","text":"Requires project {…} fix","placement":"unplaced"},
979
979
  {"key":"L0017","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":514,"string_id":"1883ad7f07fc"},
980
- {"key":"L0018","section":"Needs your attention13","text":"core 1.24.86","placement":"unplaced"},
980
+ {"key":"L0018","section":"Needs your attention13","text":"core 1.24.88","placement":"unplaced"},
981
981
  {"key":"L0019","section":"Needs your attention13","text":"Skip","placement":"unplaced"},
982
982
  {"key":"L0020","section":"Needs your attention13","text":"Built on","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":183,"string_id":"fef59346ffee"},
983
983
  {"key":"L0021","section":"Needs your attention13","text":"cloudbongos.com","placement":"unplaced"},
@@ -999,7 +999,7 @@
999
999
  "surface": "builders",
1000
1000
  "title": "Idea",
1001
1001
  "states_read": ["record","missing"],
1002
- "files_hash": "45ceed00f030bc7b",
1002
+ "files_hash": "387333f4d59fe55e",
1003
1003
  "reading_hash": "f7d644748c87e426",
1004
1004
  "counts": {"lines":52,"placed":25,"shared":8,"unplaced":19},
1005
1005
  "omitted": {"data":25},
@@ -1064,7 +1064,7 @@
1064
1064
  "surface": "builders",
1065
1065
  "title": "Ideas",
1066
1066
  "states_read": ["compose","full","develop"],
1067
- "files_hash": "3d118453abc50e92",
1067
+ "files_hash": "e0cd78a4a3e96ead",
1068
1068
  "reading_hash": "a62c05dcef3bf87f",
1069
1069
  "counts": {"lines":68,"placed":34,"shared":7,"unplaced":27},
1070
1070
  "omitted": {"data":118},
@@ -1145,7 +1145,7 @@
1145
1145
  "surface": "builders",
1146
1146
  "title": "Market Watch",
1147
1147
  "states_read": ["desk","settings","out"],
1148
- "files_hash": "fc40724d7d6eeba8",
1148
+ "files_hash": "5edfd91add4496ef",
1149
1149
  "reading_hash": "30512dda207b5ea6",
1150
1150
  "counts": {"lines":23,"placed":11,"shared":7,"unplaced":5},
1151
1151
  "omitted": {"data":5},
@@ -1181,7 +1181,7 @@
1181
1181
  "surface": "builders",
1182
1182
  "title": "Market Watch",
1183
1183
  "states_read": ["dossier","out","propose","correct","filtered"],
1184
- "files_hash": "707e88b9c33df6de",
1184
+ "files_hash": "e7612a858ce69c69",
1185
1185
  "reading_hash": "129bff28c4e4ed6d",
1186
1186
  "counts": {"lines":24,"placed":12,"shared":7,"unplaced":5},
1187
1187
  "omitted": {"data":5},
@@ -1218,7 +1218,7 @@
1218
1218
  "surface": "builders",
1219
1219
  "title": "Market Watch rules",
1220
1220
  "states_read": ["in","scoring","names","propose","out"],
1221
- "files_hash": "9287a77f105c6266",
1221
+ "files_hash": "30ee116ca9f2faa1",
1222
1222
  "reading_hash": "ca0c62e8dce51c8b",
1223
1223
  "counts": {"lines":36,"placed":12,"shared":7,"unplaced":17},
1224
1224
  "omitted": {"data":20},
@@ -1267,7 +1267,7 @@
1267
1267
  "surface": "builders",
1268
1268
  "title": "Modules",
1269
1269
  "states_read": ["browse","search-empty","lens","details","out"],
1270
- "files_hash": "fa19c00a28d82386",
1270
+ "files_hash": "4c650bba10ed03f6",
1271
1271
  "reading_hash": "6a12e9306bba5622",
1272
1272
  "counts": {"lines":59,"placed":29,"shared":7,"unplaced":23},
1273
1273
  "omitted": {"data":107},
@@ -1339,7 +1339,7 @@
1339
1339
  "surface": "builders",
1340
1340
  "title": "Money",
1341
1341
  "states_read": ["money","hosting","out"],
1342
- "files_hash": "24eceeda13f9613c",
1342
+ "files_hash": "07e5b1082ee23839",
1343
1343
  "reading_hash": "e355d5466b123e07",
1344
1344
  "counts": {"lines":83,"placed":28,"shared":7,"unplaced":48},
1345
1345
  "omitted": {"data":27},
@@ -1435,7 +1435,7 @@
1435
1435
  "surface": "builders",
1436
1436
  "title": "My agents",
1437
1437
  "states_read": ["desk","factory","saved","what-it-knows","speciality-walk","speciality-forms","nobody"],
1438
- "files_hash": "6eb2fa533e472d78",
1438
+ "files_hash": "1f5fa9dcd66e0b32",
1439
1439
  "reading_hash": "5347e0496b38727d",
1440
1440
  "counts": {"lines":100,"placed":33,"shared":7,"unplaced":60},
1441
1441
  "omitted": {"data":38},
@@ -1548,7 +1548,7 @@
1548
1548
  "surface": "builders",
1549
1549
  "title": "Primer",
1550
1550
  "states_read": ["in","toc-open","jump","out"],
1551
- "files_hash": "49bc305cc0a68912",
1551
+ "files_hash": "778c4ef963b2de8f",
1552
1552
  "reading_hash": "85790bd421e5c387",
1553
1553
  "counts": {"lines":487,"placed":57,"shared":7,"unplaced":423},
1554
1554
  "omitted": {"data":18},
@@ -2048,7 +2048,7 @@
2048
2048
  "surface": "builders",
2049
2049
  "title": "Profile",
2050
2050
  "states_read": ["own","works","activity","yours","activity-artist","activity-artist-ideas","other","artist","artist-ideas","out"],
2051
- "files_hash": "f601980d294202ae",
2051
+ "files_hash": "3eddfd923b8a83b4",
2052
2052
  "reading_hash": "3ff8d703d17e8c2a",
2053
2053
  "counts": {"lines":232,"placed":84,"shared":7,"unplaced":141},
2054
2054
  "omitted": {"data":263},
@@ -2293,7 +2293,7 @@
2293
2293
  "surface": "builders",
2294
2294
  "title": "Project settings",
2295
2295
  "states_read": ["default"],
2296
- "files_hash": "f97afd8db25ab57f",
2296
+ "files_hash": "7daa1c29c3b880ee",
2297
2297
  "reading_hash": "283df27d0d5bfc1b",
2298
2298
  "counts": {"lines":43,"placed":25,"shared":7,"unplaced":11},
2299
2299
  "omitted": {"data":33},
@@ -2349,7 +2349,7 @@
2349
2349
  "surface": "builders",
2350
2350
  "title": "Builders",
2351
2351
  "states_read": ["roster","artists","search","out"],
2352
- "files_hash": "78bee0a7d5694b4a",
2352
+ "files_hash": "17eca3360848c786",
2353
2353
  "reading_hash": "665ac2e36339f5d3",
2354
2354
  "counts": {"lines":56,"placed":17,"shared":7,"unplaced":32},
2355
2355
  "omitted": {"data":137},
@@ -2418,7 +2418,7 @@
2418
2418
  "surface": "builders",
2419
2419
  "title": "Options",
2420
2420
  "states_read": ["account","access","preferences","install-reports","sound","update"],
2421
- "files_hash": "5b875d5fa3a5aadb",
2421
+ "files_hash": "edfa4c2b264538a8",
2422
2422
  "reading_hash": "898a13fda24c3c90",
2423
2423
  "counts": {"lines":162,"placed":104,"shared":7,"unplaced":51},
2424
2424
  "omitted": {"data":23},
@@ -2593,7 +2593,7 @@
2593
2593
  "surface": "builders",
2594
2594
  "title": "The street",
2595
2595
  "states_read": ["street","fullscreen","card","focus","here","inside-rules","inside-disputes","inside-people","inside-planning","inside-automation","inside-review","inside-release","inside-treasury","floor","amend","district","desk","desk-facts","desk-quiet","desk-people-builders","desk-people-tier","desk-disputes","desk-automation-goals","desk-release-core","desk-rules","desk-rules-category","desk-planning-priorities","desk-planning-ideas","desk-planning-blockers","pins","pins-pager","living","deck","deck-treasury","sitting","object","voted","admission","settled","blocker","blocker-more","report","report-more","core","core-more","recheck","recheck-more","idea","idea-more","queue","queue-more","automation","automation-more"],
2596
- "files_hash": "ee5fc115e8cea60a",
2596
+ "files_hash": "f9f49021adf5c83f",
2597
2597
  "reading_hash": "e788e6bf4be8d68c",
2598
2598
  "counts": {"lines":325,"placed":54,"shared":6,"unplaced":265},
2599
2599
  "omitted": {"data":240},
@@ -2931,7 +2931,7 @@
2931
2931
  "surface": "builders",
2932
2932
  "title": "Studio",
2933
2933
  "states_read": ["home","out","flow","flow-pick","status","words-offer","words-edit","reviews","full","full-back","approvals","approvals-before","approvals-phone","approvals-note","approved","sent-back","approvals-page"],
2934
- "files_hash": "5f9a6a164cbdb945",
2934
+ "files_hash": "1c9edb7ed9addf39",
2935
2935
  "reading_hash": "c4e3b4188c1db755",
2936
2936
  "counts": {"lines":51,"placed":29,"shared":7,"unplaced":15},
2937
2937
  "omitted": {"data":23},
@@ -2995,7 +2995,7 @@
2995
2995
  "surface": "builders",
2996
2996
  "title": "Task",
2997
2997
  "states_read": ["record","from-goal","missing"],
2998
- "files_hash": "5347d1b5fd53ce39",
2998
+ "files_hash": "34cc9d19049cbc81",
2999
2999
  "reading_hash": "8dd803b6934e6388",
3000
3000
  "counts": {"lines":151,"placed":46,"shared":9,"unplaced":96},
3001
3001
  "omitted": {"data":111},
@@ -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": "85dc67fbd8d04fec",
3162
+ "files_hash": "b8b404b0285a8e4d",
3163
3163
  "reading_hash": "b73f848b46b0f9b8",
3164
3164
  "counts": {"lines":50,"placed":25,"shared":6,"unplaced":19},
3165
3165
  "omitted": {"data":10},
@@ -3222,7 +3222,7 @@
3222
3222
  "surface": "builders",
3223
3223
  "title": "Tweak a page",
3224
3224
  "states_read": ["doc","typed","chrome","submitted","picking","out"],
3225
- "files_hash": "9fc57b5fc123dfb4",
3225
+ "files_hash": "cb5f7a46c069e9d2",
3226
3226
  "reading_hash": "f521f8d9a7147557",
3227
3227
  "counts": {"lines":26,"placed":18,"shared":2,"unplaced":6},
3228
3228
  "omitted": {"data":19},
@@ -3261,7 +3261,7 @@
3261
3261
  "surface": "builders",
3262
3262
  "title": "Watch",
3263
3263
  "states_read": ["board","out","operations","security","sessions","autobongos"],
3264
- "files_hash": "ce010b0e2809d5f1",
3264
+ "files_hash": "f671ac6ffb1abbbb",
3265
3265
  "reading_hash": "1961a0c06b9d8477",
3266
3266
  "counts": {"lines":72,"placed":33,"shared":7,"unplaced":32},
3267
3267
  "omitted": {"data":52},
@@ -3346,7 +3346,7 @@
3346
3346
  "surface": "builders",
3347
3347
  "title": "Tasks",
3348
3348
  "states_read": ["ready","in-progress","backlog","completed","filters-open","out","founding"],
3349
- "files_hash": "12f66ee39aee12b2",
3349
+ "files_hash": "88429188a01e8339",
3350
3350
  "reading_hash": "e9b412d1ec59ceee",
3351
3351
  "counts": {"lines":122,"placed":49,"shared":7,"unplaced":66},
3352
3352
  "omitted": {"data":323},
@@ -1063,6 +1063,8 @@
1063
1063
  spriteSrc: spriteSrc,
1064
1064
  // task 1005148: the visor's two reads — the /me the gate saw, and a tool by id.
1065
1065
  me: function () { return lastMe; },
1066
- openTool: function (id) { var t = toolFor(id); if (t) loadTool(t).then(function (api) { api.open(); }, function () { /* tool did not load */ }); },
1066
+ // A tool opens only for the character whose tool it is (task 1005207): the
1067
+ // review sheet is the Artist's, so no other character can open it this way.
1068
+ openTool: function (id) { var t = toolFor(id); if (t && toolsFor(wearing()).indexOf(t) !== -1) loadTool(t).then(function (api) { api.open(); }, function () { /* tool did not load */ }); },
1067
1069
  };
1068
1070
  })();
@@ -58,6 +58,10 @@ html.pn-open { overflow: hidden; }
58
58
  box-shadow: 0 0 0 1px var(--craft, var(--rule)), var(--pn-shadow-pop);
59
59
  cursor: pointer;
60
60
  }
61
+ /* Game mode (task 1005207): the glass shows only the visor, so the parked tab
62
+ is not drawn. The visor's "Review this page" (R) opens the sheet, and closing
63
+ it leaves nothing at the edge. Classic mode keeps the tab. */
64
+ html[data-game="on"] .pn__tab { display: none; }
61
65
  .pn__tab:hover { background: var(--pn-glass); }
62
66
  .pn__tab:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
63
67
  .pn__tab-icon { width: 18px; height: 18px; }
@@ -361,7 +361,13 @@
361
361
  if (tab) {
362
362
  tab.setAttribute('aria-expanded', on ? 'false' : 'true');
363
363
  tab.setAttribute('aria-label', on ? 'Show the review panel' : 'Hide the review panel');
364
- if (on && !quiet) tab.focus();
364
+ // In game mode the tab is not drawn (page-notes.css, task 1005207), so focus
365
+ // leaves the tucked panel for the page instead of a tab nobody can see.
366
+ if (on && !quiet) {
367
+ if (tab.getClientRects && tab.getClientRects().length === 0) {
368
+ if (sheet.contains(document.activeElement) && document.activeElement.blur) document.activeElement.blur();
369
+ } else tab.focus();
370
+ }
365
371
  }
366
372
  if (on) save();
367
373
  else if (state.pending) { state.pending = false; openDraft(); }
@@ -878,7 +878,7 @@ html[data-theme="dark"] .brand-mark__mark { --mark-ink: #e2e1e4; }
878
878
  with the count over its word, the one accent button (its name may take two lines; its key is in the sheet), and of
879
879
  the key hints only Actions, as its dots (every other hint is a row in its sheet). */
880
880
  html[data-game="on"] #visor { --visor-inset-bottom: 52px; }
881
- html[data-game="on"] .visor__place, html[data-game="on"] .visor__notch, html[data-game="on"] .visor__frame--top, html[data-game="on"] .visor__name { display: none; }
881
+ html[data-game="on"] .visor__place, html[data-game="on"] .visor__progress, html[data-game="on"] .visor__notch, html[data-game="on"] .visor__frame--top, html[data-game="on"] .visor__name { display: none; }
882
882
  html[data-game="on"] .visor__foot { grid-template-columns: auto minmax(0, 1fr) auto; column-gap: var(--ds-space-hair); }
883
883
  html[data-game="on"] .visor__me, html[data-game="on"] .visor__hints { transform: none; }
884
884
  html[data-game="on"] .visor__me { gap: var(--ds-space-hair); }
@@ -308,9 +308,10 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
308
308
  /* task 1005148: with the visor up (html.visor-on, game mode; visor.js), the sky's own layers keep
309
309
  clear of the glass, as ADR 0399 §2.3 asks of a scene's pop-ups: the top row, the composer, the
310
310
  jump field and the two side panels step below the compass and the place line, and the foot and
311
- the tour rise above the visor's corner pills. Outside game mode nothing here applies. */
312
- html.visor-on .top { padding-top: calc(60px + env(safe-area-inset-top, 0px)); }
313
- html.visor-on .composer, html.visor-on .jump { top: calc(116px + env(safe-area-inset-top, 0px)); }
314
- html.visor-on .panel, html.visor-on .room { top: calc(180px + env(safe-area-inset-top, 0px)); bottom: calc(200px + env(safe-area-inset-bottom, 0px)); }
311
+ the tour rise above the visor's corner pills. Task 1005203 put the version's progress under
312
+ the place, so the top offsets grew by its 24px. Outside game mode nothing here applies. */
313
+ html.visor-on .top { padding-top: calc(84px + env(safe-area-inset-top, 0px)); }
314
+ html.visor-on .composer, html.visor-on .jump { top: calc(140px + env(safe-area-inset-top, 0px)); }
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)); }
315
316
  html.visor-on .foot { padding-bottom: calc(132px + env(safe-area-inset-bottom, 0px)); }
316
317
  html.visor-on .tour, html.visor-on .start { bottom: calc(210px + env(safe-area-inset-bottom, 0px)); }
@@ -13,7 +13,7 @@
13
13
  html[data-game="on"] .visor:not([hidden]) {
14
14
  display: block; position: fixed; inset: 0; z-index: 4; pointer-events: none;
15
15
  /* where the glass sits, so a scene's pop-up can keep clear of it (ADR 0399 §2.3) */
16
- --visor-inset-top: 64px; --visor-inset-bottom: 76px;
16
+ --visor-arc: 64px; --visor-inset-top: 88px; --visor-inset-bottom: 76px;
17
17
  --visor-inset-right: max(var(--ds-space-gutter-narrow), var(--helm-inset)); --visor-inset-left: var(--visor-inset-right);
18
18
  color: var(--ink); font-family: var(--font-body);
19
19
  }
@@ -22,14 +22,14 @@ html[data-game="on"] .visor:not([hidden]) {
22
22
  top arc, its notch and the place all sit inside the top inset, above where a scene starts its
23
23
  own top row; the foot arc runs under the foot row. */
24
24
  .visor__frame { position: absolute; left: 0; width: 100%; overflow: visible; }
25
- .visor__frame--top { top: calc(var(--visor-inset-top) - 50px); height: 42px; }
25
+ .visor__frame--top { top: calc(var(--visor-arc) - 50px); height: 42px; }
26
26
  .visor__frame--foot { bottom: calc(var(--visor-inset-bottom) - 44px); height: 18px; }
27
27
  .visor__frame path { vector-effect: non-scaling-stroke; }
28
28
  .visor__line { fill: none; stroke: var(--ink-soft); stroke-opacity: .6; stroke-width: var(--line-strong); }
29
29
  .visor__line--under { stroke: var(--bg); stroke-opacity: 1; stroke-width: 5px; }
30
30
  .visor__dot { fill: var(--ink-soft); stroke: var(--bg); stroke-width: var(--line-strong); }
31
31
  .visor__notch, .visor__sight { position: absolute; left: 50%; transform: translate(-50%, -50%); overflow: visible; }
32
- .visor__notch { top: calc(var(--visor-inset-top) - 36px); width: 12px; height: 9px; }
32
+ .visor__notch { top: calc(var(--visor-arc) - 36px); width: 12px; height: 9px; }
33
33
  .visor__sight { top: 50%; width: 24px; height: 24px; }
34
34
 
35
35
  /* type over the scene stands on a small plate of the night, cut like everything pressable and
@@ -42,11 +42,29 @@ html[data-game="on"] .visor:not([hidden]) {
42
42
 
43
43
  /* the place, under the top arc */
44
44
  .visor__place {
45
- position: absolute; left: 50%; top: calc(var(--visor-inset-top) - 26px); transform: translateX(-50%); margin: 0;
45
+ position: absolute; left: 50%; top: calc(var(--visor-arc) - 26px); transform: translateX(-50%); margin: 0;
46
46
  font-size: var(--ds-text-2xl); letter-spacing: var(--name-space-wide); line-height: 1.2;
47
47
  color: var(--ink-soft); white-space: nowrap; padding: 0 var(--ds-space-xs);
48
48
  }
49
49
 
50
+ /* the version's progress under the place (task 1005203): the kit's four segments, then the same
51
+ reading in words (a row of bars is never the only reading). Done quarters are ice, the quarter
52
+ being built is the accent, the rest are outlines; each segment wears an outline of the night so it
53
+ reads over a midday sky. It sits under the arc band, inside the top inset (--visor-inset-top). */
54
+ .visor__progress {
55
+ position: absolute; left: 50%; top: calc(var(--visor-arc) + 6px); transform: translateX(-50%);
56
+ display: flex; align-items: center; gap: var(--ds-space-xs); white-space: nowrap;
57
+ }
58
+ .visor__segs { flex-wrap: nowrap; }
59
+ .visor__segs .seg { border-color: var(--ink-soft); outline: var(--line-strong) solid var(--night-plate); }
60
+ .visor__segs .seg--done { background: var(--ink-soft); border-color: transparent; }
61
+ .visor__segs .seg--now { background: var(--accent); border-color: transparent; }
62
+ .visor__progress-words {
63
+ margin: 0; font-family: var(--name-face); font-weight: var(--name-weight); text-transform: var(--name-case);
64
+ font-size: var(--ds-text-xs); letter-spacing: var(--name-space); line-height: 1; color: var(--ink-soft);
65
+ padding: var(--ds-space-hair) var(--ds-space-xs); background: var(--night-plate); clip-path: var(--cut-path-sm);
66
+ }
67
+
50
68
  /* the foot row: you on the left, the one thing to press in the middle, the key hints on the
51
69
  right. The two side groups lean in a little, as the glass curves. */
52
70
  .visor__foot {
@@ -90,6 +90,39 @@
90
90
  var s = String(Math.abs(v)).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
91
91
  return (v < 0 ? '−' : '') + s + (Math.abs(v) === 1 ? ' credit' : ' credits');
92
92
  }
93
+ // ---------- the version's progress (task 1005203) ----------
94
+ // Four segments under the place: the version the project is building, in quarters. The
95
+ // owner chose it (2026-11-11): the same number /status reports, never an invented one. The
96
+ // version is the starred focus while it is still open, else the first one building; with
97
+ // neither (or the read failing) there are no segments, never empty ones.
98
+ var progress = null; // { id, name, pct } once read
99
+ function pickVersion(rows, focusId) {
100
+ if (!Array.isArray(rows)) return null;
101
+ var open = function (r) { return r && r.status !== 'shipped' && r.status !== 'frozen'; };
102
+ var hit = null;
103
+ for (var i = 0; i < rows.length && !hit; i++) if (focusId && rows[i] && rows[i].version_id === focusId && open(rows[i])) hit = rows[i];
104
+ for (var j = 0; j < rows.length && !hit; j++) if (rows[j] && rows[j].status === 'building') hit = rows[j];
105
+ if (!hit) return null;
106
+ var pct = Number(hit.percent_complete);
107
+ if (!isFinite(pct)) return null;
108
+ return { id: String(hit.version_id || ''), name: String(hit.name || ''), pct: Math.max(0, Math.min(100, Math.round(pct))) };
109
+ }
110
+ // Each segment is a quarter: a quarter wholly built is done; the quarter being built now is
111
+ // the accent (it is where a claim lands); the rest are outlines. Pure, for the tests.
112
+ function segStates(pct) {
113
+ var out = [], full = Math.floor(Math.max(0, Math.min(100, pct)) / 25);
114
+ for (var i = 0; i < 4; i++) out.push(i < full ? 'done' : (i === full ? 'now' : 'todo'));
115
+ return out;
116
+ }
117
+ function readProgress() {
118
+ if (typeof fetch !== 'function') return;
119
+ var get = function (u) { return fetch(u, { credentials: 'same-origin' }).then(function (r) { return r.ok ? r.json() : null; }, function () { return null; }); };
120
+ Promise.all([get('/api/bongos/versions/progress'), get('/api/bongos/versions/focus')]).then(function (res) {
121
+ progress = pickVersion(res[0] && res[0].progress, res[1] && res[1].focus_version_id);
122
+ if (progress) render();
123
+ });
124
+ }
125
+
93
126
  // A link on the hall's own host: '/work' on the builders host, '/builders/work' on the apex.
94
127
  function hallHref(p) {
95
128
  var here = String((window.location && window.location.pathname) || '');
@@ -150,6 +183,7 @@
150
183
  var n = credits();
151
184
  return frameSvg() +
152
185
  (scene.place ? '<p class="visor__place">' + esc(scene.place) + '</p>' : '') +
186
+ progressHtml() +
153
187
  '<div class="visor__foot">' +
154
188
  '<div class="visor__me">' +
155
189
  (sprite ? '<img class="visor__sprite" src="' + esc(sprite) + '" alt="" width="250" height="290" decoding="async">' : '') +
@@ -165,6 +199,16 @@
165
199
  '</div>' +
166
200
  '</div>';
167
201
  }
202
+ // The kit's segments (controls.css .seg), always beside the words that say the same thing.
203
+ function progressHtml() {
204
+ if (!progress) return '';
205
+ var segs = segStates(progress.pct).map(function (s) { return '<li class="seg' + (s === 'todo' ? '' : ' seg--' + s) + '"></li>'; }).join('');
206
+ var said = progress.id + (progress.name ? ', ' + progress.name : '') + ': ' + progress.pct + '% built';
207
+ return '<div class="visor__progress" data-visor-progress title="' + esc(said) + '">' +
208
+ '<ol class="segs visor__segs" aria-hidden="true">' + segs + '</ol>' +
209
+ '<p class="visor__progress-words"><span class="sr-only">' + esc(said) + '</span>' +
210
+ '<span aria-hidden="true">' + esc(progress.id) + ' · ' + progress.pct + '%</span></p></div>';
211
+ }
168
212
  function sheetHtml(id) {
169
213
  var rows = actionsFor(id).map(function (a) {
170
214
  return '<li><a class="visor__row" data-visor-act="' + esc(a.id) + '" ' + actionAttrs(a) + '><span>' + esc(a.label) + '</span>' + keyHtml(a.key) + '</a></li>';
@@ -319,6 +363,7 @@
319
363
  document.addEventListener('otbcraft:me', render);
320
364
  document.addEventListener('otb:craft', render);
321
365
  render();
366
+ readProgress();
322
367
  }
323
368
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
324
369
  else start();
@@ -335,5 +380,7 @@
335
380
  keyFor: keyFor,
336
381
  creditsText: creditsText,
337
382
  character: character,
383
+ pickVersion: pickVersion,
384
+ segStates: segStates,
338
385
  };
339
386
  })();
@@ -4,6 +4,7 @@ Covers: every shelled hall page (`shell-game.js`, `shell.js`). Spec: `docs/specs
4
4
 
5
5
  ## What it settled
6
6
 
7
+ - **The visor is the control, the scene is the view ([ADR 0403](../../../docs/adr/0403-in-game-mode-the-visor-is-the-control-and-the-scene-is-the-view.md), owner 2026-11-11).** Every main call to action in game mode is on the visor; a game place's scene shows the project and can be moved through, but carries no buttons of its own. A scene hands its actions to the visor (`OTBVisor.scene`). A button in a scene is a defect.
7
8
  - **Game mode is the existing full-screen switch, made hall-wide. There is no second full-screen mode.** The switch was already on every page (task 1004430) but remembered per page, so a reader had to turn it on 33 times. Game mode is one `localStorage` key, `hall-game`; while it is `on`, `shell.js` `storedFullscreen()` answers yes for every page, whatever that page remembers. What hides, the corner button and the Escape hand-off are the shell's, unchanged.
8
9
  - **`shell-game.js` holds the mode and nothing else**: `window.OTBShellGame` = `on()`, `set(bool)`, `hint()`. It stamps `html[data-game="on"]` for a sheet or script that wants to read the mode. `shell.js` reads it through a fallback object, so a page without the file is never in game mode. It is its own file because `shell.js` sits on the 1,500-line ratchet; the start screen and options menu (R03, R04) are expected to grow here, not in `shell.js`.
9
10
  - **Load order**: `shell-game.js` before `shell-trail.js` / `craft-mode.js` / `shell.js`, loaded the same way (deferred or not) as the shell on that page. Test-pinned.
@@ -2,16 +2,21 @@
2
2
 
3
3
  Covers: `visor.js` + `visor.css`, loaded on Home, `/city`, `/goal-map` and `/thinking`, and the three scene registrations (`sky-interaction.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
+ ## The rule every game page keeps (ADR 0403, owner 2026-11-11)
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.
8
+
5
9
  ## The minimal visor (Helm look 3, task 1005195)
6
10
 
7
11
  The glass was redrawn to the owner's mock-up "Cloud city: in the visor, minimal". Look and layout only: every action, key, id, aria attribute, copy string and data source in `visor.js` is as task 1005148 left it. Where this section and the older bullets below disagree about the look or where a part sits, this section is current.
8
12
 
9
13
  - **What is on the glass.** Two thin arcs (one along the top, one under the foot row), a notch under the top arc, a ring-and-dot sight at the centre, the place in the name voice under the top arc, and one foot row: the sprite in a cut box with the character's name and the credits (white count, ice word); the one accent button, cut corner, with its key; the quiet key hints (a key in a small box and a name, no box round the pair). The compass cells, the side lines, the centre cross and the round pills are gone.
10
14
  - **Where each control went.** The main action is the accent button, centred. The character's other actions (after the game-places filter) are key hints on the right; they were rows in the sheet only. **Actions (A)** is demoted from a top-right pill to a key hint and still opens the same sheet, which now stands over the hints. **Options (Esc)** is the last hint, the same link the sheet's last row is. Nothing was removed.
11
- - **In the mock-up, not built, because the visor has no data for it:** the four progress segments, the marker and name on the building in view (the visor owns no camera, ADR 0399 §2.2), and the party bars with a speaker's name and a Voice key. Each is a scene or a later task handing the visor real data; none is drawn empty.
15
+ - **The four progress segments (task 1005203).** Under the place: the version the project is building, in quarters, then the same reading in words (`BONGOS-V2 · 89%`; the screen reader hears the version's name too). The owner chose the measure (2026-11-11): the version's `percent_complete` from the public `GET /versions/progress`, the number /status reports. The version is the starred focus (`GET /versions/focus`) while it is still open, else the first one `building`; with neither, or a failed read, no segments. A quarter wholly built is the soft ink, the quarter being built is the accent, the rest are outlines; each wears an outline of the night, and the words stand on a night plate (`--night-plate`, the rule task 1005196 set), for a midday sky. The arc band is `--visor-arc` (64px) and the scene inset grew to 88px (`--visor-inset-top`), so the sky's top offsets in `thinking.css` grew by 24px. Hidden on a phone, with the place. Harness fixture: `versions__progress.json`.
16
+ - **In the mock-up, not built, because the visor has no data for it:** the marker and name on the building in view (the visor owns no camera, ADR 0399 §2.2), and the party bars with a speaker's name and a Voice key. Each is a scene or a later task handing the visor real data; none is drawn empty.
12
17
  - **It reads over any sky, with no per-hour style, and it darkens none of it** (task 1005196, owner 2026-11-11: "I don't like how dark the edges of the view are"). The glass paints nothing of its own: the bands of `--night-glass` at its top and bottom edge are gone, and so is the dark halo round its type. The marks carry themselves. The place, each key hint and your name and credits stand on a small cut plate of the night (`--night-plate`, the ground at 88%), sized to the words; every line is drawn twice (a wider solid stroke of the night under the ice hairline, `.visor__line--under`), so an arc is ice on night and a dark line on cream cloud; a key sits on a chip of the night. One new token, `--night-plate`. Tried first and dropped: a 4px night stroke round each letter with no plate, which filled the condensed capitals in.
13
18
  - **A phone.** The shared phone block in `style.css` (no breakpoint of the visor's own): the top arc, its notch and the place step aside (the top of a phone is the scene's and the shell's); the foot row stays one line with nothing leaning (the sprite, the count over its word, the accent button whose name may wrap and whose key is left to the sheet, and of the hints only Actions, as three dots on a night chip). Between a phone and a wide desk the hints wrap onto a second line, right-aligned, rather than run into the button.
14
- - **Scenes.** The top arc, the notch and the place sit inside the top 64px (`--visor-inset-top`), the band the sky already leaves free, so no scene's top row is struck through. Home no longer shifts its greeting row left (there is no pill top-right to clear). The city's door rises above the foot row at every width, because the accent button is centred where the door stood. The sky's `visor-on` offsets are unchanged.
19
+ - **Scenes.** The top arc, the notch and the place sit inside the top 64px (`--visor-arc`; the segments under them take the inset to 88px, `--visor-inset-top`), so no scene's top row is struck through. Home no longer shifts its greeting row left (there is no pill top-right to clear). The city's door rises above the foot row at every width, because the accent button is centred where the door stood. The sky's `visor-on` offsets are unchanged.
15
20
  - **Looking at it.** The stand-in scene in `tests/hall_visor.mjs` over the hall's real sheets; put a bright gradient behind it to check midday.
16
21
 
17
22
  ## What it settled (task 1005148)
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.88",
3
+ "version": "1.24.90",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.88",
9
+ "version": "1.24.90",
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.88",
3
+ "version": "1.24.90",
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",
@@ -11631,5 +11631,17 @@
11631
11631
  "id": "1005188",
11632
11632
  "text": "Pressing Escape to close a building card on the city page no longer also brings the sidebar back."
11633
11633
  }
11634
+ ],
11635
+ "1.24.89": [
11636
+ {
11637
+ "id": "1005203",
11638
+ "text": "In the game, the visor now shows four small bars for how much of the current version is built, and every main button in the game now belongs on the visor."
11639
+ }
11640
+ ],
11641
+ "1.24.90": [
11642
+ {
11643
+ "id": "1005207",
11644
+ "text": "In the game view the Artist's feedback tab no longer sits on the screen; the Artist opens the feedback sheet from the visor, and only the Artist can open it."
11645
+ }
11634
11646
  ]
11635
11647
  }
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.88'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.90'; // 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');
@@ -0,0 +1,86 @@
1
+ // tests/hall_game_review_tab.mjs — no parked review tab on the game's glass (task 1005207).
2
+ //
3
+ // Game mode (html[data-game="on"]) shows only the visor, so the Artist's docked review
4
+ // sheet (page-notes.js + page-notes.css) draws no tab at the right edge. The visor's
5
+ // "Review this page" (R) still opens the sheet, and closing it leaves nothing behind.
6
+ // Classic mode keeps the tab exactly as it was. Driven in a real browser over the
7
+ // REAL page-notes.js and the hall's real sheets.
8
+ import assert from 'node:assert/strict';
9
+ import { test, after } from 'node:test';
10
+ import { createRequire } from 'node:module';
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ const HALL = path.join(ROOT, 'modules/hall-ui/public');
18
+
19
+ const lib = require(path.join(ROOT, 'modules', 'ui-design', 'kit', 'lib.js'));
20
+ let browser = null;
21
+ try { browser = await lib.launchBrowser(); } catch (e) { if (!(e instanceof lib.NoBrowserError)) throw e; }
22
+ after(() => browser && browser.close());
23
+ const skip = !browser && 'no system Edge or Chrome';
24
+
25
+ const ORIGIN = 'http://hall.test';
26
+ const TYPES = { '.js': 'text/javascript', '.css': 'text/css' };
27
+ const PAGE = (game) => `<!doctype html><html data-theme="dark"${game ? ' data-game="on"' : ''}><head><meta charset="utf-8">
28
+ <link rel="stylesheet" href="/builders/style.css"></head>
29
+ <body data-page="home"><button id="before">page</button>
30
+ <script>window.OTB = { escapeHtml: (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => '&#' + c.charCodeAt(0) + ';') };</script>
31
+ <script src="/builders/page-notes.js"></script></body></html>`;
32
+
33
+ async function open(game) {
34
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
35
+ await ctx.route(ORIGIN + '/**', (route) => {
36
+ const p = new URL(route.request().url()).pathname;
37
+ if (p === '/') return route.fulfill({ contentType: 'text/html', body: PAGE(game) });
38
+ // The sheet's reads: an empty draft and an empty list are enough to draw it.
39
+ if (p.startsWith('/api/bongos/page-notes')) return route.fulfill({ contentType: 'application/json', body: JSON.stringify({ note: { id: 1, body_md: '', status: 'draft' }, images: [], notes: [] }) });
40
+ const f = path.join(HALL, p.replace(/^\/builders\//, ''));
41
+ if (p.startsWith('/builders/') && TYPES[path.extname(p)] && fs.existsSync(f)) return route.fulfill({ contentType: TYPES[path.extname(p)], body: fs.readFileSync(f) });
42
+ return route.fulfill({ status: 404, body: '' });
43
+ });
44
+ const page = await ctx.newPage();
45
+ const errors = [];
46
+ page.on('pageerror', (e) => errors.push(String(e.message)));
47
+ await page.goto(ORIGIN + '/');
48
+ await page.waitForFunction(() => !!window.OTBPageNotes);
49
+ // What craft-mode.js does for the Artist: dock the sheet, tucked away behind its tab.
50
+ await page.evaluate(() => window.OTBPageNotes.dock());
51
+ return { ctx, page, errors };
52
+ }
53
+ const tabShown = (page) => page.evaluate(() => {
54
+ const t = document.querySelector('#page-notes .pn__tab');
55
+ return !!t && getComputedStyle(t).display !== 'none' && t.getClientRects().length > 0;
56
+ });
57
+ const panelShown = (page) => page.evaluate(() => getComputedStyle(document.querySelector('#page-notes .pn__panel')).visibility !== 'hidden'
58
+ && !document.getElementById('page-notes').classList.contains('pn--collapsed'));
59
+
60
+ test('classic mode keeps the parked tab at the right edge', { skip }, async () => {
61
+ const { ctx, page, errors } = await open(false);
62
+ assert.equal(await tabShown(page), true);
63
+ assert.deepEqual(errors, []);
64
+ await ctx.close();
65
+ });
66
+
67
+ test('game mode: no parked tab; R opens the sheet, and closing leaves nothing at the edge', { skip }, async () => {
68
+ const { ctx, page, errors } = await open(true);
69
+ assert.equal(await tabShown(page), false, 'parked, nothing shows on the glass');
70
+ await page.waitForTimeout(350); // the slide-out
71
+ assert.equal(await panelShown(page), false);
72
+ // The visor's "Review this page" is OTBCraftMode.openTool('palette') → OTBPageNotes.open().
73
+ await page.evaluate(() => window.OTBPageNotes.open());
74
+ assert.equal(await panelShown(page), true, 'R still opens the sheet');
75
+ assert.equal(await tabShown(page), false, 'open, still no tab beside it');
76
+ await page.click('#page-notes [data-pn-close]');
77
+ await page.waitForTimeout(350);
78
+ assert.equal(await panelShown(page), false, 'closed, the sheet is gone');
79
+ assert.equal(await tabShown(page), false, 'and no tab is left behind');
80
+ assert.equal(await page.evaluate(() => document.getElementById('page-notes').contains(document.activeElement)), false,
81
+ 'focus leaves the tucked sheet instead of landing on a tab nobody can see');
82
+ await page.evaluate(() => window.OTBPageNotes.open());
83
+ assert.equal(await panelShown(page), true, 'and R opens it again');
84
+ assert.deepEqual(errors, []);
85
+ await ctx.close();
86
+ });
@@ -66,6 +66,33 @@ test('a key means an action only when nothing else spent it', () => {
66
66
  assert.equal(V.keyFor('artisan', ev('Escape')), null, 'the ambient glass never takes Escape (ADR 0399 §2.4)');
67
67
  });
68
68
 
69
+ // task 1005203: the four segments are the version the project is building, in quarters.
70
+ test('progress: the starred version while open, else the one building, else none', () => {
71
+ const V = loadPure();
72
+ const plain = (x) => JSON.parse(JSON.stringify(x));
73
+ const rows = [
74
+ { version_id: 'V1', name: 'One', status: 'shipped', percent_complete: 100 },
75
+ { version_id: 'V2', name: 'Two', status: 'building', percent_complete: 89 },
76
+ { version_id: 'V3', name: 'Three', status: 'planning', percent_complete: 39 },
77
+ ];
78
+ assert.deepEqual(plain(V.pickVersion(rows, 'V3')), { id: 'V3', name: 'Three', pct: 39 }, 'the starred version wins while it is open');
79
+ assert.equal(V.pickVersion(rows, 'V1').id, 'V2', 'a starred version that shipped falls back to the one building');
80
+ assert.equal(V.pickVersion(rows, null).id, 'V2');
81
+ assert.equal(V.pickVersion(rows.filter((r) => r.status !== 'building'), null), null, 'nothing building: no segments, never empty ones');
82
+ assert.equal(V.pickVersion(null, 'V2'), null, 'a failed read draws nothing');
83
+ assert.equal(V.pickVersion([{ version_id: 'V9', status: 'building' }], null), null, 'no percent: no invented one');
84
+ });
85
+
86
+ test('progress: each segment is a quarter, and only the quarter being built is the accent', () => {
87
+ const V = loadPure();
88
+ const s = (p) => JSON.parse(JSON.stringify(V.segStates(p)));
89
+ assert.deepEqual(s(0), ['now', 'todo', 'todo', 'todo']);
90
+ assert.deepEqual(s(24), ['now', 'todo', 'todo', 'todo']);
91
+ assert.deepEqual(s(25), ['done', 'now', 'todo', 'todo']);
92
+ assert.deepEqual(s(89), ['done', 'done', 'done', 'now']);
93
+ assert.deepEqual(s(100), ['done', 'done', 'done', 'done'], 'a finished version has nothing left to build');
94
+ });
95
+
69
96
  test('credits, never a payout: the count reads as credits, with thousands grouped', () => {
70
97
  const V = loadPure();
71
98
  assert.equal(V.creditsText(56021), '56,021 credits');
@@ -95,6 +95,26 @@ test('THE PANEL: docked at the edge in Artist alone, with no top-bar button, and
95
95
  assert.deepEqual(none.loaded, [], 'and nothing loads');
96
96
  });
97
97
 
98
+ test('the review sheet is the Artist\'s alone: openTool (the visor\'s R) opens it for no other character (task 1005207)', async () => {
99
+ const a = boot({ session: { 'hall-craft': 'artist' } });
100
+ await flush();
101
+ a.craft.openTool('palette');
102
+ await flush();
103
+ assert.deepEqual(a.opened, ['dock', 'open'], 'the Artist opens it');
104
+ for (const m of ['artisan', 'patron']) {
105
+ const p = boot({ session: { 'hall-craft': m } });
106
+ await flush();
107
+ p.craft.openTool('palette');
108
+ await flush();
109
+ assert.deepEqual(p.opened, [], `${m}: nothing opens`);
110
+ assert.deepEqual(p.loaded, [], `${m}: the sheet's script never loads`);
111
+ }
112
+ const none = boot();
113
+ none.craft.openTool('palette');
114
+ await flush();
115
+ assert.deepEqual(none.opened, [], 'with no character, nothing opens');
116
+ });
117
+
98
118
  test('a screen in the options menu frame docks no second panel: the menu around it has one', async () => {
99
119
  const p = boot({ session: { 'hall-craft': 'artist' }, embedded: true });
100
120
  await flush();