@bongos/core 1.24.79 → 1.24.81

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 (413) hide show
  1. package/.bongos-core.json +554 -394
  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/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md +142 -0
  17. package/docs/adr/README.md +4 -0
  18. package/docs/api/openapi.json +195 -3
  19. package/docs/api-reference.md +8 -4
  20. package/docs/architecture.md +7 -3
  21. package/docs/copy-inventory.md +393 -378
  22. package/docs/copy-registry.json +722 -578
  23. package/docs/design/characters/character-sprites-source.webp +0 -0
  24. package/docs/file-map.md +7 -8
  25. package/docs/module-api-changelog.md +4 -0
  26. package/docs/modules-contract.md +5 -0
  27. package/docs/onboarding/slash-commands.md +1 -1
  28. package/docs/packs/{engineer.md → artisan.md} +5 -5
  29. package/docs/packs/artist.md +50 -4
  30. package/docs/page-inventory.json +16 -7
  31. package/docs/page-readings.json +3184 -3338
  32. package/docs/{packs/ideator.md → recipes/ideation-playbook.md} +5 -48
  33. package/migrations/core_296_crafts_artisan_artist.sql +72 -0
  34. package/migrations/core_297_builder_character.sql +30 -0
  35. package/modules/copy-desk/proposals.js +1 -1
  36. package/modules/economy/credits-by-craft.js +29 -15
  37. package/modules/hall-ui/public/ai-studio.html +6 -4
  38. package/modules/hall-ui/public/ai-studio.states.json +1 -0
  39. package/modules/hall-ui/public/atlas.html +5 -5
  40. package/modules/hall-ui/public/atlas.js +3 -1
  41. package/modules/hall-ui/public/atlas.states.json +1 -0
  42. package/modules/hall-ui/public/blockers.html +5 -5
  43. package/modules/hall-ui/public/board-room.html +4 -4
  44. package/modules/hall-ui/public/board-room.states.json +1 -0
  45. package/modules/hall-ui/public/characters/artisan.png +0 -0
  46. package/modules/hall-ui/public/characters/artist.png +0 -0
  47. package/modules/hall-ui/public/characters/patron.png +0 -0
  48. package/modules/hall-ui/public/city.css +3 -4
  49. package/modules/hall-ui/public/city.html +9 -9
  50. package/modules/hall-ui/public/city.js +17 -56
  51. package/modules/hall-ui/public/city.states.json +7 -32
  52. package/modules/hall-ui/public/collab.html +4 -4
  53. package/modules/hall-ui/public/collab.states.json +1 -0
  54. package/modules/hall-ui/public/commands.html +4 -4
  55. package/modules/hall-ui/public/commands.states.json +1 -0
  56. package/modules/hall-ui/public/controls.css +63 -11
  57. package/modules/hall-ui/public/copy-desk.html +4 -4
  58. package/modules/hall-ui/public/copy-desk.states.json +1 -0
  59. package/modules/hall-ui/public/craft-mode.js +258 -64
  60. package/modules/hall-ui/public/deploy.html +4 -4
  61. package/modules/hall-ui/public/deploy.states.json +1 -0
  62. package/modules/hall-ui/public/diagram-viewer.js +2 -1
  63. package/modules/hall-ui/public/dom-utils.js +1 -1
  64. package/modules/hall-ui/public/goal-map.html +9 -6
  65. package/modules/hall-ui/public/goals.html +5 -5
  66. package/modules/hall-ui/public/goals.states.json +1 -0
  67. package/modules/hall-ui/public/government.html +4 -4
  68. package/modules/hall-ui/public/government.states.json +1 -0
  69. package/modules/hall-ui/public/hall-render.js +3 -1
  70. package/modules/hall-ui/public/home-city-draw.js +3 -3
  71. package/modules/hall-ui/public/home-city.css +4 -0
  72. package/modules/hall-ui/public/home-city.js +17 -9
  73. package/modules/hall-ui/public/home-gallery.js +8 -1
  74. package/modules/hall-ui/public/home-idea-box.css +2 -2
  75. package/modules/hall-ui/public/home-idea-box.js +3 -3
  76. package/modules/hall-ui/public/idea.html +5 -5
  77. package/modules/hall-ui/public/idea.states.json +1 -0
  78. package/modules/hall-ui/public/ideas.html +5 -5
  79. package/modules/hall-ui/public/ideas.states.json +1 -0
  80. package/modules/hall-ui/public/index-founding.states.json +1 -0
  81. package/modules/hall-ui/public/index-genesis-decide.states.json +1 -0
  82. package/modules/hall-ui/public/index-genesis-demo.states.json +1 -0
  83. package/modules/hall-ui/public/index-genesis.states.json +1 -0
  84. package/modules/hall-ui/public/index.html +11 -7
  85. package/modules/hall-ui/public/index.states.json +1 -0
  86. package/modules/hall-ui/public/market-watch-item.html +4 -4
  87. package/modules/hall-ui/public/market-watch-item.states.json +1 -0
  88. package/modules/hall-ui/public/market-watch-rules.html +4 -4
  89. package/modules/hall-ui/public/market-watch-rules.states.json +1 -0
  90. package/modules/hall-ui/public/market-watch.html +6 -9
  91. package/modules/hall-ui/public/market-watch.js +22 -45
  92. package/modules/hall-ui/public/market-watch.states.json +1 -0
  93. package/modules/hall-ui/public/modules.html +4 -4
  94. package/modules/hall-ui/public/modules.states.json +1 -0
  95. package/modules/hall-ui/public/money.html +4 -4
  96. package/modules/hall-ui/public/money.states.json +1 -0
  97. package/modules/hall-ui/public/my-agents.html +4 -4
  98. package/modules/hall-ui/public/my-agents.states.json +1 -0
  99. package/modules/hall-ui/public/options-menu.js +409 -0
  100. package/modules/hall-ui/public/paintbrush.js +3 -3
  101. package/modules/hall-ui/public/palette.js +11 -13
  102. package/modules/hall-ui/public/primer.html +5 -5
  103. package/modules/hall-ui/public/primer.states.json +1 -0
  104. package/modules/hall-ui/public/profile-crafts.js +1 -1
  105. package/modules/hall-ui/public/profile-roles.js +23 -19
  106. package/modules/hall-ui/public/profile.css +1 -1
  107. package/modules/hall-ui/public/profile.html +19 -18
  108. package/modules/hall-ui/public/profile.js +19 -18
  109. package/modules/hall-ui/public/profile.states.json +5 -4
  110. package/modules/hall-ui/public/project-settings.html +4 -4
  111. package/modules/hall-ui/public/roster.html +4 -4
  112. package/modules/hall-ui/public/roster.js +3 -3
  113. package/modules/hall-ui/public/roster.states.json +1 -0
  114. package/modules/hall-ui/public/settings.css +24 -4
  115. package/modules/hall-ui/public/settings.html +21 -17
  116. package/modules/hall-ui/public/settings.js +44 -44
  117. package/modules/hall-ui/public/settings.states.json +1 -0
  118. package/modules/hall-ui/public/shell-game.css +110 -0
  119. package/modules/hall-ui/public/shell-game.js +316 -3
  120. package/modules/hall-ui/public/shell.js +15 -66
  121. package/modules/hall-ui/public/sky-interaction.js +7 -0
  122. package/modules/hall-ui/public/speciality-tools.js +1 -2
  123. package/modules/hall-ui/public/street-card.states.json +1 -0
  124. package/modules/hall-ui/public/street-inside.js +3 -4
  125. package/modules/hall-ui/public/street.css +1 -5
  126. package/modules/hall-ui/public/street.html +6 -10
  127. package/modules/hall-ui/public/street.js +8 -59
  128. package/modules/hall-ui/public/street.states.json +7 -32
  129. package/modules/hall-ui/public/studio.html +5 -5
  130. package/modules/hall-ui/public/studio.states.json +1 -0
  131. package/modules/hall-ui/public/style.css +197 -112
  132. package/modules/hall-ui/public/task-detail.js +5 -4
  133. package/modules/hall-ui/public/task.html +5 -5
  134. package/modules/hall-ui/public/task.states.json +1 -0
  135. package/modules/hall-ui/public/thinking.css +10 -0
  136. package/modules/hall-ui/public/thinking.html +9 -6
  137. package/modules/hall-ui/public/thinking.states.json +1 -0
  138. package/modules/hall-ui/public/tweak-editor.html +5 -5
  139. package/modules/hall-ui/public/tweak-editor.js +5 -1
  140. package/modules/hall-ui/public/tweak-editor.states.json +1 -0
  141. package/modules/hall-ui/public/update-pill.js +91 -2
  142. package/modules/hall-ui/public/visor.css +80 -0
  143. package/modules/hall-ui/public/visor.js +316 -0
  144. package/modules/hall-ui/public/watch-repo-invite.js +159 -0
  145. package/modules/hall-ui/public/watch.html +6 -5
  146. package/modules/hall-ui/public/watch.js +17 -2
  147. package/modules/hall-ui/public/watch.states.json +1 -0
  148. package/modules/hall-ui/public/welcome-boot.js +15 -6
  149. package/modules/hall-ui/public/work-founding.states.json +1 -0
  150. package/modules/hall-ui/public/work.html +5 -5
  151. package/modules/hall-ui/public/work.states.json +1 -0
  152. package/modules/hall-ui/records/builders-roster.md +2 -2
  153. package/modules/hall-ui/records/craft-mode.md +13 -11
  154. package/modules/hall-ui/records/game-mode.md +2 -2
  155. package/modules/hall-ui/records/governor-street.md +1 -1
  156. package/modules/hall-ui/records/home-city.md +3 -1
  157. package/modules/hall-ui/records/market-watch-settings.md +2 -0
  158. package/modules/hall-ui/records/options-menu.md +15 -0
  159. package/modules/hall-ui/records/profile-roles.md +11 -11
  160. package/modules/hall-ui/records/sky.md +3 -3
  161. package/modules/hall-ui/records/start-screen.md +25 -0
  162. package/modules/hall-ui/records/tweak-editor.md +1 -1
  163. package/modules/hall-ui/records/update-pill.md +12 -0
  164. package/modules/hall-ui/records/visor.md +23 -0
  165. package/modules/hall-ui/records/welcome-tower.md +5 -5
  166. package/modules/hall-ui/specimens/components.states.json +2 -1
  167. package/modules/ideas/ideator-stats.js +3 -1
  168. package/modules/ideas/inbox.js +2 -2
  169. package/modules/lifecycle/activity.js +1 -1
  170. package/modules/lifecycle/cascade.js +4 -4
  171. package/modules/lifecycle/goal-spec-task.js +1 -1
  172. package/modules/lifecycle/help-requests.js +19 -15
  173. package/modules/lifecycle/kickoff-checklist.js +3 -3
  174. package/modules/lifecycle/kickoff-seed.js +1 -1
  175. package/modules/lifecycle/migrations/lifecycle_021_crafts_artisan_artist.sql +29 -0
  176. package/modules/lifecycle/mingle.js +4 -4
  177. package/modules/lifecycle/page-tweak-write.js +1 -1
  178. package/modules/lifecycle/routes/task-write-routes.js +3 -1
  179. package/modules/lifecycle/routes/tasks.js +3 -3
  180. package/modules/lifecycle/ship-card.js +2 -3
  181. package/modules/lifecycle/task-classifier.js +39 -29
  182. package/modules/lifecycle/tests/cascade.mjs +7 -7
  183. package/modules/onboarding/founding-mode.js +3 -0
  184. package/modules/onboarding/genesis-home.js +21 -1
  185. package/modules/onboarding/routes/onboarding.js +2 -0
  186. package/modules/page-notes/notes.js +3 -2
  187. package/modules/page-notes/split.js +1 -1
  188. package/modules/platform-identity/craft-rollup.js +35 -7
  189. package/modules/platform-identity/migrations/platform_identity_036_crafts_artisan_artist.sql +45 -0
  190. package/modules/platform-identity/platform-identity.js +2 -2
  191. package/modules/platform-identity/routes/community-leaderboard.js +2 -1
  192. package/modules/platform-identity/routes/community-search.js +2 -1
  193. package/modules/platform-identity/routes/sso.js +2 -1
  194. package/modules/platform-identity/tests/platform-identity.mjs +6 -6
  195. package/modules/public-landing/public/community.html +3 -5
  196. package/modules/public-landing/public/index.html +1 -1
  197. package/modules/public-landing/public/projects.html +1 -1
  198. package/modules/specialities/migrations/specialities_004_crafts_artisan_artist.sql +30 -0
  199. package/modules/specialities/routes/specialities.js +2 -2
  200. package/modules/status-ui/public/status.js +1 -1
  201. package/modules/ui-design/kit/fixtures/community-leaderboard.json +10 -10
  202. package/modules/ui-design/kit/fixtures/community-search.json +2 -2
  203. package/modules/ui-design/kit/fixtures/scouting-recruiter.json +1 -1
  204. package/modules/ui-design/public/brand.html +2 -2
  205. package/package-lock.json +2 -2
  206. package/package.json +1 -1
  207. package/release-notes.json +76 -0
  208. package/scripts/bongos/autobongos-loop.js +1 -1
  209. package/scripts/bongos/discipline-modes.json +7 -12
  210. package/scripts/bongos/fitness.js +1 -1
  211. package/scripts/bongos/gen-hall-sitemap.js +7 -7
  212. package/scripts/bongos/grade-smoke.js +2 -2
  213. package/scripts/bongos/init.js +2 -2
  214. package/scripts/bongos/newcomer-floor.config.json +1 -2
  215. package/scripts/bongos/newcomer-floor.js +4 -3
  216. package/scripts/bongos/newcomer-floor.templates.json +10 -12
  217. package/scripts/bongos/newcomer-floor.test.js +47 -38
  218. package/scripts/bongos/role-pack-guard.js +5 -4
  219. package/scripts/bongos/seed-tasks.example.json +3 -3
  220. package/scripts/bongos/setup.js +20 -107
  221. package/scripts/bongos/start.js +2 -2
  222. package/scripts/bongos/task.js +5 -4
  223. package/scripts/bongos/triage.js +1 -1
  224. package/scripts/bongos/worktree.js +16 -4
  225. package/scripts/hall-preview/README.md +2 -3
  226. package/scripts/setup-builder.js +6 -46
  227. package/src/bongos/auth-admission.js +8 -8
  228. package/src/bongos/craft-mode.js +2 -0
  229. package/src/bongos/db.js +39 -1
  230. package/src/bongos/github-repos.js +78 -0
  231. package/src/bongos/module-scope-map.js +22 -0
  232. package/src/bongos/role-stats.js +27 -11
  233. package/src/bongos/routes/github.js +100 -2
  234. package/src/bongos/routes/me.js +33 -0
  235. package/src/bongos/serve-internal.js +33 -11
  236. package/src/crafts.js +66 -0
  237. package/src/hall-helm.js +79 -0
  238. package/src/module-api.js +6 -1
  239. package/src/module-loader/manifest-schema.js +16 -0
  240. package/src/modules.js +2 -2
  241. package/src/role-responsibilities.js +6 -5
  242. package/tests/acclaim_feed_evidence_db.mjs +1 -1
  243. package/tests/activity_rollup_as_of.mjs +1 -1
  244. package/tests/api_alias_caller_ratchet.mjs +3 -3
  245. package/tests/application_lifecycle.mjs +1 -1
  246. package/tests/artist_review.mjs +1 -1
  247. package/tests/artist_review_goal.mjs +1 -1
  248. package/tests/blocker_hall_live_proof.mjs +1 -1
  249. package/tests/blocker_tasks_named_db.mjs +2 -2
  250. package/tests/canonical_profile_url.mjs +1 -1
  251. package/tests/cap_newcomer_tasks.mjs +13 -13
  252. package/tests/character_creation.mjs +294 -0
  253. package/tests/claim_gate_open_blocker.mjs +2 -2
  254. package/tests/claim_gate_rebase.mjs +6 -6
  255. package/tests/claude_materialize.mjs +1 -1
  256. package/tests/clawback_confirmed_backlog.mjs +3 -3
  257. package/tests/collab_page.mjs +8 -9
  258. package/tests/collab_self_settling.mjs +2 -2
  259. package/tests/community_leaderboard_route.mjs +13 -5
  260. package/tests/community_proof_db.mjs +3 -0
  261. package/tests/community_search_route.mjs +7 -6
  262. package/tests/conductor.mjs +1 -1
  263. package/tests/conductor_main_e2e.mjs +8 -8
  264. package/tests/copy_desk_flags_db.mjs +1 -1
  265. package/tests/copy_desk_page_approve.mjs +2 -2
  266. package/tests/copy_desk_page_claim.mjs +2 -2
  267. package/tests/copy_desk_page_next.mjs +1 -1
  268. package/tests/copy_desk_page_release.mjs +1 -1
  269. package/tests/copy_desk_page_submit.mjs +2 -2
  270. package/tests/core_258_box_session_claims_db.mjs +1 -1
  271. package/tests/craft_mode.mjs +48 -34
  272. package/tests/craft_mode_home.mjs +20 -20
  273. package/tests/craft_mode_looks.mjs +16 -15
  274. package/tests/craft_mode_nav.mjs +31 -29
  275. package/tests/craft_review_mode.mjs +46 -41
  276. package/tests/crafts_vocabulary.mjs +70 -0
  277. package/tests/cross_discipline_priority_direction.mjs +5 -5
  278. package/tests/design_reference.mjs +1 -1
  279. package/tests/fitness.mjs +7 -11
  280. package/tests/funded_project_pays_proof_db.mjs +1 -1
  281. package/tests/genesis_home.mjs +21 -1
  282. package/tests/github_chip.mjs +3 -3
  283. package/tests/github_repo_invite.mjs +379 -0
  284. package/tests/goal_criterion_confirm_ui.mjs +1 -1
  285. package/tests/goal_invite_consent.mjs +1 -1
  286. package/tests/goal_map_page.mjs +2 -2
  287. package/tests/goal_sort_staleness.mjs +1 -1
  288. package/tests/goal_spec_task.mjs +1 -1
  289. package/tests/goal_spec_task_db.mjs +3 -3
  290. package/tests/grade_engine.mjs +2 -2
  291. package/tests/grader_no_artifact.mjs +1 -1
  292. package/tests/grader_prompt_fencing.mjs +1 -1
  293. package/tests/grader_prompt_window_bound.mjs +1 -1
  294. package/tests/grader_reader_lens.mjs +2 -2
  295. package/tests/grader_severity_gate.mjs +1 -1
  296. package/tests/grader_unbacked_score.mjs +1 -1
  297. package/tests/hall_approval_queue.mjs +1 -1
  298. package/tests/hall_board_world.mjs +2 -2
  299. package/tests/hall_builders_page_pass.mjs +3 -2
  300. package/tests/hall_city.mjs +16 -30
  301. package/tests/hall_controls.mjs +52 -4
  302. package/tests/hall_fullscreen.mjs +27 -3
  303. package/tests/hall_helm.mjs +187 -0
  304. package/tests/hall_kit_charts.mjs +1 -1
  305. package/tests/hall_landing_boot.mjs +1 -1
  306. package/tests/hall_my_agents.mjs +16 -16
  307. package/tests/hall_nav.mjs +22 -15
  308. package/tests/hall_options_menu.mjs +247 -0
  309. package/tests/hall_oversight_world.mjs +1 -1
  310. package/tests/hall_palette.mjs +8 -9
  311. package/tests/hall_profile_world.mjs +3 -3
  312. package/tests/hall_record_world.mjs +6 -4
  313. package/tests/hall_settings_world.mjs +2 -1
  314. package/tests/hall_sitemap.mjs +6 -5
  315. package/tests/hall_start_screen.mjs +307 -0
  316. package/tests/hall_street.mjs +15 -28
  317. package/tests/hall_street_inside.mjs +17 -9
  318. package/tests/hall_studio_world.mjs +3 -3
  319. package/tests/hall_topbar_320.mjs +4 -2
  320. package/tests/hall_visor.mjs +224 -0
  321. package/tests/help_requests.mjs +47 -15
  322. package/tests/helpers.mjs +4 -4
  323. package/tests/home_city.mjs +6 -4
  324. package/tests/home_city_widget.mjs +33 -14
  325. package/tests/home_gallery_widget.mjs +4 -4
  326. package/tests/home_idea_box.mjs +6 -6
  327. package/tests/hub_community_page.mjs +3 -3
  328. package/tests/hub_craft_rollup.mjs +75 -36
  329. package/tests/hub_recent_ships.mjs +3 -2
  330. package/tests/idea_cascade.mjs +2 -2
  331. package/tests/idea_credit_streams_db.mjs +1 -1
  332. package/tests/idea_detail_page.mjs +1 -1
  333. package/tests/idea_ideate_full.mjs +5 -2
  334. package/tests/idea_spark_hall.mjs +1 -1
  335. package/tests/init.mjs +3 -3
  336. package/tests/instance_manifest.mjs +2 -2
  337. package/tests/kickoff_checklist.mjs +1 -1
  338. package/tests/leaderboard_roles.mjs +34 -25
  339. package/tests/lifecycle_claim_ladder_refs.mjs +55 -0
  340. package/tests/lightbulb.mjs +14 -10
  341. package/tests/main_discipline.mjs +8 -8
  342. package/tests/markdown_line_endings.mjs +1 -1
  343. package/tests/market_watch_pages.mjs +52 -74
  344. package/tests/me_streak_leaderboard_queries.mjs +3 -3
  345. package/tests/migration_allocation.mjs +1 -1
  346. package/tests/mingle.mjs +36 -21
  347. package/tests/module-scope-map.mjs +22 -2
  348. package/tests/module_api.mjs +1 -0
  349. package/tests/module_contributions.mjs +13 -13
  350. package/tests/module_manifest.mjs +29 -0
  351. package/tests/module_skills_hook.mjs +2 -2
  352. package/tests/modules.mjs +14 -7
  353. package/tests/nav_permission_atoms.mjs +2 -1
  354. package/tests/needs_rebase.mjs +1 -1
  355. package/tests/newcomer_restock.mjs +13 -13
  356. package/tests/page_notes_panel.mjs +22 -22
  357. package/tests/page_tweak_no_block.mjs +1 -1
  358. package/tests/paintbrush.mjs +7 -7
  359. package/tests/privacy_matrix.mjs +1 -0
  360. package/tests/profile_activity.mjs +20 -23
  361. package/tests/profile_earnings.mjs +1 -1
  362. package/tests/profile_nudge_prefs.mjs +4 -4
  363. package/tests/profile_role_headline.mjs +73 -46
  364. package/tests/profile_role_stats.mjs +55 -45
  365. package/tests/profile_rollup_consent.mjs +19 -6
  366. package/tests/profile_route.mjs +12 -12
  367. package/tests/profile_ui_cross_project.mjs +4 -4
  368. package/tests/project_invite_suggestions.mjs +2 -2
  369. package/tests/promote_goal_id.mjs +1 -1
  370. package/tests/publish_manifest.mjs +1 -1
  371. package/tests/rank_floor_wiring.mjs +1 -1
  372. package/tests/regrade_actual_minutes.mjs +1 -1
  373. package/tests/removal_proposal.mjs +2 -2
  374. package/tests/reward_gate.mjs +4 -4
  375. package/tests/reward_mode_cost_plus_only.mjs +1 -1
  376. package/tests/role_responsibilities.mjs +18 -12
  377. package/tests/role_stats_db.mjs +17 -16
  378. package/tests/scouting_rollup_fanout.mjs +5 -5
  379. package/tests/seed_tasks.mjs +5 -2
  380. package/tests/session_earnings_mirror_db.mjs +1 -1
  381. package/tests/session_reward_on_land_db.mjs +1 -1
  382. package/tests/session_reward_reconcile_db.mjs +2 -2
  383. package/tests/sessions_kit_pager.mjs +1 -1
  384. package/tests/settings_art_key_absent.mjs +2 -2
  385. package/tests/settings_main_role.mjs +21 -20
  386. package/tests/setup_flow.mjs +3 -3
  387. package/tests/setup_smoke.mjs +20 -0
  388. package/tests/ship_card.mjs +6 -6
  389. package/tests/specialities.mjs +16 -4
  390. package/tests/speciality_db_shape.mjs +6 -6
  391. package/tests/speciality_injection.mjs +17 -17
  392. package/tests/speciality_offer_ui.mjs +5 -5
  393. package/tests/speciality_offered.mjs +6 -5
  394. package/tests/speciality_session_skills.mjs +7 -7
  395. package/tests/speciality_skills.mjs +11 -11
  396. package/tests/speciality_walkthrough_ui.mjs +2 -2
  397. package/tests/sso_checkin_identity_spoof.mjs +2 -1
  398. package/tests/task_classifier_vocab.mjs +10 -0
  399. package/tests/task_cli.mjs +11 -3
  400. package/tests/task_criteria_unlink_db.mjs +2 -2
  401. package/tests/task_discipline_required.mjs +38 -16
  402. package/tests/task_goal_required.mjs +4 -4
  403. package/tests/task_needs_db.mjs +1 -1
  404. package/tests/tweak_replaces_artist_review.mjs +1 -1
  405. package/tests/update_pill.mjs +153 -2
  406. package/tests/upkeep_home.mjs +1 -1
  407. package/tests/v3_c1_options_proof.mjs +145 -0
  408. package/tests/visual_review.mjs +3 -3
  409. package/tests/watch_applications_queue.mjs +8 -5
  410. package/tests/watch_sealed_floor.mjs +3 -1
  411. package/tests/welcome_boot.mjs +12 -9
  412. package/tests/welcome_boot_browser.mjs +13 -13
  413. package/tests/worktree_helper.mjs +16 -1
