@bongos/core 1.19.1073 → 1.19.1075

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 (377) hide show
  1. package/.bongos-core.json +404 -549
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/bin/bongos.js +1 -3
  4. package/clients/bongos-client/README.md +1 -1
  5. package/clients/bongos-client/bongos-client.global.js +4 -60
  6. package/clients/bongos-client/index.cjs +4 -60
  7. package/clients/bongos-client/index.d.ts +5 -90
  8. package/clients/bongos-client/index.mjs +4 -60
  9. package/config/branding.neutral.json +1 -5
  10. package/config/modules.neutral.json +2 -3
  11. package/config/scheduled-routines.json +0 -2
  12. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  13. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  14. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  15. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  16. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
  17. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  18. package/docs/adr/README.md +1 -0
  19. package/docs/api/openapi.json +175 -1665
  20. package/docs/api-reference.md +9 -41
  21. package/docs/architecture.md +16 -1
  22. package/docs/branding-contract.md +0 -3
  23. package/docs/copy-inventory.md +454 -479
  24. package/docs/copy-registry.json +746 -978
  25. package/docs/file-map.md +5 -34
  26. package/docs/module-api-changelog.md +4 -0
  27. package/docs/modules-contract.md +13 -1
  28. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  29. package/docs/onboarding/diagrams/README.md +3 -3
  30. package/docs/onboarding/diagrams/assertions.json +2 -22
  31. package/docs/onboarding/primer.md +9 -28
  32. package/docs/page-inventory.json +4 -1
  33. package/docs/page-readings.json +1032 -1077
  34. package/docs/recipes/render-pilot.md +29 -10
  35. package/migrations/core_257_module_store_registry.sql +87 -0
  36. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  37. package/modules/agents/module.json +1 -0
  38. package/modules/autonomy/module.json +1 -0
  39. package/modules/builder-settings/module.json +1 -0
  40. package/modules/builder-settings/render-prefs.js +4 -5
  41. package/modules/copy-desk/docx.js +78 -5
  42. package/modules/copy-desk/module.json +1 -0
  43. package/modules/copy-desk/routes/copy-desk.js +10 -10
  44. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  45. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  46. package/modules/discord/module.json +1 -0
  47. package/modules/discord/ship-broadcast.js +3 -3
  48. package/modules/economy/credits.js +2 -2
  49. package/modules/economy/module.json +1 -0
  50. package/modules/economy/routes/credits.js +1 -2
  51. package/modules/government/catalog.js +13 -17
  52. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  53. package/modules/government/module.json +1 -0
  54. package/modules/government/protected-surfaces.json +3 -11
  55. package/modules/government/resolver.js +3 -46
  56. package/modules/government/routes/government.js +0 -2
  57. package/modules/government/session-scopes.js +43 -83
  58. package/modules/government/session-scopes.json +8 -18
  59. package/modules/grading/module.json +1 -0
  60. package/modules/hall-ui/module.json +1 -0
  61. package/modules/hall-ui/public/board-lib.js +2 -2
  62. package/modules/hall-ui/public/builders.js +29 -40
  63. package/modules/hall-ui/public/collab.html +1 -1
  64. package/modules/hall-ui/public/collab.js +1 -1
  65. package/modules/hall-ui/public/diagrams.html +3 -3
  66. package/modules/hall-ui/public/dom-utils.js +1 -1
  67. package/modules/hall-ui/public/gate.js +2 -2
  68. package/modules/hall-ui/public/gate.states.json +1 -1
  69. package/modules/hall-ui/public/hall-render.js +43 -79
  70. package/modules/hall-ui/public/index.html +5 -1
  71. package/modules/hall-ui/public/modules.js +1 -0
  72. package/modules/hall-ui/public/oversight.css +5 -5
  73. package/modules/hall-ui/public/palette.js +0 -11
  74. package/modules/hall-ui/public/primer.js +1 -1
  75. package/modules/hall-ui/public/sessions.html +1 -1
  76. package/modules/hall-ui/public/settings-sessions.js +1 -1
  77. package/modules/hall-ui/public/settings.css +0 -1
  78. package/modules/hall-ui/public/settings.html +4 -33
  79. package/modules/hall-ui/public/settings.js +3 -33
  80. package/modules/hall-ui/public/settings.states.json +2 -2
  81. package/modules/hall-ui/public/studio.html +1 -1
  82. package/modules/hall-ui/public/style.css +10 -13
  83. package/modules/hall-ui/public/task.html +4 -1
  84. package/modules/hall-ui/public/task.js +36 -1
  85. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  86. package/modules/hall-ui/public/tweak-editor.css +16 -0
  87. package/modules/hall-ui/public/tweak-editor.html +26 -3
  88. package/modules/hall-ui/public/tweak-editor.js +32 -1
  89. package/modules/hall-ui/public/work.js +1 -1
  90. package/modules/hall-ui/records/tweak-editor.md +1 -0
  91. package/modules/ideas/module.json +1 -0
  92. package/modules/ideas/projection.js +1 -1
  93. package/modules/lifecycle/db-claim-reads.js +1 -58
  94. package/modules/lifecycle/db-ship.js +4 -9
  95. package/modules/lifecycle/db.js +0 -4
  96. package/modules/lifecycle/github-push.js +12 -14
  97. package/modules/lifecycle/kickoff-checklist.js +0 -4
  98. package/modules/lifecycle/lifecycle.js +0 -8
  99. package/modules/lifecycle/module.json +1 -0
  100. package/modules/lifecycle/publish-reconciler.js +10 -10
  101. package/modules/lifecycle/routes/tasks.js +13 -15
  102. package/modules/lifecycle/ship-card.js +1 -1
  103. package/modules/lifecycle/task-visuals.js +2 -2
  104. package/modules/memory/module.json +1 -0
  105. package/modules/memory/routes/memory.js +2 -2
  106. package/modules/npm-release/module.json +1 -0
  107. package/modules/onboarding/module.json +1 -0
  108. package/modules/onboarding/onboarding-state.js +17 -36
  109. package/modules/onboarding/routes/access-requests.js +3 -3
  110. package/modules/platform-identity/module.json +1 -0
  111. package/modules/platform-identity/platform-identity.js +13 -5
  112. package/modules/platform-identity/routes/sso.js +5 -4
  113. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  114. package/modules/provisioning/capacity.js +1 -1
  115. package/modules/provisioning/module.json +1 -0
  116. package/modules/provisioning/provisioning.js +6 -5
  117. package/modules/provisioning/routes/provisioning.js +4 -4
  118. package/modules/provisioning/starter-bundles.js +5 -25
  119. package/modules/public-landing/module.json +1 -0
  120. package/modules/public-landing/public/assets/cosmos.css +1 -1
  121. package/modules/public-landing/public/contact.html +1 -1
  122. package/modules/public-landing/public/privacy.html +1 -1
  123. package/modules/public-landing/public/projects.html +101 -32
  124. package/modules/public-landing/public/projects.probes.json +2 -2
  125. package/modules/public-landing/public/projects.states.json +4 -3
  126. package/modules/public-landing/public/terms.html +1 -1
  127. package/modules/security/module.json +1 -0
  128. package/modules/sessions/db.js +1 -1
  129. package/modules/sessions/module.json +1 -0
  130. package/modules/specialities/module.json +1 -0
  131. package/modules/status-ui/module.json +1 -0
  132. package/modules/ui-design/module.json +1 -0
  133. package/package-lock.json +2 -2
  134. package/package.json +1 -1
  135. package/release-notes.json +84 -0
  136. package/scripts/gds/artifact-format.js +203 -0
  137. package/scripts/gds/artifact-staleness.js +2 -2
  138. package/scripts/gds/audit-authorship.sh +1 -13
  139. package/scripts/gds/backfill-task-rewards.js +5 -5
  140. package/scripts/gds/claim.js +0 -15
  141. package/scripts/gds/claude-materialize.js +2 -2
  142. package/scripts/gds/cli-lib.js +15 -15
  143. package/scripts/gds/codemod-rename-src-gds.js +1 -1
  144. package/scripts/gds/context-pack.js +5 -7
  145. package/scripts/gds/diagram-facts.js +12 -23
  146. package/scripts/gds/do-api.js +13 -80
  147. package/scripts/gds/doc-cli-guard.js +2 -0
  148. package/scripts/gds/doctor.js +4 -4
  149. package/scripts/gds/feedback-latest.js +139 -9
  150. package/scripts/gds/fitness-checks-identity.js +2 -7
  151. package/scripts/gds/fitness-checks-packaging.js +4 -4
  152. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  153. package/scripts/gds/fitness-ratchets.js +5 -5
  154. package/scripts/gds/fitness.js +10 -10
  155. package/scripts/gds/gen-api-client.js +4 -4
  156. package/scripts/gds/gen-api-docs.js +1 -1
  157. package/scripts/gds/gen-diagrams.js +49 -71
  158. package/scripts/gds/gen-repo-map.js +13 -14
  159. package/scripts/gds/gen-session-index.js +8 -8
  160. package/scripts/gds/http-api-client.js +12 -19
  161. package/scripts/gds/init.js +1 -1
  162. package/scripts/gds/leak-scan-allowlist.js +1 -1
  163. package/scripts/gds/lint-infra-exec.js +1 -1
  164. package/scripts/gds/local-preview-lib.js +8 -39
  165. package/scripts/gds/local-preview.js +2 -15
  166. package/scripts/gds/migration-namespace.js +3 -3
  167. package/scripts/gds/module-artifact.js +212 -0
  168. package/scripts/gds/module.js +95 -6
  169. package/scripts/gds/package-core.js +5 -116
  170. package/scripts/gds/plain-cards.js +1 -1
  171. package/scripts/gds/provision-config.js +2 -2
  172. package/scripts/gds/provision-net.js +2 -3
  173. package/scripts/gds/provision.js +9 -9
  174. package/scripts/gds/publish-manifest.js +1 -2
  175. package/scripts/gds/push-path-brief.js +10 -10
  176. package/scripts/gds/regen-instance-docs.js +1 -1
  177. package/scripts/gds/rename-history-check.js +1 -1
  178. package/scripts/gds/run-unit-tests.js +2 -16
  179. package/scripts/gds/sandbox-stage.js +39 -341
  180. package/scripts/gds/session-digest-build.js +2 -2
  181. package/scripts/gds/ship-deploy-target.js +13 -13
  182. package/scripts/gds/ship-flow.js +7 -7
  183. package/scripts/gds/ship-land.js +9 -10
  184. package/scripts/gds/ship-merge.js +4 -4
  185. package/scripts/gds/ship-preflight-steps.js +4 -4
  186. package/scripts/gds/ship-regen.js +15 -49
  187. package/scripts/gds/ship.js +13 -33
  188. package/scripts/gds/skill-preflight.js +9 -8
  189. package/scripts/gds/start.js +0 -15
  190. package/scripts/hall-preview/README.md +5 -5
  191. package/scripts/hall-preview/server.js +6 -6
  192. package/scripts/render-diagrams.sh +1 -1
  193. package/src/bongos/api-errors.js +1 -1
  194. package/src/bongos/api-prefix.js +4 -4
  195. package/src/bongos/auth-github.js +2 -3
  196. package/src/bongos/auth.js +51 -126
  197. package/src/bongos/db-kernel.js +2 -3
  198. package/src/bongos/db.js +5 -28
  199. package/src/bongos/module-scope-map.js +14 -29
  200. package/src/bongos/module-store.js +141 -0
  201. package/src/bongos/platform-visibility-gate.js +1 -1
  202. package/src/bongos/route-rank-check.js +3 -16
  203. package/src/bongos/routes/auth.js +0 -116
  204. package/src/bongos/routes/builders.js +5 -15
  205. package/src/bongos/routes/instance.js +8 -14
  206. package/src/bongos/routes/me.js +7 -23
  207. package/src/bongos/routes/modules.js +83 -0
  208. package/src/bongos/routes/security.js +3 -3
  209. package/src/bongos/routes.js +7 -2
  210. package/src/bongos/serve-internal.js +5 -6
  211. package/src/bongos-downloads.js +1 -6
  212. package/src/branding.js +2 -2
  213. package/src/build-info.js +4 -6
  214. package/src/instance-config.js +2 -2
  215. package/src/module-api.js +13 -6
  216. package/src/module-loader/catalog.js +3 -0
  217. package/src/module-loader/loader.js +4 -4
  218. package/src/module-loader/manifest-schema.js +16 -8
  219. package/src/module-seams.js +2 -3
  220. package/src/modules.js +42 -14
  221. package/tests/agents_spend_guard.mjs +1 -1
  222. package/tests/api_alias_caller_ratchet.mjs +1 -1
  223. package/tests/api_alias_exceptions.mjs +1 -1
  224. package/tests/api_client.mjs +1 -16
  225. package/tests/api_path_404.mjs +1 -1
  226. package/tests/auth_page_gate.mjs +10 -10
  227. package/tests/backfill_task_rewards.mjs +1 -1
  228. package/tests/canonical_profile_url.mjs +1 -3
  229. package/tests/claim_action.mjs +4 -1
  230. package/tests/claim_from_task_and_home.mjs +127 -0
  231. package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
  232. package/tests/cli_sessions.mjs +1 -1
  233. package/tests/consumer_layout_boot.mjs +2 -0
  234. package/tests/context_pack.mjs +13 -13
  235. package/tests/copy_desk_page_docx.mjs +33 -2
  236. package/tests/copy_inventory.mjs +7 -7
  237. package/tests/core_upgrade_runner.mjs +6 -6
  238. package/tests/credit_grant.mjs +2 -2
  239. package/tests/criterion_suggest.mjs +1 -1
  240. package/tests/deploy_divergence_line.mjs +11 -11
  241. package/tests/design_tokens_sync.mjs +3 -2
  242. package/tests/diagram_facts_offset.mjs +1 -1
  243. package/tests/do_api.mjs +106 -0
  244. package/tests/feedback_latest.mjs +122 -0
  245. package/tests/fitness.mjs +44 -42
  246. package/tests/fitness_ratchets.mjs +2 -2
  247. package/tests/gate_approvals.mjs +0 -1
  248. package/tests/github_push_land_proof.mjs +1 -1
  249. package/tests/go_live.mjs +3 -3
  250. package/tests/goal_advisory.mjs +3 -3
  251. package/tests/goal_suggest.mjs +3 -3
  252. package/tests/government_abuse_matrix.mjs +21 -19
  253. package/tests/government_ownership_scope.mjs +33 -35
  254. package/tests/government_parity.mjs +2 -2
  255. package/tests/government_protected_surfaces.mjs +56 -37
  256. package/tests/government_require_permission.mjs +3 -3
  257. package/tests/government_seed.mjs +37 -4
  258. package/tests/government_session_scope.mjs +92 -366
  259. package/tests/hall_audit.mjs +1 -1
  260. package/tests/hall_error_envelope.mjs +4 -0
  261. package/tests/hall_landing_boot.mjs +4 -1
  262. package/tests/hall_palette.mjs +1 -16
  263. package/tests/hall_record_world.mjs +6 -0
  264. package/tests/hall_tweak_editor.mjs +52 -1
  265. package/tests/helpers.mjs +1 -1
  266. package/tests/host_topology_skips.mjs +3 -4
  267. package/tests/html_comment_nesting.mjs +137 -0
  268. package/tests/infra_exec_paths.mjs +9 -10
  269. package/tests/init.mjs +4 -5
  270. package/tests/instance_manifest.mjs +18 -25
  271. package/tests/kickoff_checklist.mjs +1 -1
  272. package/tests/lib_sh_resolution.mjs +0 -221
  273. package/tests/local_preview.mjs +0 -12
  274. package/tests/migration_namespace.mjs +6 -6
  275. package/tests/module-scope-map.mjs +9 -9
  276. package/tests/module_api.mjs +2 -2
  277. package/tests/module_catalog.mjs +1 -1
  278. package/tests/module_cli.mjs +20 -20
  279. package/tests/module_contributions.mjs +6 -6
  280. package/tests/module_loader.mjs +6 -6
  281. package/tests/module_manifest.mjs +26 -17
  282. package/tests/module_route_rank.mjs +2 -2
  283. package/tests/module_store_publish.mjs +307 -0
  284. package/tests/module_store_publish_route.mjs +121 -0
  285. package/tests/module_store_registry_migration.mjs +70 -0
  286. package/tests/modules.mjs +56 -26
  287. package/tests/modules_route_wiring.mjs +1 -1
  288. package/tests/onboarding_route_signals.mjs +21 -31
  289. package/tests/onboarding_state.mjs +35 -49
  290. package/tests/paste_token_session_store.mjs +1 -1
  291. package/tests/permission_path.mjs +0 -3
  292. package/tests/platform_boot.mjs +1 -2
  293. package/tests/platform_visibility_gate.mjs +1 -1
  294. package/tests/profile_route.mjs +1 -6
  295. package/tests/project_modules_ui.mjs +17 -17
  296. package/tests/projects_hub.mjs +5 -5
  297. package/tests/projects_hub_app_step.mjs +138 -0
  298. package/tests/projects_hub_module_picker.mjs +77 -76
  299. package/tests/projects_hub_pre_uat.mjs +4 -5
  300. package/tests/provision.mjs +7 -7
  301. package/tests/provisioning_capacity.mjs +1 -1
  302. package/tests/provisioning_recommendations.mjs +2 -1
  303. package/tests/provisioning_starter_bundles.mjs +0 -22
  304. package/tests/publish_branch_route.mjs +11 -19
  305. package/tests/publish_manifest.mjs +2 -3
  306. package/tests/publish_reconciler.mjs +8 -8
  307. package/tests/push_path_brief.mjs +10 -9
  308. package/tests/rank_tier_single_source.mjs +1 -13
  309. package/tests/regrade_eligibility.mjs +1 -1
  310. package/tests/rename_history_restraint.mjs +1 -1
  311. package/tests/repo_map.mjs +12 -13
  312. package/tests/runner_drift.mjs +3 -21
  313. package/tests/sandbox_stage.mjs +57 -390
  314. package/tests/seam_wiring_guard.mjs +11 -11
  315. package/tests/search_isolation.mjs +3 -3
  316. package/tests/session_records.mjs +1 -1
  317. package/tests/session_rename_fallback.mjs +1 -22
  318. package/tests/session_start_freshness.mjs +3 -3
  319. package/tests/session_token_hash_db.mjs +1 -1
  320. package/tests/ship_card.mjs +1 -1
  321. package/tests/ship_ci_deploy.mjs +17 -17
  322. package/tests/ship_error_shape.mjs +1 -1
  323. package/tests/ship_premerge.mjs +1 -1
  324. package/tests/ship_resume.mjs +1 -1
  325. package/tests/skill_preflight.mjs +2 -2
  326. package/tests/skip_is_not_pass.mjs +1 -1
  327. package/tests/task_visual_slots.mjs +31 -0
  328. package/tests/terms_acceptance.mjs +2 -2
  329. package/tests/upgrade.mjs +4 -4
  330. package/tests/watch_sealed_floor.mjs +0 -1
  331. package/tests/wizard_draft_resume.mjs +2 -2
  332. package/tests/wizard_intent_resume.mjs +21 -10
  333. package/tests/wizard_preselect_why.mjs +18 -18
  334. package/docs/onboarding/browser-terminal-guide.md +0 -73
  335. package/docs/recipes/managed-settings-remote-control.md +0 -416
  336. package/modules/dev-box/CLAUDE.md +0 -15
  337. package/modules/dev-box/box-access.js +0 -623
  338. package/modules/dev-box/box-credential.js +0 -94
  339. package/modules/dev-box/box-onboard.js +0 -486
  340. package/modules/dev-box/boxes.js +0 -1038
  341. package/modules/dev-box/db.js +0 -39
  342. package/modules/dev-box/module.json +0 -19
  343. package/modules/dev-box/routes/box.js +0 -1223
  344. package/scripts/gds/box-auth-check.js +0 -160
  345. package/scripts/gds/box-infra.js +0 -321
  346. package/scripts/gds/box-sync.js +0 -216
  347. package/scripts/gds/box.js +0 -1332
  348. package/scripts/gds/cf-tunnel.js +0 -558
  349. package/scripts/gds/smoke-box.sh +0 -150
  350. package/scripts/gds/tree-preflight.js +0 -241
  351. package/src/bongos/app-pair.js +0 -172
  352. package/tests/app_pair.mjs +0 -168
  353. package/tests/box_access.mjs +0 -578
  354. package/tests/box_auth_check.mjs +0 -114
  355. package/tests/box_code_staleness.mjs +0 -129
  356. package/tests/box_connect_e2e.mjs +0 -196
  357. package/tests/box_cost.mjs +0 -70
  358. package/tests/box_credential.mjs +0 -125
  359. package/tests/box_dns_repoint.mjs +0 -153
  360. package/tests/box_env.mjs +0 -64
  361. package/tests/box_host_keys.mjs +0 -126
  362. package/tests/box_infra_resolve.mjs +0 -246
  363. package/tests/box_onboard.mjs +0 -317
  364. package/tests/box_scope_session.mjs +0 -119
  365. package/tests/box_sweep_docs.mjs +0 -119
  366. package/tests/box_sync.mjs +0 -55
  367. package/tests/box_sync_scope_report.mjs +0 -126
  368. package/tests/box_task_scope.mjs +0 -169
  369. package/tests/box_task_scope_pause.mjs +0 -66
  370. package/tests/box_terminal_ssrf.mjs +0 -96
  371. package/tests/boxes.mjs +0 -1585
  372. package/tests/cf_ruleset_preflight.mjs +0 -142
  373. package/tests/cf_tunnel.mjs +0 -496
  374. package/tests/docs_scrubber_damage.mjs +0 -111
  375. package/tests/module_scope_active_claims.mjs +0 -94
  376. package/tests/ship_api_client_pathspec.mjs +0 -100
  377. package/tests/tree_preflight.mjs +0 -243
