@bongos/core 1.24.79 → 1.24.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/.bongos-core.json +495 -350
  2. package/.claude/skills/ask-for-help/SKILL.md +1 -1
  3. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  4. package/.claude/skills/design/SKILL.md +1 -1
  5. package/.claude/skills/goal-create/SKILL.md +1 -1
  6. package/.claude/skills/planning-session/SKILL.md +1 -1
  7. package/clients/bongos-client/README.md +1 -1
  8. package/clients/bongos-client/bongos-client.global.js +8 -0
  9. package/clients/bongos-client/index.cjs +8 -0
  10. package/clients/bongos-client/index.d.ts +11 -0
  11. package/clients/bongos-client/index.mjs +8 -0
  12. package/docs/adr/0274-one-kernel-three-role-packs.md +2 -2
  13. package/docs/adr/0399-the-visor-is-glass-over-the-scene-and-owns-no-camera.md +91 -0
  14. package/docs/adr/0400-a-chain-reserves-work-and-each-sub-quest-is-still-claimed-alone.md +109 -0
  15. package/docs/adr/0401-one-options-menu-opens-every-settings-screen-in-a-frame.md +53 -0
  16. package/docs/adr/README.md +3 -0
  17. package/docs/api/openapi.json +195 -3
  18. package/docs/api-reference.md +8 -4
  19. package/docs/architecture.md +7 -3
  20. package/docs/copy-inventory.md +390 -370
  21. package/docs/copy-registry.json +719 -518
  22. package/docs/design/characters/character-sprites-source.webp +0 -0
  23. package/docs/file-map.md +7 -8
  24. package/docs/module-api-changelog.md +2 -0
  25. package/docs/modules-contract.md +5 -0
  26. package/docs/onboarding/slash-commands.md +1 -1
  27. package/docs/packs/{engineer.md → artisan.md} +5 -5
  28. package/docs/packs/artist.md +50 -4
  29. package/docs/page-inventory.json +15 -4
  30. package/docs/page-readings.json +3182 -3336
  31. package/docs/{packs/ideator.md → recipes/ideation-playbook.md} +5 -48
  32. package/migrations/core_296_crafts_artisan_artist.sql +72 -0
  33. package/migrations/core_297_builder_character.sql +30 -0
  34. package/modules/copy-desk/proposals.js +1 -1
  35. package/modules/economy/credits-by-craft.js +29 -15
  36. package/modules/hall-ui/public/ai-studio.html +4 -4
  37. package/modules/hall-ui/public/atlas.html +5 -5
  38. package/modules/hall-ui/public/atlas.js +3 -1
  39. package/modules/hall-ui/public/blockers.html +5 -5
  40. package/modules/hall-ui/public/board-room.html +4 -4
  41. package/modules/hall-ui/public/characters/artisan.png +0 -0
  42. package/modules/hall-ui/public/characters/artist.png +0 -0
  43. package/modules/hall-ui/public/characters/patron.png +0 -0
  44. package/modules/hall-ui/public/city.css +2 -1
  45. package/modules/hall-ui/public/city.html +9 -6
  46. package/modules/hall-ui/public/city.js +7 -1
  47. package/modules/hall-ui/public/collab.html +4 -4
  48. package/modules/hall-ui/public/commands.html +4 -4
  49. package/modules/hall-ui/public/copy-desk.html +4 -4
  50. package/modules/hall-ui/public/craft-mode.js +258 -64
  51. package/modules/hall-ui/public/deploy.html +4 -4
  52. package/modules/hall-ui/public/diagram-viewer.js +2 -1
  53. package/modules/hall-ui/public/dom-utils.js +1 -1
  54. package/modules/hall-ui/public/goal-map.html +9 -6
  55. package/modules/hall-ui/public/goals.html +5 -5
  56. package/modules/hall-ui/public/government.html +4 -4
  57. package/modules/hall-ui/public/hall-render.js +3 -1
  58. package/modules/hall-ui/public/home-city-draw.js +3 -3
  59. package/modules/hall-ui/public/home-city.css +4 -0
  60. package/modules/hall-ui/public/home-city.js +17 -9
  61. package/modules/hall-ui/public/home-gallery.js +8 -1
  62. package/modules/hall-ui/public/home-idea-box.css +2 -2
  63. package/modules/hall-ui/public/home-idea-box.js +3 -3
  64. package/modules/hall-ui/public/idea.html +5 -5
  65. package/modules/hall-ui/public/ideas.html +5 -5
  66. package/modules/hall-ui/public/index.html +11 -7
  67. package/modules/hall-ui/public/market-watch-item.html +4 -4
  68. package/modules/hall-ui/public/market-watch-rules.html +4 -4
  69. package/modules/hall-ui/public/market-watch.html +6 -9
  70. package/modules/hall-ui/public/market-watch.js +22 -45
  71. package/modules/hall-ui/public/modules.html +4 -4
  72. package/modules/hall-ui/public/money.html +4 -4
  73. package/modules/hall-ui/public/my-agents.html +4 -4
  74. package/modules/hall-ui/public/options-menu.js +409 -0
  75. package/modules/hall-ui/public/paintbrush.js +3 -3
  76. package/modules/hall-ui/public/palette.js +11 -13
  77. package/modules/hall-ui/public/primer.html +5 -5
  78. package/modules/hall-ui/public/profile-crafts.js +1 -1
  79. package/modules/hall-ui/public/profile-roles.js +23 -19
  80. package/modules/hall-ui/public/profile.css +1 -1
  81. package/modules/hall-ui/public/profile.html +19 -18
  82. package/modules/hall-ui/public/profile.js +19 -18
  83. package/modules/hall-ui/public/profile.states.json +4 -4
  84. package/modules/hall-ui/public/project-settings.html +4 -4
  85. package/modules/hall-ui/public/roster.html +4 -4
  86. package/modules/hall-ui/public/roster.js +3 -3
  87. package/modules/hall-ui/public/settings.css +24 -4
  88. package/modules/hall-ui/public/settings.html +21 -17
  89. package/modules/hall-ui/public/settings.js +44 -44
  90. package/modules/hall-ui/public/shell-game.css +65 -0
  91. package/modules/hall-ui/public/shell-game.js +283 -3
  92. package/modules/hall-ui/public/shell.js +7 -26
  93. package/modules/hall-ui/public/sky-interaction.js +7 -0
  94. package/modules/hall-ui/public/speciality-tools.js +1 -2
  95. package/modules/hall-ui/public/street-inside.js +3 -4
  96. package/modules/hall-ui/public/street.html +4 -4
  97. package/modules/hall-ui/public/studio.html +5 -5
  98. package/modules/hall-ui/public/style.css +73 -47
  99. package/modules/hall-ui/public/task-detail.js +5 -4
  100. package/modules/hall-ui/public/task.html +5 -5
  101. package/modules/hall-ui/public/thinking.css +10 -0
  102. package/modules/hall-ui/public/thinking.html +9 -6
  103. package/modules/hall-ui/public/tweak-editor.html +5 -5
  104. package/modules/hall-ui/public/tweak-editor.js +5 -1
  105. package/modules/hall-ui/public/update-pill.js +91 -2
  106. package/modules/hall-ui/public/visor.css +80 -0
  107. package/modules/hall-ui/public/visor.js +316 -0
  108. package/modules/hall-ui/public/watch-repo-invite.js +159 -0
  109. package/modules/hall-ui/public/watch.html +6 -5
  110. package/modules/hall-ui/public/watch.js +17 -2
  111. package/modules/hall-ui/public/welcome-boot.js +15 -6
  112. package/modules/hall-ui/public/work.html +5 -5
  113. package/modules/hall-ui/records/builders-roster.md +2 -2
  114. package/modules/hall-ui/records/craft-mode.md +13 -11
  115. package/modules/hall-ui/records/game-mode.md +2 -2
  116. package/modules/hall-ui/records/governor-street.md +1 -1
  117. package/modules/hall-ui/records/home-city.md +3 -1
  118. package/modules/hall-ui/records/market-watch-settings.md +2 -0
  119. package/modules/hall-ui/records/options-menu.md +15 -0
  120. package/modules/hall-ui/records/profile-roles.md +11 -11
  121. package/modules/hall-ui/records/sky.md +3 -3
  122. package/modules/hall-ui/records/start-screen.md +21 -0
  123. package/modules/hall-ui/records/tweak-editor.md +1 -1
  124. package/modules/hall-ui/records/update-pill.md +12 -0
  125. package/modules/hall-ui/records/visor.md +23 -0
  126. package/modules/hall-ui/records/welcome-tower.md +5 -5
  127. package/modules/ideas/ideator-stats.js +3 -1
  128. package/modules/ideas/inbox.js +2 -2
  129. package/modules/lifecycle/activity.js +1 -1
  130. package/modules/lifecycle/cascade.js +4 -4
  131. package/modules/lifecycle/goal-spec-task.js +1 -1
  132. package/modules/lifecycle/help-requests.js +19 -15
  133. package/modules/lifecycle/kickoff-checklist.js +3 -3
  134. package/modules/lifecycle/kickoff-seed.js +1 -1
  135. package/modules/lifecycle/migrations/lifecycle_021_crafts_artisan_artist.sql +29 -0
  136. package/modules/lifecycle/mingle.js +4 -4
  137. package/modules/lifecycle/page-tweak-write.js +1 -1
  138. package/modules/lifecycle/routes/task-write-routes.js +3 -1
  139. package/modules/lifecycle/routes/tasks.js +3 -3
  140. package/modules/lifecycle/ship-card.js +2 -3
  141. package/modules/lifecycle/task-classifier.js +39 -29
  142. package/modules/lifecycle/tests/cascade.mjs +7 -7
  143. package/modules/onboarding/founding-mode.js +3 -0
  144. package/modules/onboarding/genesis-home.js +21 -1
  145. package/modules/onboarding/routes/onboarding.js +2 -0
  146. package/modules/page-notes/notes.js +3 -2
  147. package/modules/page-notes/split.js +1 -1
  148. package/modules/platform-identity/craft-rollup.js +35 -7
  149. package/modules/platform-identity/migrations/platform_identity_036_crafts_artisan_artist.sql +45 -0
  150. package/modules/platform-identity/platform-identity.js +2 -2
  151. package/modules/platform-identity/routes/community-leaderboard.js +2 -1
  152. package/modules/platform-identity/routes/community-search.js +2 -1
  153. package/modules/platform-identity/routes/sso.js +2 -1
  154. package/modules/platform-identity/tests/platform-identity.mjs +6 -6
  155. package/modules/public-landing/public/community.html +3 -5
  156. package/modules/public-landing/public/index.html +1 -1
  157. package/modules/public-landing/public/projects.html +1 -1
  158. package/modules/specialities/migrations/specialities_004_crafts_artisan_artist.sql +30 -0
  159. package/modules/specialities/routes/specialities.js +2 -2
  160. package/modules/status-ui/public/status.js +1 -1
  161. package/modules/ui-design/kit/fixtures/community-leaderboard.json +10 -10
  162. package/modules/ui-design/kit/fixtures/community-search.json +2 -2
  163. package/modules/ui-design/kit/fixtures/scouting-recruiter.json +1 -1
  164. package/package-lock.json +2 -2
  165. package/package.json +1 -1
  166. package/release-notes.json +70 -0
  167. package/scripts/bongos/autobongos-loop.js +1 -1
  168. package/scripts/bongos/discipline-modes.json +7 -12
  169. package/scripts/bongos/fitness.js +1 -1
  170. package/scripts/bongos/gen-hall-sitemap.js +7 -7
  171. package/scripts/bongos/grade-smoke.js +2 -2
  172. package/scripts/bongos/init.js +2 -2
  173. package/scripts/bongos/newcomer-floor.config.json +1 -2
  174. package/scripts/bongos/newcomer-floor.js +4 -3
  175. package/scripts/bongos/newcomer-floor.templates.json +10 -12
  176. package/scripts/bongos/newcomer-floor.test.js +47 -38
  177. package/scripts/bongos/role-pack-guard.js +5 -4
  178. package/scripts/bongos/seed-tasks.example.json +3 -3
  179. package/scripts/bongos/setup.js +20 -107
  180. package/scripts/bongos/start.js +2 -2
  181. package/scripts/bongos/task.js +5 -4
  182. package/scripts/bongos/triage.js +1 -1
  183. package/scripts/bongos/worktree.js +16 -4
  184. package/scripts/setup-builder.js +6 -46
  185. package/src/bongos/auth-admission.js +8 -8
  186. package/src/bongos/craft-mode.js +2 -0
  187. package/src/bongos/db.js +39 -1
  188. package/src/bongos/github-repos.js +78 -0
  189. package/src/bongos/module-scope-map.js +22 -0
  190. package/src/bongos/role-stats.js +27 -11
  191. package/src/bongos/routes/github.js +100 -2
  192. package/src/bongos/routes/me.js +33 -0
  193. package/src/bongos/serve-internal.js +26 -9
  194. package/src/crafts.js +66 -0
  195. package/src/module-api.js +6 -1
  196. package/src/module-loader/manifest-schema.js +16 -0
  197. package/src/modules.js +2 -2
  198. package/src/role-responsibilities.js +6 -5
  199. package/tests/acclaim_feed_evidence_db.mjs +1 -1
  200. package/tests/activity_rollup_as_of.mjs +1 -1
  201. package/tests/api_alias_caller_ratchet.mjs +3 -3
  202. package/tests/application_lifecycle.mjs +1 -1
  203. package/tests/artist_review.mjs +1 -1
  204. package/tests/artist_review_goal.mjs +1 -1
  205. package/tests/blocker_hall_live_proof.mjs +1 -1
  206. package/tests/blocker_tasks_named_db.mjs +2 -2
  207. package/tests/canonical_profile_url.mjs +1 -1
  208. package/tests/cap_newcomer_tasks.mjs +13 -13
  209. package/tests/character_creation.mjs +294 -0
  210. package/tests/claim_gate_open_blocker.mjs +2 -2
  211. package/tests/claim_gate_rebase.mjs +6 -6
  212. package/tests/claude_materialize.mjs +1 -1
  213. package/tests/clawback_confirmed_backlog.mjs +3 -3
  214. package/tests/collab_page.mjs +8 -9
  215. package/tests/collab_self_settling.mjs +2 -2
  216. package/tests/community_leaderboard_route.mjs +13 -5
  217. package/tests/community_proof_db.mjs +3 -0
  218. package/tests/community_search_route.mjs +7 -6
  219. package/tests/conductor.mjs +1 -1
  220. package/tests/conductor_main_e2e.mjs +8 -8
  221. package/tests/copy_desk_flags_db.mjs +1 -1
  222. package/tests/copy_desk_page_approve.mjs +2 -2
  223. package/tests/copy_desk_page_claim.mjs +2 -2
  224. package/tests/copy_desk_page_next.mjs +1 -1
  225. package/tests/copy_desk_page_release.mjs +1 -1
  226. package/tests/copy_desk_page_submit.mjs +2 -2
  227. package/tests/core_258_box_session_claims_db.mjs +1 -1
  228. package/tests/craft_mode.mjs +48 -34
  229. package/tests/craft_mode_home.mjs +20 -20
  230. package/tests/craft_mode_looks.mjs +16 -15
  231. package/tests/craft_mode_nav.mjs +31 -29
  232. package/tests/craft_review_mode.mjs +46 -41
  233. package/tests/crafts_vocabulary.mjs +70 -0
  234. package/tests/cross_discipline_priority_direction.mjs +5 -5
  235. package/tests/fitness.mjs +7 -11
  236. package/tests/funded_project_pays_proof_db.mjs +1 -1
  237. package/tests/genesis_home.mjs +21 -1
  238. package/tests/github_chip.mjs +3 -3
  239. package/tests/github_repo_invite.mjs +379 -0
  240. package/tests/goal_criterion_confirm_ui.mjs +1 -1
  241. package/tests/goal_invite_consent.mjs +1 -1
  242. package/tests/goal_map_page.mjs +2 -2
  243. package/tests/goal_sort_staleness.mjs +1 -1
  244. package/tests/goal_spec_task.mjs +1 -1
  245. package/tests/goal_spec_task_db.mjs +3 -3
  246. package/tests/grade_engine.mjs +2 -2
  247. package/tests/grader_no_artifact.mjs +1 -1
  248. package/tests/grader_prompt_fencing.mjs +1 -1
  249. package/tests/grader_prompt_window_bound.mjs +1 -1
  250. package/tests/grader_reader_lens.mjs +2 -2
  251. package/tests/grader_severity_gate.mjs +1 -1
  252. package/tests/grader_unbacked_score.mjs +1 -1
  253. package/tests/hall_approval_queue.mjs +1 -1
  254. package/tests/hall_board_world.mjs +2 -2
  255. package/tests/hall_builders_page_pass.mjs +1 -1
  256. package/tests/hall_city.mjs +1 -1
  257. package/tests/hall_fullscreen.mjs +27 -3
  258. package/tests/hall_kit_charts.mjs +1 -1
  259. package/tests/hall_landing_boot.mjs +1 -1
  260. package/tests/hall_my_agents.mjs +16 -16
  261. package/tests/hall_nav.mjs +22 -15
  262. package/tests/hall_options_menu.mjs +247 -0
  263. package/tests/hall_oversight_world.mjs +1 -1
  264. package/tests/hall_palette.mjs +8 -9
  265. package/tests/hall_profile_world.mjs +3 -3
  266. package/tests/hall_record_world.mjs +2 -2
  267. package/tests/hall_settings_world.mjs +2 -1
  268. package/tests/hall_sitemap.mjs +6 -5
  269. package/tests/hall_start_screen.mjs +244 -0
  270. package/tests/hall_street.mjs +2 -2
  271. package/tests/hall_street_inside.mjs +17 -9
  272. package/tests/hall_studio_world.mjs +3 -3
  273. package/tests/hall_visor.mjs +224 -0
  274. package/tests/help_requests.mjs +47 -15
  275. package/tests/helpers.mjs +4 -4
  276. package/tests/home_city.mjs +6 -4
  277. package/tests/home_city_widget.mjs +33 -14
  278. package/tests/home_gallery_widget.mjs +4 -4
  279. package/tests/home_idea_box.mjs +6 -6
  280. package/tests/hub_community_page.mjs +3 -3
  281. package/tests/hub_craft_rollup.mjs +75 -36
  282. package/tests/hub_recent_ships.mjs +3 -2
  283. package/tests/idea_cascade.mjs +2 -2
  284. package/tests/idea_credit_streams_db.mjs +1 -1
  285. package/tests/idea_detail_page.mjs +1 -1
  286. package/tests/idea_ideate_full.mjs +5 -2
  287. package/tests/idea_spark_hall.mjs +1 -1
  288. package/tests/init.mjs +3 -3
  289. package/tests/instance_manifest.mjs +2 -2
  290. package/tests/kickoff_checklist.mjs +1 -1
  291. package/tests/leaderboard_roles.mjs +34 -25
  292. package/tests/lifecycle_claim_ladder_refs.mjs +55 -0
  293. package/tests/lightbulb.mjs +14 -10
  294. package/tests/main_discipline.mjs +8 -8
  295. package/tests/markdown_line_endings.mjs +1 -1
  296. package/tests/market_watch_pages.mjs +52 -74
  297. package/tests/me_streak_leaderboard_queries.mjs +3 -3
  298. package/tests/migration_allocation.mjs +1 -1
  299. package/tests/mingle.mjs +36 -21
  300. package/tests/module-scope-map.mjs +22 -2
  301. package/tests/module_api.mjs +1 -0
  302. package/tests/module_contributions.mjs +13 -13
  303. package/tests/module_manifest.mjs +29 -0
  304. package/tests/module_skills_hook.mjs +2 -2
  305. package/tests/modules.mjs +14 -7
  306. package/tests/nav_permission_atoms.mjs +2 -1
  307. package/tests/needs_rebase.mjs +1 -1
  308. package/tests/newcomer_restock.mjs +13 -13
  309. package/tests/page_notes_panel.mjs +22 -22
  310. package/tests/page_tweak_no_block.mjs +1 -1
  311. package/tests/paintbrush.mjs +7 -7
  312. package/tests/privacy_matrix.mjs +1 -0
  313. package/tests/profile_activity.mjs +20 -23
  314. package/tests/profile_earnings.mjs +1 -1
  315. package/tests/profile_nudge_prefs.mjs +4 -4
  316. package/tests/profile_role_headline.mjs +73 -46
  317. package/tests/profile_role_stats.mjs +55 -45
  318. package/tests/profile_rollup_consent.mjs +19 -6
  319. package/tests/profile_route.mjs +12 -12
  320. package/tests/profile_ui_cross_project.mjs +4 -4
  321. package/tests/project_invite_suggestions.mjs +2 -2
  322. package/tests/promote_goal_id.mjs +1 -1
  323. package/tests/publish_manifest.mjs +1 -1
  324. package/tests/rank_floor_wiring.mjs +1 -1
  325. package/tests/regrade_actual_minutes.mjs +1 -1
  326. package/tests/removal_proposal.mjs +2 -2
  327. package/tests/reward_gate.mjs +4 -4
  328. package/tests/reward_mode_cost_plus_only.mjs +1 -1
  329. package/tests/role_responsibilities.mjs +18 -12
  330. package/tests/role_stats_db.mjs +17 -16
  331. package/tests/scouting_rollup_fanout.mjs +5 -5
  332. package/tests/seed_tasks.mjs +5 -2
  333. package/tests/session_earnings_mirror_db.mjs +1 -1
  334. package/tests/session_reward_on_land_db.mjs +1 -1
  335. package/tests/session_reward_reconcile_db.mjs +2 -2
  336. package/tests/sessions_kit_pager.mjs +1 -1
  337. package/tests/settings_art_key_absent.mjs +2 -2
  338. package/tests/settings_main_role.mjs +21 -20
  339. package/tests/setup_flow.mjs +3 -3
  340. package/tests/setup_smoke.mjs +20 -0
  341. package/tests/ship_card.mjs +6 -6
  342. package/tests/specialities.mjs +16 -4
  343. package/tests/speciality_db_shape.mjs +6 -6
  344. package/tests/speciality_injection.mjs +17 -17
  345. package/tests/speciality_offer_ui.mjs +5 -5
  346. package/tests/speciality_offered.mjs +6 -5
  347. package/tests/speciality_session_skills.mjs +7 -7
  348. package/tests/speciality_skills.mjs +11 -11
  349. package/tests/speciality_walkthrough_ui.mjs +2 -2
  350. package/tests/sso_checkin_identity_spoof.mjs +2 -1
  351. package/tests/task_classifier_vocab.mjs +10 -0
  352. package/tests/task_cli.mjs +11 -3
  353. package/tests/task_criteria_unlink_db.mjs +2 -2
  354. package/tests/task_discipline_required.mjs +38 -16
  355. package/tests/task_goal_required.mjs +4 -4
  356. package/tests/task_needs_db.mjs +1 -1
  357. package/tests/tweak_replaces_artist_review.mjs +1 -1
  358. package/tests/update_pill.mjs +153 -2
  359. package/tests/upkeep_home.mjs +1 -1
  360. package/tests/v3_c1_options_proof.mjs +145 -0
  361. package/tests/visual_review.mjs +3 -3
  362. package/tests/watch_applications_queue.mjs +8 -5
  363. package/tests/watch_sealed_floor.mjs +3 -1
  364. package/tests/welcome_boot.mjs +12 -9
  365. package/tests/welcome_boot_browser.mjs +13 -13
  366. package/tests/worktree_helper.mjs +16 -1
