@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
@@ -3,10 +3,12 @@
3
3
  // GET /market-watch/home, which every signed-in member may make. Where the module is off
4
4
  // its routes do not exist, so the page shows its quiet off state rather than an error.
5
5
  //
6
- // task 1005045: the owner also gets a Settings tab here (task 1004938's settings page,
7
- // folded in so the sidebar says "Market Watch" once). Owner means archon on /me, and it is
8
- // cosmetic: the four settings routes are the wall (ADR 0016). Everyone else gets no tab
9
- // strip at all, and their page never asks for the settings.
6
+ // task 1005144 (ADR 0401): the owner's settings (task 1004938's page, then task 1005045's
7
+ // Settings tab here) are an entry in the ONE options menu. The menu opens this page in its
8
+ // frame (?embed=1, shell-game.js) and the page then shows the settings panel ALONE; a plain
9
+ // visit shows the watch and no tab strip, and an old #settings link goes on to the menu.
10
+ // The four settings routes stay the wall (ADR 0016): a reader who is not the owner gets the
11
+ // panel's own "only the owner" line from their 403.
10
12
  (() => {
11
13
  'use strict';
12
14
 
@@ -107,54 +109,29 @@
107
109
  });
108
110
  }
109
111
 
110
- // The viewer's rank, or null when /me cannot say. Unknown is not an owner.
111
- // The shell already asks /me once on every hall page and shares the answer on
112
- // window.__hallReads (the gov-feeds.js precedent), so this adopts that read rather
113
- // than paying a second round trip; it asks itself only where the shell did not.
114
- async function viewerRank() {
115
- try {
116
- const shared = window.__hallReads && window.__hallReads['/me'];
117
- let me;
118
- if (shared) {
119
- me = await shared; // the data, false on a 403, or null when unreadable
120
- } else {
121
- const res = await fetch('/api/bongos/me', { credentials: 'same-origin' });
122
- me = res.ok ? await res.json() : null;
123
- }
124
- const b = (me && (me.builder || me)) || {};
125
- return b.rank ? String(b.rank).toLowerCase() : null;
126
- } catch (_) {
127
- return null;
128
- }
112
+ // Inside the options menu's frame: the settings panel alone, and no watch read.
113
+ function embedded() {
114
+ return !!(window.OTBShellGame && window.OTBShellGame.embedded && window.OTBShellGame.embedded());
129
115
  }
130
-
131
- // The owner's two tabs. #settings is the address the old /market-watch-settings URL
132
- // redirects to, so the ids are the contract.
133
- function mountOwnerTabs() {
134
- $('mw-tabs').hidden = false;
135
- window.OTBKit.makeTabs({
136
- mount: $('mw-tabs'),
137
- hash: true,
138
- initial: 'watch',
139
- tabs: [
140
- { id: 'watch', label: 'Watch', panel: 'mw-panel-watch' },
141
- { id: 'settings', label: 'Settings', panel: 'mw-panel-settings' },
142
- ],
143
- });
116
+ function showSettingsOnly() {
117
+ $('mw-panel-watch').hidden = true;
118
+ $('mw-panel-settings').hidden = false;
144
119
  window.OTBMarketWatchSettings.load();
145
120
  }
121
+ // An old link to the owner's tab (/market-watch#settings) opens the menu entry instead.
122
+ function settingsLinkMoved() {
123
+ if (String(window.location.hash || '') !== '#settings') return false;
124
+ const game = window.OTBShellGame;
125
+ const menu = game && game.menuPath ? game.menuPath() : '/settings';
126
+ window.location.replace(menu + '?open=market-watch');
127
+ return true;
128
+ }
146
129
 
147
130
  async function load() {
131
+ if (embedded()) return moduleOff() ? show('mw-off') : showSettingsOnly();
132
+ if (settingsLinkMoved()) return;
148
133
  if (moduleOff()) return show('mw-off');
149
- const rank = viewerRank();
150
134
  await loadDesk();
151
- // Where the module's routes are not mounted there is nothing to set, so no tab.
152
- // A watch that failed to READ (mw-fail) still gets the tab on purpose: the settings
153
- // are their own read, and a broken watch must not lock the owner out of its switch,
154
- // cap and key — the old settings page never depended on the desk either. The failure
155
- // line stays on the Watch tab, where it belongs.
156
- if (!$('mw-off').hidden) return;
157
- if ((await rank) === 'archon') mountOwnerTabs();
158
135
  }
159
136
 
160
137
  async function loadDesk() {
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "_": "MARKET WATCH HOME, the watchtower desk (task 1004939; design of record docs/design/mocks/market-watch/home.html, option D). One read, GET /market-watch/home. Against the kit's own stub it 404s, which the page reads as the module being off, so the stub render is the quiet off state. The populated desk needs the hall-preview harness with the module reported on; its market-watch__home.json fixture was built from the committed 2026-10-28 seed through the real shapes (tests/fixtures/market-watch-seed-rows.cjs):\n\n node scripts/hall-preview/server.js --port 4639 --fixture-me --modules-on market-watch --rank archon\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/market-watch.states.json --base-in http://localhost:4639 --base-out http://localhost:4639\n\nThe 'settings' state is the owner's Settings tab (task 1005045; task 1004938's page before it), drawn from GET /market-watch/settings (the harness's market-watch__settings.json, the mockup's month). Only an archon viewer gets the tab, so the harness needs --rank archon; for anyone else the state renders the desk with no tab strip.",
3
3
  "page": "/builders/market-watch",
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",
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm">
12
12
  <!-- task 1004366: a store version's how-to reads as a document, so it borrows the
13
13
  reading room's article card (room.css is scoped to its own primer- classes). -->
14
14
  <link rel="stylesheet" href="/builders/room.css">
@@ -126,10 +126,10 @@
126
126
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
127
127
 
128
128
  <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
129
- <script src="/builders/shell-game.js?v=2026-11-09-game-mode"></script>
129
+ <script src="/builders/shell-game.js?v=2026-11-11-helm"></script>
130
130
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
131
- <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
132
- <script src="/builders/shell.js?v=2026-11-09-game-mode"></script>
131
+ <script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
132
+ <script src="/builders/shell.js?v=2026-11-11-helm"></script>
133
133
  <script src="/builders/hall-dialog.js"></script>
134
134
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
135
135
  <script src="/builders/palette.js" defer></script>
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "_": "Modules — the catalog as ONE panel of oversight rows (task 1003472): the kit filter bar, the figure band, two groups under heads. The empty state matters more than the populated one: a filter that matches nothing is the state a browse page spends real time in. Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once; the redesign added the `lens` and `details` states. Populated look: the hall-preview harness (scripts/hall-preview/fixtures/modules.json carries eighteen modules, one built by the project).",
3
3
  "page": "/builders/modules",
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",
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-09-23-changelist1">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm">
12
12
  <link rel="stylesheet" href="/builders/oversight.css">
13
13
  <link rel="stylesheet" href="/builders/project-settings.css?v=2026-11-02-outflow">
14
14
  </head>
@@ -80,10 +80,10 @@
80
80
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
81
81
 
82
82
  <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
83
- <script src="/builders/shell-game.js?v=2026-11-09-game-mode"></script>
83
+ <script src="/builders/shell-game.js?v=2026-11-11-helm"></script>
84
84
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
85
- <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
86
- <script src="/builders/shell.js?v=2026-11-09-game-mode"></script>
85
+ <script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
86
+ <script src="/builders/shell.js?v=2026-11-11-helm"></script>
87
87
  <script src="/builders/hall-dialog.js"></script>
88
88
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
89
89
  <script src="/builders/palette.js" defer></script>
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "_": "MONEY (task 1005047; once FLEET COSTS, task 1001949, ADR 0327) through modules/ui-design/kit/render.js. The page reads three authenticated routes — GET /project-account/outflow (payout.approve), GET /public/cost-summary and GET /provisioning/fleet/cost-ledger (provisioning.fleet.manage) — each section hidden until its own read answers. Against the kit's own stub every read fails, so the page shows its one 'Nothing to show yet' line, a state the floors must hold in. To look at the populated page run it against the hall-preview harness, whose fixtures answer all three (the ledger's two cloud hosts carry no recorded plan, the state live is in today):\n\n node scripts/hall-preview/server.js --port 4620 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/money.states.json --base-in http://localhost:4620 --base-out http://localhost:4620",
3
3
  "page": "/builders/money",
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",
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm">
12
12
  <!-- The oversight family's archetype: a titled row with facts and actions, the
13
13
  group head and the foot line (records/oversight-family.md). This page's
14
14
  repeated thing is that row at the desk's one-line density. -->
@@ -82,10 +82,10 @@
82
82
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
83
83
 
84
84
  <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
85
- <script src="/builders/shell-game.js?v=2026-11-09-game-mode"></script>
85
+ <script src="/builders/shell-game.js?v=2026-11-11-helm"></script>
86
86
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
87
- <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
88
- <script src="/builders/shell.js?v=2026-11-09-game-mode"></script>
87
+ <script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
88
+ <script src="/builders/shell.js?v=2026-11-11-helm"></script>
89
89
  <script src="/builders/hall-dialog.js"></script>
90
90
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
91
91
  <script src="/builders/palette.js" defer></script>
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "_": "MY AGENTS (task 1004685, the living-city prototype) through modules/ui-design/kit/render.js. Every row is filled from GET /me and its prefs, so the floors run against the hall-preview harness, which serves those reads and records the pref writes (scripts/hall-preview/pref-writes.js):\n\n node scripts/hall-preview/server.js --port 4614 --fixture-me --rank archon\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/my-agents.states.json --base-in http://builders.localhost:4614 --base-out http://builders.localhost:4614\n\nAgainst the kit's own stub the reads 404 and the page shows its sign-in line. The 'nobody' state is the same page as a builder who holds no seat (?as=nobody, a stand-in that can only take away, gov-positions.js).",
3
3
  "page": "/my-agents",
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",
@@ -0,0 +1,409 @@
1
+ // options-menu.js — the ONE options menu (task 1005144, BONGOS-V3 R04, goal 1000141;
2
+ // ADR 0401; docs/specs/bongos-v3-game-conversion.md, criterion game-opens-not-navigates).
3
+ //
4
+ // Every setting and configuration in the hall is reached from one vertical, grouped
5
+ // menu with a search box at the top. The menu IS the Settings page (/settings): its
6
+ // own panels stay where they were (the native groups below), and the six screens that
7
+ // used to be pages of their own — My agents, Project settings, Modules, Permissions,
8
+ // and Market Watch's settings and rules — open INSIDE the menu, in a frame, with
9
+ // `?embed=1` (shell-game.js strips their chrome). A plain visit to one of those old
10
+ // addresses is sent here by the server (serve-internal.js RETIRED_TO_OPTIONS), as
11
+ // `/settings?open=<entry>`, and the browser carries the old #anchor along.
12
+ //
13
+ // Two halves, the my-agents.js shape: the PURE half (the catalogue, who may see an
14
+ // entry, the search, what an address opens) is node-require-safe for
15
+ // tests/hall_options_menu.mjs; the page half draws the rail and the frames.
16
+ //
17
+ // What it does NOT do: it gates nothing. An entry is listed for the viewer the
18
+ // sidebar would have shown its page to (the same rank keys as shell.js gateAllows,
19
+ // cosmetic only); every screen keeps its own server page gate and its own routes,
20
+ // which are the wall (ADR 0016).
21
+
22
+ (function (root) {
23
+ 'use strict';
24
+
25
+ // ---------- the pure half ----------
26
+
27
+ // The menu, top to bottom. A group is a heading; an entry is one line under it.
28
+ // id the entry's address: /settings#<id> or /settings?open=<id>
29
+ // native true = one of Settings' own groups (settings.html data-group=<id>)
30
+ // embed the old page's path, opened in the frame with ?embed=1
31
+ // gate the rank key the old sidebar item used (signed | member | government | archon)
32
+ // module shown only where that module is on (window.OTB.moduleOn)
33
+ // words what a reader might type to find it, beyond its label
34
+ // anchors the places inside it a search can land on: { label, anchor, words }
35
+ var MENU = [
36
+ { head: 'You', items: [
37
+ { id: 'account', label: 'Account', native: true, gate: 'signed',
38
+ words: 'name display connections discord age privacy profile roles craft',
39
+ anchors: [
40
+ { label: 'Display name', anchor: 'display-name', words: 'name' },
41
+ { label: 'Connections', anchor: 'connections', words: 'discord link' },
42
+ { label: 'Your age', anchor: 'age', words: 'birthday' },
43
+ { label: 'Profile and privacy', anchor: 'privacy', words: 'cross-project public' },
44
+ { label: 'Preferred roles', anchor: 'disciplines', words: 'craft artisan artist discipline' },
45
+ ] },
46
+ { id: 'my-agents', label: 'Your agents', embed: '/my-agents', gate: 'signed',
47
+ words: 'claude agent ai interaction wandering rendering session cards models specialities',
48
+ anchors: [
49
+ { label: 'How Claude talks to you', anchor: 'talk', words: 'interaction technical verbosity rendering cards' },
50
+ { label: 'How your agents work', anchor: 'work', words: 'wandering autonomy' },
51
+ { label: 'Which models run', anchor: 'models', words: 'model opus sonnet' },
52
+ { label: 'Skill voices', anchor: 'skills', words: 'voice' },
53
+ { label: 'What it knows', anchor: 'knows', words: 'specialities expertise' },
54
+ ] },
55
+ ] },
56
+ { head: 'Sign-in and keys', items: [
57
+ { id: 'access', label: 'Access and keys', native: true, gate: 'signed',
58
+ words: 'cli token terminal sessions security cards passkeys autobongos runner key api gemini image',
59
+ anchors: [
60
+ { label: 'CLI access', anchor: 'cli', words: 'token terminal command line' },
61
+ { label: 'Active sessions', anchor: 'sessions-scroll', words: 'signed in devices' },
62
+ { label: 'Security cards', anchor: 'passkeys-scroll', words: 'passkey card' },
63
+ { label: 'Your Autobongos runner', anchor: 'autobongos', words: 'runner machine' },
64
+ { label: 'Image-generation key', anchor: 'art-key', words: 'gemini art' },
65
+ { label: 'Your AI keys', anchor: 'ai-keys', words: 'anthropic api key' },
66
+ ] },
67
+ ] },
68
+ { head: 'Preferences', items: [
69
+ { id: 'preferences', label: 'Preferences', native: true, gate: 'signed', words: 'usage reports' },
70
+ { id: 'sound', label: 'Sound', native: true, gate: 'signed', words: 'sounds audio chime event volume mute' },
71
+ ] },
72
+ { head: 'The project', items: [
73
+ { id: 'project-settings', label: 'Project settings', embed: '/project-settings', gate: 'government',
74
+ words: 'fundamentals name ship mode spend cap funding danger zone delete' },
75
+ { id: 'modules', label: 'Modules', embed: '/modules', gate: 'member',
76
+ words: 'module catalog enable disable toggle on off install extension' },
77
+ { id: 'permissions', label: 'Permissions and constitution', embed: '/government', gate: 'government',
78
+ words: 'ranks roles permission assignments outside access machine principals constitution laws',
79
+ anchors: [
80
+ { label: 'Ranks and permissions', anchor: 'ranks', words: 'rank permission assignments' },
81
+ { label: 'Constitution', anchor: 'constitution', words: 'laws charter' },
82
+ ] },
83
+ { id: 'market-watch', label: 'Market Watch settings', embed: '/market-watch', anchor: 'settings', gate: 'archon', module: 'market-watch',
84
+ words: 'market watch cap key switch weekly' },
85
+ { id: 'market-watch-rules', label: 'Market Watch rules', embed: '/market-watch-rules', gate: 'signed', module: 'market-watch',
86
+ words: 'market watch steps search score propose change' },
87
+ { id: 'update', label: 'Software update', native: true, gate: 'signed', words: 'version upgrade release update' },
88
+ { id: 'extensions', label: 'Extensions', native: true, gate: 'signed', contributed: true, words: 'module panels' },
89
+ ] },
90
+ ];
91
+
92
+ // The old addresses that used to be pages: path → entry id (the server's map too).
93
+ var RETIRED = {
94
+ '/my-agents': 'my-agents',
95
+ '/project-settings': 'project-settings',
96
+ '/modules': 'modules',
97
+ '/government': 'permissions',
98
+ '/market-watch-rules': 'market-watch-rules',
99
+ '/market-watch-settings': 'market-watch',
100
+ };
101
+
102
+ function entries() {
103
+ var out = [];
104
+ MENU.forEach(function (g) { g.items.forEach(function (it) { out.push(Object.assign({ head: g.head }, it)); }); });
105
+ return out;
106
+ }
107
+ function entry(id) {
108
+ var all = entries();
109
+ for (var i = 0; i < all.length; i += 1) if (all[i].id === id) return all[i];
110
+ return null;
111
+ }
112
+
113
+ // The same rank ladder as shell.js gateAllows. viewer = { authed, rank }.
114
+ function gateAllows(gate, viewer) {
115
+ var v = viewer || {};
116
+ var authed = v.authed === true;
117
+ var rank = String(v.rank || '').toLowerCase();
118
+ switch (gate) {
119
+ case 'signed': return authed;
120
+ case 'member': return authed && rank !== 'xenos' && rank !== '';
121
+ case 'government': return authed && (rank === 'metic' || rank === 'archon');
122
+ case 'archon': return authed && rank === 'archon';
123
+ default: return false;
124
+ }
125
+ }
126
+
127
+ // The menu this viewer sees: groups with at least one entry they may open.
128
+ // opts.moduleOn(key) answers module gates; opts.contributed says whether a module
129
+ // contributed a Settings panel (the Extensions entry shows only then).
130
+ function visibleMenu(viewer, opts) {
131
+ var o = opts || {};
132
+ var moduleOn = typeof o.moduleOn === 'function' ? o.moduleOn : function () { return true; };
133
+ return MENU.map(function (g) {
134
+ var items = g.items.filter(function (it) {
135
+ if (!gateAllows(it.gate, viewer)) return false;
136
+ if (it.module && !moduleOn(it.module)) return false;
137
+ if (it.contributed && !o.contributed) return false;
138
+ return true;
139
+ });
140
+ return { head: g.head, items: items };
141
+ }).filter(function (g) { return g.items.length > 0; });
142
+ }
143
+
144
+ function norm(s) { return String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim(); }
145
+
146
+ // The smart search: every word typed must appear in the line's label, its words,
147
+ // its entry's label or its group's heading. Ranked so the first line is the one
148
+ // Enter should open: a hit in the line's own label first; then a hit in its own
149
+ // words; and when nothing hit a label, the exact place (CLI access) above the
150
+ // group that merely holds it (Access and keys), because "token" means the token.
151
+ // Returns [{ id, anchor, label, where }] — `where` names the entry for a sub-line.
152
+ function search(query, menu) {
153
+ var words = norm(query).split(' ').filter(Boolean);
154
+ if (!words.length) return [];
155
+ var lines = [];
156
+ (menu || []).forEach(function (g) {
157
+ g.items.forEach(function (it) {
158
+ lines.push({ id: it.id, anchor: it.anchor || null, label: it.label, where: g.head, sub: false,
159
+ label_n: norm(it.label), own: norm([it.label, it.words].join(' ')), hay: norm([it.label, it.words, g.head].join(' ')) });
160
+ (it.anchors || []).forEach(function (a) {
161
+ lines.push({ id: it.id, anchor: a.anchor, label: a.label, where: it.label, sub: true,
162
+ label_n: norm(a.label), own: norm([a.label, a.words].join(' ')), hay: norm([a.label, a.words, it.label, g.head].join(' ')) });
163
+ });
164
+ });
165
+ });
166
+ var hits = [];
167
+ var count = function (s) { return words.filter(function (w) { return s.indexOf(w) !== -1; }).length; };
168
+ lines.forEach(function (l, i) {
169
+ if (!words.every(function (w) { return l.hay.indexOf(w) !== -1; })) return;
170
+ hits.push({ l: l, label: count(l.label_n), own: count(l.own), i: i });
171
+ });
172
+ hits.sort(function (a, b) {
173
+ return (b.label - a.label) || (b.own - a.own)
174
+ || (a.label === 0 && a.l.sub !== b.l.sub ? (a.l.sub ? -1 : 1) : 0) || (a.i - b.i);
175
+ });
176
+ return hits.map(function (h) { return { id: h.l.id, anchor: h.l.anchor, label: h.l.label, where: h.l.where }; });
177
+ }
178
+
179
+ // What an address opens: ?open=<id> with an optional #anchor, or a bare #<id>,
180
+ // or a settings panel anchor (#cli) the page itself resolves. Returns
181
+ // { id, anchor } or null (null = the page's own hash handling decides).
182
+ function target(search, hash) {
183
+ var params;
184
+ try { params = new URLSearchParams(search || ''); } catch (_) { params = null; }
185
+ var open = params ? params.get('open') : null;
186
+ var h = String(hash || '').replace(/^#/, '');
187
+ if (open && entry(open)) {
188
+ var e = entry(open);
189
+ return { id: open, anchor: h || e.anchor || null };
190
+ }
191
+ if (h && entry(h)) return { id: h, anchor: entry(h).anchor || null };
192
+ return null;
193
+ }
194
+
195
+ // The frame's address for an embedded entry: same-host path + ?embed=1 + #anchor.
196
+ // `base` is '' on the builders host, '/builders' on the apex.
197
+ function frameSrc(e, anchor, base) {
198
+ if (!e || !e.embed) return null;
199
+ var a = anchor || e.anchor || '';
200
+ return (base || '') + e.embed + '?embed=1' + (a ? '#' + encodeURIComponent(a) : '');
201
+ }
202
+
203
+ // Where closing the menu goes: ?from= when it is a same-site path, else null
204
+ // (the caller then steps back, or goes home).
205
+ function closeTo(search) {
206
+ var from = null;
207
+ try { from = new URLSearchParams(search || '').get('from'); } catch (_) { from = null; }
208
+ if (!from || from.charAt(0) !== '/' || from.charAt(1) === '/' || from.charAt(1) === '\\') return null;
209
+ return from;
210
+ }
211
+
212
+ var pure = { MENU: MENU, RETIRED: RETIRED, entries: entries, entry: entry, gateAllows: gateAllows,
213
+ visibleMenu: visibleMenu, search: search, target: target, frameSrc: frameSrc, closeTo: closeTo };
214
+ if (typeof module !== 'undefined' && module.exports) { module.exports = pure; return; }
215
+ if (typeof document === 'undefined') { root.OTBOptionsMenu = pure; return; }
216
+
217
+ // ---------- the page half ----------
218
+
219
+ // The hall's one escaper (dom-utils.js, loaded first on every page; task 1004876).
220
+ var esc = function (s) { return window.OTB.escapeHtml(s); };
221
+ var base = /^\/builders(\/|$)/.test(window.location.pathname) ? '/builders' : '';
222
+ var state = { menu: [], active: null, onNative: null, query: '', from: null };
223
+ var railEl = null;
224
+ var groupsEl = null;
225
+
226
+ // An embedded entry's section: created the first time it opens, so a screen's
227
+ // reads are paid only when someone asks for it.
228
+ function frameFor(e, anchor) {
229
+ var id = 'opt-frame-' + e.id;
230
+ var sec = document.getElementById(id);
231
+ var src = frameSrc(e, anchor, base);
232
+ if (!sec) {
233
+ sec = document.createElement('section');
234
+ sec.id = id;
235
+ sec.className = 'opt-embed';
236
+ sec.setAttribute('data-group', e.id);
237
+ sec.setAttribute('aria-label', e.label);
238
+ sec.innerHTML = '<iframe class="opt-embed__frame" title="' + esc(e.label) + '"></iframe>';
239
+ groupsEl.appendChild(sec);
240
+ sec.querySelector('iframe').src = src;
241
+ sec.dataset.src = src;
242
+ } else if (anchor && sec.dataset.src !== src) {
243
+ // A new place in a screen already open: move its hash, no reload.
244
+ try { sec.querySelector('iframe').contentWindow.location.hash = anchor; } catch (_) { sec.querySelector('iframe').src = src; }
245
+ sec.dataset.src = src;
246
+ }
247
+ return sec;
248
+ }
249
+
250
+ function activate(id, anchor, opts) {
251
+ var e = entry(id);
252
+ if (!e || !groupsEl) return false;
253
+ if (!state.menu.some(function (g) { return g.items.some(function (it) { return it.id === id; }); })) return false;
254
+ state.active = id;
255
+ groupsEl.dataset.active = id;
256
+ Array.prototype.forEach.call(groupsEl.querySelectorAll('.opt-embed'), function (s) { s.classList.remove('opt-embed--on'); });
257
+ if (e.embed) frameFor(e, anchor).classList.add('opt-embed--on');
258
+ else if (state.onNative) state.onNative(id, anchor);
259
+ markActive();
260
+ if (!opts || opts.url !== false) {
261
+ // The address says what is open, so it can be shared; ?from= stays, so
262
+ // closing still goes back to where the menu was opened.
263
+ var q = [];
264
+ if (e.embed) q.push('open=' + encodeURIComponent(id));
265
+ if (state.from) q.push('from=' + encodeURIComponent(state.from));
266
+ var frag = e.embed ? (anchor || '') : (anchor || id);
267
+ var url = window.location.pathname + (q.length ? '?' + q.join('&') : '') + (frag ? '#' + encodeURIComponent(frag) : '');
268
+ try { history.replaceState(null, '', url); } catch (_) { /* file:// */ }
269
+ }
270
+ return true;
271
+ }
272
+
273
+ function markActive() {
274
+ if (!railEl) return;
275
+ Array.prototype.forEach.call(railEl.querySelectorAll('.opt-link'), function (a) {
276
+ var on = a.dataset.key === state.active && !a.dataset.anchor;
277
+ a.classList.toggle('opt-link--active', on);
278
+ if (on) a.setAttribute('aria-current', 'page'); else a.removeAttribute('aria-current');
279
+ });
280
+ }
281
+
282
+ function linkHtml(id, label, anchor, where) {
283
+ var href = anchor ? '?open=' + encodeURIComponent(id) + '#' + encodeURIComponent(anchor) : '#' + encodeURIComponent(id);
284
+ return '<a class="opt-link" href="' + esc(href) + '" data-key="' + esc(id) + '"' + (anchor ? ' data-anchor="' + esc(anchor) + '"' : '') + '>' +
285
+ '<span class="opt-link__label">' + esc(label) + '</span>' +
286
+ (where ? '<span class="opt-link__where">' + esc(where) + '</span>' : '') + '</a>';
287
+ }
288
+
289
+ function renderList() {
290
+ var list = railEl.querySelector('.opt-list');
291
+ if (!list) return;
292
+ if (state.query) {
293
+ var hits = search(state.query, state.menu);
294
+ list.innerHTML = hits.length
295
+ ? '<p class="opt-head">Matches</p>' + hits.map(function (h) { return linkHtml(h.id, h.label, h.anchor, h.where); }).join('')
296
+ : '<p class="opt-none">Nothing matches “' + esc(state.query) + '”. Try another word.</p>';
297
+ } else {
298
+ list.innerHTML = state.menu.map(function (g) {
299
+ return '<p class="opt-head">' + esc(g.head) + '</p>' + g.items.map(function (it) { return linkHtml(it.id, it.label); }).join('');
300
+ }).join('');
301
+ }
302
+ markActive();
303
+ }
304
+
305
+ // The rail: the search box, then the groups. Typing filters; Enter opens the first match.
306
+ function draw() {
307
+ railEl.classList.add('opt-rail');
308
+ railEl.innerHTML =
309
+ '<label class="opt-search"><span class="sr-only">Search settings</span>' +
310
+ '<input type="search" id="opt-search" placeholder="Search settings" autocomplete="off" spellcheck="false"></label>' +
311
+ '<div class="opt-list"></div>';
312
+ var input = railEl.querySelector('#opt-search');
313
+ input.addEventListener('input', function () { state.query = input.value; renderList(); });
314
+ input.addEventListener('keydown', function (e) {
315
+ if (e.key === 'Enter') {
316
+ var first = railEl.querySelector('.opt-list .opt-link');
317
+ if (first) { e.preventDefault(); first.click(); }
318
+ } else if (e.key === 'Escape' && input.value) {
319
+ // The field's own Escape: clear the search (the press stays the field's).
320
+ e.preventDefault();
321
+ input.value = ''; state.query = ''; renderList();
322
+ }
323
+ });
324
+ railEl.addEventListener('click', function (e) {
325
+ var a = e.target.closest ? e.target.closest('.opt-link') : null;
326
+ if (!a) return;
327
+ e.preventDefault();
328
+ activate(a.dataset.key, a.dataset.anchor || null);
329
+ // On a phone the menu sits above the screen it opened, so bring that screen up —
330
+ // after the opened group's own scroll to the top, which would otherwise win, and
331
+ // clear of the sticky top bar.
332
+ if (window.matchMedia && window.matchMedia('(max-width: 860px)').matches) {
333
+ window.setTimeout(function () {
334
+ var bar = document.getElementById('app-topbar');
335
+ var clear = bar ? bar.getBoundingClientRect().height + 8 : 8;
336
+ window.scrollTo({ top: Math.max(0, groupsEl.getBoundingClientRect().top + window.scrollY - clear) });
337
+ }, 60);
338
+ }
339
+ });
340
+ renderList();
341
+ }
342
+
343
+ // Close the menu (game mode's Escape): back where it was opened from.
344
+ function close() {
345
+ var to = state.from || closeTo(window.location.search);
346
+ if (to) { window.location.assign(to); return; }
347
+ var ref = '';
348
+ try { ref = document.referrer ? new URL(document.referrer).origin : ''; } catch (_) { ref = ''; }
349
+ if (ref === window.location.origin && window.history.length > 1) { window.history.back(); return; }
350
+ window.location.assign(base + '/');
351
+ }
352
+
353
+ // Game mode hands the menu its last Escape (shell-game.js); outside it, the
354
+ // menu is a page like any other and the press is the shell's.
355
+ function escape() {
356
+ var game = window.OTBShellGame;
357
+ if (game && game.on && game.on()) close();
358
+ }
359
+
360
+ // settings.js calls this once it knows the viewer.
361
+ // rail, groups the two mounts
362
+ // viewer { authed, rank }
363
+ // contributed whether a module contributed a Settings panel
364
+ // onNative(id, anchor) shows one of Settings' own groups
365
+ function mount(o) {
366
+ railEl = o.rail; groupsEl = o.groups;
367
+ state.onNative = o.onNative || null;
368
+ var moduleOn = window.OTB && typeof window.OTB.moduleOn === 'function' ? window.OTB.moduleOn : function () { return true; };
369
+ state.menu = visibleMenu(o.viewer, { moduleOn: moduleOn, contributed: !!o.contributed });
370
+ state.from = closeTo(window.location.search);
371
+ if (railEl && groupsEl) draw();
372
+ return state.menu;
373
+ }
374
+ // Re-read the viewer's menu (a module contributed a panel after load).
375
+ function refresh(o) {
376
+ var moduleOn = window.OTB && typeof window.OTB.moduleOn === 'function' ? window.OTB.moduleOn : function () { return true; };
377
+ state.menu = visibleMenu(o.viewer, { moduleOn: moduleOn, contributed: !!o.contributed });
378
+ if (railEl) renderList();
379
+ }
380
+
381
+ // A link on the menu's own panels to an old page (Settings' "these moved to My agents"
382
+ // lines, a docs link) opens that entry in place instead of a round trip through the
383
+ // server's redirect back to this same page.
384
+ if (typeof document !== 'undefined' && document.addEventListener) {
385
+ document.addEventListener('click', function (e) {
386
+ if (e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
387
+ var a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
388
+ if (!a || !groupsEl || !groupsEl.contains(a) || a.getAttribute('target')) return;
389
+ var to;
390
+ try { to = new URL(a.getAttribute('href'), window.location.href); } catch (_) { return; }
391
+ if (to.origin !== window.location.origin) return;
392
+ var id = RETIRED[to.pathname.replace(/^\/builders/, '').replace(/\.html$/, '')];
393
+ if (!id) return;
394
+ var anchor = to.hash ? decodeURIComponent(to.hash.slice(1)) : null;
395
+ if (activate(id, anchor)) e.preventDefault();
396
+ });
397
+ }
398
+
399
+ // Open what the address asks for. true = it was a menu entry; false = the page decides.
400
+ function openFromUrl() {
401
+ var t = target(window.location.search, window.location.hash);
402
+ if (!t) return false;
403
+ return activate(t.id, t.anchor, { url: false });
404
+ }
405
+
406
+ root.OTBOptionsMenu = pure;
407
+ root.OTBOptions = { mount: mount, refresh: refresh, activate: activate, openFromUrl: openFromUrl,
408
+ escape: escape, close: close, active: function () { return state.active; } };
409
+ }(typeof window !== 'undefined' ? window : globalThis));
@@ -161,8 +161,8 @@
161
161
  '<span class="pb__now"><span class="pb__tag">Now</span> ' + esc(c.now) + '</span>' +
162
162
  '</li>';
163
163
  }).join('') + '</ol>' +
164
- '<p class="pb__hint">Sending submits the page: an engineer applies the new words, and they come back to you to approve before anyone sees them.' +
165
- (rows.some(function (c) { return c.remove; }) ? ' A line you remove goes to an engineer to take out.' : '') + '</p>' +
164
+ '<p class="pb__hint">Sending submits the page: an artisan applies the new words, and they come back to you to approve before anyone sees them.' +
165
+ (rows.some(function (c) { return c.remove; }) ? ' A line you remove goes to an artisan to take out.' : '') + '</p>' +
166
166
  '<p class="pb__error" role="alert">' + esc(err || '') + '</p>' +
167
167
  '<div class="pb__actions">' +
168
168
  '<button class="pb__btn" type="button" data-pb-back>Keep editing</button>' +
@@ -172,7 +172,7 @@
172
172
 
173
173
  // After sending: the words are on their way, and the look is one press away.
174
174
  function sentHtml(n) {
175
- return '<p class="pb__line" role="status">Sent for approval: ' + esc(plural(n, 'change', 'changes')) + '. An engineer applies the new words, and they come back to you in the studio to approve.</p>' +
175
+ return '<p class="pb__line" role="status">Sent for approval: ' + esc(plural(n, 'change', 'changes')) + '. An artisan applies the new words, and they come back to you in the studio to approve.</p>' +
176
176
  '<p class="pb__line"><strong>Review how this page looks too?</strong> Write what you would change about it, and each point becomes an idea under your name.</p>' +
177
177
  '<p class="pb__error" role="alert">' + esc(st.notesError || '') + '</p>' +
178
178
  '<div class="pb__actions">' +