@@ -1197,14 +1197,9 @@
1197
1197
  // unchanged. Deep links activate their group first, so scrollIntoView lands on
1198
1198
  // a visible section.
1199
1199
 
1200
- // The Dev box group (box panel, desktop app, pairing) exists only where the
1201
- // dev-box module runs — a vanilla instance has no boxes to manage, so the
1202
- // whole group (rail entry included) stays out rather than showing an empty
1203
- // shell. RAIL_GROUPS derives from the items so a #dev-box anchor no-ops too.
1204
1200
  const RAIL_ITEMS = [
1205
1201
  { id: 'account', label: 'Account' },
1206
1202
  { id: 'access', label: 'Access & keys' },
1207
- ...(window.OTB.moduleOn('dev-box') ? [{ id: 'devbox', label: 'Dev box' }] : []),
1208
1203
  { id: 'preferences', label: 'Preferences' },
1209
1204
  { id: 'sound', label: 'Sound' },
1210
1205
  // task 1004296 — every project's version and its update; settings-software-update.js.
@@ -1213,7 +1208,7 @@
1213
1208
  const RAIL_GROUPS = RAIL_ITEMS.map((i) => i.id).concat('extensions');
1214
1209
  let rail = null;
1215
1210
 
1216
- // task 2172 — stable per-panel anchors (/settings#cli, /settings#dev-box, …)
1211
+ // task 2172 — stable per-panel anchors (/settings#cli, /settings#voices, …)
1217
1212
  // so skills and docs can deep-link a panel without depending on internal
1218
1213
  // section ids. #art-key predates the map and IS its section id (the hall's
1219
1214
  // "Add your key" action + the CLI nudge target it) — kept verbatim.
@@ -1225,7 +1220,6 @@
1225
1220
  'disciplines': 'craft-scroll',
1226
1221
  'cli': 'cli-scroll',
1227
1222
  'art-key': 'art-key',
1228
- 'dev-box-app': 'boxapp-scroll',
1229
1223
  'interaction': 'interaction-scroll',
1230
1224
  'wandering': 'wandering-scroll',
1231
1225
  'rendering': 'render-scroll',
@@ -1341,19 +1335,12 @@
1341
1335
  // DISPLAY NAME — the label beside your work (task 1669); seeded from /me.
1342
1336
  setupDisplayName(builder);
1343
1337
 
1344
- // DEV BOX APP — revealed once we know the viewer is a real builder,
1345
- // and only where the dev-box module runs (the group's rail entry is gated
1346
- // the same way above — task 2172).
1347
- if (window.OTB.moduleOn('dev-box')) {
1348
- $('#boxapp-scroll').hidden = false;
1349
- loadDevboxLatestVersion(); // #924: best-effort "latest version: X" courtesy line
1350
- }
1351
1338
  // CONNECTIONS — Discord (hidden unless the server has Discord configured).
1352
1339
  setupConnections(me && me.discord ? me.discord : null);
1353
1340
  // CLI ACCESS — re-issue a CLI bearer from this browser session.
1354
1341
  setupCliAccess();
1355
1342
  // MODULE PANELS — render any Settings panels modules contributed via the
1356
- // seam (BV1.R42). None today; ready for dev-box/discord/art once they leave core.
1343
+ // seam (BV1.R42). None today; ready for discord/art once they leave core.
1357
1344
  mountContributedPanels();
1358
1345
  // Reveal the Extensions rail entry only when a module actually contributed:
1359
1346
  // the kit rail re-renders from the item list, then re-marks the active group.
@@ -1383,27 +1370,10 @@
1383
1370
  // the `needs` art_key item that drives the covered/action-needed banner.
1384
1371
  await loadArtKey(me);
1385
1372
  // Every section has now loaded/revealed — land any panel-anchor deep link
1386
- // (/settings#cli, #dev-box, …) on its visible section.
1373
+ // (/settings#cli, #voices, …) on its visible section.
1387
1374
  applyHash();
1388
1375
  }