@@ -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,323 @@
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
+ // The project's own logo where it has one (ADR 0358), else the shared mark, as the sidebar draws it.
95
+ function markHtml() {
96
+ var theme = (window.__BRANDING__ && window.__BRANDING__.theme) || {};
97
+ if (theme.logo) return '<img class="gs__mark" src="' + esc(theme.logo) + '" alt="" decoding="async">';
98
+ return '<svg class="gs__mark" viewBox="0 0 200 200" aria-hidden="true"><use href="/modules/ui-design/public/brand/mark.svg#mark"/></svg>';
99
+ }
100
+ // The Helm panel (task 1005192, ADR 0402): the mark and the name, the choices, one line at the foot.
101
+ function screenHtml(o) {
102
+ return '<div class="gs__art" aria-hidden="true">' + (o.art ? '<img class="gs__img" alt="" src="' + esc(o.art) + '" decoding="async">' : '') + '</div>' +
103
+ '<div class="gs__menu">' +
104
+ '<div class="gs__top">' +
105
+ '<div class="gs__head">' + markHtml() +
106
+ '<h2 class="gs__title" id="gs-title">' + esc(o.name || 'This project') + '</h2>' +
107
+ '</div>' +
108
+ '<div class="gs__list">' +
109
+ '<ul class="gs__items">' + MENU.map(itemHtml).join('') + '</ul>' +
110
+ '<ul class="gs__items gs__items--extras" id="gs-extras" hidden>' + EXTRAS.map(function (x) {
111
+ return '<li><a class="gs__item gs__item--extra" href="' + esc(x.href) + '">' + esc(x.label) + '</a></li>';
112
+ }).join('') + '</ul>' +
113
+ '</div>' +
114
+ '</div>' +
115
+ '<div class="gs__foot">' +
116
+ '<p class="gs__line" id="gs-line">' + esc(tipAt(read('localStorage', TIP_KEY))) + '</p>' +
117
+ '<p class="gs__note" id="gs-note" role="status" hidden></p>' +
118
+ '</div>' +
119
+ '</div>' +
120
+ '<div class="gs__load" id="gs-load" role="status" hidden>' +
121
+ '<p class="gs__when" id="gs-when" hidden></p>' +
122
+ '<ol class="gs__segs segs" id="gs-segs" aria-hidden="true" hidden></ol>' +
123
+ '<svg class="gs__city" id="gs-city" role="img" aria-label="The project city building up, week by week" hidden></svg>' +
124
+ '<p class="gs__tip"><b>Tip</b><span id="gs-tip"></span></p>' +
125
+ '</div>';
126
+ }
127
+ // One segment per week of the history: filled up to the week being shown. The words beside
128
+ // them (#gs-when) say the same thing, so the row is hidden from a screen reader.
129
+ // Counted the way whenLabel words it, so the row and the sentence never disagree.
130
+ function segsDone(weeks, t) {
131
+ var n = Math.max(0, Math.round(Number(weeks) || 0));
132
+ return Math.max(0, n - Math.round(Math.max(0, n - 1 - (Number(t) || 0))));
133
+ }
134
+ function drawSegs(weeks, t) {
135
+ var box = root && root.querySelector('#gs-segs'), n = Math.max(0, Math.round(Number(weeks) || 0));
136
+ if (!box || n < 2) return;
137
+ while (box.children.length < n) box.appendChild(document.createElement('li'));
138
+ var done = segsDone(n, t);
139
+ for (var i = 0; i < box.children.length; i++) box.children[i].className = i < done ? 'seg seg--done' : 'seg';
140
+ box.hidden = false;
141
+ }
142
+
143
+ var root = null, opts = null, timers = [], phase = null;
144
+ function later(ms, fn) { timers.push(setTimeout(fn, ms)); }
145
+ function stopTimers() { timers.forEach(clearTimeout); timers = []; }
146
+ function still() { return !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); }
147
+ function items() {
148
+ return Array.prototype.filter.call(root.querySelectorAll('.gs__item'), function (b) { return !b.closest('[hidden]'); });
149
+ }
150
+
151
+ function close() {
152
+ if (!root) return;
153
+ stopTimers();
154
+ document.removeEventListener('keydown', onKey, true);
155
+ if (root.parentNode) root.parentNode.removeChild(root);
156
+ root = null; phase = null;
157
+ document.documentElement.classList.remove('gs-open');
158
+ var done = opts && opts.done;
159
+ opts = null;
160
+ if (typeof done === 'function') done();
161
+ }
162
+ function leave() {
163
+ if (!root || phase === 'leaving') return;
164
+ phase = 'leaving';
165
+ stopTimers();
166
+ if (still()) { close(); return; }
167
+ root.classList.add('gs--leaving');
168
+ later(LEAVE_MS, close);
169
+ }
170
+
171
+ // ---- the load screen: the still, a tip, and the city building up through its history ----
172
+ function build(n) {
173
+ var e = document.createElementNS(NS, n.tag);
174
+ Object.keys(n.attrs).forEach(function (k) { e.setAttribute(k, String(n.attrs[k])); });
175
+ if (n.text != null) e.textContent = n.text;
176
+ n.kids.forEach(function (c) { e.appendChild(build(c)); });
177
+ return e;
178
+ }
179
+ function themeMode() {
180
+ var t = document.documentElement.dataset.theme;
181
+ if (t === 'light' || t === 'dark') return t;
182
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
183
+ }
184
+ function drawCity(model, t) {
185
+ var Draw = window.OTBHomeCityDraw, svg = root && root.querySelector('#gs-city'), when = root && root.querySelector('#gs-when');
186
+ if (!svg) return;
187
+ var out = Draw.citySvg(model, t, { mode: themeMode(), gov: window.GovCity || null });
188
+ svg.setAttribute('viewBox', out.viewBox);
189
+ while (svg.firstChild) svg.removeChild(svg.firstChild);
190
+ out.nodes.forEach(function (n) { svg.appendChild(build(n)); });
191
+ svg.removeAttribute('hidden');
192
+ when.textContent = whenLabel(model.W, t);
193
+ when.hidden = false;
194
+ drawSegs(model.W, t);
195
+ }
196
+ // One week after another, as state: each stop is a whole drawing, so nothing here animates.
197
+ function runHistory(model) {
198
+ if (!root || phase !== 'load') return;
199
+ stopTimers();
200
+ var stops = still() ? [model.W - 1] : historySteps(model.W, STEPS), i = 0;
201
+ (function step() {
202
+ if (!root) return;
203
+ drawCity(model, stops[i]);
204
+ i += 1;
205
+ if (i < stops.length) later(STEP_MS, step); else later(still() ? HOLD_MS : 700, leave);
206
+ })();
207
+ }
208
+ function readCity() {
209
+ var Draw = window.OTBHomeCityDraw;
210
+ if (!Draw || typeof fetch !== 'function') return;
211
+ fetch(CITY_READ, { credentials: 'same-origin' }).then(function (r) { return r.ok ? r.json() : null; }).then(function (data) {
212
+ var model = data && Draw.cityModel(data);
213
+ if (model && model.W && model.areas.length) runHistory(model);
214
+ }, function () { /* no city: the still and the tip stand, then the world */ });
215
+ }
216
+ function load() {
217
+ if (!root || phase !== 'menu') return;
218
+ phase = 'load';
219
+ set(true);
220
+ var n = Number(read('localStorage', TIP_KEY)) || 0;
221
+ write('localStorage', TIP_KEY, String(n + 1));
222
+ root.querySelector('#gs-tip').textContent = tipAt(n);
223
+ root.querySelector('.gs__menu').hidden = true;
224
+ root.querySelector('#gs-load').hidden = false;
225
+ root.classList.add('gs--load');
226
+ later(HOLD_MS, leave); // runHistory replaces this once the city answers
227
+ readCity();
228
+ }
229
+
230
+ // Quit closes the tab. A browser only lets a script close a tab a script opened, so when the
231
+ // tab is still here a moment later the screen says so instead of doing nothing.
232
+ function quit() {
233
+ try { window.close(); } catch (_) { /* refused */ }
234
+ later(300, function () {
235
+ var note = root && root.querySelector('#gs-note');
236
+ if (!note) return;
237
+ note.textContent = 'Your browser kept this tab open. Close it to quit.';
238
+ note.hidden = false;
239
+ });
240
+ }
241
+ function toggleExtras(btn) {
242
+ var box = root.querySelector('#gs-extras'), open = box.hidden;
243
+ box.hidden = !open;
244
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
245
+ if (open) box.querySelector('a').focus();
246
+ }
247
+ function onClick(e) {
248
+ var el = e.target.closest && e.target.closest('.gs__item');
249
+ if (!el || phase !== 'menu') return;
250
+ var id = el.getAttribute('data-gs');
251
+ if (id === 'start') load();
252
+ else if (id === 'extras') toggleExtras(el);
253
+ else if (id === 'quit') quit();
254
+ }
255
+ // The screen keeps every key while it is up: Escape folds Extras away and nothing else (the
256
+ // shell's Escape must not flip the bars underneath), the arrows and Tab stay inside the menu.
257
+ function onKey(e) {
258
+ if (!root) return;
259
+ if (e.key === 'Escape') {
260
+ e.preventDefault();
261
+ var box = root.querySelector('#gs-extras');
262
+ if (phase === 'menu' && !box.hidden) { var b = root.querySelector('[data-gs="extras"]'); toggleExtras(b); b.focus(); }
263
+ return;
264
+ }
265
+ if (phase !== 'menu') return;
266
+ var f = items(), at = f.indexOf(document.activeElement), step = 0;
267
+ if (e.key === 'ArrowDown') step = 1;
268
+ else if (e.key === 'ArrowUp') step = -1;
269
+ else if (e.key === 'Tab') step = e.shiftKey ? -1 : 1;
270
+ if (!step || !f.length) return;
271
+ e.preventDefault();
272
+ f[(at + step + f.length) % f.length].focus();
273
+ }
274
+
275
+ // Put the start screen up. `o`: { name, art, done } — the project's name, the still to stand
276
+ // behind both screens, and what to do once the world is showing. True when it is up.
277
+ function start(o) {
278
+ if (root || !document.body) return !!root;
279
+ opts = o || {};
280
+ phase = 'menu';
281
+ root = document.createElement('div');
282
+ root.className = 'gs';
283
+ root.id = 'game-start';
284
+ root.setAttribute('role', 'dialog');
285
+ root.setAttribute('aria-modal', 'true');
286
+ root.setAttribute('aria-labelledby', 'gs-title');
287
+ root.innerHTML = screenHtml(opts);
288
+ root.addEventListener('click', onClick);
289
+ document.addEventListener('keydown', onKey, true);
290
+ document.body.appendChild(root);
291
+ document.documentElement.classList.add('gs-open');
292
+ var first = root.querySelector('.gs__item');
293
+ if (first && first.focus) first.focus();
294
+ return true;
295
+ }
296
+
44
297
  try {
45
298
  var asked = new URLSearchParams(window.location.search || '').get('game');
46
299
  if (asked === 'on' || asked === 'off') write('localStorage', KEY, asked);
47
300
  } catch (_) { /* old engine: the stored value stands */ }