@@ -937,14 +937,14 @@
937
937
  }
938
938
  }
939
939
 
940
- // ---- section rail (grouped sub-nav, task 1723 → kit, task 2172) -----------
940
+ // ---- the options menu's native groups (task 1723 → 2172 → 1005144) ---------
941
941
  //
942
- // The sections sit in six groups behind a left rail (#settings-rail), built by
943
- // OTBKit.makeSectionRail in switch mode — the idiom this page originated. CSS
944
- // shows only the active group (.settings-groups[data-active]); a section's own
945
- // [hidden] attribute still wins, so the per-section reveal logic above is
946
- // unchanged. Deep links activate their group first, so scrollIntoView lands on
947
- // a visible section.
942
+ // The sections sit in groups; the left rail (#settings-rail) is the ONE options
943
+ // menu now (options-menu.js, ADR 0401), which also opens the screens that used
944
+ // to be pages of their own. CSS shows only the active group
945
+ // (.settings-groups[data-active]); a section's own [hidden] attribute still
946
+ // wins, so the per-section reveal logic above is unchanged. Deep links activate
947
+ // their group first, so scrollIntoView lands on a visible section.
948
948
 
949
949
  const RAIL_ITEMS = [
950
950
  { id: 'account', label: 'Account' },
@@ -955,7 +955,6 @@
955
955
  { id: 'update', label: 'Software update' },
956
956
  ];
