@bongos/core 1.23.21 → 1.24.1

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 (457) hide show
  1. package/.bongos-core.json +983 -428
  2. package/.claude/skills/blocker-review/SKILL.md +9 -1
  3. package/.claude/skills/blocker-solve/SKILL.md +1 -1
  4. package/.claude/skills/builder-claim/SKILL.md +1 -1
  5. package/.claude/skills/builder-reauth/SKILL.md +1 -1
  6. package/.claude/skills/builder-redteam/SKILL.md +1 -1
  7. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  8. package/.claude/skills/builder-setup/SKILL.md +1 -1
  9. package/.claude/skills/builder-ship/SKILL.md +1 -1
  10. package/.claude/skills/collab-review/SKILL.md +5 -2
  11. package/.claude/skills/design/SKILL.md +1 -1
  12. package/.claude/skills/goal-create/SKILL.md +6 -3
  13. package/.claude/skills/grade-recover/SKILL.md +1 -1
  14. package/.claude/skills/merge-mode/SKILL.md +1 -1
  15. package/.claude/skills/owner-review/SKILL.md +1 -1
  16. package/.claude/skills/planning-session/SKILL.md +11 -11
  17. package/.claude/skills/read-session-export/SKILL.md +1 -1
  18. package/.claude/skills/recall/SKILL.md +1 -1
  19. package/.claude/skills/scan-before-install/SKILL.md +1 -1
  20. package/.claude/skills/session-handoff/SKILL.md +1 -1
  21. package/.claude/skills/strand-fix/SKILL.md +13 -0
  22. package/.claude/skills/worktree-clean/SKILL.md +1 -1
  23. package/AGENTS.md +15 -0
  24. package/GEMINI.md +5 -0
  25. package/clients/bongos-client/README.md +1 -1
  26. package/clients/bongos-client/bongos-client.global.js +94 -0
  27. package/clients/bongos-client/index.cjs +94 -0
  28. package/clients/bongos-client/index.d.ts +154 -4
  29. package/clients/bongos-client/index.mjs +94 -0
  30. package/docs/adr/0054-session-token-reward-cost-plus.md +3 -1
  31. package/docs/adr/0055-server-mediated-branch-publish.md +1 -1
  32. package/docs/adr/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md +87 -0
  33. package/docs/adr/0386-the-funding-wall-is-a-per-project-rule-claim-time-hold-by-default.md +37 -0
  34. package/docs/adr/0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md +39 -0
  35. package/docs/adr/0388-a-module-sale-is-split-by-the-project-the-buyer-pays-the-platform-fee.md +42 -0
  36. package/docs/adr/0392-ai-generation-runs-on-a-processor-held-balance-and-the-builders-own-keys.md +33 -0
  37. package/docs/adr/0393-a-platform-account-is-a-person-and-github-is-one-way-in.md +37 -0
  38. package/docs/adr/0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md +102 -0
  39. package/docs/adr/0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md +36 -0
  40. package/docs/adr/0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md +41 -0
  41. package/docs/adr/README.md +10 -1
  42. package/docs/api/openapi.json +8619 -5885
  43. package/docs/api-reference.md +68 -9
  44. package/docs/architecture.md +37 -1
  45. package/docs/canonical-permissions.md +1 -1
  46. package/docs/copy-inventory.md +420 -337
  47. package/docs/copy-registry.json +1321 -500
  48. package/docs/file-map.md +4 -0
  49. package/docs/module-api-changelog.md +7 -0
  50. package/docs/onboarding/diagrams/03-drachmae-karma.mmd +3 -3
  51. package/docs/onboarding/diagrams/README.md +1 -1
  52. package/docs/onboarding/drachmae-and-karma.md +15 -15
  53. package/docs/onboarding/primer.md +2 -2
  54. package/docs/page-inventory.json +55 -6
  55. package/docs/page-readings.json +1185 -1011
  56. package/docs/recipes/google-sign-in.md +60 -0
  57. package/docs/recipes/ops-gotchas.md +1 -1
  58. package/migrations/core_282_builders_lower_login_idx.sql +33 -0
  59. package/migrations/core_283_claimable_ignores_page_markers.sql +53 -0
  60. package/migrations/core_283_payout_approvals.sql +183 -0
  61. package/migrations/core_284_payout_gates.sql +179 -0
  62. package/migrations/core_285_task_needs.sql +74 -0
  63. package/modules/ai-studio/CLAUDE.md +15 -0
  64. package/modules/ai-studio/billing.js +67 -0
  65. package/modules/ai-studio/context.js +29 -0
  66. package/modules/ai-studio/db.js +172 -0
  67. package/modules/ai-studio/gemini.js +75 -0
  68. package/modules/ai-studio/generate.js +119 -0
  69. package/modules/ai-studio/keys.js +73 -0
  70. package/modules/ai-studio/migrations/ai_studio_001_keys.sql +22 -0
  71. package/modules/ai-studio/migrations/ai_studio_002_ledger.sql +48 -0
  72. package/modules/ai-studio/migrations/ai_studio_003_requests.sql +55 -0
  73. package/modules/ai-studio/module.json +26 -0
  74. package/modules/ai-studio/openrouter.js +90 -0
  75. package/modules/ai-studio/payments.js +105 -0
  76. package/modules/ai-studio/pricing.js +92 -0
  77. package/modules/ai-studio/routes/ai-studio.js +252 -0
  78. package/modules/ai-studio/studio.js +121 -0
  79. package/modules/autonomy/fence.js +64 -1
  80. package/modules/builder-settings/builder-needs.js +27 -1
  81. package/modules/copy-desk/routes/copy-desk.js +5 -0
  82. package/modules/economy/achievements.js +22 -10
  83. package/modules/economy/cost-classify.js +9 -0
  84. package/modules/economy/cost-write-guard.js +52 -0
  85. package/modules/economy/cost.js +2 -1
  86. package/modules/economy/credits.js +18 -15
  87. package/modules/economy/goal-reward.js +161 -0
  88. package/modules/economy/reward.js +4 -0
  89. package/modules/economy/routes/cost.js +33 -7
  90. package/modules/government/catalog.js +6 -0
  91. package/modules/government/db.js +108 -0
  92. package/modules/government/docket.js +36 -0
  93. package/modules/government/migrations/government_035_payout_approve.sql +29 -0
  94. package/modules/government/pollers/board-expiry.js +108 -20
  95. package/modules/government/protected-surfaces.json +1 -0
  96. package/modules/government/routes/government.js +50 -18
  97. package/modules/government/routing.js +39 -5
  98. package/modules/government/waiting.js +74 -0
  99. package/modules/grading/grader-subagent.js +30 -11
  100. package/modules/hall-ui/public/ai-studio-lib.js +268 -0
  101. package/modules/hall-ui/public/ai-studio.css +193 -0
  102. package/modules/hall-ui/public/ai-studio.html +98 -0
  103. package/modules/hall-ui/public/ai-studio.js +556 -0
  104. package/modules/hall-ui/public/ai-studio.states.json +94 -0
  105. package/modules/hall-ui/public/blockers-by-person.js +156 -0
  106. package/modules/hall-ui/public/blockers-lib.js +15 -1
  107. package/modules/hall-ui/public/blockers.html +8 -0
  108. package/modules/hall-ui/public/blockers.js +1 -1
  109. package/modules/hall-ui/public/city.html +15 -15
  110. package/modules/hall-ui/public/claim-action.js +3 -0
  111. package/modules/hall-ui/public/collab.html +12 -0
  112. package/modules/hall-ui/public/craft-mode.js +4 -1
  113. package/modules/hall-ui/public/criterion-uat-ui.js +4 -1
  114. package/modules/hall-ui/public/drachmae.html +1 -1
  115. package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea.jpg +0 -0
  116. package/modules/hall-ui/public/genesis/r5-life-2-first-cells-cut.webp +0 -0
  117. package/modules/hall-ui/public/genesis/r5-life-2-first-cells.jpg +0 -0
  118. package/modules/hall-ui/public/genesis/r5-life-3-genome-cut.webp +0 -0
  119. package/modules/hall-ui/public/genesis/r5-life-3-genome.jpg +0 -0
  120. package/modules/hall-ui/public/genesis/r5-life-4-membrane-cut.webp +0 -0
  121. package/modules/hall-ui/public/genesis/r5-life-4-membrane.jpg +0 -0
  122. package/modules/hall-ui/public/genesis/r5-life-5-nervous-system-cut.webp +0 -0
  123. package/modules/hall-ui/public/genesis/r5-life-5-nervous-system.jpg +0 -0
  124. package/modules/hall-ui/public/goal-inbox.js +1 -1
  125. package/modules/hall-ui/public/goals-render.js +4 -0
  126. package/modules/hall-ui/public/goals.html +8 -0
  127. package/modules/hall-ui/public/gov-living.js +6 -2
  128. package/modules/hall-ui/public/hall-ask.css +5 -0
  129. package/modules/hall-ui/public/hall-ask.js +39 -3
  130. package/modules/hall-ui/public/notes-panel.css +20 -0
  131. package/modules/hall-ui/public/notes-panel.js +146 -0
  132. package/modules/hall-ui/public/project-settings.html +1 -1
  133. package/modules/hall-ui/public/project-settings.js +7 -5
  134. package/modules/hall-ui/public/settings-ai-keys.js +124 -0
  135. package/modules/hall-ui/public/settings.html +15 -0
  136. package/modules/hall-ui/public/street-deck.js +12 -3
  137. package/modules/hall-ui/public/studio.css +90 -8
  138. package/modules/hall-ui/public/studio.html +39 -10
  139. package/modules/hall-ui/public/studio.js +148 -18
  140. package/modules/hall-ui/public/task.html +2 -0
  141. package/modules/hall-ui/public/task.js +4 -0
  142. package/modules/hall-ui/public/tweak-editor.js +12 -3
  143. package/modules/hall-ui/public/welcome-boot.css +42 -6
  144. package/modules/hall-ui/public/welcome-boot.js +20 -6
  145. package/modules/hall-ui/public/work.js +20 -0
  146. package/modules/hall-ui/records/ai-studio.md +36 -0
  147. package/modules/hall-ui/records/blockers-people.md +24 -0
  148. package/modules/lifecycle/db-analytics.js +10 -3
  149. package/modules/lifecycle/db-claims.js +32 -4
  150. package/modules/lifecycle/db-goals.js +48 -18
  151. package/modules/lifecycle/db-shared.js +9 -0
  152. package/modules/lifecycle/db-tasks.js +15 -6
  153. package/modules/lifecycle/db-versions.js +21 -8
  154. package/modules/lifecycle/done-when.js +23 -1
  155. package/modules/lifecycle/lifecycle.js +5 -0
  156. package/modules/lifecycle/migrations/lifecycle_017_notes.sql +46 -0
  157. package/modules/lifecycle/module.json +2 -1
  158. package/modules/lifecycle/notes.js +219 -0
  159. package/modules/lifecycle/page-notice.js +124 -0
  160. package/modules/lifecycle/page-tweak-artist.js +18 -1
  161. package/modules/lifecycle/publish-reconciler.js +67 -20
  162. package/modules/lifecycle/routes/claims.js +5 -2
  163. package/modules/lifecycle/routes/goals.js +3 -1
  164. package/modules/lifecycle/routes/notes.js +91 -0
  165. package/modules/lifecycle/routes/project-settings.js +43 -35
  166. package/modules/lifecycle/routes/task-route-helpers.js +21 -0
  167. package/modules/lifecycle/routes/task-write-routes.js +21 -3
  168. package/modules/lifecycle/routes/tasks.js +12 -12
  169. package/modules/lifecycle/ship-assurance.js +4 -1
  170. package/modules/lifecycle/ship-card.js +8 -2
  171. package/modules/lifecycle/task-needs-gate.js +70 -0
  172. package/modules/market-watch/CLAUDE.md +15 -0
  173. package/modules/market-watch/db.js +340 -0
  174. package/modules/market-watch/key.js +58 -0
  175. package/modules/market-watch/migrations/market_watch_001_tables.sql +249 -0
  176. package/modules/market-watch/migrations/market_watch_002_run.sql +46 -0
  177. package/modules/market-watch/module.json +26 -0
  178. package/modules/market-watch/pollers/hub-seed.js +54 -0
  179. package/modules/market-watch/pollers/weekly-run.js +100 -0
  180. package/modules/market-watch/research.js +350 -0
  181. package/modules/market-watch/run.js +245 -0
  182. package/modules/market-watch/seed/market-data.json +38 -0
  183. package/modules/market-watch/seed/partners-data.json +497 -0
  184. package/modules/market-watch/seed.js +252 -0
  185. package/modules/market-watch/store.js +230 -0
  186. package/modules/memory/routes/memory.js +14 -2
  187. package/modules/memory/routes/search.js +8 -2
  188. package/modules/onboarding/howto-moments.js +8 -1
  189. package/modules/platform-identity/account-logins.js +192 -0
  190. package/modules/platform-identity/migrations/platform_identity_034_account_logins.sql +96 -0
  191. package/modules/platform-identity/platform-identity.js +5 -4
  192. package/modules/platform-identity/routes/my-projects.js +6 -0
  193. package/modules/platform-identity/routes/profile.js +18 -0
  194. package/modules/platform-identity/routes/public-profile.js +10 -1
  195. package/modules/platform-identity/routes/sso.js +26 -1
  196. package/modules/platform-identity/tests/platform-identity.mjs +2 -1
  197. package/modules/provisioning/credential-preflight.js +13 -3
  198. package/modules/provisioning/pollers/app-liveness.js +25 -5
  199. package/modules/provisioning/routes/provisioning.js +1 -1
  200. package/modules/provisioning/starter-bundles.js +8 -2
  201. package/modules/public-landing/public/account.html +10 -2
  202. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg +0 -0
  203. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
  204. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
  205. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +1 -1
  206. package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp +0 -0
  207. package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp.json +1 -1
  208. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg +0 -0
  209. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
  210. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
  211. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +1 -1
  212. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg +0 -0
  213. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
  214. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
  215. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +1 -1
  216. package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp +0 -0
  217. package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp.json +1 -1
  218. package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp +0 -0
  219. package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp.json +1 -1
  220. package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp +0 -0
  221. package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp.json +1 -1
  222. package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp +0 -0
  223. package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp.json +1 -1
  224. package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp +0 -0
  225. package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp.json +1 -1
  226. package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp +0 -0
  227. package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp.json +1 -1
  228. package/modules/public-landing/public/assets/world/r3-space-nebula.jpg +0 -0
  229. package/modules/public-landing/public/assets/world/r3-space-nebula.jpg.json +1 -1
  230. package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
  231. package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +1 -1
  232. package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
  233. package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +1 -1
  234. package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg +0 -0
  235. package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg.json +1 -1
  236. package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp +0 -0
  237. package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp.json +1 -1
  238. package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg +0 -0
  239. package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg.json +1 -1
  240. package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp +0 -0
  241. package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp.json +1 -1
  242. package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg +0 -0
  243. package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg.json +1 -1
  244. package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp +0 -0
  245. package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp.json +1 -1
  246. package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg +0 -0
  247. package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg.json +1 -1
  248. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp +0 -0
  249. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp.json +1 -1
  250. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg +0 -0
  251. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg.json +1 -1
  252. package/modules/public-landing/public/community.html +1 -1
  253. package/modules/public-landing/public/connections.html +2 -2
  254. package/modules/public-landing/public/guild.html +1 -1
  255. package/modules/public-landing/public/invitations.html +2 -2
  256. package/modules/public-landing/public/projects.html +5 -5
  257. package/modules/raise/views/nda.html +2 -0
  258. package/modules/specialities/db.js +32 -1
  259. package/modules/specialities/routes/specialities.js +10 -6
  260. package/modules/status-ui/public/index.html +1 -1
  261. package/package-lock.json +2 -2
  262. package/package.json +1 -1
  263. package/release-notes.json +286 -0
  264. package/scripts/bongos/autobongos-tweak-lane.js +46 -1
  265. package/scripts/bongos/backfill-subagent-rewards.js +2 -2
  266. package/scripts/bongos/claim.js +22 -1
  267. package/scripts/bongos/db-backup-nightly.sh +29 -0
  268. package/scripts/bongos/deploy-divergence.js +7 -0
  269. package/scripts/bongos/doc-cli-guard.js +6 -0
  270. package/scripts/bongos/fitness.js +3 -2
  271. package/scripts/bongos/gen-diagrams.js +3 -3
  272. package/scripts/bongos/lib.sh +4 -5
  273. package/scripts/bongos/migration-namespace.js +13 -1
  274. package/scripts/bongos/plain-cards.js +4 -0
  275. package/scripts/bongos/provision-github-setup.js +3 -1
  276. package/scripts/bongos/provision-units.js +6 -1
  277. package/scripts/bongos/publish-manifest.js +4 -0
  278. package/scripts/bongos/release.js +2 -0
  279. package/scripts/bongos/run-unit-tests.js +268 -82
  280. package/scripts/bongos/seed-tasks.example.json +56 -0
  281. package/scripts/bongos/seed-tasks.js +222 -0
  282. package/scripts/bongos/seed-verify-criteria.js +1 -2
  283. package/scripts/bongos/ship-finish.js +2 -0
  284. package/scripts/bongos/ship-resume.js +2 -0
  285. package/scripts/bongos/strand-lease.js +250 -0
  286. package/scripts/bongos/strand-watch.js +129 -12
  287. package/scripts/bongos/version-literal-guard.js +2 -2
  288. package/scripts/discord/post.js +4 -3
  289. package/scripts/gds/seed-tasks.js +7 -0
  290. package/scripts/gds/{build-bongos.js → strand-lease.js} +3 -3
  291. package/scripts/hall-preview/server.js +21 -0
  292. package/src/bongos/auth-config.js +29 -1
  293. package/src/bongos/auth-discord.js +3 -3
  294. package/src/bongos/auth-github.js +3 -6
  295. package/src/bongos/auth-google.js +217 -0
  296. package/src/bongos/device-approval.js +140 -0
  297. package/src/bongos/llm-pricing.js +17 -1
  298. package/src/bongos/metrics.js +48 -2
  299. package/src/bongos/middleware/rate-limit.js +30 -11
  300. package/src/bongos/module-scope-map.js +16 -1
  301. package/src/bongos/optimizer.js +15 -1
  302. package/src/bongos/payout-approvals.js +342 -0
  303. package/src/bongos/payout-gates.js +364 -0
  304. package/src/bongos/prelaunch-gate.js +7 -9
  305. package/src/bongos/prelaunch-switch.js +20 -0
  306. package/src/bongos/route-rank-check.js +3 -0
  307. package/src/bongos/routes/auth-google.js +246 -0
  308. package/src/bongos/routes/auth.js +38 -1
  309. package/src/bongos/routes/backup.js +33 -9
  310. package/src/bongos/routes/builders.js +57 -30
  311. package/src/bongos/routes/github.js +9 -0
  312. package/src/bongos/routes/me.js +18 -0
  313. package/src/bongos/routes/my-payouts.js +133 -0
  314. package/src/bongos/routes/payout-approvals.js +312 -0
  315. package/src/bongos/routes/payout-gates.js +179 -0
  316. package/src/bongos/routes.js +15 -1
  317. package/src/bongos/secret-box.js +18 -3
  318. package/src/bongos/serve-internal.js +47 -19
  319. package/src/bongos/sign-in-pages.js +138 -0
  320. package/src/bongos/task-needs.js +146 -0
  321. package/src/bongos-downloads.js +18 -1
  322. package/src/instance-config.js +28 -0
  323. package/src/module-api.js +14 -1
  324. package/src/platform-server.js +27 -1
  325. package/tests/account_logins.mjs +326 -0
  326. package/tests/account_logins_db.mjs +161 -0
  327. package/tests/ai_studio_billing.mjs +253 -0
  328. package/tests/ai_studio_generate.mjs +282 -0
  329. package/tests/ai_studio_keys.mjs +159 -0
  330. package/tests/ai_studio_requests.mjs +319 -0
  331. package/tests/auth_backchannel_logout_route.mjs +201 -0
  332. package/tests/auto_satisfy_criteria.mjs +10 -1
  333. package/tests/autobongos_tweak_lane.mjs +144 -0
  334. package/tests/autobongos_tweak_pin_identity.mjs +137 -0
  335. package/tests/backfill_subagent_rewards.mjs +17 -1
  336. package/tests/backup_trigger_lock.mjs +119 -0
  337. package/tests/batched_writes_parity.mjs +340 -0
  338. package/tests/blockers_people.mjs +198 -0
  339. package/tests/bounded_rate_maps.mjs +184 -0
  340. package/tests/claimable_blocker_status.mjs +1 -1
  341. package/tests/claims_batch_routes.mjs +421 -0
  342. package/tests/conductor_main_e2e.mjs +4 -0
  343. package/tests/config_read_cache.mjs +171 -0
  344. package/tests/copy_desk_page_editor.mjs +12 -0
  345. package/tests/cost_write_guard.mjs +200 -0
  346. package/tests/craft_achievements.mjs +33 -10
  347. package/tests/criterion_goal_attach.mjs +1 -1
  348. package/tests/db_backup_nightly.mjs +73 -1
  349. package/tests/deploy_divergence_line.mjs +7 -0
  350. package/tests/effective_visibility_predicate.mjs +12 -0
  351. package/tests/fitness.mjs +19 -0
  352. package/tests/fixtures/per-row-loops-legacy.cjs +245 -0
  353. package/tests/fixtures/seed-tasks/bongos-v1-idea-routing.json +256 -0
  354. package/tests/fixtures/seed-tasks/bv1-profiles.json +592 -0
  355. package/tests/fixtures/seed-tasks/bv2-ideator-surface.json +373 -0
  356. package/tests/fixtures/seed-tasks/fake-api.mjs +80 -0
  357. package/tests/github_one_trip.mjs +34 -0
  358. package/tests/github_repo_visibility.mjs +20 -0
  359. package/tests/goal_achievement.mjs +30 -1
  360. package/tests/goal_achievement_reward.mjs +205 -0
  361. package/tests/goal_archive_disposition.mjs +5 -5
  362. package/tests/goal_sort_staleness.mjs +2 -1
  363. package/tests/goal_tier.mjs +11 -1
  364. package/tests/google_fonts_nonblocking.mjs +84 -0
  365. package/tests/google_sign_in.mjs +401 -0
  366. package/tests/government_board_expiry.mjs +94 -0
  367. package/tests/government_protected_surfaces.mjs +1 -1
  368. package/tests/government_routes.mjs +146 -23
  369. package/tests/grader.mjs +31 -0
  370. package/tests/hall_ai_studio.mjs +347 -0
  371. package/tests/hall_city.mjs +8 -0
  372. package/tests/hall_page_gate_map.mjs +3 -0
  373. package/tests/hall_studio_home.mjs +61 -2
  374. package/tests/home_attention_cards.mjs +1 -1
  375. package/tests/hook_shell_tools.mjs +8 -0
  376. package/tests/hooks_drain.mjs +1 -1
  377. package/tests/host_topology_skips.mjs +2 -0
  378. package/tests/howto_moments.mjs +17 -0
  379. package/tests/human_signal.mjs +141 -0
  380. package/tests/install_script_reader.mjs +51 -0
  381. package/tests/integration_fixture_ids.mjs +82 -0
  382. package/tests/invite_expiry_db.mjs +13 -1
  383. package/tests/lib_sh_resolution.mjs +0 -4
  384. package/tests/market_watch_data.mjs +244 -0
  385. package/tests/market_watch_data_db.mjs +308 -0
  386. package/tests/market_watch_module.mjs +114 -0
  387. package/tests/market_watch_run.mjs +503 -0
  388. package/tests/metrics.mjs +124 -0
  389. package/tests/migration_namespace.mjs +14 -0
  390. package/tests/module_api.mjs +2 -0
  391. package/tests/module_loader.mjs +1 -1
  392. package/tests/module_skills_hook.mjs +69 -0
  393. package/tests/notes.mjs +182 -0
  394. package/tests/own_scope_guard.mjs +5 -0
  395. package/tests/page_tweak_marker_sql.mjs +76 -0
  396. package/tests/page_tweak_no_block.mjs +135 -0
  397. package/tests/page_tweak_notice.mjs +103 -0
  398. package/tests/payout_approvals.mjs +253 -0
  399. package/tests/payout_approvals_db.mjs +106 -0
  400. package/tests/payout_gates.mjs +322 -0
  401. package/tests/payout_gates_db.mjs +113 -0
  402. package/tests/planning_session_skill.mjs +13 -3
  403. package/tests/project_settings_page.mjs +29 -5
  404. package/tests/project_settings_spend_cap_route.mjs +235 -0
  405. package/tests/promotion_sweep.mjs +29 -1
  406. package/tests/provision.mjs +3 -2
  407. package/tests/provisioning_app_status.mjs +18 -0
  408. package/tests/provisioning_credential_preflight.mjs +53 -0
  409. package/tests/provisioning_env_manifest_edit_delete.mjs +223 -0
  410. package/tests/public_landing_projects.mjs +37 -0
  411. package/tests/publish_reconciler_overlap.mjs +60 -0
  412. package/tests/rank_change_broadcast_port.mjs +151 -0
  413. package/tests/seed_tasks.mjs +176 -0
  414. package/tests/seed_verify_criteria.mjs +2 -6
  415. package/tests/session_start_freshness.mjs +66 -1
  416. package/tests/shell_hooks.mjs +209 -0
  417. package/tests/ship_assurance.mjs +28 -0
  418. package/tests/ship_card.mjs +20 -5
  419. package/tests/site_classified_once.mjs +142 -0
  420. package/tests/speciality_db_shape.mjs +23 -0
  421. package/tests/speciality_session_skills.mjs +48 -2
  422. package/tests/strand_lease.mjs +249 -0
  423. package/tests/strand_watch.mjs +58 -0
  424. package/tests/task_criteria_routes.mjs +275 -0
  425. package/tests/task_criteria_unlink_db.mjs +123 -0
  426. package/tests/task_needs.mjs +233 -0
  427. package/tests/task_needs_db.mjs +110 -0
  428. package/tests/unit_runner_serial.mjs +266 -43
  429. package/tests/version_close_route.mjs +1 -1
  430. package/tests/version_literals.mjs +5 -7
  431. package/tests/welcome_boot.mjs +16 -5
  432. package/tests/welcome_boot_browser.mjs +36 -4
  433. package/docs/recipes/bongos-cli-release.md +0 -66
  434. package/scripts/bongos/backfill-disciplines.js +0 -144
  435. package/scripts/bongos/backfill-session-earnings.js +0 -123
  436. package/scripts/bongos/backfill-session-types.js +0 -87
  437. package/scripts/bongos/build-bongos.js +0 -94
  438. package/scripts/bongos/cleanup-orphans.js +0 -74
  439. package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +0 -152
  440. package/scripts/bongos/seed-bv1-profiles-tasks.js +0 -135
  441. package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +0 -390
  442. package/scripts/bongos/smoke-batch-claim.sh +0 -238
  443. package/scripts/bongos/smoke-batch-release.sh +0 -316
  444. package/scripts/bongos/smoke-claim-consistency.sh +0 -222
  445. package/scripts/bongos/smoke-dependencies.sh +0 -143
  446. package/scripts/gds/backfill-disciplines.js +0 -7
  447. package/scripts/gds/backfill-session-earnings.js +0 -7
  448. package/scripts/gds/backfill-session-types.js +0 -7
  449. package/scripts/gds/cleanup-orphans.js +0 -7
  450. package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +0 -6
  451. package/scripts/gds/seed-bv1-profiles-tasks.js +0 -6
  452. package/scripts/gds/seed-bv2-ideator-surface-tasks.js +0 -6
  453. package/scripts/gds/smoke-batch-claim.sh +0 -8
  454. package/scripts/gds/smoke-batch-release.sh +0 -8
  455. package/scripts/gds/smoke-claim-consistency.sh +0 -8
  456. package/scripts/gds/smoke-dependencies.sh +0 -8
  457. package/tests/smoke_dependencies_witness.mjs +0 -46