1389
1376
 
1390
- // #924: show the current published Dev Box app version next to the download
1391
- // buttons, read from the same public endpoint the app polls (/downloads/devbox/
1392
- // version → config/devbox-app.json). Best-effort: a fetch failure just leaves the
1393
- // courtesy line hidden — it must never break the settings page.
1394
- async function loadDevboxLatestVersion() {
1395
- try {
1396
- const r = await fetch('/downloads/devbox/version', { headers: { Accept: 'application/json' } });
1397
- if (!r.ok) return;
1398
- const v = await r.json();
1399
- if (v && v.version) {
1400
- const el = $('#boxapp-latest');
1401
- el.textContent = `Latest version: v${v.version}.`;
1402
- el.hidden = false;
1403
- }
1404
- } catch (_) { /* best-effort courtesy line — never break the page */ }
1405
- }
1406
-
1407
1377
  wireRail();
1408
1378
  load();
1409
1379
  })();
@@ -4,8 +4,8 @@
4
4
  "surface": "hall-ui",
5
5
  "stub": { "prefix": "/builders/" },
6
6
  "modeQuery": false,
7
- "ignoreRequests": ["/api/(gds|bongos)/", "/downloads/devbox/"],
8
- "ignoreConsole": ["/api/(gds|bongos)/", "/downloads/devbox/", "\\b401\\b", "\\b404\\b", "Unauthorized", "Not Found", "load failed", "Password field"],
7
+ "ignoreRequests": ["/api/(gds|bongos)/"],
8
+ "ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b404\\b", "Unauthorized", "Not Found", "load failed", "Password field"],
9
9
  "states": {
10
10
  "account": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#settings-h1", "#display-name-input", "#craft-chips", "#privacy-rows"] } },
11
11
  "access": { "auth": true, "url": "/settings#access", "actions": [["wait", 900]], "expect": { "visible": ["#cli-reissue-btn", "#sessions-list", "#artkey-card"] } },
@@ -189,7 +189,7 @@
189
189
  <script src="/builders/approval-queue.js"></script>
190
190
  <!-- The page names and the draft body (the tweak editor's pure rules), then
191
191
  the studio's own. -->
192
- <script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw10"></script>
192
+ <script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
193
193
  <script src="/builders/studio-lib.js?v=2026-09-28-tw16"></script>
194
194
  <script src="/builders/studio.js?v=2026-09-28-tw16"></script>
195
195
  </body>
@@ -230,7 +230,7 @@
230
230
  --z-drawer: 150;
231
231
  /* A confirm dialog is the one thing that must sit above the drawer: it is
232
232
  asking a question the drawer's content cannot answer. Added by task 1003307
233
- because harbor and gate had each guessed a bare number for exactly this
233
+ because two pages had each guessed a bare number for exactly this
234
234
  (200 and 50), which is what a missing layer looks like. */
235
235
  --z-modal: 160;
236
236
  --z-diagram-full: 9999;
@@ -1303,7 +1303,6 @@ html[data-fullscreen="on"] .fullscreen-toggle {
1303
1303
  .stat-tile:first-child { border-left: 0; }
1304
1304
  .stat-tile__label,
1305
1305
  .gate-stat__label,
1306
- .harbor-stat__label,
1307
1306
  .watch-stat__label {
1308
1307
  font-family: var(--label-face);
1309
1308
  font-size: var(--label-size);
@@ -1313,7 +1312,6 @@ html[data-fullscreen="on"] .fullscreen-toggle {
1313
1312
  }
1314
1313
  .stat-tile__num,
1315
1314
  .gate-stat__value,
1316
- .harbor-stat__value,
1317
1315
  .watch-stat__num {
1318
1316
  font-family: var(--font-display);
1319
1317
  font-size: clamp(26px, 2.6vw, 34px);
@@ -2433,11 +2431,6 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
2433
2431
  .setup__tab:first-child { padding-left: 0; }
2434
2432
  .setup__tab:hover { color: var(--ink); }
2435
2433
  .setup__tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }
2436
- .setup__tab-tag { font-family: var(--label-face); font-size: 10.5px; letter-spacing: var(--label-space); text-transform: var(--label-case); margin-left: 6px; color: var(--ink-faint); }
2437
- .setup__tab-tag--rec { color: var(--ok); }
2438
- .setup__pane { display: none; }
2439
- .setup[data-config="desktop"] .setup__pane--desktop { display: block; }
2440
- .setup[data-config="online"] .setup__pane--online { display: block; }
2441
2434
  .setup__steps { margin: 8px 0; padding-left: 20px; font-size: 13.5px; }
2442
2435
  .setup__steps li { margin: 6px 0; }
2443
2436
  /* The setup panel's prose links ("Git", "Claude Code", "primer") take the
@@ -3512,6 +3505,11 @@ a.stat-tile:hover .stat-tile__sub { color: var(--ink-soft); }
3512
3505
  .next-up__title { font-weight: 400; font-size: 14px; color: var(--ink); }
3513
3506
  .next-up__title:hover { color: var(--accent-ink); }
3514
3507
  .next-up__meta { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
3508
+ /* task 1003459: the row's Claim button reuses .task__claim's pill (the
3509
+ claimable card's own accent fill, style.css ~2769) rather than a second
3510
+ definition — align-self keeps it from stretching to the column's full
3511
+ width, which flex's default cross-axis stretch would otherwise do. */
3512
+ .next-up__item .task__claim { align-self: flex-start; margin-top: 4px; }
3515
3513
  .next-up__foot { margin: 14px 0 18px; font-size: 13px; }
3516
3514
 
3517
3515
  /* ---------- jump palette (task 2178) ----------
@@ -3638,16 +3636,16 @@ html.palette-open { overflow: hidden; }
3638
3636
  state is a HAIRLINE in its own colour with no fill, and a scope is a quiet
3639
3637
  mono chip. Neither is pressable, so neither takes the accent.
3640
3638
 
3641
- These live here because five pages had already invented them separately —
3642
- .gov-pill, .harbor-pill, .gate-pill, .badge, .mod-chip — each re-spelling the
3643
- label voice as literals. The shell had no neutral pill to take: .chip below is
3639
+ These live here because several pages had already invented them separately —
3640
+ .gov-pill, .gate-pill, .badge, .mod-chip — each re-spelling the label voice
3641
+ as literals. The shell had no neutral pill to take: .chip below is
3644
3642
  the accent-tinted FILTER chip, and it means "press me".
3645
3643
 
3646
3644
  The state colours are the hall's fixed verification vocabulary, never the
3647
3645
  world's warm accent states (ADR 0196 §2): a status must not re-tint when an
3648
3646
  instance re-brands. */
3649
3647
  /* The names below are the SAME pill, found by the task 1003307 audit:
3650
- government, harbor, gate and watch each invented one because the shell had
3648
+ government, gate and watch each invented one because the shell had
3651
3649
  none to take (modules had one too; its rows take .fact-pill by name since
3652
3650
  task 1003472). They are aliased rather than renamed because renaming would
3653
3651
  mean editing the JS row builders, and a class name is not what was wrong —
@@ -3655,7 +3653,6 @@ html.palette-open { overflow: hidden; }
3655
3653
  in their own sheets. */
3656
3654
  .fact-pill,
3657
3655
  .gov-pill,
3658
- .harbor-pill,
3659
3656
  .gate-pill,
3660
3657
  .badge {
3661
3658
  display: inline-flex;
@@ -69,6 +69,9 @@
69
69
  <script src="/builders/brand-apply.js?v=2026-06-19-r56" defer></script>
70
70
  <script src="/builders/md-reader.js" defer></script>
71
71
  <script src="/builders/hall-visual.js?v=2026-09-16-visual1" defer></script>
72
- <script src="/builders/task.js?v=2026-09-26-taskwhere" defer></script>
72
+ <!-- task 1003459: the hall's ONE claim/release action (task 1003458) — this
73
+ page's Claim it button consumes it rather than a second hand-rolled POST. -->
74
+ <script src="/builders/claim-action.js?v=2026-09-26-claimaction1" defer></script>
75
+ <script src="/builders/task.js?v=2026-09-28-taskclaim" defer></script>
73
76
  </body>
74
77
  </html>
@@ -142,6 +142,15 @@
142
142
  }
143
143
  }
144
144
 
145
+ // task 1003459 — the hall's ONE claim action (task 1003458), consumed here so
146
+ // a task's own page can claim it directly instead of only handing over the
147
+ // copyable `/builder-claim N` string. `load` is hoisted below; reusing it as
148
+ // the refresh means a successful (or 409-refused) claim re-renders the whole
149
+ // record from the server's own state, the same idiom every other write here
150
+ // already follows.
151
+ const { makeClaimAction } = window.OTBClaimAction;
152
+ const claimAction = makeClaimAction({ postJSON, toast, API, refresh: load });
153
+
145
154
  // task 759: run a lifecycle action (ship / confirm), then reload so the page
146
155
  // re-renders at the new status. On failure, surface the server's refusal and
147
156
  // re-enable the button. `load` is hoisted below.
@@ -711,7 +720,16 @@
711
720
  const ownsLastClaim = !!(viewer && lastClaim && lastClaim.github_login &&
712
721
  lastClaim.github_login === viewer.github_login);
713
722
  let lifecycle = '';
714
- if (t.status === 'confirmed' && (isSupervisor || ownsLastClaim)) {
723
+ // task 1003459 — the same door the Work board and Goals page already open.
724
+ // `ready` is the one status the claim gate can actually admit (backlog
725
+ // needs a promote first, blocked waits on a dependency, and both already
726
+ // say so in the lifecycle rail above) — every other refusal (rank, an
727
+ // owed rebase, a module/touches conflict, …) is still possible and still
728
+ // surfaces, because claimAction.claim() carries the same friendlyClaimError
729
+ // switch the board uses, not a second guess at it here.
730
+ if (t.status === 'ready') {
731
+ lifecycle = `<button class="td-btn td-btn--primary" type="button" id="claim-btn">Claim it</button>`;
732
+ } else if (t.status === 'confirmed' && (isSupervisor || ownsLastClaim)) {
715
733
  lifecycle = `<button class="td-btn td-btn--primary" type="button" id="ship-btn">Ship it</button>`;
716
734
  } else if (t.status === 'completed') {
717
735
  lifecycle = rank === 'archon'
@@ -823,6 +841,23 @@
823
841
  });
824
842
  }
825
843
 
844
+ const claimBtn = $('#claim-btn');
845
+ if (claimBtn) {
846
+ claimBtn.addEventListener('click', async () => {
847
+ if (claimBtn.disabled) return;
848
+ claimBtn.disabled = true;
849
+ claimBtn.textContent = '…';
850
+ const out = await claimAction.claim(t.id);
851
+ // A redirect to sign-in is already navigating away; re-enabling here
852
+ // would just invite a click that can't land (task.js's own pattern,
853
+ // runLifecycle above).
854
+ if (!out.ok && !out.redirected) {
855
+ claimBtn.disabled = false;
856
+ claimBtn.textContent = 'Claim it';
857
+ }
858
+ });
859
+ }
860
+
826
861
  const shipBtn = $('#ship-btn');
827
862
  if (shipBtn) {
828
863
  shipBtn.addEventListener('click', () => {
@@ -107,6 +107,23 @@
107
107
  return `uploaded · ${n} ${n === 1 ? 'line' : 'lines'} from your doc`;
108
108
  }
109
109
 
110
+ // A refused upload, as the panel says it (task 1004354): a headline naming
111
+ // what happened, the next step in plain words, and whether a fresh download
112
+ // is part of that step. The server's own what_this_means sits between them.
113
+ const REDOWNLOAD = 'Or download this page again, make your changes in the new file, and upload that.';
114
+ const UPLOAD_REFUSALS = Object.freeze({
115
+ wrong_page: { title: 'This file is a different page.', next: `Pick the file you downloaded from this page and upload it again. ${REDOWNLOAD}`, download: true },
116
+ reading_moved: { title: 'This page changed after you downloaded it.', next: 'Download this page again, copy your changes into the new file, and upload that.', download: true },
117
+ structure_changed: { title: 'Some lines in your file moved.', next: `Put the lines listed here back the way they were, one paragraph per line, and upload again. ${REDOWNLOAD}`, download: true },
118
+ not_a_docx: { title: 'That file is not a Word file.', next: 'Upload the .docx you downloaded here. From Google Docs, choose File, then Download, then Microsoft Word (.docx).', download: false },
119
+ upload_too_large: { title: 'That file is too big.', next: `Take out any pictures you pasted in and upload it again. ${REDOWNLOAD}`, download: true },
120
+ page_not_held: { title: 'Someone else is writing this page now.', next: 'Reload the editor to see who holds it. Your file is still on your computer.', download: false },
121
+ page_in_flight: { title: 'This page has already been submitted.', next: 'Reload the editor. Its words are frozen until it ships or is sent back.', download: false },
122
+ });
123
+ function uploadRefusal(code) {
124
+ return UPLOAD_REFUSALS[code] || { title: 'Your file was not saved.', next: `Try the upload again. ${REDOWNLOAD}`, download: true };
125
+ }
126
+
110
127
  // A page's name as the artist reads it ("Home", "Projects"). The inventory's
111
128
  // title is the page's <title>, and on the apex that is a branding template
112
129
  // ("{{productName}}: {{tagline}}"), which is not a name. So a templated title
@@ -131,7 +148,7 @@
131
148
  return PAGE_ID_RE.test(id) ? id : null;
132
149
  }
133
150
 
134
- const api = { NOTES, sectionsOf, isChanged, noteFor, draftBody, changedCount, counterText, initialValues, rowsFor, liveUrl, uploadedLabel, displayTitle, pageIdFrom };
151
+ const api = { NOTES, sectionsOf, isChanged, noteFor, draftBody, changedCount, counterText, initialValues, rowsFor, liveUrl, uploadedLabel, uploadRefusal, displayTitle, pageIdFrom };
135
152
  if (typeof window !== 'undefined') window.OTBTweakEditor = api;
136
153
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
137
154
  })();
@@ -404,6 +404,22 @@ html:not([data-fullscreen="on"]) .tw-bar { top: calc(var(--topbar-h, 56px) + 12p
404
404
  .tw-stage .tw-linkish:focus-visible { outline: 2px solid var(--tw-accent); outline-offset: 2px; }
405
405
  .tw-done__error { margin: 0; font-size: 14px; line-height: 1.5; color: var(--tw-link); }
406
406
 
407
+ /* A REFUSED UPLOAD (task 1004354). Not on the artboard, which has no failing
408
+ state: the "is in" panel's own glass and scrim, so a refusal is as
409
+ unmissable as a submit. It scrolls inside itself on a short phone rather
410
+ than hide its buttons below the window. */
411
+ .tw-refused .tw-done__panel { max-height: calc(100vh - 32px); overflow-y: auto; }
412
+ .tw-refused__list {
413
+ margin: 0;
414
+ padding-left: 18px;
415
+ font-size: 14px;
416
+ line-height: 1.5;
417
+ color: var(--tw-ink-soft);
418
+ }
419
+ .tw-refused__list li { margin: 2px 0; overflow-wrap: anywhere; }
420
+ .tw-refused__list[hidden] { display: none; }
421
+ .tw-refused__next { margin: 0; font-size: 15px; line-height: 1.5; color: var(--tw-ink); }
422
+
407
423
  /* PHONE WIDTH (not on the artboard, which is 1440 wide). The bar stops being
408
424
  one pill and becomes a glass card of two rows: where you are and how far,
409
425
  then the four doors, wrapping. The paper takes the width, and each margin
@@ -11,7 +11,7 @@
11
11
  <link rel="preconnect" href="https://fonts.googleapis.com">
12
12
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
13
  <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
14
- <link rel="stylesheet" href="/builders/tweak-editor.css?v=2026-09-28-tw10">
14
+ <link rel="stylesheet" href="/builders/tweak-editor.css?v=2026-09-28-tw23">
15
15
  </head>
16
16
  <!-- THE TWEAK EDITOR (task 1004321 / BV2.TW10, goal 1000095; ADR 0341 D4 and
17
17
  D11). docs/design/mocks/tweak-mode/Editor.dc.html is the master design:
@@ -93,6 +93,29 @@
93
93
  <div class="tw-picks" id="tw-picks" hidden></div>
94
94
  </div>
95
95
  </div>
96
+
97
+ <!-- A refused upload (task 1004354): the "is in" panel's glass over the
98
+ whole window, so it cannot scroll away. What happened, the lines
99
+ it names, and what to do. The notice on the paper stays after it
100
+ closes. -->
101
+ <div class="tw-done tw-refused" id="tw-refused" hidden>
102
+ <div class="tw-done__panel" role="alertdialog" aria-modal="true" aria-labelledby="tw-refused-title" aria-describedby="tw-refused-what">
103
+ <div class="tw-done__head">
104
+ <span class="tw-next__label">nothing was saved</span>
105
+ <h2 class="tw-done__title" id="tw-refused-title" tabindex="-1">Your file was not saved.</h2>
106
+ <p class="tw-done__sub" id="tw-refused-what"></p>
107
+ </div>
108
+ <ul class="tw-refused__list" id="tw-refused-list" hidden></ul>
109
+ <div class="tw-next">
110
+ <span class="tw-next__label">what to do</span>
111
+ <p class="tw-refused__next" id="tw-refused-next"></p>
112
+ </div>
113
+ <div class="tw-done__acts">
114
+ <button class="tw-take" id="tw-refused-download" type="button">download this page again</button>
115
+ <button class="tw-linkish" id="tw-refused-close" type="button">close</button>
116
+ </div>
117
+ </div>
118
+ </div>
96
119
  </main>
97
120
 
98
121
  <footer class="app-foot" id="app-foot"></footer>
@@ -105,7 +128,7 @@
105
128
  <script src="/builders/shell.js?v=2026-09-28-archive"></script>
106
129
  <script src="/builders/palette.js" defer></script>
107
130
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
108
- <script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw10"></script>
109
- <script src="/builders/tweak-editor.js?v=2026-09-28-tw10"></script>
131
+ <script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
132
+ <script src="/builders/tweak-editor.js?v=2026-09-28-tw23"></script>
110
133
  </body>
111
134
  </html>
@@ -244,6 +244,34 @@
244
244
  const msg = (d && d.what_this_means) || errMessage(data) || fallback;
245
245
  const items = refusalItems(d || {});
246
246
  notice(`<p>${escapeHtml(msg)}</p>${items.length ? `<ul>${items.map((i) => `<li>${escapeHtml(i)}</li>`).join('')}</ul>` : ''}`);
247
+ return { msg, items };
248
+ }
249
+
250
+ // A refused UPLOAD also opens the panel over the whole window (task
251
+ // 1004354): an artist uploads from the bar, often far down the page, where a
252
+ // notice at the top of the paper is off screen. It names what happened and
253
+ // the next step; the paper's notice stays after it closes.
254
+ function showRefused(data, said) {
255
+ const code = data && data.error ? data.error.code : null;
256
+ const r = lib.uploadRefusal(code);
257
+ $('tw-refused-title').textContent = r.title;
258
+ $('tw-refused-what').textContent = said.msg;
259
+ const list = $('tw-refused-list');
260
+ list.innerHTML = said.items.map((i) => `<li>${escapeHtml(i)}</li>`).join('');
261
+ list.hidden = !said.items.length;
262
+ $('tw-refused-next').textContent = r.next;
263
+ $('tw-refused-download').hidden = !r.download;
264
+ $('tw-bar').inert = true;
265
+ $('tw-paper').inert = true;
266
+ $('tw-refused').hidden = false;
267
+ $('tw-refused-title').focus();
268
+ }
269
+ function closeRefused() {
270
+ if ($('tw-refused').hidden) return;
271
+ $('tw-refused').hidden = true;
272
+ $('tw-bar').inert = false;
273
+ $('tw-paper').inert = false;
274
+ $('tw-upload').focus();
247
275
  }
248
276
 
249
277
  // Leaving with an unsaved change: one last save that outlives the page.
@@ -294,7 +322,7 @@
294
322
  btn.disabled = false;
295
323
  if (!r.ok) {
296
324
  btn.textContent = 'upload .docx';
297
- refusal(r.data, 'The file could not be read. Nothing was saved.');
325
+ showRefused(r.data, refusal(r.data, 'The file could not be read. Nothing was saved.'));
298
326
  return;
299
327
  }
300
328
  notice('');
@@ -403,6 +431,7 @@
403
431
  document.addEventListener('focusout', (ev) => {
404
432
  if (ev.target && ev.target.classList && ev.target.classList.contains('tw-line')) flush();
405
433
  });
434
+ document.addEventListener('keydown', (ev) => { if (ev.key === 'Escape') closeRefused(); });
406
435
  window.addEventListener('resize', () => { document.querySelectorAll('textarea.tw-line').forEach(fit); });
407
436
 
408
437
  document.addEventListener('click', async (ev) => {
@@ -411,6 +440,8 @@
411
440
  if (t.closest('#tw-upload')) { $('tw-file').click(); return; }
412
441
  if (t.closest('#tw-submit')) { submit(); return; }
413
442
  if (t.closest('#tw-take')) { takeNext(); return; }
443
+ if (t.closest('#tw-refused-close')) { closeRefused(); return; }
444
+ if (t.closest('#tw-refused-download')) { closeRefused(); download(); return; }
414
445
  if (t.closest('#tw-claim')) {
415
446
  const b = t.closest('#tw-claim');
416
447
  b.disabled = true;
@@ -606,7 +606,7 @@
606
606
  }
607
607
 
608
608
  // Editors and the Claude desktop/CLI app autolink any bare "#NNN" in a session
609
- // transcript to the dev box's GitHub remote (…/issues/NNN), which 404s because
609
+ // transcript to the checkout's GitHub remote (…/issues/NNN), which 404s because
610
610
  // our records number tasks separately from GitHub. So the scroll we hand the
611
611
  // builder must never carry a bare "#NNN": write "task NNN" + the clickable hall
612
612
  // URL, and strip "#NNN" out of the (free-text) task description too.
@@ -25,6 +25,7 @@ The paper is paper and the room is the room whatever the hall's theme: the stage
25
25
  - **A focused line gets a hairline accent ring** beside the artboard's warm wash (the hall's focus rule, `tests/hall_audit.mjs` §10).
26
26
  - **"pick another" opens the other pages in place** as chips under the actions (the studio artboard's own "pick another"), where the editor artboard linked to the studio, whose flow panel is TW16's.
27
27
  - **Boxes grow to their words** rather than the artboard's rows 1 or 2.
28
+ - **A refused upload opens a panel** (task 1004354, `#tw-refused`): the "is in" panel's glass and scrim over the whole window, a headline per refusal (`lib.uploadRefusal`), the server's words, the lines it names, "what to do", and "download this page again" or close (Escape too). An artist uploads from the bar, often far down the page, where the paper's notice was off screen. The notice stays on the paper after the panel closes. The kit has no file-upload action, so the panel is proven in `tests/hall_tweak_editor.mjs` and by shots of a real browser at 1440 and 390, not by a states.json state.
28
29
  - **Not on the artboard at all:** the phone layout (the bar becomes a two-row glass card, notes drop under their lines), the shell's full-screen button top right, the read-only and refusal notices on the paper, the chrome view (the switch off), and light mode.
29
30
 
30
31
  ## The proof
@@ -2,6 +2,7 @@
2
2
  "key": "ideas",
3
3
  "title": "Ideas",
4
4
  "description": "The idea inbox (capture, triage, criterion-proposal ratification) and the blockers queue — the methodology surfaces that feed the backlog. A core-domain module carved out of the GDS core in BV1.R78 (ADR 0093 §1); default-on because every instance captures ideas and tracks what's stuck on its owner. Registers the `ideas.capture` kernel PORT the discord #ideas inbound resolves (ADR 0093 §5 — retiring the temporary `captureIdea` doorway leak).",
5
+ "version": "1.0.0",
5
6
  "coreVersion": "^1.8.0",
6
7
  "default": true,
7
8
  "maintenance": {
@@ -50,7 +50,7 @@ const IDEA_COLS = IDEA_COL_NAMES.join(', ');
50
50
  //
51
51
  // THE DOOR IS READ OFF THE SESSION, not claimed in the body. A web cookie is the
52
52
  // hall (the ideas form, the sky's composer), a device-flow token is the CLI
53
- // (capture.js, spark.js), anything else (a box token, a program on the port) is
53
+ // (capture.js, spark.js), anything else (a program on the port) is
54
54
  // the API — so a filer cannot say they came through a door they did not. The one
55
55
  // value a body may ADD is 'planning': a planning session's capture is a CLI
56
56
  // filing no session can tell apart, so capture.js --via planning names it. The
@@ -34,61 +34,6 @@ async function getActiveClaimsForBuilder(builderId) {
34
34
  return rows;
35
35
  }
36
36
 
37
- // Task-scoped dev box (goal 1000051 / R92, ADR 0148 §2). The set-union of the
38
- // module KEYS a builder's active claims put in scope: each active claim's task →
39
- // its goal → goal.scope_modules (the ADR 0086 scope wall), unioned + deduped
40
- // across all the builder's active claims. This is the "what code does this
41
- // builder's current work need" signal the dev-box source-access resolver
42
- // (modules/dev-box/box-access.js, wired in R93/R94) maps to git sparse-checkout
43
- // globs via the doorway `moduleScopeMap`. Returns [] when the builder holds no
44
- // active claim, or when every claimed task carries no goal / an empty
45
- // scope_modules — the box then falls back to BASE-only (the pre-existing Bongos
46
- // instance). KEYS only; glob resolution is the box module's job (it reaches
47
- // moduleScopeMap through the doorway, never a core-internal require — the
48
- // R90/ADR-0148 boundary). Honours the #538 deps.pool test seam
49
- // (NODE_ENV==='test' only), like the goals reads above.
50
- async function moduleScopeForActiveClaims(builderId, deps = {}) {
51
- const activePool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
52
- const id = Number(builderId);
53
- if (!Number.isInteger(id)) {
54
- throw new Error('moduleScopeForActiveClaims: builderId must be numeric');
55
- }
56
- const { rows } = await activePool.query(
57
- `SELECT DISTINCT m AS module_key
58
- FROM claims c
59
- JOIN tasks t ON t.id = c.task_id
60
- JOIN goals g ON g.id = t.goal_id
61
- CROSS JOIN LATERAL unnest(g.scope_modules) AS m
62
- WHERE c.builder_id = $1 AND c.released_at IS NULL
63
- ORDER BY m`,
64
- [id]
65
- );
66
- return rows.map((r) => r.module_key);
67
- }
68
-
69
- // Task 1002729 — the companion signal moduleScopeForActiveClaims cannot carry.
70
- // That query INNER JOINs goals and unnests scope_modules, so it returns [] for
71
- // BOTH "this builder holds no claim" and "this builder holds claims whose goals
72
- // declare no scope_modules". Those must not be treated alike: the first is a
73
- // genuinely unscoped box (BASE-only is right), the second is a builder actively
74
- // working who would be handed a box with no modules/ at all. Live evidence for
75
- // why this matters: 247 of 248 source grants on the cloudbongos fleet resolved to
76
- // BASE, including an Archon holding 7 active claims — every one of those claims
77
- // sat on a goal with scope_modules = {}. Counts CLAIMS, deliberately not joining
78
- // goals, so it stays true regardless of how the goal wall is (or isn't) declared.
79
- async function activeClaimCountForBuilder(builderId, deps = {}) {
80
- const activePool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
81
- const id = Number(builderId);
82
- if (!Number.isInteger(id)) {
83
- throw new Error('activeClaimCountForBuilder: builderId must be numeric');
84
- }
85
- const { rows } = await activePool.query(
86
- `SELECT count(*)::int AS n FROM claims WHERE builder_id = $1 AND released_at IS NULL`,
87
- [id]
88
- );
89
- return rows.length ? Number(rows[0].n) : 0;
90
- }
91
-
92
37
  // task 1642 — every active claim bound to a given CLI session. "Active" is the
93
38
  // claims-table convention (released_at IS NULL); there is NO `status` column on
94
39
  // claims (a common misread — the task's status lives on `tasks`). Keyed on
@@ -159,7 +104,7 @@ async function getLastClaimHolder(taskId) {
159
104
  // reading it.
160
105
  //
161
106
  // `deps.pool` is the family's test-only pool seam (task 538), honoured ONLY under
162
- // NODE_ENV==='test' (the same line moduleScopeForActiveClaims uses above), so
107
+ // NODE_ENV==='test' (the same line the family's other reads use), so
163
108
  // tests/grade_diff_hash.mjs can drive the lift with a fake row instead of a
164
109
  // real Postgres (task 1003490). Production callers pass the task id alone.
165
110
  async function getTaskGrade(taskId, deps = {}) {
@@ -217,12 +162,10 @@ async function getTaskSessionLogs(taskId) {
217
162
  }
218
163
 
219
164
  module.exports = {
220
- activeClaimCountForBuilder,
221
165
  getActiveClaimsForBuilder,
222
166
  getActiveClaimsForSession,
223
167
  getLastClaimHolder,
224
168
  getTaskClaims,
225
169
  getTaskGrade,
226
170
  getTaskSessionLogs,
227
- moduleScopeForActiveClaims,
228
171
  };
@@ -224,17 +224,17 @@ async function resolveClaim({ claimId, builderId, outcome, notesMd, valueSummary
224
224
  }
225
225
 
226
226
  // Task #1048 (ADR 0046 follow-up): record the builder's sandbox self-approval
227
- // as a durable fact on the task. When a dev-box builder reviews a staged
227
+ // as a durable fact on the task. When a builder reviews a staged
228
228
  // game change on their sandbox and approves shipping it (TTY 'y', or an
229
229
  // explicit --approved re-run on the dominant non-TTY path), ship.js threads
230
230
  // { stamp, url, method } through this resolve call. We stamp `by` + `at`
231
231
  // SERVER-SIDE (never trust the client for identity or time) and persist the
232
232
  // blob to tasks.sandbox_review. Only on a 'shipped' resolve carrying a
233
- // payload — laptop ships, non-game diffs, quick-tunnel boxes, --skip-stage,
233
+ // payload — laptop ships, non-game diffs, --skip-stage,
234
234
  // and un-approved re-runs send nothing, leaving the column NULL.
235
235
  if (outcome === 'shipped' && sandboxReview && typeof sandboxReview === 'object' && sandboxReview.stamp) {
236
236
  // Sanitize the client-supplied fields (defense in depth): the builder's
237
- // box controls stamp/url/method, so clamp lengths, whitelist `method`, and
237
+ // machine controls stamp/url/method, so clamp lengths, whitelist `method`, and
238
238
  // accept only an https:// URL. This keeps a forged/oversized value — or a
239
239
  // future stored-XSS string like a `javascript:` URL rendered in the hall —
240
240
  // out of the persisted record. `by`/`at` stay SERVER-stamped (never
@@ -672,11 +672,6 @@ async function shipTask({ taskId, builderId, landAssurance }, deps = {}) {
672
672
  console.log('[gds] graduation broadcast post-ship failed:', e && e.message);
673
673
  }
674
674
  }
675
- if (rankGraduation) {
676
- await seams.emitAsync('builder.box-reconcile-needed', {
677
- builderId: actualShipperId, rank: 'thetes', status: rankGraduation.status, actor: 'auto-graduation:#692',
678
- }).catch(() => {});
679
- }
680
675
  await recordShipAssurance({ insertAuditLog, taskId, builderId: actualShipperId, assurance: shipAssuranceValue, log });
681
676
  return {
682
677
  ...shipRow,
@@ -1103,7 +1098,7 @@ async function confirmTask({ taskId, builderId }, deps = {}) {
1103
1098
  // first-class way to KILL a task that is no longer relevant. The only routes to
1104
1099
  // status='abandoned' were offboarding a builder (offboardBuilder, which seals
1105
1100
  // each open claim) and a raw SQL UPDATE — so stale/moot tasks sat in 'ready' as
1106
- // claimable noise (the box-teardown chores #819/#822/#827 and the dup tasks
1101
+ // claimable noise (the teardown chores #819/#822/#827 and the dup tasks
1107
1102
  // #907/#926 that other sessions had already shipped, all retired 2026-06-13).
1108
1103
  // This is the missing transition.
1109
1104
  //
@@ -203,14 +203,12 @@ const {
203
203
  } = require('./db-overrides.js');
204
204
  const { applyGrade } = require('./db-grade.js');
205
205
  const {
206
- activeClaimCountForBuilder,
207
206
  getActiveClaimsForBuilder,
208
207
  getActiveClaimsForSession,
209
208
  getLastClaimHolder,
210
209
  getTaskClaims,
211
210
  getTaskGrade,
212
211
  getTaskSessionLogs,
213
- moduleScopeForActiveClaims,
214
212
  } = require('./db-claim-reads.js');
215
213
  const {
216
214
  claimTask,
@@ -333,8 +331,6 @@ module.exports = {
333
331
  getClaimPublishedSha, // task 1547 — read the published tip for the reconcile gate
334
332
  shippedTasksByPublishedSha, // task 1004090 / BV2.R15 — the reverse read, for removal attribution
335
333
  getActiveClaimsForBuilder,
336
- moduleScopeForActiveClaims, // goal 1000051 / R92 — dev-box task-scoped source access (ADR 0148 §2)
337
- activeClaimCountForBuilder, // task 1002729 — distinguishes "no claim" from "claim with an empty goal wall"
338
334
  getActiveClaimsForSession,
339
335
  getLastClaimHolder, // --regrade ownership guard (#678)
340
336
  getTaskGrade, // V4.R08 — diff-hash reuse check before grading