957
957
  const RAIL_GROUPS = RAIL_ITEMS.map((i) => i.id).concat('extensions');
958
- let rail = null;
959
958
 
960
959
  // task 2172 — stable per-panel anchors (/settings#cli, /settings#sounds, …)
961
960
  // so skills and docs can deep-link a panel without depending on internal
@@ -984,7 +983,6 @@
984
983
  const groups = document.getElementById('settings-groups');
985
984
  if (!groups) return;
986
985
  groups.dataset.active = group;
987
- rail?.activate(group);
988
986
  // A panel whose read is expensive loads when its group opens, not with the page.
989
987
  if (typeof CustomEvent === 'function') document.dispatchEvent(new CustomEvent('settings:group', { detail: { group } }));
990
988
  if (!opts || opts.scroll !== false) window.scrollTo({ top: 0 });
@@ -1001,45 +999,52 @@
1001
999
  return grouped ? { group: grouped.getAttribute('data-group'), el } : null;
1002
1000
  }
1003
1001
 
1004
- // Activate the hash's group; for a panel anchor also scroll the panel into
1005
- // view (rail highlight = the group). Runs on load, after data lands (late-
1006
- // revealed sections), and on hashchange.
1002
+ // One of Settings' own groups, opened by the options menu (options-menu.js); a
1003
+ // panel anchor (#cli) also scrolls its panel into view.
1004
+ function showNative(group, anchor, opts) {
1005
+ const t = anchor ? resolveHash(anchor) : null;
1006
+ activateGroup(t ? t.group : group, { scroll: false });
1007
+ if (t && t.el && (!opts || opts.scroll !== false)) t.el.scrollIntoView({ behavior: 'smooth', block: 'start' });
1008
+ else if (!t || !t.el) window.scrollTo({ top: 0 });
1009
+ }
1010
+
1011
+ // Land the address on its place. The menu reads ?open= and a menu entry's id
1012
+ // (#account, ?open=my-agents#talk); a panel anchor (#cli) is this page's.
1013
+ // Runs on load, after data lands (late-revealed sections), and on hashchange.
1007
1014
  function applyHash(opts) {
1015
+ if (leaveIfMoved()) return;
1016
+ const menu = window.OTBOptions;
1017
+ if (menu && menu.openFromUrl()) return;
1008
1018
  const t = resolveHash(window.location.hash);
1009
1019
  if (!t) return;
1010
- activateGroup(t.group, { scroll: false });
1011
- if (t.el && (!opts || opts.scroll !== false)) {
1012
- t.el.scrollIntoView({ behavior: 'smooth', block: 'start' });
1013
- } else if (!t.el) {
1014
- window.scrollTo({ top: 0 });
1015
- }
1020
+ if (menu) menu.activate(t.group, null, { url: false });
1021
+ showNative(t.group, t.el ? window.location.hash : null, opts);
1016
1022
  }