48
301
  stamp();
49
- window.OTBShellGame = { on: on, set: set, hint: hint };
302
+
303
+ // ---------- the options menu (task 1005144, BONGOS-V3 R04, ADR 0401) ----------
304
+ // Every setting lives in ONE menu, the Settings page (options-menu.js). A screen
305
+ // that used to have its own page (My agents, Project settings, Modules, …) now
306
+ // opens INSIDE that menu, in a frame, with `?embed=1`. Such a page is EMBEDDED:
307
+ // html[data-embed="on"], no sidebar, top bar, foot or page head (style.css), no
308
+ // full-screen switch, a link to another page opens in the whole window, and an
309
+ // Escape nobody used goes to the menu. Only inside a frame: the same URL opened
310
+ // on its own is the page with its chrome (the server sends a plain visit of the
311
+ // old address to the menu, serve-internal.js).
312
+ var embedded = false;
313
+ try {
314
+ embedded = new URLSearchParams(window.location.search || '').get('embed') === '1'
315
+ && !!window.parent && window.parent !== window;
316
+ } catch (_) { embedded = false; }
317
+ if (embedded) {
318
+ document.documentElement.dataset.embed = 'on';
319
+ // The shell reads this before it draws the switch, so no button, no Escape-to-leave.
320
+ if (document.body && document.body.setAttribute) document.body.setAttribute('data-fullscreen-switch', 'off');
321
+ }
322
+ // The hall's address for the menu: '/settings' on the builders host, '/builders/settings' on the apex.
323
+ function menuPath() {
324
+ var p = String((window.location && window.location.pathname) || '');
325
+ return (p.indexOf('/builders/') === 0 || p === '/builders' ? '/builders' : '') + '/settings';
326
+ }
327
+ function inField(el) {
328
+ var tag = el && el.tagName ? String(el.tagName).toLowerCase() : '';
329
+ return tag === 'input' || tag === 'textarea' || tag === 'select' || !!(el && el.isContentEditable);
330
+ }
331
+ // ADR 0399 §2.4 row 6: in game mode, the last Escape — one no dialog, picker,
332
+ // palette, sheet or scene pop-up used — is the options menu's. It is judged after
333
+ // the dispatch (a page listener may sit later on the window), and it is marked
334
+ // used before the shell's own check runs, so the bars do not flash back first.
335
+ // On the menu itself it closes the menu; inside a frame it is the menu's too.
336
+ function onEscape(e) {
337
+ if (!e || e.key !== 'Escape' || e.defaultPrevented || inField(e.target)) return;
338
+ if (!embedded && !on()) return;
339
+ window.setTimeout(function () {
340
+ if (e.defaultPrevented) return;
341
+ try { e.preventDefault(); } catch (_) { /* already finished */ }
342
+ var host = embedded ? window.parent : window;
343
+ if (host && host.OTBOptions && typeof host.OTBOptions.escape === 'function') { host.OTBOptions.escape(); return; }
344
+ if (embedded) return;
345
+ var here = String(window.location.pathname || '') + String(window.location.search || '') + String(window.location.hash || '');
346
+ window.location.assign(menuPath() + '?from=' + encodeURIComponent(here));
347
+ }, 0);
348
+ }
349
+ if (window.addEventListener) window.addEventListener('keydown', onEscape);
350
+ // Inside the menu, a link to ANOTHER page leaves the menu for it; a link within the page stays.
351
+ if (embedded && document.addEventListener) {
352
+ document.addEventListener('click', function (e) {
353
+ var a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
354
+ if (!a || a.getAttribute('target')) return;
355
+ try {
356
+ var to = new URL(a.getAttribute('href'), window.location.href);
357
+ if (to.origin !== window.location.origin || to.pathname !== window.location.pathname) a.setAttribute('target', '_top');
358
+ } catch (_) { /* a href the browser cannot read: leave it alone */ }
359
+ }, true);
360
+ }
361
+ window.OTBShellGame = { on: on, set: set, hint: hint, start: start, tipAt: tipAt, historySteps: historySteps, whenLabel: whenLabel, segsDone: segsDone, MENU: MENU, TIPS: TIPS,
362
+ embedded: function () { return embedded; }, menuPath: menuPath };
50
363
  })();