@@ -0,0 +1,156 @@
1
+ // blockers-by-person.js — open blockers grouped UNDER THE PEOPLE they wait on (task 1004999).
2
+ //
3
+ // WHY. The Blockers page listed every open blocker under one heading, "Requires project
4
+ // owner fix", whoever it actually waited on. A blocker has no assignee; who it waits on
5
+ // is the seat its ask names (owner, a building's Lead, a board), and who sits in that
6
+ // seat changes. The server resolves it at read time with the City's own routing
7
+ // (GET /government/blockers/people), so this page and the City can never disagree about
8
+ // whose a blocker is. This file only draws the answer.
9
+ //
10
+ // The viewer's own group comes first, marked "You". A blocker that resolves to nobody is
11
+ // shown under "Nobody yet" — never dropped. A signed-out viewer, or one the read refuses,
12
+ // sees no section at all: the queue below stays the page.
13
+ //
14
+ // UMD like blockers-lib.js: the pure `peopleHtml` is required by
15
+ // tests/blockers_people.mjs; the mount runs only in a browser.
16
+ (function (root, factory) {
17
+ var mod = factory();
18
+ if (typeof module !== 'undefined' && module.exports) module.exports = mod;
19
+ else root.OTBBlockersPeople = mod;
20
+ }(typeof self !== 'undefined' ? self : this, function () {
21
+ 'use strict';
22
+
23
+ function nameOf(p) {
24
+ return (p && (p.name || p.login)) || (p && p.id != null ? 'Builder ' + p.id : 'Someone');
25
+ }
26
+
27
+ // "as the owner" · "as the Planning Lead" · "as the owner, since Planning has no Lead".
28
+ function asWords(item) {
29
+ var as = item.as || 'their seat';
30
+ if (item.fell_to_owner && /^lead:([a-z]+)$/.test(String(item.asked || ''))) {
31
+ var b = String(item.asked).slice(5);
32
+ return 'as ' + as + ', since ' + b.charAt(0).toUpperCase() + b.slice(1) + ' has no Lead';
33
+ }
34
+ return 'as ' + as;
35
+ }
36
+
37
+ function rowHtml(item, o) {
38
+ var esc = o.esc;
39
+ var title = esc(item.title || ('Blocker ' + item.id));
40
+ var href = o.blockerHref ? o.blockerHref(item.id) : null;
41
+ var titleHtml = href
42
+ ? '<a class="ov-row__title" href="' + esc(href) + '">' + title + '</a>'
43
+ : '<span class="ov-row__title">' + title + '</span>';
44
+ var facts = [esc(asWords(item))];
45
+ if (item.opened_at) facts.push('filed ' + esc(o.fmtDate(item.opened_at)));
46
+ var q = item.question ? '<span class="ov-row__sub">' + esc(item.question) + '</span>' : '';
47
+ return '<li class="ov-row ov-row--dense"><div class="ov-row__main">' + titleHtml + q
48
+ + '<div class="ov-row__facts"><span class="ov-row__when">' + facts.join(' · ') + '</span></div></div></li>';
49
+ }
50
+
51
+ function groupHtml(name, n, note, items, o) {
52
+ return '<div class="ov-group">'
53
+ + '<div class="ov-group__head"><span class="ov-group__name">' + o.esc(name) + '</span>'
54
+ + '<span class="ov-group__n">' + n + '</span>'
55
+ + (note ? '<span class="ov-group__note">' + o.esc(note) + '</span>' : '')
56
+ + '</div><ul class="ov-rows">' + items.map(function (it) { return rowHtml(it, o); }).join('') + '</ul></div>';
57
+ }
58
+
59
+ // peopleHtml(data, { esc, fmtDate, viewerId, blockerHref }) → markup, or '' when there
60
+ // is nothing to show. `data` is GET /government/blockers/people.
61
+ function peopleHtml(data, o) {
62
+ if (!o || typeof o.esc !== 'function' || typeof o.fmtDate !== 'function') {
63
+ throw new TypeError('peopleHtml: opts.esc and opts.fmtDate are required');
64
+ }
65
+ if (!data || !data.available) return '';
66
+ var people = Array.isArray(data.people) ? data.people.slice() : [];
67
+ var nobody = Array.isArray(data.nobody) ? data.nobody : [];
68
+ if (!people.length && !nobody.length) return '';
69
+ var me = o.viewerId == null ? null : String(o.viewerId);
70
+ people.sort(function (a, b) { return (a.person.id === me ? 0 : 1) - (b.person.id === me ? 0 : 1); });
71
+ var html = people.map(function (g) {
72
+ var mine = g.person.id === me;
73
+ return groupHtml(mine ? 'You' : nameOf(g.person), g.count, mine ? 'waiting on you' : '', g.items, o);
74
+ }).join('');
75
+ if (nobody.length) html += groupHtml('Nobody yet', nobody.length, 'no one sits in the seat it waits on', nobody, o);
76
+ return html;
77
+ }
78
+
79
+ // mineHtml(data, opts) → the viewer's own rows, for Collab's "waiting on you" block.
80
+ // data is GET /government/blockers/for-me ({ available, count, items }).
81
+ function mineHtml(data, o) {
82
+ if (!o || typeof o.esc !== 'function' || typeof o.fmtDate !== 'function') {
83
+ throw new TypeError('mineHtml: opts.esc and opts.fmtDate are required');
84
+ }
85
+ if (!data || !data.available || !Array.isArray(data.items) || !data.items.length) return '';
86
+ return data.items.map(function (it) { return rowHtml(it, o); }).join('');
87
+ }
88
+
89
+ var blockerHref = function (id) { return '#/blocker/' + encodeURIComponent(id); };
90
+
91
+ // Collab's block: only what waits on the viewer. Hidden when nothing does.
92
+ function mountMine(doc, kit) {
93
+ var section = doc.getElementById('cb-blockers-scroll');
94
+ var list = doc.getElementById('cb-blockers');
95
+ if (!section || !list) return;
96
+ fetch('/api/bongos/government/blockers/for-me', { credentials: 'same-origin' })
97
+ .then(function (r) { return r.ok ? r.json() : null; })
98
+ .catch(function () { return null; })
99
+ .then(function (data) {
100
+ var html = mineHtml(data, { esc: kit.esc, fmtDate: kit.fmtAbsDate, blockerHref: blockerHref });
101
+ if (!html) return;
102
+ list.innerHTML = html;
103
+ var n = doc.getElementById('cb-blockers-n');
104
+ if (n) n.textContent = String(data.count);
105
+ section.hidden = false;
106
+ });
107
+ }
108
+
109
+ // The browser half: fetch, draw, reveal. Any failure leaves the section hidden.
110
+ // Collab loads dom-utils but not the kit, so the two helpers fall back to the shell's.
111
+ function helpers() {
112
+ var kit = window.OTBKit || {};
113
+ var esc = kit.esc || (window.OTB && window.OTB.escapeHtml);
114
+ var fmtDate = kit.fmtAbsDate || function (iso) {
115
+ var d = new Date(iso);
116
+ return isNaN(d.getTime()) ? '' : d.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' });
117
+ };
118
+ return typeof esc === 'function' ? { esc: esc, fmtAbsDate: fmtDate } : null;
119
+ }
120
+
121
+ function mount() {
122
+ var doc = typeof document !== 'undefined' ? document : null;
123
+ var h = doc ? helpers() : null;
124
+ if (!h) return;
125
+ mountMine(doc, h);
126
+ var section = doc.getElementById('blockers-people-scroll');
127
+ var body = doc.getElementById('blockers-people-body');
128
+ var kit = h;
129
+ if (!section || !body) return;
130
+ var get = function (path) {
131
+ return fetch('/api/bongos' + path, { credentials: 'same-origin' })
132
+ .then(function (r) { return r.ok ? r.json() : null; })
133
+ .catch(function () { return null; });
134
+ };
135
+ Promise.all([get('/government/blockers/people'), get('/me')]).then(function (res) {
136
+ var data = res[0];
137
+ var me = res[1] && (res[1].builder || res[1]);
138
+ var html = peopleHtml(data, {
139
+ esc: kit.esc,
140
+ fmtDate: kit.fmtAbsDate,
141
+ viewerId: me && me.id != null ? me.id : null,
142
+ blockerHref: blockerHref,
143
+ });
144
+ if (!html) return;
145
+ body.innerHTML = html;
146
+ section.hidden = false;
147
+ });
148
+ }
149
+
150
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
151
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount);
152
+ else mount();
153
+ }
154
+
155
+ return { peopleHtml: peopleHtml, mineHtml: mineHtml, asWords: asWords };
156
+ }));
@@ -101,5 +101,19 @@
101
101
  };