1017
1023
 
1018
1024
  // task 1005044 — a link to an agent setting that moved to My agents
1019
- // (settings-moved.js) goes on there; replace(), so Back does not bounce here.
1025
+ // (settings-moved.js) opens it in the menu, where My agents lives now (task 1005144).
1020
1026
  function leaveIfMoved() {
1021
1027
  const to = window.OTBSettingsMoved && window.OTBSettingsMoved.movedTarget(window.location.hash);
1022
- if (to) window.location.replace(to);
1023
- return !!to;
1028
+ if (!to) return false;
1029
+ const anchor = to.split('#')[1] || null;
1030
+ if (window.OTBOptions) window.OTBOptions.activate('my-agents', anchor);
1031
+ return true;
1024
1032
  }
1025
1033
 
1026
- function wireRail() {
1027
- const mount = document.getElementById('settings-rail');
1028
- if (!mount || !window.OTBKit) return;
1029
- rail = window.OTBKit.makeSectionRail({
1030
- mount,
1031
- items: RAIL_ITEMS,
1032
- mode: 'switch',
1033
- onSwitch: (key) => {
1034
- activateGroup(key);
1035
- // Reflect the group in the URL so the view is shareable; replaceState
1036
- // keeps Back for leaving the page, not retracing rail clicks.
1037
- history.replaceState(null, '', window.location.pathname + window.location.search + `#${key}`);
1038
- },
1034
+ // The options menu (task 1005144): settings.js hands it the viewer once /me answers.
1035
+ function wireMenu(builder, contributed) {
1036
+ const menu = window.OTBOptions;
1037
+ if (!menu) return;
1038
+ menu.mount({
1039
+ rail: document.getElementById('settings-rail'),
1040
+ groups: document.getElementById('settings-groups'),
1041
+ viewer: { authed: true, rank: builder && builder.rank },
1042
+ contributed,
1043
+ onNative: (id, anchor) => showNative(id, anchor),
1039
1044
  });
1040
- rail.activate('account');
1045
+ if (!menu.openFromUrl() && !resolveHash(window.location.hash)) menu.activate('account', null, { url: false });
1041
1046
  applyHash({ scroll: false });
1042
- window.addEventListener('hashchange', () => { if (!leaveIfMoved()) applyHash(); });
1047
+ window.addEventListener('hashchange', () => applyHash());
1043
1048
  }
1044
1049
 
1045
1050
  // Hide the rail + groups entirely while the not-signed-in gate is showing —
@@ -1104,13 +1109,10 @@
1104
1109
  // MODULE PANELS — render any Settings panels modules contributed via the
1105
1110
  // seam (BV1.R42). None today; ready for discord/art once they leave core.
1106
1111
  mountContributedPanels();
1107
- // Reveal the Extensions rail entry only when a module actually contributed:
1108
- // the kit rail re-renders from the item list, then re-marks the active group.
1112
+ // THE OPTIONS MENU (task 1005144): the rail is the one menu now. The Extensions
1113
+ // entry shows only when a module actually contributed a panel.
1109
1114
  const contributed = $('#contributed-panels');
1110
- if (rail && contributed && contributed.children.length > 0) {
1111
- rail.refresh([...RAIL_ITEMS, { id: 'extensions', label: 'Extensions' }]);
1112
- rail.activate($('#settings-groups')?.dataset.active || 'account');
1113
- }
1115
+ wireMenu(builder, !!(contributed && contributed.children.length > 0));
1114
1116
 
1115
1117
  // Sounds are switched off for now (task 1004554 — .claude/sounds/play.js's
1116
1118
  // SOUNDS_ON). The per-sound toggles (#786) and the per-event picker (task 582)
@@ -1129,7 +1131,5 @@
1129
1131
  applyHash();
1130
1132
  }
1131
1133
 
1132
- if (leaveIfMoved()) return;
1133
- wireRail();
1134
1134
  load();
1135
1135
  })();