@@ -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>' +
@@ -557,7 +538,6 @@
557
538
  '</button>' +
558
539
  (CRAFT ? CRAFT.switchHtml() : '') +
559
540
  '<button class="zoom-toggle" id="zoom-toggle" type="button" aria-label="Text size"></button>' +
560
- '<button class="theme-toggle" id="theme-toggle" type="button" aria-label="Switch theme"></button>' +
561
541
  // task 1004327: the full-screen switch, LAST so it sits in the corner the
562
542
  // collapsed bar keeps in full screen — the one control that stays put.
563
543
  // task 1004430: on every page now, unless the page opts out.
@@ -665,44 +645,21 @@
665
645
  window.setTimeout(function () { if (!e.defaultPrevented && isFullscreen()) setFullscreen(false); }, 0);
666
646
  }
667
647
 
668
- // ---------- theme (light / dark) ----------
669
- // The pack's :root override carries only light values; the dark neutrals live
670
- // in style.css under html[data-theme="dark"] (higher specificity than :root,
671
- // so they win over the pack while the instance accent survives). Stored
672
- // preference beats the OS setting; no stored preference follows it live.
673
- var THEME_KEY = 'hall-theme';
674
- var themeMedia = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
675
-
676
- function storedTheme() {
677
- try { var v = window.localStorage.getItem(THEME_KEY); return v === 'dark' || v === 'light' ? v : null; } catch (_) { return null; }
678
- }
679
- function effectiveTheme() {
680
- return storedTheme() || (themeMedia && themeMedia.matches ? 'dark' : 'light');
681
- }
648
+ // ---------- theme: the hall is dark only (task 1005192, ADR 0402) ----------
649
+ // Helm is the hall's one look, so there is no light/dark switch and no stored
650
+ // choice: the server stamps html[data-theme="dark"] on every hall page
651
+ // (src/hall-helm.js) and this sets it again, for a page read without that stamp.
652
+ // The old 'hall-theme' preference is never read. Scripts that only READ the
653
+ // attribute (the city, the sky, the atlas) keep working: they always read dark.
682
654
  function applyTheme() {
683
- var t = effectiveTheme();
684
- document.documentElement.dataset.theme = t;
685
- var btn = document.getElementById('theme-toggle');
686
- if (btn) {
687
- var toDark = t !== 'dark';
688
- btn.setAttribute('aria-label', toDark ? 'Switch to dark theme' : 'Switch to light theme');
689
- btn.title = btn.getAttribute('aria-label');
690
- btn.innerHTML = toDark
691
- ? '<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M14.5 10.8A6 6 0 0 1 7.2 3.5a6 6 0 1 0 7.3 7.3Z"/></svg>'
692
- : '<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><circle cx="9" cy="9" r="3.5"/><path d="M9 1.5v2m0 11v2M1.5 9h2m11 0h2M3.7 3.7l1.4 1.4m7.8 7.8 1.4 1.4m0-10.6-1.4 1.4M5.1 12.9l-1.4 1.4"/></svg>';
693
- }
694
- }
695
- function toggleTheme() {
696
- var next = effectiveTheme() === 'dark' ? 'light' : 'dark';
697
- try { window.localStorage.setItem(THEME_KEY, next); } catch (_) { /* private mode */ }
698
- applyTheme();
655
+ document.documentElement.dataset.theme = 'dark';
699
656
  }
