@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
@@ -1,7 +1,8 @@
1
1
  // craft-mode.js — the hall's craft mode (task 1004492, goal 1000095 WA6).
2
2
  //
3
3
  // On entering the hall (once per browser session) a picker asks "What are you
4
- // doing now?": Building, Artistry, Ideating or Governing. The
4
+ // doing now?": Artisan, Artist or Patron (task 1005141; old saved modes are
5
+ // read as today's — LEGACY_MODES). The
5
6
  // answer is ONE attribute, html[data-craft="<mode>"], that every hall page can
6
7
  // read, and a switch in the top bar changes it at any time without a reload.
7
8
  // Each change also fires `otb:craft` on document ({ detail: { mode } }).
@@ -28,10 +29,10 @@
28
29
  'use strict';
29
30
 
30
31
  var ICONS = {
31
- building: '<path d="M2 13.5h12M3.5 13.5V8.5h4v5M7.5 13.5V4.5h5v9"/>',
32
- artistry: '<path d="M8 2a6 6 0 1 0 0 12c.9 0 1.4-.6 1.4-1.3 0-.8-.6-1-.6-1.7 0-.6.5-1 1.2-1h1.6A2.4 2.4 0 0 0 14 7.6C14 4.5 11.3 2 8 2Z"/><path d="M5 6.2h.01M7.6 4.6h.01M10.4 5.2h.01M4.6 9h.01" stroke-width="2.2"/>',
32
+ artisan: '<path d="M2 13.5h12M3.5 13.5V8.5h4v5M7.5 13.5V4.5h5v9"/>',
33
+ artist: '<path d="M8 2a6 6 0 1 0 0 12c.9 0 1.4-.6 1.4-1.3 0-.8-.6-1-.6-1.7 0-.6.5-1 1.2-1h1.6A2.4 2.4 0 0 0 14 7.6C14 4.5 11.3 2 8 2Z"/><path d="M5 6.2h.01M7.6 4.6h.01M10.4 5.2h.01M4.6 9h.01" stroke-width="2.2"/>',
33
34
  ideating: '<path d="M6 12.5h4M6.6 14.5h2.8M8 1.8a4.2 4.2 0 0 0-2.6 7.5c.6.5 1 1.2 1 2h3.2c0-.8.4-1.5 1-2A4.2 4.2 0 0 0 8 1.8Z"/>',
34
- governing: '<path d="M2 6 8 2.5 14 6M3.5 6.5v6M6.5 6.5v6M9.5 6.5v6M12.5 6.5v6M2 13.5h12"/>',
35
+ patron: '<path d="M2 6 8 2.5 14 6M3.5 6.5v6M6.5 6.5v6M9.5 6.5v6M12.5 6.5v6M2 13.5h12"/>',
35
36
  brush: '<path d="M13.6 2.4 7.4 8.6"/><path d="M7.4 8.6c-1.3-.7-3-.1-3.4 1.5-.3 1.3-.8 2.2-1.9 2.9 2.4.7 4.6.1 5.6-1.4.6-.9.5-2.1-.3-3Z"/>',
36
37
  note: '<path d="M9.5 2.5h-6v11h9v-6"/><path d="M5.5 8h5M5.5 10.5h3.5"/><path d="M12.5 1.5v4M10.5 3.5h4"/>',
37
38
  modes: '<rect x="2.5" y="2.5" width="4.5" height="4.5" rx="1"/><rect x="9" y="2.5" width="4.5" height="4.5" rx="1"/><rect x="2.5" y="9" width="4.5" height="4.5" rx="1"/><rect x="9" y="9" width="4.5" height="4.5" rx="1"/>',
@@ -39,11 +40,15 @@
39
40
  // The four crafts. `short` is the switch's label; `line` is the
40
41
  // picker's one sentence — what you do in the mode, never a feature not built yet.
41
42
  var MODES = [
42
- { id: 'building', label: 'Building', short: 'Building', line: 'Claim tasks and ship code.' },
43
- { id: 'artistry', label: 'Artistry', short: 'Artistry', line: 'Review how pages look and read.' },
44
- { id: 'ideating', label: 'Ideating', short: 'Ideating', line: 'Turn thoughts into ideas.' },
45
- { id: 'governing', label: 'Governing', short: 'Governing', line: 'Decide, approve and keep it healthy.' },
43
+ { id: 'artisan', label: 'Artisan', short: 'Artisan', line: 'Claim tasks and ship code.' },
44
+ { id: 'artist', label: 'Artist', short: 'Artist', line: 'Shape how pages look and read, and turn thoughts into ideas.' },
45
+ { id: 'patron', label: 'Patron', short: 'Patron', line: 'Watch, propose, and with a seat decide.' },
46
46
  ];
47
+ // The game's three characters (task 1005141, BONGOS-V3): Building became Artisan,
48
+ // Artistry and Ideating became Artist, Governing became Patron. A mode stored
49
+ // under its old id is read as the new one (LEGACY_MODES in valid()), so nobody
50
+ // loses their choice to the rename.
51
+ var LEGACY_MODES = { building: 'artisan', artistry: 'artist', ideating: 'artist', governing: 'patron' };
47
52
  var IDS = MODES.map(function (m) { return m.id; });
48
53
 
49
54
  // ---------- GOVERNING IS A SEAT (task 1004685, the living-city prototype) ----------
@@ -55,15 +60,21 @@
55
60
  // so a hall that cannot read the seats is unchanged. Cosmetic, like the mode itself.
56
61
  var seat;
57
62
  function seatName() { return typeof seat === 'string' ? seat : null; }
58
- function offered(mode) { return mode !== 'governing' || seat !== null; }
59
- function labelOf(m) { return m.id === 'governing' && seatName() ? seatName() : m.label; }
60
- function lineOf(m) { return m.id === 'governing' && seatName() ? 'Your seat. ' + m.line : m.line; }
63
+ // A builder who holds a character (task 1005147) is offered that one alone: the
64
+ // switch, the session picker and the welcome's role choices all read offered(), so
65
+ // none of them can wear another. Changing character is signing out (CHARACTER below).
66
+ function offered(mode) {
67
+ if (typeof account === 'string' && mode !== account) return false;
68
+ return mode !== 'patron' || seat !== null;
69
+ }
70
+ function labelOf(m) { return m.id === 'patron' && seatName() ? seatName() : m.label; }
71
+ function lineOf(m) { return m.id === 'patron' && seatName() ? 'Your seat. ' + m.line : m.line; }
61
72
  function paintSeat() {
62
73
  var q = document.querySelector ? function (s) { return document.querySelector(s); } : function () { return null; };
63
- var opt = q('#craft-switch [data-craft-opt="governing"]');
64
- var gov = MODES[IDS.indexOf('governing')];
74
+ var opt = q('#craft-switch [data-craft-opt="patron"]');
75
+ var gov = MODES[IDS.indexOf('patron')];
65
76
  if (opt) {
66
- if (offered('governing')) opt.removeAttribute('hidden'); else opt.setAttribute('hidden', '');
77
+ if (offered('patron')) opt.removeAttribute('hidden'); else opt.setAttribute('hidden', '');
67
78
  opt.setAttribute('title', labelOf(gov) + ' — ' + lineOf(gov));
68
79
  var lab = opt.querySelector && opt.querySelector('.craft-switch__label');
69
80
  if (lab) lab.textContent = labelOf(gov);
@@ -73,7 +84,7 @@
73
84
  var keep = picker.querySelector('[data-craft-again]');
74
85
  var again = !keep || keep.checked;
75
86
  var inside = picker.contains && picker.contains(document.activeElement);
76
- picker.innerHTML = pickerHtml(read());
87
+ picker.innerHTML = creating ? createHtml(creating) : pickerHtml(read());
77
88
  var box = picker.querySelector('[data-craft-again]');
78
89
  if (box) box.checked = again;
79
90
  var pre = inside && (picker.querySelector('.craft-door--pre') || picker.querySelector('[data-craft-close]'));
@@ -81,11 +92,11 @@
81
92
  }
82
93
  }
83
94
  function setPositions(p) {
84
- seat = !p || p.known !== true ? undefined : (p.has_position ? (p.title || 'Governing') : null);
85
- if (seat === null && stored() === 'governing') {
95
+ seat = !p || p.known !== true ? undefined : (p.has_position ? (p.title || 'Patron') : null);
96
+ if (seat === null && stored() === 'patron') {
86
97
  drop('sessionStorage', MODE_KEY);
87
- if (get('localStorage', KEEP_KEY) === 'governing') drop('localStorage', KEEP_KEY);
88
- if (document.documentElement && document.documentElement.dataset.craft === 'governing') apply(null);
98
+ if (get('localStorage', KEEP_KEY) === 'patron') drop('localStorage', KEEP_KEY);
99
+ if (document.documentElement && document.documentElement.dataset.craft === 'patron') apply(null);
89
100
  }
90
101
  paintSeat();
91
102
  }
@@ -101,25 +112,25 @@
101
112
  // every craft has agents, and board 6 pairs it with the city in every mode.
102
113
  // task 1005071 (owner, 2026-11-04): Help (the old Docs group) joins ALWAYS — the
103
114
  // Primer is the newcomer's way in, and only Building could see it.
104
- var ALWAYS_TABS = ['roster', 'collab', 'profile', 'my-agents',
105
- 'primer', 'commands', 'atlas', 'modules', 'apidocs'];
115
+ // task 1005144: My agents and Modules are options menu entries now, not tabs.
116
+ var ALWAYS_TABS = ['roster', 'collab', 'profile',
117
+ 'primer', 'commands', 'atlas', 'apidocs'];
106
118
  // No mode lists 'home': the craft plate under the brand is the Home button in
107
119
  // every mode (task 1005065), so a Home tab beside it is a second home icon
108
120
  // (task 1005070). With no mode the plate is absent and the Home tab shows.
109
121
  var MODE_TABS = {
110
- building: ['goals', 'goal-map', 'work', 'blockers', 'thinking', 'studio',
122
+ artisan: ['goals', 'goal-map', 'work', 'blockers', 'thinking', 'studio',
111
123
  'market-watch'],
112
- artistry: ['studio', 'goals', 'thinking'],
113
- ideating: ['goal-map', 'thinking', 'goals', 'market-watch'],
114
- governing: ['city', 'blockers', 'board-room',
115
- 'watch', 'project-settings', 'money', 'deploy', 'government'],
124
+ artist: ['studio', 'goals', 'thinking', 'goal-map', 'market-watch'],
125
+ patron: ['city', 'blockers', 'board-room',
126
+ 'watch', 'money', 'deploy'],
116
127
  };
117
128
  // A mode can lead with ONE tab and fold the rest of its own under a dropdown
118
129
  // (owner, 2026-10-08: the city is Governing's tab, everything else under it).
119
130
  // The folded items are MOVED into the dropdown, never copied, so the ids, the
120
131
  // gate attributes and the active mark the shell set all travel with them; a
121
132
  // placeholder keeps each one's place so leaving the mode puts it back exactly.
122
- var MODE_LEAD = { governing: { lead: 'city', label: 'More governing' } };
133
+ var MODE_LEAD = { patron: { lead: 'city', label: 'More' } };
123
134
  var FOLD_ID = 'craft-fold';
124
135
  // The tab ids a mode shows, or null for every tab.
125
136
  function tabsFor(mode) {
@@ -224,7 +235,7 @@
224
235
  // in both crafts' lists) and a page no craft owns (a task, an idea) stay put; so
225
236
  // does Home, which redraws itself in place. The street has no nav item, so it is
226
237
  // named here as Governing's. Tests swap the hop for a recorder (OTBCraftHop).
227
- var OWN_PAGES = { governing: ['street'] };
238
+ var OWN_PAGES = { patron: ['street'] };
228
239
  function pageKeys() {
229
240
  var keys = [];
230
241
  var nav = document.getElementById('app-sidebar');
@@ -254,7 +265,7 @@
254
265
  return false;
255
266
  }
256
267
  function homeHref(mode) {
257
- var street = mode === 'governing' && window.GovCity && window.GovCity.STREET_HREF;
268
+ var street = mode === 'patron' && window.GovCity && window.GovCity.STREET_HREF;
258
269
  return street || '/';
259
270
  }
260
271
  function goHome(mode) {
@@ -269,11 +280,11 @@
269
280
  // draw — the mode's words beside the page title, and its door's scene framed
270
281
  // under the brand (artHtml, the picker's own art, so the two never drift).
271
282
  // Building and no mode have no entry here and wear nothing.
272
- var LOOKS = { artistry: 'the studio · night', ideating: 'the sky', governing: 'the city' };
283
+ var LOOKS = { artist: 'the studio · night', patron: 'the city' };
273
284
  // The plate is the Home button in every mode (owner, 2026-11-04, task 1005065): the
274
285
  // mode's scene with a home mark over it. Building wears no look, so its plate shows
275
286
  // the city, the scene its Home is.
276
- var PLATE_ART = { building: 'governing', artistry: 'artistry', ideating: 'ideating', governing: 'governing' };
287
+ var PLATE_ART = { artisan: 'patron', artist: 'artist', patron: 'patron' };
277
288
  var HOME_MARK = '<span class="craft-plate__home" aria-hidden="true"><svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round">' +
278
289
  '<path d="M3 8.5 9 3l6 5.5V15a1 1 0 0 1-1 1h-3.5v-4h-3v4H4a1 1 0 0 1-1-1V8.5Z"/></svg></span>';
279
290
  function remove(el) { if (el && el.parentNode) el.parentNode.removeChild(el); }
@@ -373,7 +384,7 @@
373
384
  }
374
385
  function applyTallies(mode) {
375
386
  var box = document.getElementById('craft-tallies');
376
- if (mode !== 'governing' || !tallyPage()) { remove(box); return; }
387
+ if (mode !== 'patron' || !tallyPage()) { remove(box); return; }
377
388
  var mark = document.getElementById('craft-eyebrow');
378
389
  if (!box && mark && mark.parentNode) {
379
390
  box = document.createElement('span');
@@ -385,7 +396,7 @@
385
396
  if (!box) return;
386
397
  askTallies().then(function (t) {
387
398
  var live = document.getElementById('craft-tallies');
388
- if (!live || document.documentElement.dataset.craft !== 'governing') return;
399
+ if (!live || document.documentElement.dataset.craft !== 'patron') return;
389
400
  var html = tallyHtml(t);
390
401
  live.innerHTML = html;
391
402
  if (html) live.removeAttribute('hidden'); else live.setAttribute('hidden', '');
@@ -401,17 +412,16 @@
401
412
  // (same ?v as this file) and calls its open(). Cosmetic, like the mode: the
402
413
  // routes behind a tool hold its walls (ADR 0016).
403
414
  var TOOLS = {
404
- artistry: [
415
+ artist: [
405
416
  // Named "Artist review" since task 1004499: the public site's button
406
417
  // (public-landing assets/artist-review.js) opens this same panel.
407
418
  // `docked` (task 1004771): no top-bar button. The panel itself sits on
408
419
  // the right edge behind a small tab, and carries the review-mode choice.
409
- { id: 'palette', icon: 'artistry', short: 'Review', label: 'Artist review: write feedback on this page', script: 'page-notes.js', api: 'OTBPageNotes', docked: true },
420
+ { id: 'palette', icon: 'artist', short: 'Review', label: 'Artist review: write feedback on this page', script: 'page-notes.js', api: 'OTBPageNotes', docked: true },
410
421
  // Task 1004497: this page's words, edited where they stand as a proposed wording change.
411
422
  { id: 'paintbrush', icon: 'brush', short: 'Edit words', label: 'Edit this page\'s words', script: 'paintbrush.js', api: 'OTBPaintbrush' },
412
- ],
413
- // Task 1004498: the ideator's notes about this page, split into ideas (lightbulb.js).
414
- ideating: [
423
+ // Task 1004498: notes about this page, split into ideas (lightbulb.js) — the
424
+ // ideator's tool, the artist's since the two crafts folded (task 1005141).
415
425
  { id: 'lightbulb', icon: 'note', short: 'Ideas', label: 'Write ideas about this page', script: 'lightbulb.js', api: 'OTBLightbulb' },
416
426
  ],
417
427
  };
@@ -441,8 +451,8 @@
441
451
  // artist keeps Artistry's tools and gains that experience's own after them.
442
452
  function toolsFor(mode) {
443
453
  var own = Object.prototype.hasOwnProperty.call(TOOLS, mode) ? TOOLS[mode] : [];
444
- if (!review || mode === 'artistry') return own;
445
- return TOOLS.artistry.concat(own);
454
+ if (!review || mode === 'artist') return own;
455
+ return TOOLS.artist.concat(own);
446
456
  }
447
457
  function openTool(t, b) {
448
458
  loadTool(t).then(function (api) { api.open(); }, function () { b.setAttribute('aria-disabled', 'true'); });
@@ -493,7 +503,7 @@
493
503
  // 360px the bar has no room for three, so craft-review.css folds it away.
494
504
  own.forEach(function (t) {
495
505
  var el = document.getElementById('craft-tool-' + t.id);
496
- if (el && el.classList) el.classList.toggle('craft-tool--guest', !!review && TOOLS.artistry.indexOf(t) === -1);
506
+ if (el && el.classList) el.classList.toggle('craft-tool--guest', !!review && TOOLS.artist.indexOf(t) === -1);
497
507
  });
498
508
  }
499
509
 
@@ -509,23 +519,22 @@
509
519
  // picker ends it. Cosmetic like the mode: a tab the gate hides stays hidden.
510
520
  var REVIEW_KEY = 'hall-craft-review';
511
521
  var EXPERIENCES = [
512
- { id: 'ideating', label: 'Ideator experience' },
513
- { id: 'governing', label: 'Governing experience' },
514
- { id: 'building', label: 'Building experience' },
515
- { id: 'artistry', label: 'Artistry experience' },
522
+ { id: 'patron', label: 'Patron experience' },
523
+ { id: 'artisan', label: 'Artisan experience' },
524
+ { id: 'artist', label: 'Artist experience' },
516
525
  ];
517
526
  // The experience being reviewed, or null. Read from the session once, then
518
527
  // held here, so a browser whose storage throws still reviews this page.
519
528
  var review = null;
520
529
  function storedReview() {
521
- return read() === 'artistry' ? valid(get('sessionStorage', REVIEW_KEY)) : null;
530
+ return read() === 'artist' ? valid(get('sessionStorage', REVIEW_KEY)) : null;
522
531
  }
523
532
  // The mode the hall wears: the experience under review, else the chosen mode.
524
533
  function wearing() { return review || read(); }
525
534
  function startReview(mode) {
526
535
  mode = valid(mode);
527
536
  if (!mode) return;
528
- set('sessionStorage', MODE_KEY, 'artistry');
537
+ set('sessionStorage', MODE_KEY, 'artist');
529
538
  set('sessionStorage', ASKED_KEY, '1');
530
539
  set('sessionStorage', REVIEW_KEY, mode);
531
540
  review = mode;
@@ -534,7 +543,7 @@
534
543
  function stopReview() {
535
544
  drop('sessionStorage', REVIEW_KEY);
536
545
  review = null;
537
- apply('artistry');
546
+ apply('artist');
538
547
  }
539
548
 
540
549
  // The scribble wall: the top bar's empty space, doodled on while reviewing.
@@ -576,7 +585,10 @@
576
585
  function icon(id, cls) {
577
586
  return '<svg class="' + cls + '" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + ICONS[id] + '</svg>';
578
587
  }
579
- function valid(v) { return IDS.indexOf(v) !== -1 ? v : null; }
588
+ function valid(v) {
589
+ if (v && Object.prototype.hasOwnProperty.call(LEGACY_MODES, v)) v = LEGACY_MODES[v];
590
+ return IDS.indexOf(v) !== -1 ? v : null;
591
+ }
580
592
  function get(store, key) { try { return window[store].getItem(key); } catch (_) { return null; } }
581
593
  function set(store, key, v) { try { window[store].setItem(key, v); } catch (_) { /* private mode */ } }
582
594
  function drop(store, key) { try { window[store].removeItem(key); } catch (_) { /* private mode */ } }
@@ -590,6 +602,7 @@
590
602
  // earlier session. null = no mode, which is the hall as it always was. A stored
591
603
  // Governing reads as no mode for a builder known to hold no seat (task 1004685).
592
604
  function stored() {
605
+ if (typeof account === 'string') return account;
593
606
  return valid(get('sessionStorage', MODE_KEY)) || valid(get('localStorage', KEEP_KEY));
594
607
  }
595
608
  function read() {
@@ -640,13 +653,15 @@
640
653
  function switchHtml() {
641
654
  if (pageOptsOut()) return '';
642
655
  review = storedReview();
656
+ var held = heldCharacter();
657
+ if (held) return '<div class="craft-switch craft-switch--character" id="craft-switch" role="group" aria-label="Your character">' + chipHtml(held) + '</div>';
643
658
  var current = review || read();
644
659
  return '<div class="craft-switch' + (current ? '' : ' craft-switch--unset') + '" id="craft-switch" role="group" aria-label="Mode">' +
645
660
  MODES.map(function (m) {
646
661
  return '<button class="craft-switch__opt craft-switch__opt--' + m.id + '" type="button" data-craft-opt="' + m.id + '"' +
647
662
  ' aria-pressed="' + (m.id === current ? 'true' : 'false') + '" title="' + esc(labelOf(m) + ' — ' + lineOf(m)) + '"' +
648
663
  (offered(m.id) ? '' : ' hidden') + '>' +
649
- icon(m.id, 'craft-switch__icon') + '<span class="craft-switch__label">' + esc(m.id === 'governing' && seatName() ? seatName() : m.short) + '</span></button>';
664
+ icon(m.id, 'craft-switch__icon') + '<span class="craft-switch__label">' + esc(m.id === 'patron' && seatName() ? seatName() : m.short) + '</span></button>';
650
665
  }).join('') +
651
666
  // At phone width the options fold away and this one button opens the picker.
652
667
  '<button class="craft-switch__open" type="button" data-craft-open aria-haspopup="dialog" aria-label="Choose your mode" title="Choose your mode">' +
@@ -659,6 +674,7 @@
659
674
  function onSwitchClick(e) {
660
675
  var btn = e.target.closest && e.target.closest('button');
661
676
  if (!btn) return;
677
+ if (btn.hasAttribute('data-craft-me')) { changeCharacter(); return; }
662
678
  if (btn.hasAttribute('data-craft-open')) { openPicker(); return; }
663
679
  var mode = btn.getAttribute('data-craft-opt');
664
680
  // At phone width only the current mode shows; tapping it opens the full picker.
@@ -698,24 +714,17 @@
698
714
  }
699
715
  function artHtml(id) {
700
716
  var html = '', rnd, i;
701
- if (id === 'building') {
717
+ if (id === 'artisan') {
702
718
  html = '<span class="craft-art__bar" style="left:20px;top:22px;width:110px"></span>' +
703
719
  '<span class="craft-art__bar craft-art__bar--dim" style="left:20px;top:42px;width:150px"></span>' +
704
720
  '<span class="craft-art__pill" style="left:20px;top:68px"></span><span class="craft-art__card"></span>';
705
- } else if (id === 'artistry') {
721
+ } else if (id === 'artist') {
706
722
  rnd = seeded(7);
707
723
  for (i = 0; i < 14; i++) html += dot(Math.round(rnd() * 250), Math.round(rnd() * 66), rnd() > 0.8 ? 2 : 1, 'opacity:' + (0.4 + rnd() * 0.6).toFixed(2), 'star');
708
724
  html += '<span class="craft-art__moon"></span><span class="craft-art__ground"></span>' +
709
725
  '<span class="craft-art__house" style="left:24px;height:54px;width:26px"></span><span class="craft-art__house" style="left:60px;height:44px;width:34px"></span>' +
710
726
  dot(32, 66, 3, '', 'lit') + dot(70, 72, 3, '', 'lit');
711
- } else if (id === 'ideating') {
712
- rnd = seeded(11);
713
- html = '<span class="craft-art__ring"></span>';
714
- for (i = 0; i < 18; i++) {
715
- var proto = rnd() > 0.7, r = proto ? 6 : (rnd() > 0.75 ? 5 : 2);
716
- html += dot(Math.round(rnd() * 260), Math.round(6 + rnd() * 90), r, '', proto ? 'proto' : (r > 3 ? 'glow' : 'gold'));
717
- }
718
- } else if (id === 'governing') {
727
+ } else if (id === 'patron') {
719
728
  html = '<svg class="craft-art__city" viewBox="0 0 300 110" aria-hidden="true">' + cityPaths() + '</svg>';
720
729
  }
721
730
  return '<span class="craft-art craft-art--' + id + '" aria-hidden="true">' + html + '</span>';
@@ -737,7 +746,7 @@
737
746
  '<div class="craft-picker__doors" data-doors="' + shown.length + '">' + doors + '</div>' +
738
747
  '<div class="craft-picker__foot">' +
739
748
  '<label class="craft-picker__again"><input type="checkbox" data-craft-again checked> Ask me again next session</label>' +
740
- (preselect === 'artistry' ? '<span class="craft-picker__hint">Artists start on Artistry.</span>' : '') +
749
+ (preselect === 'artist' ? '<span class="craft-picker__hint">Artists start here.</span>' : '') +
741
750
  '</div>' +
742
751
  '</div>';
743
752
  }
@@ -746,7 +755,8 @@
746
755
  }
747
756
  function onPickerKey(e) {
748
757
  if (!picker) return;
749
- if (e.key === 'Escape') { e.preventDefault(); closePicker(); return; }
758
+ // Character creation is a choice to make, not a box to dismiss: Escape stays put.
759
+ if (e.key === 'Escape') { e.preventDefault(); if (!creating) closePicker(); return; }
750
760
  if (e.key !== 'Tab') return;
751
761
  var f = focusables();
752
762
  if (!f.length) return;
@@ -755,6 +765,11 @@
755
765
  else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
756
766
  }
757
767
  function onPickerClick(e) {
768
+ if (creating) {
769
+ var c = e.target.closest && e.target.closest('button[data-char-pick]');
770
+ if (c) pickCharacter(c.getAttribute('data-char-pick'));
771
+ return;
772
+ }
758
773
  if (e.target === picker) { closePicker(); return; }
759
774
  var btn = e.target.closest && e.target.closest('button');
760
775
  if (!btn) return;
@@ -787,6 +802,7 @@
787
802
  document.removeEventListener('keydown', onPickerKey, true);
788
803
  if (picker.parentNode) picker.parentNode.removeChild(picker);
789
804
  picker = null;
805
+ creating = false;
790
806
  document.documentElement.classList.remove('craft-picker-open');
791
807
  if (returnFocus && returnFocus.focus) { try { returnFocus.focus(); } catch (_) { /* gone */ } }
792
808
  returnFocus = null;
@@ -797,7 +813,173 @@
797
813
  if (!builder) return null;
798
814
  var main = builder.main_discipline;
799
815
  var crafts = Array.isArray(builder.preferred_disciplines) ? builder.preferred_disciplines : [];
800
- return (main === 'artist' || (!main && crafts[0] === 'artist')) ? 'artistry' : null;
816
+ return (main === 'artist' || (!main && crafts[0] === 'artist')) ? 'artist' : null;
817
+ }
818
+
819
+ // ---------- character creation (task 1005147, BONGOS-V3 G2) ----------
820
+ // A builder picks one of three characters when they join, and the pick is kept on
821
+ // their ACCOUNT (PUT /me/character, core_297), so it follows them to every device.
822
+ // /me says which one they hold; null means not picked yet, and the hall then shows
823
+ // character creation before anything else (the welcome and the session picker wait).
824
+ // Holding a character replaces the per-session mode: the top-bar switch becomes the
825
+ // character's sprite, and offered() names that character alone. Switching is signing
826
+ // out and back in (the spec's settled point 2): the sprite's "Change character"
827
+ // clears the pick and signs out, and the next sign-in finds none and asks again.
828
+ // Identity and view only — rank still gates every verb on the server (ADR 0016).
829
+ //
830
+ // The sprites are the owner's art (Builder → Artisan, Artist → Artist, Ruler →
831
+ // Patron; the Governor is the spare), one file per character under characters/.
832
+ var account; // undefined: not told; null: none picked; else the character
833
+ var creating = false; // the preselected door while character creation is open
834
+ var CHAR_KEY = 'hall-character'; // localStorage: the last held character, so the first paint is right
835
+ var CHARACTER_LINES = {
836
+ artisan: 'Claim work and build it. Your record is the leaderboard.',
837
+ artist: 'Shape how things look and read, and turn thoughts into ideas. Your work hangs in the museum.',
838
+ patron: 'Watch the project, propose, and with a seat decide.',
839
+ };
840
+ // Only ever one of the three names reaches the URL (valid() is the allowlist).
841
+ function spriteSrc(id) { return '/builders/characters/' + (valid(id) || 'artisan') + '.png' + (SCRIPT_V ? '?v=' + SCRIPT_V : ''); }
842
+ function spriteHtml(id, cls) {
843
+ return '<img class="craft-sprite ' + cls + '" src="' + spriteSrc(id) + '" alt="" width="250" height="290" decoding="async">';
844
+ }
845
+ function heldCharacter() {
846
+ if (typeof account === 'string') return account;
847
+ if (account === null) return null;
848
+ return valid(get('localStorage', CHAR_KEY));
849
+ }
850
+ function modeOf(id) { return MODES[IDS.indexOf(id)]; }
851
+ function chipHtml(id) {
852
+ var m = modeOf(id);
853
+ var name = m.label; // a character's name, never the seat's title
854
+ return '<button class="craft-switch__me craft-switch__opt--' + id + '" type="button" data-craft-me aria-haspopup="dialog"' +
855
+ ' title="' + esc('You are the ' + name + '. Change character') + '">' +
856
+ spriteHtml(id, 'craft-sprite--chip') + '<span class="craft-switch__label">' + esc(name) + '</span></button>';
857
+ }
858
+ function paintCharacter() {
859
+ var sw = document.getElementById('craft-switch');
860
+ if (!sw) return;
861
+ var held = heldCharacter();
862
+ if (held) {
863
+ sw.className = 'craft-switch craft-switch--character';
864
+ sw.setAttribute('aria-label', 'Your character');
865
+ sw.innerHTML = chipHtml(held);
866
+ } else if (sw.classList && sw.classList.contains('craft-switch--character')) {
867
+ // A character dropped (signed out mid-change): back to the plain switch.
868
+ var tmp = document.createElement('div');
869
+ tmp.innerHTML = switchHtml();
870
+ var fresh = tmp.firstChild;
871
+ if (fresh && sw.parentNode) { sw.parentNode.replaceChild(fresh, sw); fresh.addEventListener('click', onSwitchClick); }
872
+ }
873
+ }
874
+ // What /me said. Called by characterGate; also exported for a page that re-reads /me.
875
+ function setCharacter(c) {
876
+ account = c === null ? null : (valid(c) || undefined);
877
+ if (typeof account === 'string') set('localStorage', CHAR_KEY, account);
878
+ else if (account === null) drop('localStorage', CHAR_KEY);
879
+ paintCharacter();
880
+ if (typeof account === 'string' && offered(account)) {
881
+ if (document.documentElement.dataset.craft !== account && !review) apply(account);
882
+ }
883
+ }
884
+ function createHtml(preselect) {
885
+ var shown = MODES.filter(function (m) { return offered(m.id); });
886
+ var doors = shown.map(function (m) {
887
+ return '<button class="craft-door craft-door--character craft-door--' + m.id + (m.id === preselect ? ' craft-door--pre' : '') + '" type="button" data-char-pick="' + m.id + '">' +
888
+ '<span class="craft-plinth" aria-hidden="true">' + spriteHtml(m.id, 'craft-sprite--door') + '</span>' +
889
+ '<span class="craft-door__head"><span class="craft-door__name">' + esc(m.label) + '</span></span>' +
890
+ '<span class="craft-door__line">' + esc(CHARACTER_LINES[m.id] || m.line) + '</span></button>';
891
+ }).join('');
892
+ return '<div class="craft-picker__panel craft-picker__panel--create" role="dialog" aria-modal="true" aria-labelledby="craft-picker-title" aria-describedby="craft-picker-lede">' +
893
+ '<p class="craft-picker__eyebrow">Character creation</p>' +
894
+ '<h2 class="craft-picker__title" id="craft-picker-title">Choose your character</h2>' +
895
+ '<p class="craft-picker__lede" id="craft-picker-lede">This is who you are in the project. To change it later, sign out and pick again.</p>' +
896
+ '<div class="craft-picker__doors" data-doors="' + shown.length + '">' + doors + '</div>' +
897
+ '<p class="craft-picker__error" data-char-error role="alert" hidden></p>' +
898
+ '</div>';
899
+ }
900
+ function openCreate(preselect) {
901
+ if (picker || pageOptsOut() || !document.body) return;
902
+ creating = valid(preselect) || 'artisan';
903
+ returnFocus = document.activeElement;
904
+ picker = document.createElement('div');
905
+ picker.className = 'craft-picker craft-picker--create';
906
+ picker.id = 'craft-picker';
907
+ picker.innerHTML = createHtml(creating);
908
+ picker.addEventListener('click', onPickerClick);
909
+ document.addEventListener('keydown', onPickerKey, true);
910
+ document.body.appendChild(picker);
911
+ document.documentElement.classList.add('craft-picker-open');
912
+ var pre = picker.querySelector('.craft-door--pre') || picker.querySelector('[data-char-pick]');
913
+ if (pre && pre.focus) pre.focus();
914
+ }
915
+ function client() {
916
+ var C = window.BongosClient;
917
+ return C && C.createClient ? C.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false }) : null;
918
+ }
919
+ var saving = false;
920
+ function pickCharacter(mode) {
921
+ mode = valid(mode);
922
+ if (!mode || !offered(mode) || saving) return;
923
+ var api = client();
924
+ var err = picker && picker.querySelector('[data-char-error]');
925
+ if (!api) { if (err) { err.textContent = 'Your character could not be saved. Reload and try again.'; err.removeAttribute('hidden'); } return; }
926
+ saving = true;
927
+ if (err) err.setAttribute('hidden', '');
928
+ api.request('PUT', '/api/bongos/me/character', { body: { character: mode } }).then(function (res) {
929
+ saving = false;
930
+ if (!res || !res.ok) throw new Error('character not saved');
931
+ closePicker();
932
+ choose(mode, true);
933
+ setCharacter(res.data && res.data.character ? res.data.character : mode);
934
+ }).then(null, function () {
935
+ saving = false;
936
+ var e2 = picker && picker.querySelector('[data-char-error]');
937
+ if (e2) { e2.textContent = 'Your character could not be saved. Try again.'; e2.removeAttribute('hidden'); }
938
+ });
939
+ }
940
+ // The sprite's one action: change character, which is signing out (spec point 2).
941
+ function signOut() {
942
+ var link = document.getElementById('logout-link');
943
+ if (link && link.click) { link.click(); return; }
944
+ var api = client();
945
+ var home = function () { if (window.location) window.location.assign('/'); };
946
+ if (api) api.request('POST', '/api/bongos/auth/logout').then(home, home); else home();
947
+ }
948
+ var changing = false; // one dialog at a time, however many clicks
949
+ function changeCharacter() {
950
+ if (changing) return;
951
+ changing = true;
952
+ var held = heldCharacter();
953
+ var name = held ? modeOf(held).label : 'your character';
954
+ var D = window.OTBDialog;
955
+ var ask = D && D.confirm ? D.confirm({
956
+ title: 'Change character?',
957
+ body: 'You are the ' + name + '. To change, you sign out, and you pick a character again when you sign back in.',
958
+ confirmLabel: 'Sign out and change',
959
+ }) : Promise.resolve(false);
960
+ ask.then(function (yes) {
961
+ changing = false;
962
+ if (!yes) return;
963
+ var api = client();
964
+ var done = function () { drop('localStorage', CHAR_KEY); drop('localStorage', KEEP_KEY); drop('sessionStorage', MODE_KEY); signOut(); };
965
+ if (api) api.request('PUT', '/api/bongos/me/character', { body: { character: null } }).then(done, done); else done();
966
+ });
967
+ }
968
+ // The shell's first call once /me has answered, ahead of the welcome and the
969
+ // session picker: true when character creation is up (so nothing else stacks on it).
970
+ // The last /me the gate was handed (task 1005148): the visor reads the credits off it.
971
+ var lastMe = null;
972
+ function characterGate(access, me) {
973
+ if (!access || access.authed !== true || !me || !me.builder || pageOptsOut()) return false;
974
+ lastMe = me;
975
+ try { document.dispatchEvent(new CustomEvent('otbcraft:me')); } catch (_) { /* old engine */ }
976
+ if (me.character === undefined) return false; // an older core, or not told: no characters yet
977
+ setCharacter(me.character);
978
+ if (account !== null) return false;
979
+ if (creating) return true;
980
+ var main = me.builder.main_discipline;
981
+ openCreate(main === 'artist' ? 'artist' : 'artisan');
982
+ return true;
801
983
  }
802
984
 
803
985
  // ---------- the shell's three calls ----------
@@ -820,9 +1002,11 @@
820
1002
  if (note && notes && !onStudio) loadTool(notes).then(function (api) { api.openNote(note[1]); }, function () { /* the page still works */ });
821
1003
  }
822
1004
  // Once a signed-in builder is known, ask — unless this session already has a
823
- // mode, already saw the picker, or the page opts out.
1005
+ // mode, already saw the picker, or the page opts out. A screen inside the options menu's
1006
+ // frame never asks (task 1005144): the menu page around it does, once.
824
1007
  function onAccess(access, builder) {
825
1008
  if (!access || access.authed !== true || !builder || pageOptsOut() || !sessionOk()) return;
1009
+ if (window.OTBShellGame && window.OTBShellGame.embedded && window.OTBShellGame.embedded()) return;
826
1010
  if (read() || get('sessionStorage', ASKED_KEY)) return;
827
1011
  openPicker(preselectFor(builder));
828
1012
  }
@@ -867,5 +1051,15 @@
867
1051
  // task 1004685 (prototype): the seats the viewer holds, and the Governing seat's name.
868
1052
  setPositions: setPositions,
869
1053
  seatName: seatName,
1054
+ // task 1005147: character creation. characterGate is the shell's first call once
1055
+ // /me answers; character() is the one held (null: none, undefined: not told yet).
1056
+ characterGate: characterGate,
1057
+ setCharacter: setCharacter,
1058
+ character: function () { return account; },
1059
+ changeCharacter: changeCharacter,
1060
+ spriteSrc: spriteSrc,
1061
+ // task 1005148: the visor's two reads — the /me the gate saw, and a tool by id.
1062
+ me: function () { return lastMe; },
1063
+ openTool: function (id) { var t = toolFor(id); if (t) loadTool(t).then(function (api) { api.open(); }, function () { /* tool did not load */ }); },
870
1064
  };
871
1065
  })();
@@ -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/hall-ask.css">
14
14
  <link rel="stylesheet" href="/builders/project-settings.css">
@@ -79,10 +79,10 @@
79
79
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
80
80
 
81
81
  <script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
82
- <script src="/builders/shell-game.js?v=2026-11-09-game-mode"></script>
82
+ <script src="/builders/shell-game.js?v=2026-11-11-helm"></script>
83
83
  <script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
84
- <script src="/builders/craft-mode.js?v=2026-11-05-goals"></script>
85
- <script src="/builders/shell.js?v=2026-11-09-game-mode"></script>
84
+ <script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
85
+ <script src="/builders/shell.js?v=2026-11-11-helm"></script>
86
86
  <script src="/builders/hall-dialog.js"></script>
87
87
  <script src="/builders/palette.js" defer></script>
88
88
  <script src="/builders/hall-kit.js"></script>
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "_": "DEPLOY (task 1003159, ADR 0293; the npm-release module's sections, task 1002622) through modules/ui-design/kit/render.js. The page reads authenticated routes gated on core.pin.move, so against the kit's own stub it renders its restricted or failed-read state. To look at the populated page — the deploy door AND the module's 'where the work is' + version history, from a fixture taken off the live registry and ledger — run the hall-preview harness with the module on:\n\n BONGOS_MODULE_NPM_RELEASE=1 node scripts/hall-preview/server.js --port 4602 --rank archon --fixture-me --modules-on npm-release,provisioning\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/deploy.states.json --base-in http://localhost:4602 --base-out http://localhost:4602",
3
3
  "page": "/builders/deploy",
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",
@@ -318,7 +318,8 @@
318
318
  }
319
319
  // Esc exits fullscreen from anywhere.
320
320
  document.addEventListener('keydown', (e) => {
321
- if ((e.key === 'Escape' || e.key === 'Esc') && fullscreen) toggleFullscreen(fullscreen);
321
+ // Spent once it left the viewer's full screen (ADR 0399 §2.4 row 5, task 1005144).
322
+ if ((e.key === 'Escape' || e.key === 'Esc') && fullscreen) { e.preventDefault(); toggleFullscreen(fullscreen); }
322
323
  });
323
324
  // Re-fit on resize: the fullscreen viewer always, and any inline viewer the
324
325
  // reader hasn't manually zoomed/panned (so they stay responsive).
@@ -185,7 +185,7 @@
185
185
  function disciplines() {
186
186
  const m = window.__MODULES__;
187
187
  const list = m && Array.isArray(m.disciplines) ? m.disciplines : null;
188
- return list && list.length ? list.slice() : ['engineer', 'ideator'];
188
+ return list && list.length ? list.slice() : ['artisan', 'artist'];
189
189
  }
190
190
 
191
191
  // Fill a Kind <select> from the vocabulary the SERVER serves (GET /me