@@ -0,0 +1,65 @@
1
+ /* shell-game.css — the game's start screen and load screen (task 1005143; shell-game.js start()).
2
+ Both stand over the welcome's painting, darkened by the hall's scrim. The menu and the load
3
+ screen are each a card of the hall's own tokens, so they follow the hall's theme and the city
4
+ drawn in the load card sits on its own ground. Colours, sizes, corners and spacing are tokens;
5
+ nothing here is typed in. Rules: modules/hall-ui/records/start-screen.md. */
6
+ html.gs-open { overflow: hidden; }
7
+ .gs {
8
+ position: fixed; inset: 0; z-index: var(--ds-z-modal, 160); overflow: auto;
9
+ display: flex; align-items: center; justify-content: center;
10
+ padding: var(--ds-space-lg) var(--ds-space-gutter-narrow);
11
+ background: var(--bg); color: var(--ink); font-family: var(--font-body);
12
+ transition: opacity .5s var(--ds-ease);
13
+ }
14
+ .gs--leaving { opacity: 0; pointer-events: none; }
15
+
16
+ /* the still: the painting, covering the screen, under the scrim so a card reads on any part of it */
17
+ .gs__art { position: absolute; inset: 0; }
18
+ .gs__art::after { content: ""; position: absolute; inset: 0; background: var(--scrim); }
19
+ .gs__img { width: 100%; height: 100%; object-fit: cover; display: block; }
20
+
21
+ /* the two cards */
22
+ .gs__menu, .gs__load {
23
+ position: relative; width: 100%; box-sizing: border-box; padding: var(--ds-space-md);
24
+ background: var(--bg-card); border-radius: var(--ds-radius-panel); box-shadow: var(--ds-shadow-pop);
25
+ }
26
+ .gs__menu[hidden], .gs__load[hidden] { display: none; }
27
+
28
+ /* the start screen: the project's name over five choices, one column */
29
+ .gs__menu { max-width: 24rem; }
30
+ .gs__title {
31
+ margin: var(--ds-space-xs) 0 var(--ds-space-md); text-align: center; text-wrap: balance;
32
+ font-family: var(--font-display); font-size: var(--ds-text-title); font-weight: 700; line-height: 1.1;
33
+ }
34
+ .gs__items { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--ds-space-xs); }
35
+ .gs__items[hidden] { display: none; }
36
+ .gs__items--extras { margin-top: var(--ds-space-sm); flex-direction: row; flex-wrap: wrap; justify-content: center; }
37
+ .gs__item {
38
+ display: flex; align-items: center; justify-content: center; box-sizing: border-box;
39
+ width: 100%; min-height: 48px; padding: var(--ds-space-xs) var(--ds-space-md);
40
+ background: transparent; color: inherit; border: 1px solid var(--rule);
41
+ border-radius: var(--ds-radius-pill); font: inherit; font-size: var(--ds-text-xl); font-weight: 600;
42
+ text-decoration: none; cursor: pointer;
43
+ transition: background-color .15s var(--ds-ease), border-color .15s var(--ds-ease);
44
+ }
45
+ .gs__item:hover { background: var(--bg); border-color: var(--ink-soft); text-decoration: none; }
46
+ .gs__item:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
47
+ /* Start is the one filled choice, as the hall's primary pill is */
48
+ .gs__item[data-gs="start"] { background: var(--accent); color: var(--accent-on); border-color: transparent; }
49
+ .gs__item[data-gs="start"]:hover { background: var(--accent-lift); border-color: transparent; }
50
+ .gs__item--extra { width: auto; min-height: 40px; font-size: var(--ds-text-md); font-weight: 500; }
51
+ .gs__note { margin: var(--ds-space-md) 0 0; text-align: center; font-size: var(--ds-text-md); color: var(--ink-soft); }
52
+ .gs__note[hidden] { display: none; }
53
+
54
+ /* the load screen: the city building up, the week it is showing, and the tip under it */
55
+ .gs__load { max-width: 44rem; display: flex; flex-direction: column; gap: var(--ds-space-sm); text-align: center; }
56
+ .gs__city { display: block; width: 100%; height: auto; max-height: 56vh; }
57
+ .gs__city[hidden], .gs__when[hidden] { display: none; }
58
+ .gs__when { margin: 0; font-size: var(--ds-text-sm); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
59
+ .gs__tip { margin: 0; font-size: var(--ds-text-lg); line-height: 1.45; text-wrap: pretty; }
60
+ .gs__tip b {
61
+ display: inline-block; margin-right: var(--ds-space-xs); padding: 0 var(--ds-space-xs);
62
+ border: 1px solid var(--rule); border-radius: var(--ds-radius-pill);
63
+ font-size: var(--ds-text-2xs); font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
64
+ color: var(--ink-soft); vertical-align: middle;
65
+ }
@@ -9,10 +9,10 @@
9
9
  //
10
10
  // The mode is one localStorage key (`hall-game`), so it follows the browser across
11
11
  // every hall page, and one attribute, html[data-game="on"], for a sheet or script
12
- // that wants to read it. Leaving full screen in game mode (the button, Escape)
12
+ // that wants to read it. Leaving full screen in game mode (the corner button)
13
13
  // brings the bars back for THAT page load only: the next page opens without them
14
14
  // again, and a page's own remembered choice is never overwritten. `?game=on` /
15
- // `?game=off` on any hall URL sets it by hand until the start screen does (R03).
15
+ // `?game=off` on any hall URL sets it by hand; the start screen's Start sets it on (R03, below).
16
16
  // Loaded BEFORE shell.js on every hall page; a page without this file is simply
17
17
  // never in game mode.
18
18
  (function () {
@@ -41,10 +41,290 @@
41
41
  write('sessionStorage', HINT_KEY, '1');
42
42
  return true;
43
43
  }
44
+
45
+ // ---------- the start screen and the load screen (task 1005143, BONGOS-V3 R03) ----------
46
+ // A fresh sign-in opens on the start screen: Start, Options, Achievements, Extras, Quit over a
47
+ // still. Start turns game mode on and passes through the load screen, where the project city
48
+ // builds itself up week by week under a tip, and then the world is there. welcome-boot.js calls
49
+ // start() on Home (where a sign-in lands) and hands in the still; this file draws both screens.
50
+ // Rules are in modules/hall-ui/records/start-screen.md.
51
+ var MENU = [
52
+ { id: 'start', label: 'Start' },
53
+ { id: 'options', label: 'Options', href: '/settings' }, // the ONE options menu (task 1005144)
54
+ { id: 'achievements', label: 'Achievements', href: '/profile' },
55
+ { id: 'extras', label: 'Extras' },
56
+ { id: 'quit', label: 'Quit' },
57
+ ];
58
+ var EXTRAS = [
59
+ { label: 'The thinking sky', href: '/thinking' },
60
+ { label: 'The atlas', href: '/atlas' },
61
+ { label: 'The docs', href: '/docs' },
62
+ ];
63
+ var TIPS = [
64
+ 'Escape opens the options menu, where every setting lives.',
65
+ 'Every tower is a goal. It gains a floor each time work ships under it.',
66
+ 'A crane marks a site someone worked on this week.',
67
+ 'A salmon dot on a tower means something there is waiting on you.',
68
+ 'Ctrl K, or Cmd K on a Mac, jumps to any page.',
69
+ ];
70
+ var TIP_KEY = 'hall-game-tip';
71
+ var CITY_READ = '/api/bongos/home/city?weeks=10';
72
+ var STEPS = 30, STEP_MS = 90; // the history runs in about three seconds
73
+ var HOLD_MS = 1400; // how long the still and the tip stand when there is no city to build
74
+ var LEAVE_MS = 500; // the fade into the world (shell-game.css)
75
+ var NS = 'http://www.w3.org/2000/svg';
76
+
77
+ function tipAt(n) { return TIPS[((Number(n) || 0) % TIPS.length + TIPS.length) % TIPS.length]; }
78
+ // The weeks the load screen stops at, oldest first, ending exactly on now.
79
+ function historySteps(weeks, steps) {
80
+ var last = Math.max(0, (Number(weeks) || 0) - 1), n = Math.max(1, Number(steps) || 1), out = [];
81
+ for (var i = 0; i <= n; i++) out.push(Math.round((last * i / n) * 100) / 100);
82
+ return out;
83
+ }
84
+ function whenLabel(weeks, t) {
85
+ var ago = Math.round(Math.max(0, (Number(weeks) || 0) - 1) - t);
86
+ return ago <= 0 ? 'The city, now' : 'The city, ' + ago + (ago === 1 ? ' week ago' : ' weeks ago');
87
+ }
88
+ function esc(v) { return window.OTB.escapeHtml(v); }
89
+ function itemHtml(m) {
90
+ var inner = ' class="gs__item" data-gs="' + m.id + '"';
91
+ if (m.href) return '<li><a' + inner + ' href="' + esc(m.href) + '">' + esc(m.label) + '</a></li>';
92
+ return '<li><button type="button"' + inner + (m.id === 'extras' ? ' aria-expanded="false" aria-controls="gs-extras"' : '') + '>' + esc(m.label) + '</button></li>';
93
+ }
94
+ function screenHtml(o) {
95
+ return '<div class="gs__art" aria-hidden="true">' + (o.art ? '<img class="gs__img" alt="" src="' + esc(o.art) + '" decoding="async">' : '') + '</div>' +
96
+ '<div class="gs__menu">' +
97
+ '<h2 class="gs__title" id="gs-title">' + esc(o.name || 'This project') + '</h2>' +
98
+ '<ul class="gs__items">' + MENU.map(itemHtml).join('') + '</ul>' +
99
+ '<ul class="gs__items gs__items--extras" id="gs-extras" hidden>' + EXTRAS.map(function (x) {
100
+ return '<li><a class="gs__item gs__item--extra" href="' + esc(x.href) + '">' + esc(x.label) + '</a></li>';
101
+ }).join('') + '</ul>' +
102
+ '<p class="gs__note" id="gs-note" role="status" hidden></p>' +
103
+ '</div>' +
104
+ '<div class="gs__load" id="gs-load" role="status" hidden>' +
105
+ '<svg class="gs__city" id="gs-city" role="img" aria-label="The project city building up, week by week" hidden></svg>' +
106
+ '<p class="gs__when" id="gs-when" hidden></p>' +
107
+ '<p class="gs__tip"><b>Tip</b><span id="gs-tip"></span></p>' +
108
+ '</div>';
109
+ }
110
+
111
+ var root = null, opts = null, timers = [], phase = null;
112
+ function later(ms, fn) { timers.push(setTimeout(fn, ms)); }
113
+ function stopTimers() { timers.forEach(clearTimeout); timers = []; }
114
+ function still() { return !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); }
115
+ function items() {
116
+ return Array.prototype.filter.call(root.querySelectorAll('.gs__item'), function (b) { return !b.closest('[hidden]'); });
117
+ }
118
+
119
+ function close() {
120
+ if (!root) return;
121
+ stopTimers();
122
+ document.removeEventListener('keydown', onKey, true);
123
+ if (root.parentNode) root.parentNode.removeChild(root);
124
+ root = null; phase = null;
125
+ document.documentElement.classList.remove('gs-open');
126
+ var done = opts && opts.done;
127
+ opts = null;
128
+ if (typeof done === 'function') done();
129
+ }
130
+ function leave() {
131
+ if (!root || phase === 'leaving') return;
132
+ phase = 'leaving';
133
+ stopTimers();
134
+ if (still()) { close(); return; }
135
+ root.classList.add('gs--leaving');
136
+ later(LEAVE_MS, close);
137
+ }
138
+
139
+ // ---- the load screen: the still, a tip, and the city building up through its history ----
140
+ function build(n) {
141
+ var e = document.createElementNS(NS, n.tag);
142
+ Object.keys(n.attrs).forEach(function (k) { e.setAttribute(k, String(n.attrs[k])); });
143
+ if (n.text != null) e.textContent = n.text;
144
+ n.kids.forEach(function (c) { e.appendChild(build(c)); });
145
+ return e;
146
+ }
147
+ function themeMode() {
148
+ var t = document.documentElement.dataset.theme;
149
+ if (t === 'light' || t === 'dark') return t;
150
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
151
+ }
152
+ function drawCity(model, t) {
153
+ var Draw = window.OTBHomeCityDraw, svg = root && root.querySelector('#gs-city'), when = root && root.querySelector('#gs-when');
154
+ if (!svg) return;
155
+ var out = Draw.citySvg(model, t, { mode: themeMode(), gov: window.GovCity || null });
156
+ svg.setAttribute('viewBox', out.viewBox);
157
+ while (svg.firstChild) svg.removeChild(svg.firstChild);
158
+ out.nodes.forEach(function (n) { svg.appendChild(build(n)); });
159
+ svg.removeAttribute('hidden');
160
+ when.textContent = whenLabel(model.W, t);
161
+ when.hidden = false;
162
+ }
163
+ // One week after another, as state: each stop is a whole drawing, so nothing here animates.
164
+ function runHistory(model) {
165
+ if (!root || phase !== 'load') return;
166
+ stopTimers();
167
+ var stops = still() ? [model.W - 1] : historySteps(model.W, STEPS), i = 0;
168
+ (function step() {
169
+ if (!root) return;
170
+ drawCity(model, stops[i]);
171
+ i += 1;
172
+ if (i < stops.length) later(STEP_MS, step); else later(still() ? HOLD_MS : 700, leave);
173
+ })();
174
+ }
175
+ function readCity() {
176
+ var Draw = window.OTBHomeCityDraw;
177
+ if (!Draw || typeof fetch !== 'function') return;
178
+ fetch(CITY_READ, { credentials: 'same-origin' }).then(function (r) { return r.ok ? r.json() : null; }).then(function (data) {
179
+ var model = data && Draw.cityModel(data);
180
+ if (model && model.W && model.areas.length) runHistory(model);
181
+ }, function () { /* no city: the still and the tip stand, then the world */ });
182
+ }
183
+ function load() {
184
+ if (!root || phase !== 'menu') return;
185
+ phase = 'load';
186
+ set(true);
187
+ var n = Number(read('localStorage', TIP_KEY)) || 0;
188
+ write('localStorage', TIP_KEY, String(n + 1));
189
+ root.querySelector('#gs-tip').textContent = tipAt(n);
190
+ root.querySelector('.gs__menu').hidden = true;
191
+ root.querySelector('#gs-load').hidden = false;
192
+ root.classList.add('gs--load');
193
+ later(HOLD_MS, leave); // runHistory replaces this once the city answers
194
+ readCity();
195
+ }
196
+
197
+ // Quit closes the tab. A browser only lets a script close a tab a script opened, so when the
198
+ // tab is still here a moment later the screen says so instead of doing nothing.
199
+ function quit() {
200
+ try { window.close(); } catch (_) { /* refused */ }
201
+ later(300, function () {
202
+ var note = root && root.querySelector('#gs-note');
203
+ if (!note) return;
204
+ note.textContent = 'Your browser kept this tab open. Close it to quit.';
205
+ note.hidden = false;
206
+ });
207
+ }
208
+ function toggleExtras(btn) {
209
+ var box = root.querySelector('#gs-extras'), open = box.hidden;
210
+ box.hidden = !open;
211
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
212
+ if (open) box.querySelector('a').focus();
213
+ }
214
+ function onClick(e) {
215
+ var el = e.target.closest && e.target.closest('.gs__item');
216
+ if (!el || phase !== 'menu') return;
217
+ var id = el.getAttribute('data-gs');
218
+ if (id === 'start') load();
219
+ else if (id === 'extras') toggleExtras(el);
220
+ else if (id === 'quit') quit();
221
+ }
222
+ // The screen keeps every key while it is up: Escape folds Extras away and nothing else (the
223
+ // shell's Escape must not flip the bars underneath), the arrows and Tab stay inside the menu.
224
+ function onKey(e) {
225
+ if (!root) return;
226
+ if (e.key === 'Escape') {
227
+ e.preventDefault();
228
+ var box = root.querySelector('#gs-extras');
229
+ if (phase === 'menu' && !box.hidden) { var b = root.querySelector('[data-gs="extras"]'); toggleExtras(b); b.focus(); }
230
+ return;
231
+ }
232
+ if (phase !== 'menu') return;
233
+ var f = items(), at = f.indexOf(document.activeElement), step = 0;
234
+ if (e.key === 'ArrowDown') step = 1;
235
+ else if (e.key === 'ArrowUp') step = -1;
236
+ else if (e.key === 'Tab') step = e.shiftKey ? -1 : 1;
237
+ if (!step || !f.length) return;
238
+ e.preventDefault();
239
+ f[(at + step + f.length) % f.length].focus();
240
+ }
241
+
242
+ // Put the start screen up. `o`: { name, art, done } — the project's name, the still to stand
243
+ // behind both screens, and what to do once the world is showing. True when it is up.
244
+ function start(o) {
245
+ if (root || !document.body) return !!root;
246
+ opts = o || {};
247
+ phase = 'menu';
248
+ root = document.createElement('div');
249
+ root.className = 'gs';
250
+ root.id = 'game-start';
251
+ root.setAttribute('role', 'dialog');
252
+ root.setAttribute('aria-modal', 'true');
253
+ root.setAttribute('aria-labelledby', 'gs-title');
254
+ root.innerHTML = screenHtml(opts);
255
+ root.addEventListener('click', onClick);
256
+ document.addEventListener('keydown', onKey, true);
257
+ document.body.appendChild(root);
258
+ document.documentElement.classList.add('gs-open');
259
+ var first = root.querySelector('.gs__item');
260
+ if (first && first.focus) first.focus();
261
+ return true;
262
+ }
263
+
44
264
  try {
45
265
  var asked = new URLSearchParams(window.location.search || '').get('game');
46
266
  if (asked === 'on' || asked === 'off') write('localStorage', KEY, asked);
47
267
  } catch (_) { /* old engine: the stored value stands */ }
48
268
  stamp();
49
- window.OTBShellGame = { on: on, set: set, hint: hint };
269
+
270
+ // ---------- the options menu (task 1005144, BONGOS-V3 R04, ADR 0401) ----------
271
+ // Every setting lives in ONE menu, the Settings page (options-menu.js). A screen
272
+ // that used to have its own page (My agents, Project settings, Modules, …) now
273
+ // opens INSIDE that menu, in a frame, with `?embed=1`. Such a page is EMBEDDED:
274
+ // html[data-embed="on"], no sidebar, top bar, foot or page head (style.css), no
275
+ // full-screen switch, a link to another page opens in the whole window, and an
276
+ // Escape nobody used goes to the menu. Only inside a frame: the same URL opened
277
+ // on its own is the page with its chrome (the server sends a plain visit of the
278
+ // old address to the menu, serve-internal.js).
279
+ var embedded = false;
280
+ try {
281
+ embedded = new URLSearchParams(window.location.search || '').get('embed') === '1'
282
+ && !!window.parent && window.parent !== window;
283
+ } catch (_) { embedded = false; }
284
+ if (embedded) {
285
+ document.documentElement.dataset.embed = 'on';
286
+ // The shell reads this before it draws the switch, so no button, no Escape-to-leave.
287
+ if (document.body && document.body.setAttribute) document.body.setAttribute('data-fullscreen-switch', 'off');
288
+ }
289
+ // The hall's address for the menu: '/settings' on the builders host, '/builders/settings' on the apex.
290
+ function menuPath() {
291
+ var p = String((window.location && window.location.pathname) || '');
292
+ return (p.indexOf('/builders/') === 0 || p === '/builders' ? '/builders' : '') + '/settings';
293
+ }
294
+ function inField(el) {
295
+ var tag = el && el.tagName ? String(el.tagName).toLowerCase() : '';
296
+ return tag === 'input' || tag === 'textarea' || tag === 'select' || !!(el && el.isContentEditable);
297
+ }
298
+ // ADR 0399 §2.4 row 6: in game mode, the last Escape — one no dialog, picker,
299
+ // palette, sheet or scene pop-up used — is the options menu's. It is judged after
300
+ // the dispatch (a page listener may sit later on the window), and it is marked
301
+ // used before the shell's own check runs, so the bars do not flash back first.
302
+ // On the menu itself it closes the menu; inside a frame it is the menu's too.
303
+ function onEscape(e) {
304
+ if (!e || e.key !== 'Escape' || e.defaultPrevented || inField(e.target)) return;
305
+ if (!embedded && !on()) return;
306
+ window.setTimeout(function () {
307
+ if (e.defaultPrevented) return;
308
+ try { e.preventDefault(); } catch (_) { /* already finished */ }
309
+ var host = embedded ? window.parent : window;
310
+ if (host && host.OTBOptions && typeof host.OTBOptions.escape === 'function') { host.OTBOptions.escape(); return; }
311
+ if (embedded) return;
312
+ var here = String(window.location.pathname || '') + String(window.location.search || '') + String(window.location.hash || '');
313
+ window.location.assign(menuPath() + '?from=' + encodeURIComponent(here));
314
+ }, 0);
315
+ }
316
+ if (window.addEventListener) window.addEventListener('keydown', onEscape);
317
+ // Inside the menu, a link to ANOTHER page leaves the menu for it; a link within the page stays.
318
+ if (embedded && document.addEventListener) {
319
+ document.addEventListener('click', function (e) {
320
+ var a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
321
+ if (!a || a.getAttribute('target')) return;
322
+ try {
323
+ var to = new URL(a.getAttribute('href'), window.location.href);
324
+ if (to.origin !== window.location.origin || to.pathname !== window.location.pathname) a.setAttribute('target', '_top');
325
+ } catch (_) { /* a href the browser cannot read: leave it alone */ }
326
+ }, true);
327
+ }
328
+ window.OTBShellGame = { on: on, set: set, hint: hint, start: start, tipAt: tipAt, historySteps: historySteps, whenLabel: whenLabel, MENU: MENU, TIPS: TIPS,
329
+ embedded: function () { return embedded; }, menuPath: menuPath };
50
330
  })();