700
657
 
701
658
  // ---------- zoom (accessibility: enlarge the whole page) ----------
702
659
  // task 1475 (idea 280): a one-click "make everything bigger" control for
703
660
  // readability. Cycles 100% -> 125% -> 150% -> 100% via CSS `zoom` on <html>,
704
661
  // which enlarges px AND rem sizes uniformly (a font-size bump would miss the
705
- // hall's many px values). Persisted per-browser like the theme preference.
662
+ // hall's many px values). Persisted per-browser.
706
663
  var ZOOM_KEY = 'hall-zoom';
707
664
  var ZOOM_STEPS = [1, 1.25, 1.5];
708
665
  function storedZoom() {
@@ -1196,7 +1153,7 @@
1196
1153
  maybeRenderCoreUpdate();
1197
1154
  // 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
1155
  // 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);
1156
+ 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
1157
  var signin = document.getElementById('shell-signin');
1201
1158
  if (signin) signin.hidden = access.authed !== false;
1202
1159
  var user = document.getElementById('shell-user');
@@ -1345,8 +1302,6 @@
1345
1302
  if (document.documentElement.classList.contains('drawer-open')) closeDrawer();
1346
1303
  else openDrawer();
1347
1304
  });
1348
- var theme = document.getElementById('theme-toggle');
1349
- if (theme) theme.addEventListener('click', toggleTheme);
1350
1305
  var zoom = document.getElementById('zoom-toggle');
