@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
@@ -4,9 +4,9 @@
4
4
 
5
5
  ## What the page says now
6
6
 
7
- - **The main craft's headline** — "N works shipped" (engineer), "N pages approved" (artist), "N thoughts filed" (ideator) — leads the tally. It was also the subtitle under the h1 until task 1004827 removed the page's sub-line (no top block).
7
+ - **The main craft's headline** — "N works shipped" (artisan), "N pages approved" (artist; an artist with ideas and no approved page leads with "N thoughts filed", since task 1005141 folded the ideator into the artist) — leads the tally. It was also the subtitle under the h1 until task 1004827 removed the page's sub-line (no top block).
8
8
  - **The identity band** keeps the avatar, name, rank badge, handle, member-since and the "Answerable for" block unchanged. The "Preferred work:" line became **craft chips**, main role first ("Artist, main role"), then every other craft the server lists in `roles.shown`, which is a craft that has *earned* credits (the owner's 2026-09-30 rule; the server decides it, never this page).
9
- - **The tally** replaces the credits/karma column: the studio's corner tally carried onto the profile, the total first and then the two numbers the main craft leads with (engineer: works shipped, karma; artist: pages approved, this week; ideator: thoughts filed, ignited).
9
+ - **The tally** replaces the credits/karma column: the studio's corner tally carried onto the profile, the total first and then the two numbers the main craft leads with (artisan: works shipped, karma; artist: pages approved, thoughts filed — or thoughts filed, ignited when they have ideas and no approved page).
10
10
  - **By the numbers** is the main craft's set of tiles, then one headline tile per other shown craft. The artist adds the studio's "N of M tweaked" per surface underneath, on `OTBKit.barSeriesHtml` with this sheet only moving the three cells into the mock's layout (name and count on a line, the bar under them).
11
11
 
12
12
  ## Where the words live
@@ -16,28 +16,28 @@
16
16
  ## Rules an edit must keep
17
17
 
18
18
  - **A number the instance could not read is a dash, never a zero.** The role ports are optional; "0 pages approved" would be a claim about the person.
19
- - **The ~ on an ideator's total size** is the sky's historical-estimate mark; its tile's tooltip says how much is estimated.
19
+ - **The ~ on an artist's total size** is the sky's historical-estimate mark; its tile's tooltip says how much is estimated.
20
20
  - **The per-surface rows follow the MAIN craft only.**
21
21
  - Every page that boots `profile.js` in a vm must load `profile-roles.js` first (`tests/helpers.mjs` `bootProfilePage` and `tests/profile_ui_cross_project.mjs` do).
22
22
 
23
23
  ## Harness
24
24
 
25
- Three fixtures, one per craft: `builders__profile` (the engineer, id 95, the viewer's own), `<redacted>` (an artist who never shipped), `builders__2002__profile` (an ideator who never shipped, part of whose size is estimated). `profile.states.json` renders them as `own`, `artist` and `ideator`.
25
+ Three fixtures: `builders__profile` (the artisan, id 95, the viewer's own), `<redacted>` (an artist who never shipped), `builders__2002__profile` (an artist whose work is ideas, who never shipped, part of whose size is estimated; the ideator fixture until task 1005141). `profile.states.json` renders them as `own`, `artist` and `artist-ideas`.
26
26
 
27
27
  ## Deliberate differences from the mock
28
28
 
29
- - The engineer's tally shows **karma** where the mock shows "+120 this week", and the fourth engineer tile is **achievements** where the mock shows a shipping streak: neither weekly engineer credits nor a weekly streak is in the profile read, and a number the page cannot read is not drawn.
30
- - The engineer tile "works shipped" has no "across N goals" foot, the ideator's "thoughts filed" no "since <month>" foot, the artist's "credits from pages" no "N a page" foot, and the surface rows no "where" sub-label: none of those is in the read.
31
- - **Karma ends every artist's and ideator's set of tiles**, which the mock does not draw. The owner's call, reviewing this build on 2026-10-01: "karma for artists and ideators is definitely key". The engineer's set carries it already.
29
+ - The artisan's tally shows **karma** where the mock shows "+120 this week", and the fourth artisan tile is **achievements** where the mock shows a shipping streak: neither weekly artisan credits nor a weekly streak is in the profile read, and a number the page cannot read is not drawn.
30
+ - The artisan tile "works shipped" has no "across N goals" foot, the artist's "thoughts filed" no "since <month>" foot, the artist's "credits from pages" no "N a page" foot, and the surface rows no "where" sub-label: none of those is in the read.
31
+ - **Karma ends the artist's set of tiles** (every main craft but the artisan's), which the mock does not draw. The owner's call, reviewing this build on 2026-10-01: "karma for artists and ideators is definitely key" (the ideator is part of the artist craft since task 1005141). The artisan's set carries it already.
32
32
  - The tiles' grid fits at 148px rather than 170px, so five tiles sit on one row inside the hall's narrower panel.
33
33
 
34
34
  ## The Activity tabs, one per role (task 1004435, WA6.RP05)
35
35
 
36
36
  The section used to offer All / Engineer / Artist / Ideator / UI tabs over one task ledger, so an ideator's tab listed tasks whose discipline happened to be "ideator" and an artist's listed only artist-labelled tasks they had shipped. Now there is **one tab per role the person has** (`roles.shown`, the header's chips, main first), each counted the way the header leads, each its craft's own record, each with its own panel:
37
37
 
38
- - **Engineer**: the works completed and goals created ledgers, WHOLE (no craft filter: most of the corpus is unclassified and a filter reads as "you did less").
39
- - **Ideator**: the room's public trail, `public/profile-crafts.js`. One read of up to the route's cap of thoughts (`GET /inbox/by-builder/:id`, which already carried each thought's `state` and `size`), drawn as the sky's five marks by the day each was filed (size = diameter, brightness = a star's opacity, a folded-in thought tethered to the one it joined), the legend in the sky's words from `OTBIdeaObjects`, and every drawn thought a row in the ledger under it: state, size (~ for a historical estimate), still alive, and the old trail's "what became of it" cell folded in whole. A mark and its row light together on hover or focus; clicking a mark focuses its row.
40
- - **Artist**: the pages they rewrote in Tweak Mode that were approved, four at a time with "Show N more", from `GET /copy-desk/artists/:id/pages` (signed in; copy-desk's `composeArtistPages`). Each row: page, surface, approval day, changed-line count, and a before/after: the round's first changed line beside the approval renders (`/tasks/:id/visuals`, `before-` and `after-desktop-light`) as small thumbnails.
38
+ - **Artisan**: the works completed and goals created ledgers, WHOLE (no craft filter: most of the corpus is unclassified and a filter reads as "you did less").
39
+ - **Artist, the ideas half** (its own Ideator tab until task 1005141; now drawn under the approved pages on the one Artist tab): the room's public trail, `public/profile-crafts.js`. One read of up to the route's cap of thoughts (`GET /inbox/by-builder/:id`, which already carried each thought's `state` and `size`), drawn as the sky's five marks by the day each was filed (size = diameter, brightness = a star's opacity, a folded-in thought tethered to the one it joined), the legend in the sky's words from `OTBIdeaObjects`, and every drawn thought a row in the ledger under it: state, size (~ for a historical estimate), still alive, and the old trail's "what became of it" cell folded in whole. A mark and its row light together on hover or focus; clicking a mark focuses its row.
40
+ - **Artist, the pages half**: the pages they rewrote in Tweak Mode that were approved, four at a time with "Show N more", from `GET /copy-desk/artists/:id/pages` (signed in; copy-desk's `composeArtistPages`). Each row: page, surface, approval day, changed-line count, and a before/after: the round's first changed line beside the approval renders (`/tasks/:id/visuals`, `before-` and `after-desktop-light`) as small thumbnails.
41
41
 
42
42
  Signed out, the section is one sign-in line (owner decision (a)).
43
43
 
@@ -59,7 +59,7 @@ The Overview's charts used to stop at "No works shipped yet" before they read a
59
59
  - **The works charts still need a shipped task**, and without one they are a single line under the figure ("No works shipped yet ..."), not two empty cards. Someone with neither credits nor works gets one line for the whole section.
60
60
  - **On a phone** the figure stacks: the line, then "where it came from". The plot's viewBox is half again a card's on a wide screen so its type matches the cards', and the kit's own on a phone (read at draw time, at the same 720px the grid stacks).
61
61
 
62
- Harness: `builders__earnings.json` serves any id; the artist (2001) and the ideator (2002) carry their own earnings beside an empty `builders__<id>__activity.json`, and the `artist`/`ideator` states expect `#chart-drachmae` and `.pearn__kinds`.
62
+ Harness: `builders__earnings.json` serves any id; the artist (2001) and the ideas artist (2002) carry their own earnings beside an empty `builders__<id>__activity.json`, and the `artist`/`artist-ideas` states expect `#chart-drachmae` and `.pearn__kinds`.
63
63
 
64
64
  ### Deliberate differences from the mock (RP06)
65
65
 
@@ -1,6 +1,6 @@
1
1
  # The sky — /thinking and /goal-map (tasks 1004231, 1004232, 1004233)
2
2
 
3
- **THE SKY landed at `/thinking` (task 1004231 / sky part 3 of 5, goal 1000095; [ADR 0331](../../../docs/adr/0331-the-sky-is-the-ideators-home.md), ADR 0227 §11)** — the ideator's home. It replaced BOTH the room (task 1004098, `/thinking`) and the first ambient sky (task 1004105, `/sky` — the URL 301s here now) with the prototype the owner adopted as the design of record: `docs/design/mocks/sky/sky.template.html` is the **pixel contract** — copy, layout, colours, the thirteen tour steps, the toggles, the keys (`L` list, `?` tour, `0` fit, `/` compose) — built for real over `GET /sky` and `GET /sky/mine` (part 2). The room survives inside it as the list drawer: every drawn thought is a row with its whole sentence, one key away. Eight things it settled:
3
+ **THE SKY landed at `/thinking` (task 1004231 / sky part 3 of 5, goal 1000095; [ADR 0331](../../../docs/adr/0331-the-sky-is-the-ideators-home.md), ADR 0227 §11)** — the ideator's home (the artist craft's since task 1005141 folded the ideator into it). It replaced BOTH the room (task 1004098, `/thinking`) and the first ambient sky (task 1004105, `/sky` — the URL 301s here now) with the prototype the owner adopted as the design of record: `docs/design/mocks/sky/sky.template.html` is the **pixel contract** — copy, layout, colours, the thirteen tour steps, the toggles, the keys (`L` list, `?` tour, `0` fit, `/` compose) — built for real over `GET /sky` and `GET /sky/mine` (part 2). The room survives inside it as the list drawer: every drawn thought is a row with its whole sentence, one key away. Eight things it settled:
4
4
 
5
5
  - **The page has no hall chrome, on purpose.** `thinking.html` is the template's markup (no shell.js, no sidebar); `thinking.css` is its `<style>`, one file; the world's tokens are not loaded — the template's values ARE the contract. The hall's doors (the record, the goal, the task) open from the panel, resolved against `hallBase()` (apex `/builders` or the subdomain). The title stamps `{{worldName}}` like every hall page.
6
6
  - **One script became seven files under the 1,500-line ratchet, on ONE shared object.** `window.OTBSky`: `SKY.S` is every `let` the prototype's closure held; everything else is a function or a constant. Load order is pinned in the HTML — `sky-data.js` (helpers, the vocabulary, the two adaptors, the bands, the world, the thoughts, the five reads) → `sky-layout.js` (clock, routes, explore, camera) → `sky-draw.js` (the drawing, the hit test) → `sky-interaction.js` (card, menu, pointer, keys, lenses, stats, jump, legend) → `sky-panel.js` (the panel, the drawer) → `sky-composer.js` (gravity, quick + full filing, the nod) → `sky-tour.js` (the thirteen steps, the frame loop, the boot). A file only CALLS a later file through `SKY.name(...)`, never at load time. The README says a split is not a deviation; a changed behaviour inside one is — and every deviation a gate or a route forced is listed in the task's ship notes, one line each.
@@ -37,8 +37,8 @@ Pinned by `tests/hall_sky.mjs` (31 cases: the reads, the five objects, the two c
37
37
 
38
38
  **Owner decision 2026-09-27: "Your thinking is only your thinking."** Part 4's "one page, two doors" above is SUPERSEDED: the two pages share the renderer and nothing else. The owner confirmed that "the your projects tab" means the whole-project lens, its switch and the project-map features. What this settled:
39
39
  - **The lens and the features separate; the drawing does not.** `thinking.css`, the seven `sky-*.js` files and every draw path stay shared. What each page may be in is `S.lenses` (set once in `sky-data.js` from `data-sky-lens`): `['simple', 'mine']` on `/thinking`, `['all']` on `/goal-map`. `setLens` clamps to it, so nothing — a button, the tour, a jump, the panel's "descend" — can take a page into a lens it lacks, and no button leaves for the other page (`location.assign` is gone from `sky-interaction.js`).
40
- - **Absent, not hidden.** `/thinking` carries no "the whole project" button, no "my ideas", no "explore" and no "where do you begin" dialog; `/goal-map` carries no switcher at all and none of the ideator's pills (recent, claude layer, my influence). The scripts touch a one-page control through `SKY.hide` / `SKY.press` / `SKY.on`, which do nothing when the element is not there. `thinking.css` gained one rule, `.brand + .tools { margin-left: auto; }`, so `/goal-map`'s tools keep the switcher's push to the right and its top strip reads as before.
41
- - **Two tours from one list.** Every step in `ALL_STEPS` says which sky it belongs to; `/thinking` walks nine of the template's thirteen (the ideator's steps, the wells and rings, the clock, the composer, simple), `/goal-map` walks eight — the wells, rings, life, routes, settled systems, the clock and explore — opened by one new step, "This is the whole project.", because the template's first step is "This is your sky." and belongs to `/thinking`. The project tour turns explore off for its length and gives it back. Each tour has its own seen key (S.tourKey: sky-tour-seen here, sky-map-tour-seen on the map), so one does not silence the other.
40
+ - **Absent, not hidden.** `/thinking` carries no "the whole project" button, no "my ideas", no "explore" and no "where do you begin" dialog; `/goal-map` carries no switcher at all and none of `/thinking`'s pills (recent, claude layer, my influence). The scripts touch a one-page control through `SKY.hide` / `SKY.press` / `SKY.on`, which do nothing when the element is not there. `thinking.css` gained one rule, `.brand + .tools { margin-left: auto; }`, so `/goal-map`'s tools keep the switcher's push to the right and its top strip reads as before.
41
+ - **Two tours from one list.** Every step in `ALL_STEPS` says which sky it belongs to; `/thinking` walks nine of the template's thirteen (the thinking sky's steps, the wells and rings, the clock, the composer, simple), `/goal-map` walks eight — the wells, rings, life, routes, settled systems, the clock and explore — opened by one new step, "This is the whole project.", because the template's first step is "This is your sky." and belongs to `/thinking`. The project tour turns explore off for its length and gives it back. Each tour has its own seen key (S.tourKey: sky-tour-seen here, sky-map-tour-seen on the map), so one does not silence the other.
42
42
  - **"Descend into the well" stays in its sky.** On `/goal-map` it only flies; on `/thinking` it steps "simple" up to "your thinking" (the one lens there that zooms). The legend drops "unexplored" on `/thinking`, which has no explore.
43
43
  - **Kept on both:** the list drawer (the room on `/thinking`, the ledger on `/goal-map`), the clock, the composer, "what if", the panel. Filing a thought from the project map still works; the owner's decision names the lens and the overlay, not the composer.
44
44
 
@@ -0,0 +1,25 @@
1
+ # The start screen and the load screen — a sign-in opens the game (task 1005143, BONGOS-V3 R03)
2
+
3
+ Covers: Home (`shell-game.js` `start()`, `shell-game.css`, the hand-off in `welcome-boot.js`). Spec: `docs/specs/bongos-v3-game-conversion.md` C1 `game-opens-not-navigates`. Game mode itself is in `game-mode.md`.
4
+
5
+ ## What it settled
6
+
7
+ - **A fresh sign-in opens on the start screen, on Home.** Home is where a sign-in lands (`postLoginLanding`), and `welcome-boot.js` already knows a fresh sign-in from a return visit (the flag the sign-in click leaves). On one, `OTBWelcome.onAccess` calls `OTBShellGame.start({ name, art, done })` in place of its own greeting. The day's first visit without a sign-in keeps the welcome; a signed-out visitor keeps the welcome. A sign-in that returns to a deep link or another page goes straight there, as the welcome always did. `shell.js` is untouched.
8
+ - **Five choices, one column: Start, Options, Achievements, Extras, Quit.** Start is the one filled pill. Options is a link to `/settings`, which IS the one options menu since task 1005144 (`records/options-menu.md`); Achievements is `/profile`. Extras opens three links under the menu (the thinking sky, the atlas, the docs). Quit calls `window.close()`; a browser only closes a tab a script opened, so when the tab is still there 300ms later the screen says "Close it to quit" instead of doing nothing.
9
+ - **Start turns game mode on** (`set(true)`), then shows the load screen. Nothing else in the hall's interface sets it.
10
+ - **The load screen is the still, a tip, and the city building up.** The still is the welcome's painting for the time of day, handed in by `welcome-boot.js` (this file names no picture). In a card of the hall's own tokens, the project city is drawn by `home-city-draw.js` `citySvg` from the one bounded read Home already uses (`GET /home/city?weeks=10`), at 31 stops from the oldest week to now, about three seconds, with "The city, N weeks ago" under it. Each stop is a whole drawing set as state; nothing animates in JavaScript. With reduced motion it is the finished city at once. With no city to draw (the read failed, the scripts are missing) the still and the tip stand for 1.4s. Either way the world opens.
11
+ - **Tips rotate** (`localStorage 'hall-game-tip'` counts them), so each load shows the next one. A tip must be true of the hall today; the first one is the Escape rule from `game-mode.md` (rewritten by task 1005144: Escape opens the options menu).
12
+ - **The craft picker waits for the world.** While the start screen is up the shell asks no picker (`onAccess` returned true). `done` runs once the screen is gone and asks `OTBCraftMode.onAccess` then.
13
+ - **Keys.** While either screen is up it keeps every key: the arrows and Tab walk the menu, Escape folds Extras away and does nothing else, so the shell's Escape cannot flip the bars underneath. Once the world shows, the last Escape opens the options menu (task 1005144); the screen's capture listener calls `preventDefault`, so the menu never opens from under it.
14
+ - **The look is Helm (task 1005192, [ADR 0402](../../../docs/adr/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md)).** The start screen is one full-height night panel over the painting (`--night-glass`, an ice hairline left and right at `--line-strong`), `--helm-panel-w` wide. On a desk it stands off the left edge by `--helm-inset`; that token is a `clamp`, so on a phone the panel is the whole width and the sheet still has no `@media`. In it: the mark (the project's logo when it has one) over the name in the name voice; Start as a full-width accent bar under `--accent-on` that names its key ("Enter", drawn by CSS so the choice's own text stays "Start"); the other four as quiet rows on `--rule-soft` hairlines; and at the foot, over the strong hairline, the tip the load screen will show next (`#gs-line`), with the Quit note under it when the browser keeps the tab. The rows are list rows in a panel, so they are bars, not cut buttons. A row's focus is the inset two-tone ring (`--press-focus`), which is what makes focus visible on the accent bar.
15
+ - **The load screen is the same panel, centred** over the painting under the hall's `--scrim`: the week in the name voice (`#gs-when`), one segment per week of the history (`#gs-segs`, the shared `.segs` / `.seg`; filled up to the week being shown, counted the way the sentence words it, and hidden from a screen reader because the sentence says the same), the city, and the tip under a hairline. With no city there are no segments.
16
+ - **The literal floor is zero.** Every colour, size, corner, shadow, face and measure in `shell-game.css` is a hall-tier token (`style.css`); `tests/hall_start_screen.mjs` fails on a typed one. The fade into the world is one CSS opacity transition, stopped by the hall's reduced-motion rule.
17
+
18
+ ## For the tasks after this one
19
+
20
+ - R04 (task 1005144): point Options at the options menu and move Achievements/Extras targets if those pages lose their interfaces. Escape on the start screen is free for the menu to use.
21
+ - `shell-game.js` is loaded on every hall page but only Home calls `start()`; `shell-game.css` is linked on Home only. A second page that wants the start screen links the sheet and calls `start()` with its own still.
22
+
23
+ - New art for the start screen (the city in the clouds) is task 1005196; `welcome-boot.js` still hands in the welcome's painting.
24
+
25
+ Tests: `tests/hall_start_screen.mjs` (the hand-off and pure parts in a vm; both screens, the panel's place at desk and phone widths, the segments, Quit, the no-city and reduced-motion paths in a real browser).
@@ -6,7 +6,7 @@ Files: `tweak-editor.html` (the markup), `tweak-editor.css` (the artboard's inli
6
6
 
7
7
  - **Which page.** `?page=<page id>` (`landing:index`). With none, the page the caller holds (`GET /copy-desk/next`'s `resume`), and with nothing held, the "is in" panel's next up, so a bare link still leads somewhere. The studio (TW16) links here.
8
8
  - **One read opens it.** `GET /copy-desk/pages/:pageId/draft` (added here: `GET /copy-desk/pages/:pageId` carries neither the lines nor the draft) answers every reading line with its margin note, the open round and `held_by_you`, and the saved draft, only on the current reading (`draft_on_older_reading` otherwise). The note is the server's: `engineer` is `pages.goesToEngineer`, the submit's own rule, so a shared line with no registry row says "goes to an engineer", which is what the submit will do with it.
9
- - **The autosave is the whole draft, debounced (700ms) and on blur**, one save in flight at a time, skipped when the body equals the last accepted one; a last `keepalive` PUT on `pagehide`. An emptied box is saved as a removal, `{ key, remove: true }` (task 1004696): its margin note says it is to be removed and goes to an engineer, and the submit files that engineer task (an empty `after` alone is still the server's `line_empty`), and the counter reads "not saved" with the server's own words above the paper when a save is refused.
9
+ - **The autosave is the whole draft, debounced (700ms) and on blur**, one save in flight at a time, skipped when the body equals the last accepted one; a last `keepalive` PUT on `pagehide`. An emptied box is saved as a removal, `{ key, remove: true }` (task 1004696): its margin note says it is to be removed and goes to an engineer, and the submit files that artisan task (an empty `after` alone is still the server's `line_empty`), and the counter reads "not saved" with the server's own words above the paper when a save is refused.
10
10
  - **Holding decides writing.** A page nobody holds is read-only with one button, "take this page" (the claim); another builder's is read-only and names them; a submitted one is frozen. Upload and submit are the holder's only, as their routes are.
11
11
  - **Every write is named** (`WRITES`: the draft PUT, the .docx POST, the submit, the claim on a server-given `take.path`), and `tests/hall_tweak_editor.mjs` pins the list. The upload is a raw `fetch` because the generated client JSON-encodes every body.
12
12
  - **It opens full screen.** The artboard is the whole window, so the first visit sets the shell's switch (task 1004327) on; after that the shell remembers the artist's choice for this page. In full screen the switch button wears the room's glass, since the stage is dark in both themes.
@@ -10,3 +10,15 @@ The owner, 2026-11-03: upgrading Bongos from a project's hall was "way too obstr
10
10
  - **shell.js got smaller.** The banner's 109 lines became a 61-line loader that fetches `/core-update` once per page and loads the pill only when there is news or a run in flight (1,499 → 1,451 lines).
11
11
 
12
12
  Tests: `tests/update_pill.mjs` (the pure half, and the real shell + pill in a DOM stub: who sees it, never a link, both doors, refusals, a blocked window, held, a run across pages, done → Reload), `tests/deploy_handoff.mjs` (the hub's three refusals, and its page run for real). Each origin, opener and version check was deleted once and watched to go red. Harness fixture: `core-update.json` (behind, so the pill shows).
13
+
14
+ ## The pop-up (task 1005191)
15
+
16
+ The owner, 2026-11-10: the pill alone was not seen. Game mode (task 1005142) opens every page without its bars, and full screen hid everything in the top bar's right side except its own button, so a hosted project sat three minor versions behind with nobody told. What this settled:
17
+
18
+ - **A ready update also asks, once.** `update-pill.js` `maybeAsk` opens the hall's one dialog (`OTBDialog.open`, no bespoke dialog): "Update the builders hall?", up to three release-note lines, the takes-a-few-minutes note, both versions, then *Later* and *Update now*. It reads the same `/software-update` answer the card reads, so both name one version and the two-step rule (ADR 0293 D4) holds.
19
+ - **Update now runs inside the press.** A hosted project opens the hub's window, which a browser allows only from the click; the progress card then opens under the pill.
20
+ - **Any other way of closing it is "later".** That version is not asked about again in this browser for a day (`bongos.update.later`); a newer version asks at once. The pill stays, so later never means lost.
21
+ - **Who is not asked:** a viewer the door would refuse, the hub's handoff window, the `/deploy` page, a builder held at the setup screen, a held version, an update already running. A self-hosted project is told the command, with one *Close* button.
22
+ - **The pill stays with the bars away** (`style.css`, `html[data-fullscreen="on"] .upd-pill`), beside the button that brings the bars back.
23
+
24
+ Tests: `tests/update_pill.mjs`, the ten cases under "the pop-up".
@@ -0,0 +1,23 @@
1
+ # The visor — the first-person HUD over every scene (task 1005148, BONGOS-V3 R08; ADR 0399)
2
+
3
+ Covers: `visor.js` + `visor.css`, loaded on Home, `/city`, `/goal-map` and `/thinking`, and the three scene registrations (`sky-interaction.js`, `city.js`, `home-city.js`). Spec: `docs/specs/bongos-v3-game-conversion.md` C2 `you-are-a-character-in-first-person`. Game mode itself is in `game-mode.md`.
4
+
5
+ ## What it settled
6
+
7
+ - **The look the owner agreed (2026-11-10, five mock-up rounds).** A quiet layout with "hairline instruments" from the helmet round: thin curved visor lines across the top, bottom and both sides, a small centre cross, and a compass of nine slanted cells along the top line with the one salmon marker under its centre. No tracker or radar (tried and dropped), no clipped corners: the pills stay round (DESIGN.md "pill or nothing"). No glow (DESIGN.md's rejected near-black-plus-neon HUD). The frame is one SVG in a 1000×1000 box stretched over the stage with non-scaling strokes, so it is a hairline at every size.
8
+ - **What sits where.** Bottom-left: the character's main action, the one accent pill, with its key. Bottom-right: the sprite (task 1005147's art, `OTBCraftMode.spriteSrc`) with the character's name and the credit count (`/me` `builder.total_credits` through `OTBCraftMode.me()`; credits only, never a payout). Top-right: Actions (key A), which opens the sheet: the character's actions, then "This place" (the scene's own actions and keys), then Options on Escape. A place line under the compass names the scene; the sky leaves it empty because its own heading names it.
9
+ - **Each character's actions** (`ACTIONS`): Artisan Claim a task C (`/work`), Blockers B, Goal sky G, Your thinking T. Artist Review this page R, Edit its words E, Write an idea I (the three craft-mode tools, through `OTBCraftMode.openTool`), Studio S. Patron Board room O, Governor city V, Blockers B, Watch W. Every one is a page or tool the hall already has; the visor gates nothing, and the server's rank checks answer exactly as before (ADR 0399 §2.5). No key a scene binds is reused (the sky's L 0 / ?, the gallery's F, the palette's K, the city card's digits, arrows and Tab).
10
+ - **Glass, not layout (ADR 0399 §2.1).** `#visor` is `position: fixed` with `pointer-events: none`; only the pills and the sheet take a pointer. A drag across a frame line pans the sky (test-pinned).
11
+ - **No camera (§2.2).** A scene registers once: `window.__visorScene = { name, place, actions, keys }`, then `OTBVisor.scene(...)` if the visor is already loaded (it also reads the global at start, so load order between the two does not matter). The sky offers "Back to centre" (its own `fitAll`); the Governor City and Home have no camera yet and offer none, with no disabled button. R13 adds Home's.
12
+ - **Escape (§2.4).** The open sheet takes the press in capture with `preventDefault` and `stopPropagation`, so the sky's pick under it survives; the ambient glass never takes Escape, so the last press reaches the options menu. The letter keys listen in bubble on `window` and skip a press a scene already spent (`defaultPrevented`), one with a modifier, one in a field, and any press while the character picker, the start screen or an `aria-modal` dialog is up.
13
+ - **When it shows.** Game mode (`html[data-game="on"]`) AND a registered scene AND a character (the held one, else this session's craft mode) AND not inside the options menu's frame. Otherwise the hall is exactly as before.
14
+ - **Scenes keep clear of the glass.** While it shows, `visor.js` puts `visor-on` on `<html>`, and `thinking.css` moves the sky's top row, composer, jump field and side panels below the compass, and its foot and tour above the corner pills. A new scene with fixed furniture does the same in its own sheet.
15
+ - **Stacking.** The glass is z 4: above the scene, under the sky's pop-ups (5–8). The Governor City's card lives inside `.city-main`'s stacking context, so the glass is drawn over it; nothing collides (the card is centred, the pills are in the corners).
16
+
17
+ ## For the tasks after this one
18
+
19
+ - R09 (one hotkey registry): `ACTIONS` keys and the scene `keys` lines are what it registers; the sheet is where a rebind shows.
20
+ - R13 (the sky camera on Home's city): register `fitAll` / `flyTo` as actions in `home-city.js`'s `__visorScene`.
21
+ - R11 (Patron for everyone): no visor change; the Patron's actions already open pages whose routes keep their rank walls.
22
+
23
+ Tests: `tests/hall_visor.mjs` (the pure parts in a vm; the glass, the pointer pass-through, the Escape order, the sheet, the Artist's tools and the key guards in a real browser over a stand-in scene). The script-order pins in `tests/hall_city.mjs` and `tests/goal_map_page.mjs` name the visor after the shell.
@@ -32,14 +32,14 @@ finally had Gemini paint the city by day and by night and asked for those to be
32
32
  - **Banner.** "Working on the core of <project>" from `__BRANDING__.identity` (never a hardcoded word),
33
33
  with `core <version>` under it when the public `GET /version` reports one.
34
34
  - **Sequence.** Veil lifts, the city powers up (pulses brighten), the greeting comes up at ~3.4s.
35
- **Skip** jumps to the greeting; **Esc**, "Your home page" and Engineer leave slowly: the picture sinks
35
+ **Skip** jumps to the greeting; **Esc**, "Your home page" and Artisan leave slowly: the picture sinks
36
36
  past the rooftops while the welcome fades into the hall underneath (about 2.4s), and the hall can be
37
37
  clicked and scrolled from the first moment. A role bound for another room sinks into the veil, then
38
38
  opens that room. Under `prefers-reduced-motion` nothing moves and leaving is immediate.
39
- - **Roles** (the owner's mapping): Owner → `/city`, Ideator → `/thinking`, Artist → `/studio`,
40
- Engineer → stays on Home. A pick also calls `OTBCraftMode.choose()` with governing / ideating /
41
- artistry / building. **Owner shows only where `OTBCraftMode.offered('governing')`** — Governing is a
42
- seat — and the list is built when "Start with your role" is pressed, after the seats have settled.
39
+ - **Roles** (the owner's mapping, renamed by task 1005141): Patron → `/city`, Artist → `/studio`,
40
+ Artisan → stays on Home (the Ideator door, once `/thinking`, folded into Artist). A pick also calls
41
+ `OTBCraftMode.choose()` with patron / artist / artisan. **Patron shows only where
42
+ `OTBCraftMode.offered('patron')`** — the patron craft is a seat — and the list is built when "Start with your role" is pressed, after the seats have settled.
43
43
  - **No painting** (a 404) hides the image and leaves the sky gradient; everything else works.
44
44
  - **Colours are the paintings'**, in both hall themes: `welcome-boot.css` and `welcome-boot.js` are on
45
45
  `tests/hall_audit.mjs`'s literal exemptions with that reason.
@@ -1,6 +1,7 @@
1
1
  {
2
- "_": "The hall's shared components, each in each tone and state (task 1005086, goal 1000138): the status pill, the panel, the empty state, the loading state, the toast per kind, the tabs and view toggle, the buttons, the field and the dialog. Rendered by the ui-design kit in both modes at 1440/390/320: node modules/ui-design/kit/render.js --states modules/hall-ui/specimens/components.states.json. The public design-system reference (task 1005083) is meant to build on this.",
2
+ "_": "The hall's shared components, each in each tone and state (task 1005086, goal 1000138): the status pill, the panel, the empty state, the loading state, the toast per kind, the tabs and view toggle, the buttons, the field and the dialog. Rendered by the ui-design kit, dark only like the hall (task 1005192), at 1440/390/320: node modules/ui-design/kit/render.js --states modules/hall-ui/specimens/components.states.json. The public design-system reference (task 1005083) is meant to build on this.",
3
3
  "page": "/components",
4
+ "modes": ["dark"],
4
5
  "surface": "modules/hall-ui/specimens",
5
6
  "stub": {
6
7
  "prefix": "/"
@@ -132,7 +132,9 @@ async function readIdeatorStats(builderId, deps = {}) {
132
132
  if (reward && typeof reward.creditsByCraft === 'function') {
133
133
  const byCraft = await reward.creditsByCraft([builderId]);
134
134
  const row = byCraft.get(String(builderId));
135
- ideatorCredits = row ? row.ideator : 0;
135
+ // The idea streams alone — artist credits also carry page money since the
136
+ // ideator folded into the artist (task 1005141), so `artist` would overstate this.
137
+ ideatorCredits = row ? (Number.isFinite(row.ideas) ? row.ideas : 0) : 0;
136
138
  }
137
139
 
138
140
  let ratifiedIds = null;
@@ -396,9 +396,9 @@ async function routeIdeaToGoal({
396
396
  // default — createTask auto-assigns the capped suggestion) and
397
397
  // `discipline` — NO LONGER 'unclassified' (task 1002988). createTask refuses a
398
398
  // craft it cannot infer; refusing a filer is the wrong trade for an INBOX, so we
399
- // ask the same question and fall back to 'engineer' (free to re-triage; a lost capture is not).
399
+ // ask the same question and fall back to 'artisan' (free to re-triage; a lost capture is not).
400
400
  const task = await lifecycle.createTask({
401
- discipline: (lifecycle.resolveWriteDiscipline?.({ title, description: bodyMd, touches: declaredTouches })?.discipline) || 'engineer',
401
+ discipline: (lifecycle.resolveWriteDiscipline?.({ title, description: bodyMd, touches: declaredTouches })?.discipline) || 'artisan',
402
402
  versionId: goal.version_id,
403
403
  title,
404
404
  description: bodyMd,
@@ -45,7 +45,7 @@ const { creditsEarnedBySql } = require('./db-tasks.js');
45
45
  // (migrations 086 + 176). Anything else is treated as "no filter" rather than
46
46
  // 400ing the read: a caller naming a craft that does not exist gets the honest
47
47
  // full list, not an error card in the middle of a profile.
48
- const CRAFTS = ['engineer', 'artist', 'ideator', 'ui', 'unclassified'];
48
+ const CRAFTS = ['artisan', 'artist', 'ui', 'unclassified'];
49
49
 
50
50
  // One page of an activity list. Clamped here as well as at the route, because
51
51
  // these are the functions a CLI or a later surface will call directly.
@@ -71,8 +71,8 @@
71
71
  // key string, stable + unique. It is half the source_ref, so RENAMING
72
72
  // a key re-files every task the rule ever generated. Treat it as
73
73
  // permanent.
74
- // discipline the role the generated work is FOR ('artist', 'ideator',
75
- // 'engineer'). This is the whole point of the mechanism: the
74
+ // discipline the role the generated work is FOR ('artist' or
75
+ // 'artisan'). This is the whole point of the mechanism: the
76
76
  // discipline of the generated task is not the discipline of the
77
77
  // ship that caused it.
78
78
  // when(task, ctx) a predicate over the shipped task row and the run CONTEXT.
@@ -216,7 +216,7 @@ const CASCADES = Object.freeze([
216
216
  key: REMOVAL_RULE_KEY,
217
217
  // ENGINEER, not artist: confirming asks whether deleted code is genuinely
218
218
  // gone or was moved, which is a question about the diff.
219
- discipline: 'engineer',
219
+ discipline: 'artisan',
220
220
  kind: 'unclassified',
221
221
  // The anti-recursion guard FIRST: a cascade-generated task must never
222
222
  // cascade, so it is rejected before any other test runs.
@@ -250,7 +250,7 @@ const TRIGGERS = Object.freeze({
250
250
  const IDEA_CASCADES = Object.freeze([
251
251
  Object.freeze({
252
252
  key: 'engineer-scoping-on-full-idea',
253
- discipline: 'engineer',
253
+ discipline: 'artisan',
254
254
  kind: 'unclassified',
255
255
  // A Full Idea is the ideator's hand-off point: the five questions are
256
256
  // answered and the thing is described well enough to cost. What it still
@@ -50,7 +50,7 @@ function specTaskFields(goal, { createdBy = null } = {}) {
50
50
  ].join('\n'),
51
51
  status: 'ready',
52
52
  kind: 'spike',
53
- discipline: 'ideator',
53
+ discipline: 'artist',
54
54
  source: SPEC_SOURCE,
55
55
  sourceRef: specSourceRef(id),
56
56
  createdBy,
@@ -14,19 +14,22 @@
14
14
 
15
15
  'use strict';
16
16
 
17
- const { pool } = require('../../src/module-api');
17
+ const { pool, crafts: craftVocab } = require('../../src/module-api');
18
18
 
19
- // ADR 0174's crafts. Note the vocabulary seam: `builders.preferred_disciplines`
20
- // spells this same craft 'engineer', not 'builder'. THIS FUNCTION IS THE ONE PLACE
21
- // that knows the mapping — the matching surface (C02) must consume it rather than
22
- // re-deriving, or the two spellings drift and a builder silently matches nothing.
23
- const HELP_CRAFTS = Object.freeze(['artist', 'builder', 'ideator', 'ui']);
24
- const CRAFT_TO_DISCIPLINE = Object.freeze({ artist: 'artist', ideator: 'ideator', builder: 'engineer', ui: 'ui' });
19
+ // The crafts a request can be addressed to: the two working characters plus the
20
+ // module-contributed `ui`. Since task 1005141 (BONGOS-V3) the help-request spelling
21
+ // and the discipline spelling are THE SAME WORD — the old seam where this craft was
22
+ // 'builder' here and 'engineer' on a builder is closed. The maps stay, because they
23
+ // are also where an OLD spelling is read as today's: a rolled-back release can still
24
+ // write 'engineer'/'ideator' into preferred_disciplines, and craftMapSql() below runs
25
+ // against that column.
26
+ const HELP_CRAFTS = Object.freeze(['artisan', 'artist', 'ui']);
27
+ const CRAFT_TO_DISCIPLINE = Object.freeze({ artisan: 'artisan', artist: 'artist', ui: 'ui' });
25
28
 
26
- const DISCIPLINE_TO_CRAFT = Object.freeze({ artist: 'artist', ideator: 'ideator', engineer: 'builder', ui: 'ui' });
29
+ const DISCIPLINE_TO_CRAFT = Object.freeze({ artisan: 'artisan', artist: 'artist', ui: 'ui', engineer: 'artisan', ideator: 'artist' });
27
30
 
28
31
  function helpCrafts() { return [...HELP_CRAFTS]; }
29
- function disciplineForCraft(craft) { return CRAFT_TO_DISCIPLINE[craft] || null; }
32
+ function disciplineForCraft(craft) { return CRAFT_TO_DISCIPLINE[craftVocab.normalizeCraft(craft)] || null; }
30
33
  function craftForDiscipline(d) { return DISCIPLINE_TO_CRAFT[d] || null; }
31
34
 
32
35
  // The caller's OWN crafts, read from builders.preferred_disciplines. This exists
@@ -90,7 +93,8 @@ function capRows(n) {
90
93
  function validateHelpRequest(input) {
91
94
  const errors = [];
92
95
  const b = input || {};
93
- const craft = b.needs_craft == null ? null : String(b.needs_craft);
96
+ // An old spelling ('builder', 'engineer', 'ideator') is today's craft, not an unknown one.
97
+ const craft = b.needs_craft == null ? null : craftVocab.normalizeCraft(String(b.needs_craft));
94
98
  const person = b.needs_builder_id == null ? null : Number(b.needs_builder_id);
95
99
 
96
100
  if (craft === null && person === null) {
@@ -133,7 +137,7 @@ async function createHelpRequest(input, deps = {}) {
133
137
  (requested_by, needs_craft, needs_builder_id, what_is_stuck, task_id, goal_id)
134
138
  VALUES ($1, $2, $3, $4, $5, $6)
135
139
  RETURNING ${COLS}`,
136
- [input.requestedBy, input.needsCraft ?? null, input.needsBuilderId ?? null,
140
+ [input.requestedBy, input.needsCraft == null ? null : craftVocab.normalizeCraft(String(input.needsCraft)), input.needsBuilderId ?? null,
137
141
  String(input.whatIsStuck).trim(), input.taskId ?? null, input.goalId ?? null]
138
142
  );
139
143
  return rows[0];
@@ -150,7 +154,7 @@ async function createHelpRequest(input, deps = {}) {
150
154
  // EXCEPT its author, since answering your own request is not help.
151
155
  async function listOpenForBuilder({ builderId, crafts = [], limit = MAX_ROWS }, deps = {}) {
152
156
  const activePool = deps.pool || pool;
153
- const safeCrafts = (Array.isArray(crafts) ? crafts : []).filter((c) => HELP_CRAFTS.includes(c));
157
+ const safeCrafts = craftVocab.normalizeCrafts(Array.isArray(crafts) ? crafts : []).filter((c) => HELP_CRAFTS.includes(c));
154
158
  const { rows } = await activePool.query(
155
159
  `SELECT ${LIST_COLS} ${LIST_FROM}
156
160
  WHERE h.status = 'open'
@@ -193,7 +197,7 @@ async function countOpenForBuilder(builderId, deps = {}) {
193
197
  }
194
198
 
195
199
  // The DISCIPLINE_TO_CRAFT map as a SQL CASE, generated from the same object the
196
- // JS path uses — so the two can never disagree about what an 'engineer' is.
200
+ // JS path uses — so the two can never disagree about what an 'artisan' is.
197
201
  function craftMapSql(col) {
198
202
  const whens = Object.entries(DISCIPLINE_TO_CRAFT)
199
203
  .map(([d, c]) => `WHEN '${d}' THEN '${c}'`).join(' ');
@@ -293,7 +297,7 @@ async function settleFromLedger(deps = {}) {
293
297
  // The author exclusion stays too: your own settled asks are the OTHER list.
294
298
  async function listArchivedForBuilder({ builderId, crafts = [], limit = MAX_ROWS }, deps = {}) {
295
299
  const activePool = deps.pool || pool;
296
- const safeCrafts = (Array.isArray(crafts) ? crafts : []).filter((c) => HELP_CRAFTS.includes(c));
300
+ const safeCrafts = craftVocab.normalizeCrafts(Array.isArray(crafts) ? crafts : []).filter((c) => HELP_CRAFTS.includes(c));
297
301
  const { rows } = await activePool.query(
298
302
  `SELECT ${LIST_COLS} ${LIST_FROM}
299
303
  WHERE h.status <> 'open'
@@ -350,7 +354,7 @@ async function getHelpRequest(id, deps = {}) {
350
354
  // closing an ask is not accepting work, it is ending a question.
351
355
  async function setHelpRequestStatus({ id, builderId, status, crafts = [] }, deps = {}) {
352
356
  const activePool = deps.pool || pool;
353
- const safeCrafts = (Array.isArray(crafts) ? crafts : []).filter((c) => HELP_CRAFTS.includes(c));
357
+ const safeCrafts = craftVocab.normalizeCrafts(Array.isArray(crafts) ? crafts : []).filter((c) => HELP_CRAFTS.includes(c));
354
358
  const askerOnly = status === 'withdrawn';
355
359
  const { rows } = await activePool.query(
356
360
  `UPDATE lifecycle_help_requests
@@ -59,11 +59,11 @@ const MERGE_GATE = {
59
59
 
60
60
  const INSTANCE_LOOK = {
61
61
  title: 'Fill the instance look with your own LLM',
62
- // 'ideator' is a DISCIPLINE, not a kind — tasks_kind_check rejects it as a
62
+ // 'artist' is a DISCIPLINE, not a kind — tasks_kind_check rejects it as a
63
63
  // kind and the task silently vanished from the checklist (the one non-infra
64
64
  // kickoff step). Deciding the instance look is a decision by an ideator.
65
65
  kind: 'decision',
66
- discipline: 'ideator',
66
+ discipline: 'artist',
67
67
  description: 'init wrote a lean config/branding.json and left the LOOK to you (a locked project rule: brand with your ' +
68
68
  'own LLM, not a generator). Hand docs/branding-fill-prompt.md to your AI to design a palette/fonts/wordmark, or keep the vanilla look.',
69
69
  };
@@ -178,7 +178,7 @@ function genesisChecklist({ existing = false } = {}) {
178
178
  // The ref names the step, never its position, so the brownfield step slotting in
179
179
  // ahead of scope's own cannot re-key it into a duplicate.
180
180
  out[stage] = list.map((t) => ({
181
- ...t, kind: 'decision', discipline: 'ideator', ref: t === GENESIS_EXISTING_FIRST ? 'genesis:scope:existing' : `genesis:${stage}`,
181
+ ...t, kind: 'decision', discipline: 'artist', ref: t === GENESIS_EXISTING_FIRST ? 'genesis:scope:existing' : `genesis:${stage}`,
182
182
  }));
183
183
  }
184
184
  return out;
@@ -57,7 +57,7 @@ async function seedKickoffIfEmpty({ builderId = null } = {}, deps = {}) {
57
57
  description: t.description || '',
58
58
  status: 'ready',
59
59
  kind: t.kind || 'infra',
60
- discipline: t.discipline || 'engineer',
60
+ discipline: t.discipline || 'artisan',
61
61
  source: 'first-sign-in',
62
62
  createdBy: builderId,
63
63
  // BV1.R11 (task 1003598): this runs on an instance with zero tasks and, in
@@ -0,0 +1,29 @@
1
+ -- lifecycle_021_crafts_artisan_artist.sql — task 1005141 (BONGOS-V3 C1): the lifecycle
2
+ -- module's craft columns follow core_296's rename (engineer → artisan, ideator → artist).
3
+ --
4
+ -- lifecycle_help_requests.needs_craft spelled the engineering craft 'builder' (ADR 0174);
5
+ -- that seam closes here — it is 'artisan' like everywhere else. The CHECK is a superset
6
+ -- of old and new words so a rolled-back release can still write (see core_296).
7
+ --
8
+ -- lifecycle_mingle_pairs records the two crafts AS MATCHED, for audit, under a
9
+ -- cross-craft CHECK, in the help-request spelling. builder → artisan is renamed. An artist × ideator pair is left
10
+ -- exactly as recorded: the fold would make it artist × artist, which is both a false
11
+ -- record of why it was matched and a CHECK violation. The code normalizes on read.
12
+
13
+ BEGIN;
14
+
15
+ ALTER TABLE lifecycle_help_requests DROP CONSTRAINT IF EXISTS lifecycle_help_requests_craft_chk;
16
+ UPDATE lifecycle_help_requests SET needs_craft = 'artisan' WHERE needs_craft IN ('builder', 'engineer');
17
+ UPDATE lifecycle_help_requests SET needs_craft = 'artist' WHERE needs_craft = 'ideator';
18
+ ALTER TABLE lifecycle_help_requests ADD CONSTRAINT lifecycle_help_requests_craft_chk
19
+ CHECK (needs_craft IS NULL OR needs_craft IN ('artisan', 'artist', 'patron', 'ui', 'builder', 'ideator'));
20
+
21
+ UPDATE lifecycle_mingle_pairs SET craft_lo = 'artisan'
22
+ WHERE craft_lo IN ('builder', 'engineer') AND craft_hi <> 'artisan';
23
+ UPDATE lifecycle_mingle_pairs SET craft_hi = 'artisan'
24
+ WHERE craft_hi IN ('builder', 'engineer') AND craft_lo <> 'artisan';
25
+
26
+ INSERT INTO schema_migrations (version) VALUES ('lifecycle_021_crafts_artisan_artist')
27
+ ON CONFLICT (version) DO NOTHING;
28
+
29
+ COMMIT;
@@ -38,7 +38,7 @@
38
38
  //
39
39
  // THE CRAFT VOCABULARY IS NOT REDEFINED HERE. help-requests.js declares itself the
40
40
  // one place that knows how a craft maps to `builders.preferred_disciplines`
41
- // ('builder' the craft is 'engineer' the discipline), so this file consumes
41
+ // (and reads an old 'engineer'/'ideator' discipline as today's), so this file consumes
42
42
  // `craftMapSql` / `craftForDiscipline` from it rather than re-deriving. Two
43
43
  // spellings of the same craft would silently match nobody.
44
44
  //
@@ -67,14 +67,14 @@ const MAX_ROWS = 100;
67
67
  const MATCHING = Object.freeze({
68
68
  rule: 'cross_craft',
69
69
  description:
70
- 'A pair is two builders whose crafts do not overlap at all — an engineer with '
71
- + 'an artist or an ideator, never two of the same. The premise is that the '
70
+ 'A pair is two builders whose crafts do not overlap at all — an artisan with '
71
+ + 'an artist or a UI designer, never two of the same. The premise is that the '
72
72
  + 'person you need is often not another one of you.',
73
73
  // Stated because it is the road not taken, and an unexplained absence reads as
74
74
  // an oversight to the next person holding the matcher.
75
75
  rejected: 'same_craft — it duplicates the craft-matched Collab request path (criterion collab-page-matches-my-craft).',
76
76
  // DISJOINT, not merely "different". A builder who is both an artist and an
77
- // engineer shares a craft with every engineer, so pairing them would put two of
77
+ // artisan shares a craft with every artisan, so pairing them would put two of
78
78
  // the same in a room and call it cross-craft.
79
79
  overlap: 'none',
80
80
  consent: 'Both switches required: the project enables mingle AND the builder opts in. Either off means no pair.',
@@ -161,7 +161,7 @@ async function submitPageTweak({ taskId, builderId, versionId = null, freeze },
161
161
  // page does not wait for it.
162
162
  status: 'backlog',
163
163
  kind: 'feature',
164
- discipline: 'engineer',
164
+ discipline: 'artisan',
165
165
  priority: 4,
166
166
  touches: u.touches || [],
167
167
  source: u.source,
@@ -23,7 +23,7 @@ const hierarchy = require('../hierarchy');
23
23
  const { suggestCriteria } = require('../criterion-suggest');
24
24
  const { buildGoalAdvisory } = require('../goal-advisory');
25
25
  const { buildDependencyAdvisory } = require('../dependency-advisory');
26
- const { classifyTaskKind, VALID_KINDS, WRITABLE_DISCIPLINES, resolveWriteDiscipline, VOTABLE_TASK_STATUSES, TASK_FIELD_BOUNDS } = require('../task-classifier');
26
+ const { classifyTaskKind, VALID_KINDS, WRITABLE_DISCIPLINES, resolveWriteDiscipline, VOTABLE_TASK_STATUSES, TASK_FIELD_BOUNDS, normalizeDisciplineValue } = require('../task-classifier');
27
27
  const { isModuleKey, moduleKeys } = api.moduleScopeMap;
28
28
  const { validateOrRespond, LIMITS, parseId } = api;
29
29
  const { badKind, badNeeds, detectsMigration } = require('./task-route-helpers.js');
@@ -438,6 +438,8 @@ module.exports = function registerTaskWriteRoutes(router) {
438
438
  const hasParent = Object.prototype.hasOwnProperty.call(body, 'parent_task_id');
439
439
  const hasKind = Object.prototype.hasOwnProperty.call(body, 'kind');
440
440
  const hasDiscipline = Object.prototype.hasOwnProperty.call(body, 'discipline');
441
+ // An old craft spelling is today's craft (task 1005141): written as the new word.
442
+ if (hasDiscipline) body.discipline = normalizeDisciplineValue(body.discipline);
441
443
  // V3.R15 #239: newcomer_friendly is the canonical name; xenos_claimable is
442
444
  // the pre-rename alias and still accepted. Either presence triggers the
443
445
  // same update path.
@@ -63,7 +63,7 @@ const NO_PR_STATUS = Object.freeze({
63
63
  pr_state: 'none', merged: false, merge_sha: null, deploy_status: 'none', pr_url: null, pr_number: null, pr_title: null, pr_body: null,
64
64
  branch_hint: 'No pull request titled for this task is among the 100 most recent. Pass ?branch=<name> to ask about a specific branch.' });
65
65
  const seams = api;
66
- const { classifyTaskKind, VALID_KINDS, VALID_DISCIPLINES, VOTABLE_TASK_STATUSES, TASK_FIELD_BOUNDS } = require('../task-classifier');
66
+ const { classifyTaskKind, VALID_KINDS, VALID_DISCIPLINES, VOTABLE_TASK_STATUSES, TASK_FIELD_BOUNDS, normalizeDisciplineValue } = require('../task-classifier');
67
67
  const { isModuleKey, moduleKeys } = api.moduleScopeMap;
68
68
  const { validateOrRespond, LIMITS, parseId } = api;
69
69
  // The onboarding state machine + the evergreen newcomer-chore restock carved into
@@ -146,7 +146,7 @@ module.exports = function buildTasksRouter() {
146
146
  const CREATED_BY_MSG = "created_by must be a positive builder id, or 'none' for the unattributed.";
147
147
 
148
148
  router.get('/tasks', auth.requireBuilder, async (req, res) => {
149
- const { version, status, kind, discipline, goal_id } = req.query;
149
+ const { version, status, kind, goal_id } = req.query; const discipline = normalizeDisciplineValue(req.query.discipline); // old craft spellings read as today's (task 1005141)
150
150
  if (kind && !VALID_KINDS.has(kind)) {
151
151
  return badKind(res);
152
152
  }
@@ -203,7 +203,7 @@ module.exports = function buildTasksRouter() {
203
203
  router.get('/tasks/claimable',
204
204
  auth.requireBuilder,
205
205
  async (req, res) => {
206
- const { version, discipline, fit } = req.query;
206
+ const { version, fit } = req.query; const discipline = normalizeDisciplineValue(req.query.discipline);
207
207
  if (discipline && !VALID_DISCIPLINES.has(discipline)) {
208
208
  return res.fail('bad_discipline', 400, { valid: Array.from(VALID_DISCIPLINES) });
209
209
  }
@@ -223,9 +223,8 @@ function fmtMins(n) {
223
223
  // "Open the work" button firing a slash command that no longer resolves. A
224
224
  // module-contributed discipline still routes to its skill ('ui' -> /design).
225
225
  const DISCIPLINE_PACK = {
226
- engineer: 'docs/packs/engineer.md',
226
+ artisan: 'docs/packs/artisan.md',
227
227
  artist: 'docs/packs/artist.md',
228
- ideator: 'docs/packs/ideator.md',
229
228
  };
230
229
  const DISCIPLINE_SKILL = { ui: 'design' };
231
230
 
@@ -241,7 +240,7 @@ function nextActionFor(stage, { taskId, discipline, sandboxUrl } = {}) {
241
240
  if (skill) return { kind: 'prompt', label: 'Open the work', cmd: `/${skill}` };
242
241
  // Default to the Engineer pack for an unknown or unset discipline, the same way
243
242
  // this used to default to /dev: engineering is the project's default loop.
244
- const pack = DISCIPLINE_PACK[discipline] || DISCIPLINE_PACK.engineer;
243
+ const pack = DISCIPLINE_PACK[discipline] || DISCIPLINE_PACK.artisan;
245
244
  return { kind: 'prompt', label: 'Open the work', cmd: `Read ${pack} and start the work` };
246
245
  }
247
246
  return { kind: 'prompt', label: 'Continue', cmd: '/builder-start' };