@@ -110,8 +110,6 @@
110
110
  // You
111
111
  accomplishments: '<path d="M6 2.5l1.6 4.2M12 2.5l-1.6 4.2"/><circle cx="9" cy="10.8" r="4.2"/><circle cx="9" cy="10.8" r="1.8"/>',
112
112
  profile: '<rect x="2.5" y="4" width="13" height="10" rx="1.5"/><circle cx="6.3" cy="8" r="1.6"/><path d="M4.1 12c.3-1.1 1.1-1.7 2.2-1.7s1.9.6 2.2 1.7"/><path d="M10.4 7.5h3M10.4 10.3h3"/>',
113
- // task 1004685 (prototype): My agents — a helper's head on an antenna, not a person.
114
- agents: '<rect x="4" y="6" width="10" height="8.5" rx="2.5"/><path d="M9 6V3.6"/><circle cx="9" cy="2.7" r="0.9"/><path d="M7 10.2v0.6M11 10.2v0.6"/><path d="M1.8 9v3M16.2 9v3"/>',
115
113
  // Community
116
114
  collab: '<path d="M2.5 4a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v4.5a1 1 0 0 1-1 1H6.5L4 11.5v-2h-.5a1 1 0 0 1-1-1V4Z"/><path d="M11.5 6.5h3a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1H14v2l-2.5-2H8.5a1 1 0 0 1-1-1V9.5"/>',
