@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
@@ -428,3 +428,32 @@ test('category: every bundled module declares one (core modules get categories t
428
428
  .filter((k) => !JSON.parse(fs.readFileSync(path.join(dir, k, 'module.json'), 'utf8')).category);
429
429
  assert.deepEqual(missing, [], `declare "category": { "parent": … } in: ${missing.join(', ')}`);
430
430
  });
431
+
432
+ // --- language (task 1004794) ---
433
+
434
+ test('language: absent defaults to javascript; "javascript" is accepted', () => {
435
+ const plain = baseManifest();
436
+ assert.equal(validateManifest(plain).valid, true);
437
+ const js = { ...baseManifest(), language: 'javascript' };
438
+ assert.equal(validateManifest(js).valid, true, validateManifest(js).errors.join('; '));
439
+ });
440
+
441
+ test('language: a value off the closed list is refused with the allowed list', () => {
442
+ for (const bad of ['python', 'JavaScript', '', 42, null]) {
443
+ const r = validateManifest({ ...baseManifest(), language: bad });
444
+ assert.equal(r.valid, false, `language ${JSON.stringify(bad)} should be refused`);
445
+ assert.ok(r.errors.some((e) => e.startsWith('language:') && e.includes('allowed: javascript')), r.errors.join('; '));
446
+ }
447
+ });
448
+
449
+ test('language: every module.json in the repo still validates (existing modules load unchanged)', async () => {
450
+ const fs = await import('node:fs');
451
+ const dir = path.join(ROOT, 'modules');
452
+ const keys = fs.readdirSync(dir).filter((k) => fs.existsSync(path.join(dir, k, 'module.json')));
453
+ assert.ok(keys.length > 10, 'expected the bundled modules, saw ' + keys.length);
454
+ for (const k of keys) {
455
+ const m = JSON.parse(fs.readFileSync(path.join(dir, k, 'module.json'), 'utf8'));
456
+ const r = validateManifest(m);
457
+ assert.equal(r.valid, true, `${k}: ${r.errors.join('; ')}`);
458
+ }
459
+ });
@@ -23,11 +23,11 @@ function t(name, fn) {
23
23
 
24
24
  t('an artist-craft builder gets the skills', () => {
25
25
  assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['artist'] } }), true);
26
- assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['engineer', 'artist'] } }), true);
26
+ assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['artisan', 'artist'] } }), true);
27
27
  });
28
28
 
29
29
  t('anyone else gets nothing (the 2026-09-02 rule holds)', () => {
30
- assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['engineer'] } }), false);
30
+ assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['artisan'] } }), false);
31
31
  assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: [] } }), false);
32
32
  assert.equal(wantsModuleSkills({ builder: {} }), false);
33
33
  });
package/tests/modules.mjs CHANGED
@@ -130,11 +130,15 @@ test('isModuleEnabled throws on an unknown module name (programmer error)', () =
130
130
  assert.throws(() => isModuleEnabled('nope', resolveModules({})), /unknown module/);
131
131
  });
132
132
 