1351
1306
  if (zoom) zoom.addEventListener('click', cycleZoom);
1352
1307
  var full = document.getElementById('fullscreen-toggle');
@@ -1354,9 +1309,6 @@
1354
1309
  full.addEventListener('click', function () { setFullscreen(!isFullscreen()); });
1355
1310
  window.addEventListener('keydown', onFullscreenKey);
1356
1311
  }
1357
- if (themeMedia && themeMedia.addEventListener) {
1358
- themeMedia.addEventListener('change', function () { if (!storedTheme()) applyTheme(); });
1359
- }
1360
1312
  var scrim = document.getElementById('app-scrim');
1361
1313
  if (scrim) scrim.addEventListener('click', closeDrawer);
1362
1314
  var sidebar = document.getElementById('app-sidebar');
@@ -1443,7 +1395,7 @@
1443
1395
  positions: me.positions || null,
1444
1396
  // task 1005076: whether setup is complete; incomplete shows setup-gate.js.
1445
1397
  setup: (me.onboarding && me.onboarding.setup) || null,
1446
- builder: me.builder,
1398
+ builder: me.builder, character: me.character, // task 1005147: the character held (null: pick one)
1447
1399
  });
1448
1400
  }).catch(function () { /* cosmetic only */ });
1449
1401
  }