102
102
  }
103
103
 
104
- return { blockingLine: blockingLine, blockerFacts: blockerFacts };
104
+ // Which human signal a blocker wears (task 1004946, owner 2026-10-28): every
105
+ // blocker waits on a person, but some ask them to DO a thing and some to
106
+ // CHOOSE. The stored ask (government_asks, on GET /blockers as `ask`) says
107
+ // which: two or more REAL options is a choice, a HUMAN DECISION. An option
108
+ // that only defers ("Not yet", "later") is no choice at all, so "Do it / Not
109
+ // yet" is a HUMAN TASK, as is a blocker with no ask. One rule for the Blockers
110
+ // page and Home's slice, so the two never disagree.
111
+ var DEFER_KEYS = /^(later|not[-_ ]?yet|notnow|not[-_ ]now|wait|defer|skip|no)$/i;
112
+ function humanKind(b) {
113
+ var opts = b && b.ask && Array.isArray(b.ask.options) ? b.ask.options : [];
114
+ var real = opts.filter(function (o) { return o && !DEFER_KEYS.test(String(o.key || '')); });
115
+ return real.length >= 2 ? 'decision' : 'task';
116
+ }
117
+
118
+ return { blockingLine: blockingLine, blockerFacts: blockerFacts, humanKind: humanKind };
105
119
  }));