133
- test('disciplines: engineer + artist + ideator are CORE — offered with no modules on', () => {
133
+ test('disciplines: artisan + artist are CORE — offered with no modules on', () => {
134
134
  // Ideation is methodology core (idea_inbox / capture / triage ship to every
135
135
  // instance). 'artist' joined them at task 1003719 / ADR 0271 — see the
136
136
  // CORE_DISCIPLINES comment for why the R59 module gate had stopped existing.
137
- assert.deepEqual(enabledDisciplines(resolveModules({})), ['engineer', 'artist', 'ideator']);
137
+ // Since task 1005141 (BONGOS-V3) engineer is 'artisan' and ideator folded into
138
+ // 'artist', so the core crafts are two, not three.
139
+ assert.deepEqual(enabledDisciplines(resolveModules({})), ['artisan', 'artist']);
140
+ assert.ok(!enabledDisciplines(resolveModules({})).includes('engineer'), 'the old spelling is not offered');
141
+ assert.ok(!enabledDisciplines(resolveModules({})).includes('ideator'), 'ideator is not a separate craft');
138
142
  });
139
143
 
140
144
  test('THE REGRESSION: every craft the DB CHECK accepts is offered by SOME instance shape', () => {
@@ -153,8 +157,10 @@ test('THE REGRESSION: every craft the DB CHECK accepts is offered by SOME instan
153
157
  const widest = new Set(enabledDisciplines(resolveModules({ instance: { modules: Object.fromEntries(
154
158
  Object.keys(EFFECTIVE).map((k) => [k, true])
155
159
  ) }, registry: EFFECTIVE })));
156
- // Mirrors the CHECK on builders.preferred_disciplines (migrations 086 + 176).
157
- for (const craft of ['engineer', 'artist', 'ideator', 'ui']) {
160
+ // Mirrors the CURRENT-spelling crafts in the CHECK on builders.preferred_disciplines
161
+ // (migrations 086 + 176, renamed by core_296). The CHECK also still admits the old
162
+ // 'engineer'/'ideator' for rollback — those are normalized on input, never offered.
163
+ for (const craft of ['artisan', 'artist', 'ui']) {
158
164
  assert.ok(
159
165
  widest.has(craft),
160
166
  `the DB accepts preferred_disciplines='${craft}' but no instance shape offers it — ` +
@@ -182,7 +188,7 @@ test('clientModules is a safe boolean projection covering every known module + d
182
188
  const c = clientModules(resolveModules({ instance: OTB, registry: SAMPLE_REG }));
183
189
  for (const k of KNOWN_MODULES) assert.equal(typeof c.enabled[k], 'boolean');
184
190
  assert.ok(Array.isArray(c.disciplines));
185
- assert.ok(c.disciplines.includes('engineer'));
191
+ assert.ok(c.disciplines.includes('artisan'));
186
192
  });
187
193
 
188
194
  test('the resolved set is frozen (callers cannot mutate the singleton)', () => {
@@ -249,11 +255,12 @@ test('a branded sibling (GDS_BRANDING_FILE set, no GDS_MODULES_FILE) defaults to
249
255
  // A non-default brand must fall through to the neutral (all-off) module set.
250
256
  const m = loadModules({ env: { GDS_BRANDING_FILE: 'config/branding.cloudbongos.json' } });
251
257
  for (const k of KNOWN_MODULES) assert.equal(m[k], false, `${k} must be off for a branded sibling`);
252
- // The three core crafts (engineer + artist + ideator — task 1003719 / ADR 0271),
258
+ // The two core crafts (artisan + artist — task 1003719 / ADR 0271, renamed and
259
+ // folded by task 1005141),
253
260
  // plus 'ui' from ui-design, which is default-on for EVERY instance ("every
254
261
  // instance has a face") and is not one of KNOWN_MODULES, so the all-off
255
262
  // assertion above never reaches it (task 1003487).
256
- assert.deepEqual(enabledDisciplines(m), ['engineer', 'artist', 'ideator', 'ui']);
263
+ assert.deepEqual(enabledDisciplines(m), ['artisan', 'artist', 'ui']);
257
264
  });
258
265
 
259
266
  // ── the DB override layer (task 1002857, migration core_248) ────────────────────
@@ -102,7 +102,8 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
102
102
  // task 1005051: Roadmap left it (folded into Goals; /roadmap redirects).
103
103
  // task 1005070: the remembered nav (the rail paints before /me).
104
104
  // task 1005088: the sidebar mark is drawn by reference to the one brand mark.
105
- const SHELL_SHA = 'e9c13c142bcd2a95';
105
+ // task 1005144: My agents, Project settings, Permissions and Modules left for the options menu.
106
+ const SHELL_SHA = 'a4d41b30b41e49f4';
106
107
 
107
108
  test('shell.js has not changed without its cache-bust being bumped', () => {
108
109
  const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
@@ -52,7 +52,7 @@ try {
52
52
  // core_233) and the seeds already hold that slot, so a 'planning' row here fails setup (task 1004861).
53
53
  await q(`INSERT INTO versions (id,name,status,track) VALUES ($1,'nr','building','internal') ON CONFLICT (id) DO NOTHING`, [VER]);
54
54
  await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank) VALUES ($1,'nr-w','nr-worker','NR Worker','thetes')`, [WORKER]);
55
- await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,shipped_by,discipline) VALUES ($1,$2,'nr fixture','confirmed',10,'feature',$3,'engineer')`, [TASK, VER, WORKER]);
55
+ await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,shipped_by,discipline) VALUES ($1,$2,'nr fixture','confirmed',10,'feature',$3,'artisan')`, [TASK, VER, WORKER]);
56
56
  await q(`INSERT INTO claims (task_id,builder_id,claimed_at) VALUES ($1,$2,now())`, [TASK, WORKER]);
57
57
 
58
58
  // 1) flag a CONFIRMED task → flagged, reason + at set, returns true.
@@ -23,11 +23,11 @@ function t(name, fn) {
23
23
 
24
24
  // ---- parseSeq ----
25
25
  t('parseSeq: matches discipline+template, rejects others', () => {
26
- assert.equal(r.parseSeq('tmpl:engineer:docs-hygiene-pass:5', 'engineer', 'docs-hygiene-pass'), 5);
27
- assert.equal(r.parseSeq('tmpl:engineer:docs-hygiene-pass:5', 'artist', 'docs-hygiene-pass'), null);
28
- assert.equal(r.parseSeq('tmpl:engineer:other:5', 'engineer', 'docs-hygiene-pass'), null);
29
- assert.equal(r.parseSeq('manual:engineer:x', 'engineer', 'docs-hygiene-pass'), null);
30
- assert.equal(r.parseSeq('tmpl:engineer:docs-hygiene-pass:nope', 'engineer', 'docs-hygiene-pass'), null);
26
+ assert.equal(r.parseSeq('tmpl:artisan:docs-hygiene-pass:5', 'artisan', 'docs-hygiene-pass'), 5);
27
+ assert.equal(r.parseSeq('tmpl:artisan:docs-hygiene-pass:5', 'artist', 'docs-hygiene-pass'), null);
28
+ assert.equal(r.parseSeq('tmpl:artisan:other:5', 'artisan', 'docs-hygiene-pass'), null);
29
+ assert.equal(r.parseSeq('manual:artisan:x', 'artisan', 'docs-hygiene-pass'), null);
30
+ assert.equal(r.parseSeq('tmpl:artisan:docs-hygiene-pass:nope', 'artisan', 'docs-hygiene-pass'), null);
31
31
  });
32
32
 
33
33
  // ---- buildDescription ----
@@ -40,22 +40,22 @@ t('buildDescription: folds done-when + "always one waiting" footer', () => {
40
40
 
41
41
  // ---- templateIdFromRef ----
42
42
  t('templateIdFromRef: extracts the template_id, honouring the discipline', () => {
43
- assert.equal(r.templateIdFromRef('tmpl:engineer:unit-test-pass:3', 'engineer'), 'unit-test-pass');
44
- assert.equal(r.templateIdFromRef('tmpl:engineer:unit-test-pass:3', 'artist'), null, 'discipline mismatch');
45
- assert.equal(r.templateIdFromRef('manual:engineer:x', 'engineer'), null, 'non-chore ref');
46
- assert.equal(r.templateIdFromRef('tmpl:engineer:unit-test-pass', 'engineer'), null, 'too few parts');
43
+ assert.equal(r.templateIdFromRef('tmpl:artisan:unit-test-pass:3', 'artisan'), 'unit-test-pass');
44
+ assert.equal(r.templateIdFromRef('tmpl:artisan:unit-test-pass:3', 'artist'), null, 'discipline mismatch');
45
+ assert.equal(r.templateIdFromRef('manual:artisan:x', 'artisan'), null, 'non-chore ref');
46
+ assert.equal(r.templateIdFromRef('tmpl:artisan:unit-test-pass', 'artisan'), null, 'too few parts');
47
47
  });
48
48
 
49
49
  // ---- loadTemplates: real templates.json (per-discipline LIST since task 1488) ----
50
50
  t('loadTemplates: real file is a per-discipline list of valid chores', () => {
51
51
  const tmpl = r.loadTemplates();
52
- for (const disc of ['engineer', 'artist', 'ideator']) {
52
+ for (const disc of ['artisan', 'artist', 'artist']) {
53
53
  assert.ok(Array.isArray(tmpl[disc]) && tmpl[disc].length >= 1, `${disc} is a non-empty list`);
54
54
  for (const c of tmpl[disc]) {
55
55
  assert.ok(c.template_id && c.version_id && c.title, `${disc}/${c.template_id || '?'} has id/version/title`);
56
56
  }
57
57
  }
58
- const engIds = tmpl.engineer.map((c) => c.template_id);
58
+ const engIds = tmpl.artisan.map((c) => c.template_id);
59
59
  assert.ok(engIds.includes('unit-test-pass') && engIds.includes('docs-hygiene-pass'),
60
60
  `engineer offers the unit-test chore + the docs sweep (got ${engIds.join(', ')})`);
61
61
  });
@@ -63,7 +63,7 @@ t('loadTemplates: real file is a per-discipline list of valid chores', () => {
63
63
  // ---- loadFloors: real config, floor >= 2 so one is always left on claim ----
64
64
  t('loadFloors: real config keeps floor >= 2 per discipline (a spare on every claim)', () => {
65
65
  const f = r.loadFloors();
66
- for (const disc of ['engineer', 'artist', 'ideator']) {
66
+ for (const disc of ['artisan', 'artist', 'artist']) {
67
67
  assert.ok(f[disc] >= 2, `${disc} floor ${f[disc]} must be >= 2 so one remains the instant one is claimed`);
68
68
  }
69
69
  });
@@ -147,7 +147,7 @@ t('restockForClaim threads its loaded map into the no-match fallback', () => {
147
147
  // ---- restockForClaim: skip path is DB-free (awaited) ----
148
148
  // (returns an array of per-template results; the skip path is a 1-element array.)
149
149
  assert.deepEqual(await r.restockForClaim(null), [{ skipped: true }]);
150
- assert.deepEqual(await r.restockForClaim({ source: 'auto-newcomer-floor-ai', discipline: 'engineer' }), [{ skipped: true }]);
150
+ assert.deepEqual(await r.restockForClaim({ source: 'auto-newcomer-floor-ai', discipline: 'artisan' }), [{ skipped: true }]);
151
151
  assert.deepEqual(await r.restockForClaim({ source: 'newcomer-floor-manual', discipline: 'artist' }), [{ skipped: true }]);
152
152
  assert.deepEqual(await r.restockForClaim({ source: 'newcomer-template', discipline: 'bogus' }), [{ skipped: true }]);
153
153
  passed += 1;
@@ -1,8 +1,8 @@
1
1
  // tests/page_notes_panel.mjs — the hall side of the artist's page feedback doc
2
2
  // (task 1004496): the docked review panel craft-mode.js puts at the right edge
3
- // in Artistry (task 1004771: no top-bar button), and the reader in page-notes.js. The server half is tests/page_notes.mjs.
3
+ // in Artist (Artistry until task 1005141; task 1004771: no top-bar button), and the reader in page-notes.js. The server half is tests/page_notes.mjs.
4
4
  //
5
- // Runs the REAL craft-mode.js over a small DOM: the panel docks in Artistry
5
+ // Runs the REAL craft-mode.js over a small DOM: the panel docks in Artist
6
6
  // alone and closes when the mode changes, page-notes.js loads once (same ?v as
7
7
  // craft-mode.js) and a ?note= link opens the doc in any mode. Then
8
8
  // the REAL page-notes.js reader: it escapes the doc and shows only this module's
@@ -75,19 +75,19 @@ function boot({ session = {}, search = '' } = {}) {
75
75
  }
76
76
  const flush = () => new Promise((r) => setTimeout(r, 0));
77
77
 
78
- test('THE PANEL: docked at the edge in Artistry alone, with no top-bar button, and closed when the mode changes', async () => {
79
- const p = boot({ session: { 'hall-craft': 'artistry' } });
78
+ test('THE PANEL: docked at the edge in Artist alone, with no top-bar button, and closed when the mode changes', async () => {
79
+ const p = boot({ session: { 'hall-craft': 'artist' } });
80
80
  await flush();
81
- assert.deepEqual(p.tools().map((t) => t.id), ['craft-tool-paintbrush'], 'the top bar keeps Edit words alone (task 1004771)');
82
- assert.equal(p.tools()[0].nextSibling, p.sw, 'right before the mode switch');
81
+ assert.deepEqual(p.tools().map((t) => t.id), ['craft-tool-paintbrush', 'craft-tool-lightbulb'], 'the top bar keeps Edit words, and the lightbulb that joined from Ideating (task 1005141) — no Review button (task 1004771)');
82
+ assert.equal(p.tools()[0].nextSibling, p.tools()[1], 'in TOOLS order');
83
+ assert.equal(p.tools()[1].nextSibling, p.sw, 'right before the mode switch');
83
84
  assert.deepEqual(p.opened, ['dock'], 'the review panel docks instead');
84
- for (const m of ['building', 'governing']) p.craft.choose(m);
85
- assert.deepEqual(p.opened, ['dock', 'close', 'close'], 'and leaves with Artistry');
86
- p.craft.choose('ideating');
87
- assert.deepEqual(p.tools().map((t) => t.id), ['craft-tool-lightbulb'], 'Ideating shows its lightbulb alone (task 1004498)');
88
- p.craft.choose('artistry');
85
+ for (const m of ['artisan', 'patron']) p.craft.choose(m);
86
+ assert.deepEqual(p.opened, ['dock', 'close', 'close'], 'and leaves with Artist');
87
+ assert.deepEqual(p.tools().map((t) => t.id), [], 'Artisan and Patron carry no tool, the lightbulb included');
88
+ p.craft.choose('artist');
89
89
  await flush();
90
- assert.equal(p.opened.filter((x) => x === 'dock').length, 2, 'back in Artistry it docks again');
90
+ assert.equal(p.opened.filter((x) => x === 'dock').length, 2, 'back in Artist it docks again');
91
91
  const none = boot();
92
92
  await flush();
93
93
  assert.equal(none.tools().length, 0, 'no tools with no mode at all');
@@ -95,9 +95,9 @@ test('THE PANEL: docked at the edge in Artistry alone, with no top-bar button, a
95
95
  });
96
96
 
97
97
  test('the panel script loads once, at the same version as craft-mode.js', async () => {
98
- const p = boot({ session: { 'hall-craft': 'artistry' } });
98
+ const p = boot({ session: { 'hall-craft': 'artist' } });
99
99
  await flush();
100
- p.craft.choose('artistry');
100
+ p.craft.choose('artist');
101
101
  await flush();
102
102
  assert.deepEqual(p.loaded, ['/builders/page-notes.js?v=2026-10-04-test']);
103
103
  });
@@ -106,7 +106,7 @@ test('a ?note= link opens that doc on arrival, in any mode', async () => {
106
106
  const p = boot({ search: '?note=42' });
107
107
  await flush();
108
108
  assert.deepEqual(p.opened, ['note 42']);
109
- const q = boot({ session: { 'hall-craft': 'governing' }, search: '?x=1&note=7' });
109
+ const q = boot({ session: { 'hall-craft': 'patron' }, search: '?x=1&note=7' });
110
110
  await flush();
111
111
  assert.deepEqual(q.opened, ['note 7']);
112
112
  const bad = boot({ search: '?note=7x' });
@@ -204,7 +204,7 @@ function dockedPanel({ reviewing = null, opened = false } = {}) {
204
204
  reviewing: () => reviewing,
205
205
  review: (id) => { reviewing = id; calls.push('review ' + id); },
206
206
  stopReview: () => { reviewing = null; calls.push('stop'); },
207
- experiences: () => [{ id: 'ideating', label: 'Ideator experience', short: 'Ideating' }, { id: 'building', label: 'Building experience', short: 'Building' }],
207
+ experiences: () => [{ id: 'patron', label: 'Patron experience', short: 'Patron' }, { id: 'artisan', label: 'Artisan experience', short: 'Artisan' }],
208
208
  iconHtml: () => '<svg></svg>',
209
209
  };
210
210
  const window = { OTB: realOTB(), location: { pathname: '/studio' }, OTBCraftMode: craft };
@@ -256,17 +256,17 @@ test('DOCKED: a touch never peeks, the tab click toggles, Escape and the X tuck
256
256
  assert.ok(p.collapsed(), 'docked, the X tucks it away too');
257
257
  assert.equal(p.sheet.parentNode, p.body, 'and leaves the tab on the page, the only way back in');
258
258
  p.api.close();
259
- assert.equal(p.sheet.parentNode, null, 'leaving Artistry (close) is what removes it');
259
+ assert.equal(p.sheet.parentNode, null, 'leaving Artist (close) is what removes it');
260
260
  });
261
261
 
262
262
  test('THE MODE ROW names each mode in the mode switch word and starts a review on a press', () => {
263
263
  const p = dockedPanel();
264
- assert.match(p.sheet.innerHTML, /<span>Ideating<\/span>/, 'the switch says Ideating, so the row does');
265
- assert.doesNotMatch(p.sheet.innerHTML, />Ideator</);
264
+ assert.match(p.sheet.innerHTML, /<span>Patron<\/span>/, 'the switch says Patron, so the row does');
265
+ assert.doesNotMatch(p.sheet.innerHTML, /Patron experience/, 'the switch word, never the long label');
266
266
  assert.doesNotMatch(p.sheet.innerHTML, /Stop reviewing/, 'no way out of a review that has not started');
267
- p.press('data-pn-mode', 'building');
268
- assert.ok(p.calls.includes('review building'));
269
- assert.match(p.sheet.querySelector('#pn-modes').innerHTML, /data-pn-mode="building" aria-pressed="true"/, 'the row repaints with the live one pressed');
267
+ p.press('data-pn-mode', 'artisan');
268
+ assert.ok(p.calls.includes('review artisan'));
269
+ assert.match(p.sheet.querySelector('#pn-modes').innerHTML, /data-pn-mode="artisan" aria-pressed="true"/, 'the row repaints with the live one pressed');
270
270
  assert.match(p.sheet.querySelector('#pn-modes').innerHTML, /Stop reviewing/);
271
271
  p.press('data-pn-mode', '');
272
272
  assert.ok(p.calls.includes('stop'), 'Stop reviewing ends it');
@@ -58,7 +58,7 @@ try {
58
58
  await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank) VALUES ($1,$2,$2,$2,'archon')`, [id, login]);
59
59
  }
60
60
  const seedTask = (id, title, status, touches, source = null) => q(
61
- `INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline,touches,source) VALUES ($1,$2,$3,$4,10,'feature','engineer',$5,$6)`,
61
+ `INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline,touches,source) VALUES ($1,$2,$3,$4,10,'feature','artisan',$5,$6)`,
62
62
  [id, VER, title, status, touches, source]
63
63
  );
64
64
 
@@ -1,4 +1,4 @@
1
- // tests/paintbrush.mjs — the Artistry paintbrush (task 1004497): a page's own
1
+ // tests/paintbrush.mjs — the Artist paintbrush (task 1004497): a page's own
2
2
  // words, edited where they stand, as a proposed wording change.
3
3
  //
4
4
  // Runs the REAL paintbrush.js, with the REAL tweak-editor-lib.js and
@@ -192,12 +192,12 @@ function boot({ round = null, draft = null, claim = { status: 200, body: { ok: t
192
192
  }
193
193
  const settle = () => new Promise((r) => setTimeout(r, 5));
194
194
 
195
- test('craft-mode.js puts the paintbrush beside the palette in Artistry, loading its own script', () => {
195
+ test('craft-mode.js puts the paintbrush beside the palette in Artist, loading its own script', () => {
196
196
  assert.match(CRAFT, /\{ id: 'paintbrush', icon: 'brush', short: 'Edit words', label: 'Edit this page\\'s words', script: 'paintbrush.js', api: 'OTBPaintbrush' \}/);
197
197
  const tools = /var TOOLS = \{([\s\S]*?)\n \};/.exec(CRAFT)[1];
198
- assert.match(tools, /^\s*artistry: \[/m, 'it is an Artistry tool');
199
- assert.doesNotMatch(tools, /building:|governing:/, 'Building and Governing have none');
200
- assert.doesNotMatch(/ideating: \[([\s\S]*?)\]/.exec(tools)[1], /paintbrush/, 'and the paintbrush is not Ideating\'s (task 1004498)');
198
+ assert.match(tools, /^\s*artist: \[/m, 'it is an Artist tool (Artistry until task 1005141)');
199
+ assert.doesNotMatch(tools, /artisan:|patron:/, 'Artisan and Patron have none');
200
+ assert.doesNotMatch(tools, /building:|artistry:|ideating:|governing:/, 'no tool list survives under a pre-rename mode id');
201
201
  });
202
202
 
203
203
  test('pressing it on a page nobody holds OFFERS the page — nothing is editable and nothing is written until the artist takes it', async () => {
@@ -398,7 +398,7 @@ test('emptying a line\'s words and leaving it marks it to be removed, instead of
398
398
  assert.deepEqual(JSON.parse(JSON.stringify(put.body.lines)), [{ key: 'L0002', remove: true }]);
399
399
  });
400
400
 
401
- test('REVIEW lists a removal as its own row, says it goes to an engineer, and SEND submits it with the rewrites', async () => {
401
+ test('REVIEW lists a removal as its own row, says it goes to an artisan, and SEND submits it with the rewrites', async () => {
402
402
  const p = held();
403
403
  p.brush.open();
404
404
  await settle();
@@ -409,7 +409,7 @@ test('REVIEW lists a removal as its own row, says it goes to an engineer, and SE
409
409
  await settle();
410
410
  const html = p.bar().innerHTML;
411
411
  assert.match(html, /pb__change--remove[\s\S]*Remove<\/span> See every goal/);
412
- assert.match(html, /A line you remove goes to an engineer to take out\./);
412
+ assert.match(html, /A line you remove goes to an artisan to take out\./);
413
413
  p.press('data-pb-send');
414
414
  await settle();
415
415
  const lastPut = p.writes().filter((c) => c.method === 'PUT').pop();
@@ -491,6 +491,7 @@ require.cache[moduleApiPath] = {
491
491
  loaded: true,
492
492
  exports: {
493
493
  pool,
494
+ crafts: require('../src/crafts.js'),
494
495
  parsePagination: helpers.parsePagination,
495
496
  validateOrRespond: helpers.validateOrRespond,
496
497
  corsPublicGet: () => (_req, _res, next) => next(),
@@ -4,7 +4,7 @@
4
4
  // you did, by role", including the idea trail.
5
5
  //
6
6
  // The done-when, as assertions: the owner opens their profile, switches to the
7
- // ideator view, sees EVERY idea they ever captured with what became of it, and
7
+ // artist view (the ideas trail), sees EVERY idea they ever captured with what became of it, and
8
8
  // can click through to the task a promotion produced.
9
9
  //
10
10
  // Five things are pinned here, and each one is a bug that is invisible in review:
@@ -212,7 +212,7 @@ await test('goals: a private goal is walled off in SQL, before the LIMIT', async
212
212
 
213
213
  await test("goals: a goal's craft is what its TASKS carry — no discipline column on goals", async () => {
214
214
  resetSql();
215
- await activity.activityGoalsByBuilder(95, { role: 'engineer' });
215
+ await activity.activityGoalsByBuilder(95, { role: 'artisan' });
216
216
  const sql = listSql().sql;
217
217
  assert.ok(/array_agg\(DISTINCT COALESCE\(t\.discipline/.test(sql), 'the craft set is derived from the tasks');
218
218
  assert.ok(/= ANY\(c\.disciplines\)/.test(sql), 'the craft filter tests that derived set');
@@ -304,7 +304,7 @@ seams.registerProvider('lifecycle', {
304
304
  recentShippedTasksByBuilder: async () => [],
305
305
  activityTasksByBuilder: async (id, opts) => {
306
306
  portCalls.push(['tasks', id, opts]);
307
- return { rows: [{ id: 42, title: 'A work', discipline: 'engineer' }], total: 1 };
307
+ return { rows: [{ id: 42, title: 'A work', discipline: 'artisan' }], total: 1 };
308
308
  },
309
309
  activityGoalsByBuilder: async (id, opts) => {
310
310
  portCalls.push(['goals', id, opts]);
@@ -440,7 +440,7 @@ const TRAIL = [
440
440
 
441
441
 
442
442
 
443
- await test('the ideator view shows EVERY idea filed, whatever became of it', async () => {
443
+ await test("the artist tab's idea trail shows EVERY idea filed, whatever became of it", async () => {
444
444
  const p = bootProfile();
445
445
  await settle(); await settle(); await settle();
446
446
  const html = p.htmlFor('activity-ideas');
@@ -469,33 +469,30 @@ await test('the other three outcomes each say what happened', async () => {
469
469
  });
470
470
 
471
471
  // Task 1004435 (WA6.RP05): one tab per role the person has, each its craft's
472
- // own record. The engineer tab is the works and goals, WHOLE (no craft filter:
473
- // most of the corpus is unclassified); the ideator tab is the trail; the artist
474
- // tab is the approved pages. No tab is "tasks carrying that label" any more.
472
+ // own record. The artisan tab is the works and goals, WHOLE (no craft filter:
473
+ // most of the corpus is unclassified); the artist tab (task 1005141 — the old
474
+ // ideator folded into it) is the approved pages AND the idea trail. No tab is
475
+ // "tasks carrying that label" any more.
475
476
  await test('each role tab asks the server for that craft\'s OWN record', async () => {
476
- const p = bootProfile({ roles: ['ideator', 'engineer', 'artist'] });
477
+ const p = bootProfile({ roles: ['artist', 'artisan'] });
477
478
  await settle(); await settle(); await settle();
478
- assert.ok(p.requests.some((u) => u.includes('/inbox/by-builder/')), 'the first tab (the ideator) reads the trail');
479
- assert.ok(!p.requests.some((u) => u.includes('section=goals')), 'and no goals ledger until the engineer tab opens');
480
- let before = p.requests.length;
481
- p.switchCraft('engineer');
479
+ assert.ok(p.requests.some((u) => u.includes('/inbox/by-builder/')), 'the first tab (the artist) reads the idea trail');
480
+ assert.ok(p.requests.some((u) => /\/copy-desk\/artists\/95\/pages\?limit=\d+&offset=0/.test(u)), 'and their approved pages, a few at a time');
481
+ assert.ok(!p.requests.some((u) => u.includes('role=artist')), 'never "tasks labelled artist"');
482
+ assert.ok(!p.requests.some((u) => u.includes('section=goals')), 'and no goals ledger until the artisan tab opens');
483
+ const before = p.requests.length;
484
+ p.switchCraft('artisan');
482
485
  await settle(); await settle();
483
- let after = p.requests.slice(before);
484
- assert.ok(after.some((u) => u.includes('section=tasks') && !u.includes('role=')), 'the engineer tab reads the whole works ledger');
486
+ const after = p.requests.slice(before);
487
+ assert.ok(after.some((u) => u.includes('section=tasks') && !u.includes('role=')), 'the artisan tab reads the whole works ledger');
485
488
  assert.ok(after.some((u) => u.includes('section=goals') && !u.includes('role=')), 'and the whole goals ledger');
486
- before = p.requests.length;
487
- p.switchCraft('artist');
488
- await settle(); await settle();
489
- after = p.requests.slice(before);
490
- assert.ok(after.some((u) => /\/copy-desk\/artists\/95\/pages\?limit=\d+&offset=0/.test(u)), 'the artist tab reads their approved pages, a few at a time');
491
- assert.ok(!after.some((u) => u.includes('role=artist')), 'never "tasks labelled artist"');
492
489
  });
493
490
 
494
491
  await test('a role the person does not have gets no tab and no read', async () => {
495
- const p = bootProfile({ roles: ['engineer'] });
492
+ const p = bootProfile({ roles: ['artisan'] });
496
493
  await settle(); await settle(); await settle();
497
- assert.ok(p.requests.some((u) => u.includes('section=tasks')), 'the only tab is the engineer\'s');
498
- assert.ok(!p.requests.some((u) => u.includes('/inbox/by-builder/')), 'no trail read for a person with no ideator role');
494
+ assert.ok(p.requests.some((u) => u.includes('section=tasks')), 'the only tab is the artisan\'s');
495
+ assert.ok(!p.requests.some((u) => u.includes('/inbox/by-builder/')), 'no trail read for a person with no artist role');
499
496
  assert.ok(!p.requests.some((u) => u.includes('/copy-desk/artists/')), 'no pages read for a person with no artist role');
500
497
  });
501
498
 
@@ -183,7 +183,7 @@ const PEOPLE = [
183
183
  end: '412 credits',
184
184
  },
185
185
  {
186
- who: 'ideator', id: 2002, name: 'Builder 9',
186
+ who: 'artist', id: 2002, name: 'Builder 9',
187
187
  lede: 'Builder 9 has never shipped a task. Every credit here came from ideas.',
188
188
  kinds: [['Ideas that became shipped work', '96'], ['Work split from their ideas', '38'], ['Ratified by the board', '30'], ['Full Ideas passed', '22']],
189
189
  end: '186 credits',
@@ -25,12 +25,12 @@ test('missingProfileFields: empty, whitespace, and absent all count as missing',
25
25
 
26
26
  test('missingProfileFields: a real value on either side drops only that entry', () => {
27
27
  assert.deepEqual(p.missingProfileFields({ bio: 'I build things', preferredDisciplines: [] }), ['disciplines']);
28
- assert.deepEqual(p.missingProfileFields({ bio: '', preferredDisciplines: ['engineer'] }), ['bio']);
29
- assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: ['engineer'] }), []);
28
+ assert.deepEqual(p.missingProfileFields({ bio: '', preferredDisciplines: ['artisan'] }), ['bio']);
29
+ assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: ['artisan'] }), []);
30
30
  });
31
31
 
32
32
  test('missingProfileFields: a non-array disciplines value is missing, not a crash', () => {
33
- assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: 'engineer' }), ['disciplines']);
33
+ assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: 'artisan' }), ['disciplines']);
34
34
  assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: {} }), ['disciplines']);
35
35
  });
36
36
 
@@ -47,7 +47,7 @@ test('nudgeDismissed: defaults to NOT dismissed; only a real boolean overrides',
47
47
 
48
48
  test('shouldNudge: shows only when something is missing AND not dismissed', () => {
49
49
  const missing = { bio: '', preferredDisciplines: [] };
50
- const complete = { bio: 'x', preferredDisciplines: ['engineer'] };
50
+ const complete = { bio: 'x', preferredDisciplines: ['artisan'] };
51
51
  assert.equal(p.shouldNudge({ behaviorPrefs: {}, ...missing }), true);
52
52
  assert.equal(p.shouldNudge({ behaviorPrefs: { profile_nudge_dismissed: true }, ...missing }), false);
53
53
  assert.equal(p.shouldNudge({ behaviorPrefs: {}, ...complete }), false);