@@ -1488,9 +1440,6 @@
1488
1440
  closeDrawer: closeDrawer,
1489
1441
  isFullscreen: isFullscreen,
1490
1442
  setFullscreen: setFullscreen,
1491
- // task 1004544: a page with its own light/dark switch (the governor city
1492
- // and street) repaints the top bar's after writing 'hall-theme'.
1493
- applyTheme: applyTheme,
1494
1443
  // task 1004477: a page that renders an exit after boot asks the shell to fill it.
1495
1444
  fillExits: TRAIL.fillExits,
1496
1445
  positions: function () { return access.positions; }, // task 1004713: seats held (null while unknown)
@@ -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
  })();
@@ -621,8 +621,7 @@
621
621
  <span>Role it sits under</span>
622
622
  <select name="discipline" required>
623
623
  <option value="">Pick one…</option>
624
- <option value="engineer">Engineer</option>
625
- <option value="ideator">Ideator</option>
624
+ <option value="artisan">Artisan</option>
626
625
  <option value="artist">Artist</option>
627
626
  <option value="ui">UI design</option>
628
627
  </select>
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "_": "THE MATTER CARD THAT ACTS (task 1004528, goal 1000125) on the street page, through modules/ui-design/kit/render.js. Kept beside street.states.json rather than inside it, so the street and its interiors (task 1004526) and the card each own their states. The populated card needs the hall-preview harness, whose government__docket.json puts the review-seat amendment on the board source (board:47) and whose government__board__items.json carries its ballot (item 47), with a canned answer for a vote:\n\n node scripts/hall-preview/server.js --port 4625 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/street-card.states.json --base-in http://localhost:4625 --base-out http://localhost:4625\n\nTask 1004566 adds a card per kind the docket carries (blocker, red-team report, core update, module re-check, a Full Idea and the review queue), each compact and with \"more\" open; the harness fixture carries them. Against the kit stub there is no docket and no pin, so these states are for the harness.",
3
3
  "page": "/builders/street",