@@ -33,6 +33,13 @@
33
33
  bar. task 1003456 added the write half: filing (any signed-in
34
34
  builder) below the heading, and per-row Resolve/Link (blocker.review
35
35
  — metic+archon) rendered by blockers.js when the viewer holds it. -->
36
+ <!-- task 1004999: the same open blockers, grouped under the people they wait
37
+ on (GET /government/blockers/people — the City's routing). Hidden until
38
+ that read answers; the queue below stays the page without it. -->
39
+ <section class="scroll" id="blockers-people-scroll" aria-labelledby="blockers-people-h" hidden>
40
+ <h2 id="blockers-people-h" class="scroll__h">Waiting on people</h2>
41
+ <div id="blockers-people-body"></div>
42
+ </section>
36
43
  <section class="scroll" id="blockers-scroll" aria-labelledby="blockers-h">
37
44
  <h2 id="blockers-h" class="scroll__h">Requires project owner fix<span class="count-pill" id="blockers-count" hidden></span></h2>
38
45
  <div id="blockers-file-area"></div>
@@ -67,5 +74,6 @@
67
74
  <script src="/builders/blockers-lib.js"></script>
68
75
  <script src="/builders/hall-ask.js"></script>
69
76
  <script src="/builders/blockers.js"></script>
77
+ <script src="/builders/blockers-by-person.js"></script>
70
78
  </body>
71
79
  </html>
@@ -147,7 +147,7 @@
147
147
  // bookkeeping, so it stays a plain control beside the facts (owner
148
148
  // 2026-10-23: a card only where the page asks the reader to decide).
149
149
  return window.OTBAsk.html({
150
- attrs: { 'data-id': id },
150
+ attrs: { 'data-id': id }, human: lib.humanKind(b),
151
151
  q: b.title || ('Blocker ' + id), qHref: blockerHref(id),
152
152
  saidHtml: facts.noteHtml + '<div class="ov-row__facts blockers-facts">' + facts.factsHtml
153
153
  + '<button type="button" class="btn-ghost blockers-facts__link" data-link-open="' + kit.esc(id) + '">Link a task</button></div>',
@@ -106,24 +106,24 @@
106
106
  </div>
107
107
 
108
108
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
109
- <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
110
- <script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
111
- <script src="/builders/shell.js?v=2026-10-25-escape"></script>
109
+ <script src="/builders/shell-trail.js?v=2026-10-08-city-tab" defer></script>
110
+ <script src="/builders/craft-mode.js?v=2026-10-09-living3" defer></script>
111
+ <script src="/builders/shell.js?v=2026-10-25-escape" defer></script>
112
112
  <script src="/builders/palette.js" defer></script>
113
- <script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
114
- <script src="/builders/gov-feeds.js?v=2026-10-09-living3"></script>
115
- <script src="/builders/city-draw.js?v=2026-10-17-tidy"></script>
113
+ <script src="/builders/gov-living.js?v=2026-10-09-positions1" defer></script>
114
+ <script src="/builders/gov-feeds.js?v=2026-10-09-living3" defer></script>
115
+ <script src="/builders/city-draw.js?v=2026-10-17-tidy" defer></script>
116
116
  <!-- a building's card answers its first ask in place through the deck's own
117
117
  route (task 1004702): the desk rows (street-desk.js over the street's kit)
118
118
  and the deck's card and sender (street-deck.js) -->
119
- <script src="/builders/hall-kit.js?v=2026-10-23-decide12"></script>
120
- <script src="/builders/street-draw.js?v=2026-10-09-living3"></script>
121
- <script src="/builders/street-card.js?v=2026-10-04-record1"></script>
122
- <script src="/builders/street-inside.js?v=2026-10-09-living3"></script>
123
- <script src="/builders/street-desk.js?v=2026-10-04-record1"></script>
124
- <script src="/builders/street-deck.js?v=2026-10-13-glass1"></script>
125
- <script src="/builders/city-decide.js?v=2026-10-23-decide12"></script>
126
- <script src="/builders/city-switchboard.js?v=2026-10-23-board5"></script>
127
- <script src="/builders/city.js?v=2026-10-23-decide12"></script>
119
+ <script src="/builders/hall-kit.js?v=2026-10-23-decide12" defer></script>
120
+ <script src="/builders/street-draw.js?v=2026-10-09-living3" defer></script>
121
+ <script src="/builders/street-card.js?v=2026-10-04-record1" defer></script>
122
+ <script src="/builders/street-inside.js?v=2026-10-09-living3" defer></script>
123
+ <script src="/builders/street-desk.js?v=2026-10-04-record1" defer></script>
124
+ <script src="/builders/street-deck.js?v=2026-10-13-glass1" defer></script>
125
+ <script src="/builders/city-decide.js?v=2026-10-23-decide12" defer></script>
126
+ <script src="/builders/city-switchboard.js?v=2026-10-23-board5" defer></script>
127
+ <script src="/builders/city.js?v=2026-10-23-decide12" defer></script>
128
128
  </body>
129
129
  </html>
@@ -89,6 +89,9 @@
89
89
  // a private goal, so this must not invent one either.
90
90
  case 'GOAL_MEMBERSHIP_REQUIRED':
91
91
  return errMessage(data) || 'That task belongs to a goal you are not a member of.';
92
+ // task 1004950: the task needs hands on the live server.
93
+ case 'NEEDS_SERVER_ACCESS':
94
+ return errMessage(data) || 'This task needs server access, which you do not have yet, so pick another task or ask the project owner for it.';
92
95
  case 'DEPS_NOT_SHIPPED':
93
96
  return 'That task waits on unshipped dependencies. Pick another for now.';
94
97
  // 10. the module-level sibling of TOUCHES_CONFLICT, and a REFUSAL where
@@ -63,6 +63,17 @@
63
63
  <button class="cb-ask__copy cb-copy-all" id="cb-for-me-copy" type="button" hidden></button>
64
64
  </section>
65
65
 
66
+ <!-- task 1004999: open blockers whose seat resolves to this viewer (as the
67
+ owner, a building's Lead, or a board member) — GET
68
+ /government/blockers/for-me, drawn by blockers-by-person.js. Hidden
69
+ until something waits on them. -->
70
+ <section class="scroll cb-block" id="cb-blockers-scroll" aria-labelledby="cb-blockers-h" hidden>
71
+ <div class="cb-block__head">
72
+ <h2 class="scroll__h" id="cb-blockers-h">Blockers waiting on you <span class="cb-block__n" id="cb-blockers-n"></span></h2>
73
+ </div>
74
+ <ul class="ov-rows" id="cb-blockers"></ul>
75
+ </section>
76
+
66
77
  <section class="scroll cb-block" aria-labelledby="cb-mine-h">
67
78
  <div class="cb-block__head">
68
79
  <h2 class="scroll__h" id="cb-mine-h">Your asks <span class="cb-block__n" id="cb-mine-n"></span></h2>
@@ -155,5 +166,6 @@
155
166
  <script src="/builders/palette.js" defer></script>
156
167
  <script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
157
168
  <script src="/builders/collab.js?v=2026-09-24-archive"></script>
169
+ <script src="/builders/blockers-by-person.js"></script>
158
170
  </body>
159
171
  </html>
@@ -750,7 +750,10 @@
750
750
  // one) opens it on arrival, in any mode.
751
751
  var note = /[?&]note=([1-9]\d{0,17})(?:&|$)/.exec((window.location && window.location.search) || '');
752
752
  var notes = toolFor('palette');
753
- if (note && notes) loadTool(notes).then(function (api) { api.openNote(note[1]); }, function () { /* the page still works */ });
753
+ // The Studio reads the doc as a full page of its own (studio.js), so the
754
+ // side sheet stays shut there.
755
+ var onStudio = document.body && document.body.getAttribute('data-page') === 'studio';
756
+ if (note && notes && !onStudio) loadTool(notes).then(function (api) { api.openNote(note[1]); }, function () { /* the page still works */ });
754
757
  }
755
758
  // Once a signed-in builder is known, ask — unless this session already has a
756
759
  // mode, already saw the picker, or the page opts out.
@@ -62,7 +62,10 @@
62
62
  const pill = (s === 'open' || s === 'legacy_satisfied') ? ''
63
63
  : `<span class="crit-state crit-state--${esc(s)}">${esc(STATE_LABELS[s])}</span>`;
64
64
  const tag = c && c.backend_only ? '<span class="crit-tag" title="No screen to record: this is signed off without a video">Backend-only</span>' : '';
65
- return pill + tag;
65
+ // task 1004922: a criterion waiting on a person to try it is a HUMAN TASK.
66
+ const ask = typeof window !== 'undefined' && window.OTBAsk;
67
+ const human = s === 'awaiting_uat' && ask ? ask.human('task') : '';
68
+ return human + pill + tag;
66
69
  }
67
70
 
68
71
  // The trailing action. canReview = the viewer may sign off (Metic+; the server
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Drachmae &amp; karma — {{worldName}}</title>
7
- <meta name="description" content="How you get paid and how you earn trust: the cost-plus {{currency}} reward, the kind multipliers, the monthly budget cap, and how karma differs from {{currency}}.">
7
+ <meta name="description" content="How your work is counted and how you earn trust: the cost-plus {{currency}} reward, the kind multipliers, the monthly budget cap, and how karma differs from {{currency}}.">
8
8
  <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>◈</text></svg>">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -522,7 +522,7 @@
522
522
  });
523
523
  // Was a link only; now it answers in place: the overlay is where it resolves.
524
524
  return window.OTBAsk.html({
525
- cls: 'ginbox__item',
525
+ cls: 'ginbox__item', human: lib.humanKind(b),
526
526
  where: 'Waiting on the project owner',
527
527
  q: b.title || `Blocker ${b.id}`, qHref: `#/blocker/${encodeURIComponent(b.id)}`,
528
528
  saidHtml: facts.factsHtml + facts.noteHtml,
@@ -336,6 +336,8 @@
336
336
  const kindTag = window.OTB.kindIsUnset(t.kind)
337
337
  ? ''
338
338
  : `<span class="task__tag">${escapeHtml(t.kind)}</span>`;
339
+ // task 1004950 — what the task needs beyond the build loop, said in words.
340
+ const needsTags = (Array.isArray(t.needs) ? t.needs : []).map((n) => `<span class="task__tag task__tag--needs">${n === 'owner' ? 'needs the project owner' : n === 'server' ? 'needs server access' : escapeHtml(n)}</span>`).join('');
339
341
  const newcomerTag = t.newcomer_friendly ? '<span class="task__tag task__tag--newcomer">newcomer</span>' : '';
340
342
  const rankTag = rankTagHtml(t.requires_rank);
341
343
  const touches = Array.isArray(t.touches) ? t.touches : [];
@@ -387,6 +389,7 @@
387
389
  ${kindTag}
388
390
  ${disciplineTag}
389
391
  ${newcomerTag}
392
+ ${needsTags}
390
393
  ${rankTag}
391
394
  ${t.priority ? `<span class="task__tag">${escapeHtml(t.priority)}</span>` : ''}
392
395
  <span>${minutesText}</span>
@@ -924,6 +927,7 @@
924
927
  // task 1953 — my own pending request (any viewer) + the goal's pending queue
925
928
  // (managers only); async, re-renders Actions + the pending panel on arrival.
926
929
  loadGoalMembership(g.id, canManage);
930
+ if (window.OTBNotes) window.OTBNotes.mount($('#goal-notes'), { goal_id: g.id }); // task 1004745
927
931
  void sat; void crit; // now read inside renderCompletion()
928
932
  }
929
933
  function renderActions(g, { joined, canManage }) {
@@ -17,6 +17,7 @@
17
17
  styles now live in style.css's hall-kit section. -->
18
18
  <link rel="stylesheet" href="/builders/hall-ask.css">
19
19
  <link rel="stylesheet" href="/builders/goals.css">
20
+ <link rel="stylesheet" href="/builders/notes-panel.css">
20
21
  </head>
21
22
  <body data-page="goals" data-page-title="Goals">
22
23
 
@@ -130,6 +131,12 @@
130
131
  </div>
131
132
  </div>
132
133
 
134
+ <!-- task 1004745: notes on the goal, grouped by purpose (notes-panel.js). -->
135
+ <div class="scroll goal-notes">
136
+ <h2 class="scroll__h">Notes</h2>
137
+ <div id="goal-notes"></div>
138
+ </div>
139
+
133
140
  <div class="scroll" style="margin-top:20px">
134
141
  <div class="goal-work__head">
135
142
  <h2 class="scroll__h">Tasks</h2>
@@ -271,6 +278,7 @@
271
278
  <script src="/builders/goal-author-lib.js"></script>
272
279
  <script src="/builders/goal-author.js"></script>
273
280
  <script src="/builders/hall-ask.js"></script>
281
+ <script src="/builders/notes-panel.js"></script>
274
282
  <script src="/builders/goals-render.js?v=2026-09-30-uat2"></script>
275
283
  <script src="/builders/goals-page.js?v=2026-09-30-uat2"></script>
276
284
  </body>
@@ -344,8 +344,12 @@
344
344
  const format = askFormat(human);
345
345
  // the server's route (task 1004705), when the docket was routed
346
346
  const served = ctx.routed ? obj(base.route) : null;
347
+ // task 1004999: the server names who sits in the seat now, so a card can say
348
+ // "waits on Lars, the owner" and not only "waits on the owner".
349
+ const servedPeople = served ? arr(served.people).filter((p) => p && p.id != null)
350
+ .map((p) => ({ id: str(p.id), login: str(p.login), name: str(p.name || p.login) })) : [];
347
351
  const route = served
348
- ? { seat: str(served.seat), label: str(served.label), person: null, fell_to_owner: !!served.fell_to_owner, members: [] }
352
+ ? { seat: str(served.seat), label: str(served.label), person: servedPeople[0] || null, people: servedPeople, fell_to_owner: !!served.fell_to_owner, members: servedPeople.map((p) => p.id) }
349
353
  : ctx.resolveSeat(human && human.waits_on, base.building);
350
354
  const nobody = served && obj(served.nobody) ? { why: str(served.nobody.why), outcome: served.nobody.outcome == null ? null : str(served.nobody.outcome) } : null;
351
355
  // an ask nobody answers has done what its "if nobody answers" line says
@@ -375,7 +379,7 @@
375
379
  human: human || null,
376
380
  format,
377
381
  shown: format.ok ? human : null,
378
- waits_on: { seat: route.seat, label: route.label, person: route.person || null, fell_to_owner: !!route.fell_to_owner, real: !!served },
382
+ waits_on: { seat: route.seat, label: route.label, person: route.person || null, people: route.people || (route.person ? [route.person] : []), fell_to_owner: !!route.fell_to_owner, real: !!served },
379
383
  needs_you: needsYou,
380
384
  nobody,
381
385
  // the card's words are the agent's own stored ask (task 1004703)
@@ -33,6 +33,11 @@ html[data-theme="dark"] body {
33
33
  .hask:last-child { margin-bottom: 0; }
34
34
  .hask__where { display: flex; align-items: center; gap: 8px; min-height: 24px; font-size: 13px; color: var(--ink-soft); min-width: 0; }
35
35
  .hask__dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
36
+ /* the human signal (task 1004922): a person on the hall's "needs you" tint,
37
+ HUMAN TASK or HUMAN DECISION; also drawn bare beside an Awaiting UAT row */
38
+ .hhuman { flex: none; display: inline-flex; align-items: center; gap: 7px; min-height: 28px; padding: 3px 12px 3px 8px; box-sizing: border-box; border-radius: 999px; background: var(--accent-tint); color: var(--accent-ink); font-family: var(--font-mono); font-size: 12.5px; font-weight: 700; letter-spacing: 0.07em; line-height: 1; white-space: nowrap; vertical-align: middle; }
39
+ .hhuman__i { flex: none; width: 20px; height: 20px; }
40
+ .hask__human { display: flex; align-items: center; min-height: 24px; }
36
41
  .hask__where-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
37
42
  .hask__q { margin: 0; font-size: 21px; font-weight: 300; letter-spacing: -0.02em; line-height: 1.2; overflow-wrap: anywhere; }
38
43
  .hask__q a { color: inherit; text-decoration: none; }
@@ -10,11 +10,20 @@
10
10
  // path: switching a row to the card changes how it looks, never what it calls.
11
11
  // An option with `href` is a link (an act that needs its own panel).
12
12
  //
13
- // OTBAsk.html({ where, q, qHref, said, saidHtml, tag, cls, attrs,
13
+ // OTBAsk.html({ where, q, qHref, said, saidHtml, tag, cls, attrs, human,
14
14
  // row: bool, opts: [{ label, result, rec, href, attrs }] })
15
15
  //
16
16
  // Every string is escaped here; `saidHtml` is the one pre-built slot, for a
17
17
  // caller whose facts are already escaped markup (blockerFacts).
18
+ //
19
+ // THE HUMAN SIGNAL (task 1004922, owner 2026-10-28). Everything that waits on
20
+ // a person says so with a person: HUMAN DECISION (a person at a fork) when
21
+ // they must choose, HUMAN TASK (a person with a hand up) when they must do
22
+ // something only a person can do. Every card is a decision unless the caller
23
+ // passes `human: 'task'` (a blocker's owner fix); `human: false` drops it.
24
+ // `OTBAsk.human(kind)` is the same badge for a surface that is not a card
25
+ // (the Awaiting UAT row). The chat half of the rule — `🙋 **HUMAN TASK:**` /
26
+ // `🔀 **HUMAN DECISION:**` — lives in docs/voice.md and the Conductor.
18
27
  (function () {
19
28
  // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
20
29
  // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
@@ -32,19 +41,46 @@
32
41
  : `<button class="${cls}" type="button"${attrsOf(o.attrs)}>${inner}</button>`;
33
42
  }
34
43
 
44
+ // 18px grid, 1.5 stroke, currentColor — the rail's glyph recipe.
45
+ const HUMAN = {
46
+ task: {
47
+ label: 'HUMAN TASK',
48
+ title: 'A person needs to do this',
49
+ svg: '<circle cx="8" cy="5" r="2.2"/><path d="M3.5 16v-2.5a4.5 4.5 0 0 1 9 0V16"/><path d="M12.5 9.5 14.5 3"/>',
50
+ },
51
+ decision: {
52
+ label: 'HUMAN DECISION',
53
+ title: 'A person needs to decide this',
54
+ svg: '<circle cx="7" cy="6" r="2.2"/><path d="M2.5 16v-2a4.5 4.5 0 0 1 9 0v2"/><path d="M13 2.5v3M13 5.5l-2 2.2M13 5.5l2 2.2"/>',
55
+ },
56
+ };
57
+
58
+ function human(kind) {
59
+ const h = HUMAN[kind];
60
+ if (!h) return '';
61
+ return `<span class="hhuman hhuman--${kind}" title="${h.title}">`
62
+ + `<svg class="hhuman__i" viewBox="0 0 18 18" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${h.svg}</svg>`
63
+ + `<span class="hhuman__t">${h.label}</span></span>`;
64
+ }
65
+
35
66
  function html(c) {
36
67
  const tag = c.tag || 'li';
37
68
  const opts = (c.opts || []).filter(Boolean);
38
69
  const q = c.qHref ? `<a href="${esc(c.qHref)}">${esc(c.q)}</a>` : esc(c.q);
70
+ const badge = human(c.human === undefined ? 'decision' : c.human);
71
+ const where = c.where ? `<span class="hask__where-t">${esc(c.where)}</span>` : '';
39
72
  return `<${tag} class="hask${c.cls ? ' ' + esc(c.cls) : ''}"${attrsOf(c.attrs)}>`
40
- + (c.where ? `<div class="hask__where"><span class="hask__dot" aria-hidden="true"></span><span class="hask__where-t">${esc(c.where)}</span></div>` : '')
73
+ // With a where line the badge leads it; without one (the Blockers page
74
+ // dropped its repeated eyebrow) the badge stands on a line of its own.
75
+ + (where ? `<div class="hask__where">${badge || '<span class="hask__dot" aria-hidden="true"></span>'}${where}</div>`
76
+ : (badge ? `<div class="hask__human">${badge}</div>` : ''))
41
77
  + `<h4 class="hask__q">${q}</h4>`
42
78
  + (c.saidHtml ? `<div class="hask__said">${c.saidHtml}</div>` : (c.said ? `<p class="hask__said">${esc(c.said)}</p>` : ''))
43
79
  + (opts.length ? `<div class="hask__opts${c.row ? ' hask__opts--row' : ''}">${opts.map(optHtml).join('')}</div>` : '')
44
80
  + `</${tag}>`;
45
81
  }
46
82
 
47
- const api = { html, esc };
83
+ const api = { html, esc, human };
48
84
  if (typeof window !== 'undefined') window.OTBAsk = api;
49
85
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
50
86
  })();
@@ -0,0 +1,20 @@
1
+ /* modules/hall-ui/public/notes-panel.css — the Notes panel on task and goal pages
2
+ (task 1004745). Notes sit in groups by purpose; each group is a small block with
3
+ its newest few notes and a fold for the rest. Uses the hall's tokens only. */
4
+
5
+ .np-group { margin: var(--sp-md) 0 0; }
6
+ .np-group__h { font-size: 14px; margin: 0 0 var(--sp-xs); display: flex; align-items: baseline; gap: var(--sp-sm); flex-wrap: wrap; }
7
+ .np-count { font-size: 12px; color: var(--ink-soft); }
8
+ .np-group__hint { font-size: 12px; font-weight: normal; color: var(--ink-soft); }
9
+ .np-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-sm); }
10
+ .np-note { border: var(--line) solid var(--border); border-radius: 8px; padding: var(--sp-sm) var(--sp-md); background: var(--surface); }
11
+ .np-note__body { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
12
+ .np-note__meta { margin: var(--sp-xs) 0 0; font-size: 12px; color: var(--ink-soft); display: flex; gap: var(--sp-md); flex-wrap: wrap; align-items: center; }
13
+ .np-move select { font-size: 12px; }
14
+ .np-link { background: none; border: 0; padding: 0; color: var(--accent); cursor: pointer; font: inherit; font-size: 12px; text-decoration: underline; }
15
+ .np-empty { color: var(--ink-soft); }
16
+ .np-form { margin: var(--sp-lg) 0 0; display: grid; gap: var(--sp-sm); }
17
+ .np-field { display: grid; gap: var(--sp-xs); font-size: 13px; }
18
+ .np-field textarea { width: 100%; box-sizing: border-box; font: inherit; }
19
+ .np-actions { display: flex; gap: var(--sp-md); align-items: end; flex-wrap: wrap; }
20
+ .np-field--inline { display: flex; align-items: center; gap: var(--sp-sm); }