117
115
  roster: '<circle cx="6.5" cy="6.3" r="2.3"/><path d="M2.5 15c.4-2.3 1.9-3.7 4-3.7s3.6 1.4 4 3.7"/><circle cx="12.5" cy="5.8" r="2"/><path d="M11.4 10.5c.4-.1.7-.2 1.1-.2 1.9 0 3.3 1.3 3.7 3.5"/>',
@@ -119,16 +117,13 @@
119
117
  primer: '<path d="M9 5C7.5 3.9 5.5 3.5 2.5 3.5v10c3 0 5 .4 6.5 1.5 1.5-1.1 3.5-1.5 6.5-1.5v-10c-3 0-5 .4-6.5 1.5Z"/><path d="M9 5v10"/>',
120
118
  commands: '<rect x="2.5" y="3.5" width="13" height="11" rx="1.5"/><path d="M5.5 7.3 7.7 9l-2.2 1.7M9.5 11.2h3"/>',
121
119
  atlas: '<circle cx="9" cy="9" r="6.5"/><path d="M2.5 9h13M9 2.5c1.7 1.8 2.6 4 2.6 6.5s-.9 4.7-2.6 6.5c-1.7-1.8-2.6-4-2.6-6.5S7.3 4.3 9 2.5Z"/>',
122
- modules: '<rect x="2.5" y="2.5" width="5.5" height="5.5" rx="1"/><rect x="10" y="2.5" width="5.5" height="5.5" rx="1"/><rect x="2.5" y="10" width="5.5" height="5.5" rx="1"/><path d="M12.75 10.5v4.5M10.5 12.75h4.5"/>',
123
120
  apidocs: '<path d="M6.5 3C5 3 4.5 3.7 4.5 5v2c0 1-.6 2-1.5 2 .9 0 1.5 1 1.5 2v2c0 1.3.5 2 2 2M11.5 3c1.5 0 2 .7 2 2v2c0 1 .6 2 1.5 2-.9 0-1.5 1-1.5 2v2c0 1.3-.5 2-2 2"/>',
124
121
  // Government
125
122
  watch: '<path d="M1.8 9S4.5 4 9 4s7.2 5 7.2 5-2.7 5-7.2 5S1.8 9 1.8 9Z"/><circle cx="9" cy="9" r="2.2"/>',
126
123
  gate: '<path d="M9 2.5 15 5v4c0 3.5-2.4 6-6 7-3.6-1-6-3.5-6-7V5l6-2.5Z"/><path d="M6.5 9l1.8 1.8 3.4-3.6"/>',
127
124
  sessions: '<path d="M7 5h8M7 9h8M7 13h8"/><circle cx="3.8" cy="5" r=".7" fill="currentColor"/><circle cx="3.8" cy="9" r=".7" fill="currentColor"/><circle cx="3.8" cy="13" r=".7" fill="currentColor"/>',