4
+ "modes": ["dark"],
4
5
  "_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
5
6
  "storage": { "session": { "hall-craft-asked": "1" } },
6
7
  "surface": "hall-ui",
@@ -81,7 +81,6 @@
81
81
  let top = h;
82
82
  if (craft === 'artist') { const bh = Math.round(h * 0.8); S.box(out, T, x, 0, w, bh, T.work, { d: 6, win: true, lit: lit }); S.sawtooth(out, x, -bh, w, 3, 12, T.roof, T.glass); top = bh + 12; }
83
83
  else if (craft === 'ui') { S.box(out, T, x, 0, w, h, T.glass, { d: 6, grid: 3 }); S.box(out, T, x - 2, 0, w + 4, 4, T.roof, { d: 6, z: h }); top = h + 4; }
84
- else if (craft === 'ideator') { S.box(out, T, x, 0, w, 22, T.work, { d: 6, door: true }); S.gable(out, x, -22, w, 14, T.roof); top = 36; }
85
84
  else S.box(out, T, x, 0, w, h, T.work, { d: 6, win: true, lit: lit });
86
85
  if (!lit) {
87
86
  const fx = x + w / 2;
@@ -410,7 +409,7 @@
410
409
  stageEdge: p.draw === 'awaiting a tester' ? 'rgba(251,149,105,0.6)' : T.ruleStrong,
411
410
  })),
412
411
  legend: [
413
- { key: 'a', text: 'Shape is the craft: tower for engineer, studio roof for artist, glass front for UI, a small pavilion for ideator.' },
412
+ { key: 'a', text: 'Shape is the craft: tower for artisan, studio roof for artist, glass front for UI.' },
414
413
  { key: 'b', text: 'Stage is the status: staked plot, slab, scaffold, built, then lit once shipped. A flag means it waits on a check.' },
415
414
  ],
416
415
  };
@@ -670,7 +669,7 @@
670
669
  }
671
670
 
672
671
  // ---------- the real districts ----------
673
- const CRAFTS = Object.freeze(['engineer', 'artist', 'ui', 'ideator']);
672
+ const CRAFTS = Object.freeze(['artisan', 'artist', 'ui']);
674
673
  // A task's status as the prototype's construction stage: [drawing stage, pill words].
675
674
  const STAGES = Object.freeze({
676
675
  shipped: ['shipped', 'shipped'], confirmed: ['graded · landing', 'graded · landing'], completed: ['being graded', 'being graded'],
@@ -699,7 +698,7 @@
699
698
  const shown = mine.slice().sort((a, b) => rank(a) - rank(b) || recent(a) - recent(b) || Number(a.id) - Number(b.id)).slice(0, MAX_ITEMS)
700
699
  .sort((a, b) => PIPELINE.indexOf(a.status) - PIPELINE.indexOf(b.status) || Number(a.id) - Number(b.id));
701
700
  const items = shown.map((t) => {
702
- const craft = CRAFTS.indexOf(t.discipline) >= 0 ? t.discipline : 'engineer';
701
+ const craft = CRAFTS.indexOf(t.discipline) >= 0 ? t.discipline : 'artisan';
703
702
  const st = STAGES[t.status];
704
703
  const built = ['ready', 'backlog'].indexOf(st[0]) < 0;
705
704
  return [String(t.title || 'task ' + t.id), String(t.kind || 'task'), craft, st[0], built ? taskHeight(t) : 0, st[1], '/builders/task/' + encodeURIComponent(t.id)];
@@ -128,8 +128,6 @@ html[data-fullscreen="on"] .street-top { padding-right: 184px; }
128
128
  .street-hud__dot--hot { background: var(--accent); }
129
129
  .street-hud__dot--moving { width: 6px; height: 6px; border: 0; background: var(--ink); }
130
130
  .street-hud__n { font-size: 24px; font-weight: 300; font-variant-numeric: tabular-nums; }
131
- .street-mode { margin-left: 36px; flex: none; background: none; border: 1px solid var(--street-rule-strong); border-radius: 999px; height: 36px; padding: 0 14px; display: flex; align-items: center; gap: 8px; cursor: pointer; color: var(--ink); font-family: var(--font-body); font-size: 13px; text-transform: lowercase; }
132
- .street-mode svg { display: block; stroke-width: 1.4; }
133
131
 
134
132
  /* the street band: the prototype's 1440-wide artboard, scaled by street.js */
135
133
  /* The living street's parallax (task 1004685) is Keith Clark's, sideways and
@@ -309,20 +307,18 @@ html[data-fullscreen="on"] .street-top { padding-right: 184px; }
309
307
  .street-card__lines li { padding: 8px 0; border-top: 1px solid var(--street-rule); font-size: 14px; line-height: 1.4; }
310
308
  .street-card__result { margin: 0; font-size: 15px; line-height: 1.45; }
311
309
 
312
- .street-mark:focus-visible, .street-mode:focus-visible, .street-pin:focus-visible, .street-door:focus-visible, .street-sign:focus-visible, .street-card__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
310
+ .street-mark:focus-visible, .street-pin:focus-visible, .street-door:focus-visible, .street-sign:focus-visible, .street-card__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
313
311
 
314
312
  /* narrower: the strip tightens, then wraps; the readings scroll sideways */
315
313
  @media (max-width: 1180px) {
316
314
  .street-hud { gap: 20px; }
317
315
  .street-hud__n { font-size: 20px; }
318
- .street-mode { margin-left: 20px; }
319
316
  }
320
317
  /* narrow: two rows inside the 80 px top-bar floor, the wordmark and the switch
321
318
  on the first, the three figures on the second as their own sideways strip */
322
319
  @media (max-width: 900px) {
323
320
  .street-top { height: auto; max-height: 80px; display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "brand mode" "hud hud"; row-gap: 2px; padding: 5px 16px; }
324
321
  .street-top__brand { grid-area: brand; }
325
- .street-mode { grid-area: mode; margin-left: 0; height: 34px; }
326
322
  .street-hud { grid-area: hud; margin-left: 0; gap: 18px; min-width: 0; overflow-x: auto; overflow-y: hidden; white-space: nowrap; height: 30px; }
327
323
  .street-hud__item { gap: 6px; flex: none; }
328
324
  .street-hud .street-label { font-size: 11px; }