128
- settings: '<circle cx="9" cy="9" r="2.5"/><path d="M9 2.5v2m0 9v2M2.5 9h2m9 0h2M4.4 4.4l1.4 1.4m6.4 6.4 1.4 1.4m0-9.2-1.4 1.4M5.8 12.2l-1.4 1.4"/>',
129
125
  deploy: '<path d="M9 12V3M5.5 6.5 9 3l3.5 3.5M3 12.5v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-2"/>',
130
126
  money: '<path d="M4 2.5h10v13l-1.7-1-1.6 1-1.7-1-1.7 1-1.6-1-1.7 1v-13Z"/><path d="M6.5 6h5M6.5 9h5M6.5 12h3"/>',
131
- permissions: '<circle cx="6" cy="12" r="3.5"/><path d="M8.5 9.5 15 3M12.5 5.5l2 2M10.5 7.5l1.5 1.5"/>',
132
127
  // The governor city: a pediment over columns, the Governing mode's own glyph.
133
128
  city: '<path d="M2.5 7 9 3l6.5 4M4 7.5v6.5M7 7.5v6.5M11 7.5v6.5M14 7.5v6.5M2.5 15.5h13"/>',
134
129
  // the foot
@@ -302,9 +297,9 @@
302
297
  // /profile?id=N from the leaderboard); 'signed' just means "show this 'my
303
298
  // profile' shortcut once you're signed in".
304
299
  { id: 'profile', label: 'Profile', icon: 'profile', href: '/profile', gate: 'signed' },
305
- // task 1004685 (prototype, board 6): MY AGENTS — how your own Claude works for you
306
- // and which settings the project's laws lock. 'signed' is its page's real floor.
307
- { id: 'my-agents', label: 'My agents', icon: 'agents', href: '/my-agents', gate: 'signed' },
300
+ // task 1005144 (ADR 0401): My agents, Project settings, Permissions and Modules
301
+ // left the sidebar — they are entries in the ONE options menu (the profile chip),
302
+ // and their old URLs open it (serve-internal.js RETIRED_TO_OPTIONS).
308
303
  ] },
309
304
  // task 1005071: the board's own rooms get a head of their own. Gated on the SAME
310
305
  // atom as both items (perm:, which floors below any rank), so the head can never
@@ -360,9 +355,6 @@
360
355
  // surface, so this group's gate and audience are unchanged.
361
356
  { id: 'watch', label: 'Watch', icon: 'watch', href: '/watch', gate: 'government', position: true },
362
357
  // task 1005049: Sessions and Autobongos (/gate) are tabs on Watch; both URLs redirect there.
363
- // The project's own fundamentals (task 1003280, goal 1000072). Mirrors the
364
- // server's requireGovernmentPage floor; cosmetic only, as every item here is.
365
- { id: 'project-settings', label: 'Project settings', icon: 'settings', href: '/project-settings', gate: 'government', position: true },
366
358
  // task 1005047 (owner decision 2026-11-03, idea 1001312): the owner's one money page —
367
359
  // spend and payouts (once on Project settings), the treasury (once the Economy page)
368
360
  // and the hosting bills (once 'Fleet costs', /fleet, which now redirects to its
@@ -379,17 +371,7 @@
379
371
  // is the platform hall's page alone, and every other hall takes its update from
380
372
  // Settings → Software update, whose Update button carries the hub hand-off.
381
373
  { id: 'deploy', label: 'Deploy', icon: 'deploy', href: '/deploy', gate: 'archon', module: 'provisioning render-deploy', position: true },
382
- // task 1005045: the owner's Market Watch settings (task 1004938) are an owner-only tab on
383
- // /market-watch now, so the sidebar says "Market Watch" once (owner decision 2026-11-03).
384
- // BV1.R113: the roles-and-permissions tab (ADR 0151). The one genuinely
385
- // Archon-only item the 'government' gate comment anticipated — cosmetic
386
- // only. The wall is the `government.manage` atom checked on each of the
387
- // tab's nine data routes (route-rank-check.js); the page SHELL itself is
388
- // metic+, because R16 made /government three rooms and only this one is
389
- // the archon surface.
390
- { id: 'government', label: 'Permissions', icon: 'permissions', href: '/government', gate: 'archon', position: true } // NOT 'Ranks': the ladder is explained in the Primer, and a nav item
391
- // reading 'Ranks' beside it is the confusion this rename exists to remove. This tab
392
- // is where a rank's permissions are granted, so it says what it does.,
374
+ // Project settings and Permissions left this group in task 1005144: options menu entries.
393
375
  ] },
394
376
  // task 1005071: 'Docs' → 'Help', and every craft shows it (craft-mode.js ALWAYS_TABS).
395
377
  { group: 'Help', gate: 'signed', items: [
@@ -409,7 +391,6 @@
409
391
  // both reads behind it are sign-in gated, so 'signed' is the real floor here.
410
392
  { id: 'commands', label: 'Commands', icon: 'commands', href: '/commands', gate: 'signed' },
411
393
  { id: 'atlas', label: 'Repo Atlas', icon: 'atlas', href: '/atlas', gate: 'member' },
412
- { id: 'modules', label: 'Modules', icon: 'modules', href: '/modules', gate: 'member' },
413
394
  // The rendered OpenAPI reference lives on the APEX (/docs), not this builders
414
395
  // subdomain — external so the rewrite shim doesn't turn it into /builders/docs.
415
396
  { id: 'apidocs', label: 'API docs', icon: 'apidocs', href: '/docs', gate: 'signed', external: true },
@@ -516,7 +497,7 @@
516
497
  // task 1831: the profile chip IS the Settings link — one spot, no separate
517
498
  // nav-item. It's an <a> so keyboard focus + Enter work for free; visibility
518
499
  // stays 'signed'-gated via applyAccess (shown only when authed + filled).
519
- '<a class="shell-user" id="shell-user" href="/settings" title="Settings" aria-label="Settings" hidden>' +
500
+ '<a class="shell-user" id="shell-user" href="/settings" title="Options" aria-label="Options" hidden>' +
520
501
  '<span class="shell-user__avatar" id="shell-user-avatar" aria-hidden="true"></span>' +
521
502
  '<span class="shell-user__meta"><span class="shell-user__name" id="shell-user-name"></span>' +
522
503
  '<span class="shell-user__rank" id="shell-user-rank"></span></span>' +
@@ -1196,7 +1177,7 @@
1196
1177
  maybeRenderCoreUpdate();
1197
1178
  // task 1004492: a builder with no mode yet this session is asked once — unless Home's welcome (task 1004785, welcome-boot.js) greets the day's first visit instead; it marks the picker asked.
1198
1179
  // task 1005076: neither is drawn over the setup screen; setup comes first.
1199
- if (!holdForSetup(state && state.builder) && !(window.OTBWelcome && window.OTBWelcome.onAccess(access, state && state.builder)) && CRAFT) CRAFT.onAccess(access, state && state.builder);
1180
+ if (!holdForSetup(state && state.builder) && !(CRAFT && CRAFT.characterGate && CRAFT.characterGate(access, state)) && !(window.OTBWelcome && window.OTBWelcome.onAccess(access, state && state.builder)) && CRAFT) CRAFT.onAccess(access, state && state.builder);
1200
1181
  var signin = document.getElementById('shell-signin');
1201
1182
  if (signin) signin.hidden = access.authed !== false;
1202
1183
  var user = document.getElementById('shell-user');
@@ -1443,7 +1424,7 @@
1443
1424
  positions: me.positions || null,
1444
1425
  // task 1005076: whether setup is complete; incomplete shows setup-gate.js.
1445
1426
  setup: (me.onboarding && me.onboarding.setup) || null,
1446
- builder: me.builder,
1427
+ builder: me.builder, character: me.character, // task 1005147: the character held (null: pick one)
1447
1428
  });
1448
1429
  }).catch(function () { /* cosmetic only */ });
1449
1430
  }
@@ -147,4 +147,11 @@
147
147
  ].filter(([cls]) => cls !== 'unseen' || S.lenses.includes('all')).map(([cls, name, what]) => `<button type="button" class="obj" title="${esc(what)}"><span class="g"><span class="mk ${cls}"></span></span>${esc(name)}</button>`).join('');
148
148
 
149
149
  Object.assign(SKY, { card, menu, glyphHtml, ideaTitle, sizeWords, showCard, showMenu, hideMenu, pick, setLens, togglePill, updateStats, altText, TAU, hash });
150
+
151
+ // task 1005148: the sky as the visor's scene (ADR 0399 §2.2). The visor owns no camera:
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);
150
157
  })();