@bongos/core 1.18.0 → 1.18.18

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 (553) hide show
  1. package/.bongos-core.json +1740 -640
  2. package/.claude/skills/builder-box/SKILL.md +6 -0
  3. package/.claude/skills/builder-sequence/SKILL.md +97 -0
  4. package/.claude/skills/builder-ship/SKILL.md +8 -2
  5. package/.claude/skills/builder-start/SKILL.md +11 -0
  6. package/.claude/skills/builder-sync/SKILL.md +41 -0
  7. package/.claude/skills/fix-task/SKILL.md +64 -0
  8. package/.claude/skills/goal-create/SKILL.md +158 -0
  9. package/.claude/skills/goal-review/SKILL.md +3 -1
  10. package/.claude/skills/idea-triage/SKILL.md +14 -8
  11. package/.claude/skills/merge-mode/SKILL.md +2 -2
  12. package/.claude/skills/new-project/SKILL.md +1 -0
  13. package/.claude/skills/planning-session/SKILL.md +45 -18
  14. package/.env.local.example +4 -3
  15. package/README.md +5 -3
  16. package/bin/bongos.js +1 -0
  17. package/clients/bongos-client/README.md +2 -2
  18. package/clients/bongos-client/bongos-client.global.js +82 -18
  19. package/clients/bongos-client/index.cjs +82 -18
  20. package/clients/bongos-client/index.d.ts +131 -29
  21. package/clients/bongos-client/index.mjs +82 -18
  22. package/config/branding.neutral.json +11 -2
  23. package/config/governance.neutral.json +21 -0
  24. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
  25. package/docs/adr/0090-metic-task-authoring.md +1 -1
  26. package/docs/adr/0142-project-context-is-prose-only.md +41 -0
  27. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
  28. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  29. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  30. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  31. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  32. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  33. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  34. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  35. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  36. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  37. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  38. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  39. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  40. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  41. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  42. package/docs/adr/README.md +15 -2
  43. package/docs/api/openapi.json +2648 -483
  44. package/docs/api-reference.md +73 -13
  45. package/docs/architecture.md +4 -2
  46. package/docs/branding-contract.md +3 -1
  47. package/docs/canonical-permissions.md +21 -8
  48. package/docs/file-map.md +15 -7
  49. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  50. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  51. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  52. package/docs/onboarding/diagrams/assertions.json +4 -7
  53. package/docs/onboarding/drachmae-and-karma.md +3 -3
  54. package/docs/onboarding/primer.md +17 -16
  55. package/docs/onboarding/slash-commands.md +124 -0
  56. package/docs/recipes/builder-box-lifecycle.md +23 -0
  57. package/docs/recipes/devbox-app-release.md +14 -4
  58. package/docs/recipes/instance-first-run.md +16 -0
  59. package/docs/recipes/local-dev.md +81 -0
  60. package/docs/recipes/ops-gotchas.md +19 -1
  61. package/docs/recipes/overnight-builder.md +1 -1
  62. package/docs/recipes/packaging-the-core.md +13 -0
  63. package/docs/recipes/parallel-goal-waves.md +174 -0
  64. package/docs/recipes/ship-and-regrade.md +2 -2
  65. package/docs/recipes/standalone-live-docs.md +48 -0
  66. package/docs/recipes/upgrading-the-core.md +5 -2
  67. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  68. package/migrations/003_pms.sql +6 -7
  69. package/migrations/006_pms_v2.sql +3 -31
  70. package/migrations/039_newcomer_friendly_column.sql +5 -0
  71. package/migrations/102_remove_box_first_provision_approval.sql +4 -0
  72. package/migrations/core_192_github_oauth_tokens.sql +33 -0
  73. package/migrations/core_193_task_needs_rebase.sql +28 -0
  74. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  75. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  76. package/migrations/core_196_session_reward_topup.sql +69 -0
  77. package/modules/autonomy/pollers/routine-timers.js +9 -8
  78. package/modules/builder-settings/wandering-prefs.js +61 -18
  79. package/modules/dev-box/app/README.md +73 -0
  80. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  81. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  82. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  83. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  84. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  85. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  86. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  87. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  88. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  89. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  90. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  91. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  92. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  93. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  94. package/modules/dev-box/app/build-resources/icon.png +0 -0
  95. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  96. package/modules/dev-box/app/electron-builder.yml +95 -0
  97. package/modules/dev-box/app/package-lock.json +5291 -0
  98. package/modules/dev-box/app/package.json +30 -0
  99. package/modules/dev-box/app/renderer/app.js +319 -0
  100. package/modules/dev-box/app/renderer/index.html +45 -0
  101. package/modules/dev-box/app/renderer/styles.css +246 -0
  102. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  103. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  104. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  105. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  106. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  107. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  108. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  109. package/modules/dev-box/app/src/box-controller.js +287 -0
  110. package/modules/dev-box/app/src/brand.generated.json +13 -0
  111. package/modules/dev-box/app/src/brand.js +16 -0
  112. package/modules/dev-box/app/src/config.js +19 -0
  113. package/modules/dev-box/app/src/gds-client.js +90 -0
  114. package/modules/dev-box/app/src/main.js +610 -0
  115. package/modules/dev-box/app/src/pairing.js +57 -0
  116. package/modules/dev-box/app/src/preload.js +29 -0
  117. package/modules/dev-box/app/src/session-store.js +58 -0
  118. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  119. package/modules/dev-box/app/src/task-picker.js +70 -0
  120. package/modules/dev-box/app/src/tray.js +99 -0
  121. package/modules/dev-box/app/src/updater.js +55 -0
  122. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  123. package/modules/dev-box/app/test/unit.mjs +526 -0
  124. package/modules/dev-box/box-access.js +121 -4
  125. package/modules/dev-box/boxes.js +20 -0
  126. package/modules/dev-box/module.json +1 -1
  127. package/modules/dev-box/routes/box.js +87 -3
  128. package/modules/economy/credits.js +116 -25
  129. package/modules/economy/reward.js +8 -0
  130. package/modules/economy/streak.js +2 -1
  131. package/modules/governance/CLAUDE.md +15 -0
  132. package/modules/governance/catalog.js +419 -0
  133. package/modules/governance/config.js +174 -0
  134. package/modules/governance/db.js +125 -0
  135. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  136. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  137. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  138. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  139. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  140. package/modules/governance/module.json +10 -0
  141. package/modules/governance/protected-surfaces.js +88 -0
  142. package/modules/governance/protected-surfaces.json +80 -0
  143. package/modules/governance/rank-roles.js +83 -0
  144. package/modules/governance/resolver.js +221 -0
  145. package/modules/governance/routes/governance.js +89 -0
  146. package/modules/governance/routes/role-sync.js +49 -0
  147. package/modules/governance/session-scopes.js +127 -0
  148. package/modules/governance/session-scopes.json +38 -0
  149. package/modules/hall-ui/public/atlas.css +117 -0
  150. package/modules/hall-ui/public/atlas.html +3 -114
  151. package/modules/hall-ui/public/brand-apply.js +34 -6
  152. package/modules/hall-ui/public/builders.js +187 -219
  153. package/modules/hall-ui/public/diagrams.html +2 -0
  154. package/modules/hall-ui/public/dom-utils.js +27 -0
  155. package/modules/hall-ui/public/drachmae.html +2 -0
  156. package/modules/hall-ui/public/drachmae.js +54 -1
  157. package/modules/hall-ui/public/gate.css +98 -0
  158. package/modules/hall-ui/public/gate.html +3 -95
  159. package/modules/hall-ui/public/goal-inbox.js +4 -1
  160. package/modules/hall-ui/public/goals-page.js +626 -114
  161. package/modules/hall-ui/public/goals.css +79 -1
  162. package/modules/hall-ui/public/goals.html +38 -11
  163. package/modules/hall-ui/public/hall-widgets.js +62 -52
  164. package/modules/hall-ui/public/harbor.css +123 -0
  165. package/modules/hall-ui/public/harbor.html +3 -120
  166. package/modules/hall-ui/public/index.html +11 -4
  167. package/modules/hall-ui/public/instances-panel.js +4 -1
  168. package/modules/hall-ui/public/modules.css +63 -0
  169. package/modules/hall-ui/public/modules.html +3 -60
  170. package/modules/hall-ui/public/my-projects.html +78 -0
  171. package/modules/hall-ui/public/my-projects.js +142 -0
  172. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  173. package/modules/hall-ui/public/not-ready.html +2 -0
  174. package/modules/hall-ui/public/pair.css +73 -0
  175. package/modules/hall-ui/public/pair.html +3 -70
  176. package/modules/hall-ui/public/palette.js +302 -0
  177. package/modules/hall-ui/public/primer.css +13 -0
  178. package/modules/hall-ui/public/primer.html +2 -0
  179. package/modules/hall-ui/public/profile.html +2 -0
  180. package/modules/hall-ui/public/ranks.html +2 -0
  181. package/modules/hall-ui/public/roadmap.css +179 -0
  182. package/modules/hall-ui/public/roadmap.html +9 -172
  183. package/modules/hall-ui/public/roadmap.js +329 -316
  184. package/modules/hall-ui/public/scouting.html +45 -0
  185. package/modules/hall-ui/public/scouting.js +73 -0
  186. package/modules/hall-ui/public/sessions.css +16 -0
  187. package/modules/hall-ui/public/sessions.html +3 -13
  188. package/modules/hall-ui/public/settings.html +2 -0
  189. package/modules/hall-ui/public/settings.js +4 -4
  190. package/modules/hall-ui/public/shell.js +143 -18
  191. package/modules/hall-ui/public/style.css +231 -39
  192. package/modules/hall-ui/public/task-detail.js +347 -26
  193. package/modules/hall-ui/public/task.html +6 -1
  194. package/modules/hall-ui/public/watch.html +2 -0
  195. package/modules/hall-ui/public/work.html +8 -6
  196. package/modules/hall-ui/public/work.js +1 -1
  197. package/modules/ideas/inbox.js +61 -11
  198. package/modules/ideas/routes/inbox.js +23 -0
  199. package/modules/lifecycle/claim-eligibility.js +72 -0
  200. package/modules/lifecycle/db.js +519 -139
  201. package/modules/lifecycle/done-when.js +18 -3
  202. package/modules/lifecycle/github-push.js +19 -2
  203. package/modules/lifecycle/goal-advisory.js +114 -0
  204. package/modules/lifecycle/goal-authz.js +62 -0
  205. package/modules/lifecycle/lifecycle.js +9 -0
  206. package/modules/lifecycle/publish-reconciler.js +109 -6
  207. package/modules/lifecycle/routes/claims.js +68 -23
  208. package/modules/lifecycle/routes/done-when.js +110 -15
  209. package/modules/lifecycle/routes/goals.js +8 -38
  210. package/modules/lifecycle/routes/tasks.js +38 -6
  211. package/modules/lifecycle/ship-card.js +6 -0
  212. package/modules/onboarding/onboarding-state.js +1 -1
  213. package/modules/platform-identity/CLAUDE.md +15 -0
  214. package/modules/platform-identity/hub-device.js +147 -0
  215. package/modules/platform-identity/hub-keys.js +151 -0
  216. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  217. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  218. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  219. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  220. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  221. package/modules/platform-identity/module.json +18 -0
  222. package/modules/platform-identity/platform-identity.js +531 -0
  223. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  224. package/modules/platform-identity/routes/my-projects.js +140 -0
  225. package/modules/platform-identity/routes/projects.js +107 -0
  226. package/modules/platform-identity/routes/scouting.js +35 -0
  227. package/modules/platform-identity/routes/sso.js +402 -0
  228. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  229. package/modules/platform-identity/ui/.gitkeep +0 -0
  230. package/modules/security/routes/reports.js +10 -0
  231. package/modules/sessions/db.js +166 -21
  232. package/modules/sessions/module.json +1 -1
  233. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  234. package/modules/sessions/routes/sessions.js +33 -0
  235. package/modules/status-ui/public/explore.html +54 -0
  236. package/modules/status-ui/public/explore.js +67 -0
  237. package/package-lock.json +2 -2
  238. package/package.json +1 -1
  239. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  270. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  271. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  272. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  273. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  274. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  275. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  276. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  277. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  278. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  279. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  280. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  281. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  282. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  283. package/public-landing/create.html +252 -62
  284. package/public-landing/gate/README.md +137 -0
  285. package/public-landing/gate/dispmaps/bar.png +0 -0
  286. package/public-landing/gate/dispmaps/flat.png +0 -0
  287. package/public-landing/gate/dispmaps/key.png +0 -0
  288. package/public-landing/gate/index.html +587 -0
  289. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  290. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  291. package/public-landing/gate/space.css +799 -0
  292. package/public-landing/gate/space.js +1826 -0
  293. package/public-landing/gate/sprites/enter.png +0 -0
  294. package/public-landing/gate/sprites/search.png +0 -0
  295. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  296. package/public-landing/gate/sprites/tile.png +0 -0
  297. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  298. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  299. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  300. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  301. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  302. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  303. package/scripts/gds/abandon.js +6 -4
  304. package/scripts/gds/api.js +10 -5
  305. package/scripts/gds/api.ps1 +26 -0
  306. package/scripts/gds/api.sh +6 -0
  307. package/scripts/gds/audit-ships.js +7 -1
  308. package/scripts/gds/backfill-sessions.js +78 -18
  309. package/scripts/gds/backup.js +23 -1
  310. package/scripts/gds/box-connect-lib.js +124 -31
  311. package/scripts/gds/box-sync.js +66 -0
  312. package/scripts/gds/box.js +225 -53
  313. package/scripts/gds/cf-tunnel.js +145 -6
  314. package/scripts/gds/claim.js +34 -3
  315. package/scripts/gds/cli-lib.js +161 -21
  316. package/scripts/gds/code.js +6 -2
  317. package/scripts/gds/deploy-divergence.js +209 -0
  318. package/scripts/gds/dev-lib.js +309 -0
  319. package/scripts/gds/dev.js +322 -0
  320. package/scripts/gds/doctor.js +90 -61
  321. package/scripts/gds/fetch-art-key.js +1 -1
  322. package/scripts/gds/fetch-sound-prefs.js +2 -2
  323. package/scripts/gds/fitness.js +228 -12
  324. package/scripts/gds/gds-db-backup.service +16 -1
  325. package/scripts/gds/gds-db-offsite.service +9 -1
  326. package/scripts/gds/gen-api-client.js +1 -1
  327. package/scripts/gds/gen-api-docs.js +36 -14
  328. package/scripts/gds/gen-diagrams.js +45 -28
  329. package/scripts/gds/gen-file-map.js +2 -1
  330. package/scripts/gds/gen-freshness.js +32 -0
  331. package/scripts/gds/gen-repo-map.js +3 -2
  332. package/scripts/gds/gen-session-index.js +14 -5
  333. package/scripts/gds/hooks/pre-push.js +2 -2
  334. package/scripts/gds/init.js +60 -3
  335. package/scripts/gds/install-git-hooks.js +1 -1
  336. package/scripts/gds/key-card.js +15 -6
  337. package/scripts/gds/key-status.js +1 -1
  338. package/scripts/gds/linkify-refs.js +35 -9
  339. package/scripts/gds/lint-migrations.js +102 -0
  340. package/scripts/gds/login.js +58 -11
  341. package/scripts/gds/mirror-redact.js +5 -0
  342. package/scripts/gds/package-core.js +263 -4
  343. package/scripts/gds/paste-token.js +7 -1
  344. package/scripts/gds/preflight.js +126 -0
  345. package/scripts/gds/provision.js +268 -19
  346. package/scripts/gds/publish-manifest.js +22 -1
  347. package/scripts/gds/reauth.js +7 -3
  348. package/scripts/gds/regen-instance-docs.js +47 -0
  349. package/scripts/gds/run-unit-tests.js +42 -16
  350. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  351. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  352. package/scripts/gds/sequence.js +425 -0
  353. package/scripts/gds/session-cost.js +10 -1
  354. package/scripts/gds/setup.js +72 -5
  355. package/scripts/gds/shell.js +5 -3
  356. package/scripts/gds/ship.js +433 -59
  357. package/scripts/gds/smoke-gds.js +139 -8
  358. package/scripts/gds/start.js +171 -22
  359. package/scripts/gds/status.js +1 -1
  360. package/scripts/gds/triage.js +10 -9
  361. package/scripts/gds/upgrade.js +271 -7
  362. package/scripts/migrate.sh +10 -0
  363. package/scripts/status-mirror-sync.js +114 -84
  364. package/src/bongos/api-errors.js +1 -0
  365. package/src/bongos/auth.js +809 -107
  366. package/src/bongos/db-kernel.js +27 -0
  367. package/src/bongos/db.js +105 -0
  368. package/src/bongos/github-repos.js +93 -0
  369. package/src/bongos/module-scope-map.js +27 -1
  370. package/src/bongos/permission-path-check.js +136 -102
  371. package/src/bongos/repo-info.js +47 -11
  372. package/src/bongos/route-rank-check.js +429 -46
  373. package/src/bongos/routes/_helpers.js +12 -1
  374. package/src/bongos/routes/auth.js +312 -13
  375. package/src/bongos/routes/backup.js +58 -2
  376. package/src/bongos/routes/github.js +90 -0
  377. package/src/bongos/routes/instance.js +15 -3
  378. package/src/bongos/routes/me.js +35 -1
  379. package/src/bongos/routes.js +2 -0
  380. package/src/bongos/serve-internal.js +84 -15
  381. package/src/branding.js +49 -2
  382. package/src/instance-config.js +46 -0
  383. package/src/module-api.js +140 -1
  384. package/src/platform-server.js +86 -9
  385. package/tests/abandon_task.mjs +59 -3
  386. package/tests/api_cli_flags.mjs +53 -0
  387. package/tests/auth_page_gate.mjs +13 -0
  388. package/tests/backfill_sessions.mjs +127 -1
  389. package/tests/backup_cli.mjs +54 -0
  390. package/tests/bongos_box_connect.mjs +77 -0
  391. package/tests/box_access.mjs +130 -1
  392. package/tests/box_credential.mjs +125 -0
  393. package/tests/box_host_keys.mjs +24 -0
  394. package/tests/box_infra_resolve.mjs +149 -0
  395. package/tests/box_scope_session.mjs +6 -2
  396. package/tests/box_sync.mjs +55 -0
  397. package/tests/box_task_scope.mjs +99 -0
  398. package/tests/branding.mjs +41 -23
  399. package/tests/cf_ruleset_preflight.mjs +142 -0
  400. package/tests/cf_tunnel.mjs +37 -0
  401. package/tests/claim_gate_rebase.mjs +98 -0
  402. package/tests/clawback_confirmed_backlog.mjs +103 -0
  403. package/tests/cli_lib_formatters.mjs +20 -0
  404. package/tests/confirm_attribution.mjs +30 -46
  405. package/tests/credit_grant.mjs +198 -10
  406. package/tests/criterion_authoring_gate.mjs +272 -0
  407. package/tests/criterion_progress.mjs +33 -1
  408. package/tests/dev_lib.mjs +253 -0
  409. package/tests/devbox_artifact_gate.mjs +75 -0
  410. package/tests/devbox_dmg_backdrop.mjs +137 -0
  411. package/tests/devbox_icons.mjs +304 -0
  412. package/tests/devbox_installer_brand.mjs +232 -0
  413. package/tests/devbox_key_register.mjs +234 -0
  414. package/tests/docs_openapi_route.mjs +110 -0
  415. package/tests/doctor_hooks.mjs +141 -0
  416. package/tests/extract_json.mjs +140 -0
  417. package/tests/fitness.mjs +89 -0
  418. package/tests/gen_freshness.mjs +96 -0
  419. package/tests/github_repos_helpers.mjs +91 -0
  420. package/tests/github_token_store.mjs +121 -0
  421. package/tests/goal_advisory.mjs +251 -0
  422. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  423. package/tests/goal_routes.mjs +59 -4
  424. package/tests/goal_tier.mjs +12 -3
  425. package/tests/governance_catalog.mjs +129 -0
  426. package/tests/governance_claim_eligibility.mjs +262 -0
  427. package/tests/governance_config.mjs +187 -0
  428. package/tests/governance_ownership_scope.mjs +410 -0
  429. package/tests/governance_page_gates.mjs +324 -0
  430. package/tests/governance_parity.mjs +336 -0
  431. package/tests/governance_principals.mjs +232 -0
  432. package/tests/governance_protected_surfaces.mjs +208 -0
  433. package/tests/governance_require_permission.mjs +223 -0
  434. package/tests/governance_resolver.mjs +84 -0
  435. package/tests/governance_role_sync.mjs +231 -0
  436. package/tests/governance_seed.mjs +127 -0
  437. package/tests/governance_session_scope.mjs +434 -0
  438. package/tests/governance_vanilla_boot.mjs +203 -0
  439. package/tests/grade_attribution.mjs +7 -7
  440. package/tests/hall_audit.mjs +223 -0
  441. package/tests/hall_nav.mjs +191 -0
  442. package/tests/hall_palette.mjs +206 -0
  443. package/tests/hall_signout.mjs +124 -0
  444. package/tests/hook_user_agents.mjs +10 -0
  445. package/tests/idea_triage.mjs +162 -0
  446. package/tests/idp_device_exchange.mjs +198 -0
  447. package/tests/idp_federation.mjs +100 -0
  448. package/tests/init.mjs +51 -0
  449. package/tests/instance_config.mjs +65 -0
  450. package/tests/instance_mismatch_guard.mjs +23 -6
  451. package/tests/instance_release_config.mjs +104 -0
  452. package/tests/linkify_refs.mjs +15 -2
  453. package/tests/migrations_forward_safe.mjs +86 -0
  454. package/tests/module_api.mjs +1 -1
  455. package/tests/module_contributions.mjs +55 -0
  456. package/tests/module_loader.mjs +10 -5
  457. package/tests/module_scope_active_claims.mjs +94 -0
  458. package/tests/needs_rebase.mjs +92 -0
  459. package/tests/one_active_claim_per_session.mjs +76 -0
  460. package/tests/override_attribution.mjs +10 -8
  461. package/tests/override_request_ownership.mjs +8 -4
  462. package/tests/package_core.mjs +246 -1
  463. package/tests/parse_cookie_decode.mjs +42 -0
  464. package/tests/pay_on_land.mjs +129 -0
  465. package/tests/platform_boot.mjs +100 -1
  466. package/tests/post_login_landing.mjs +42 -0
  467. package/tests/preflight_checks.mjs +72 -0
  468. package/tests/provision.mjs +289 -7
  469. package/tests/pruned_lockfile_integrity.mjs +79 -0
  470. package/tests/publish_branch_route.mjs +53 -0
  471. package/tests/publish_reconciler.mjs +187 -2
  472. package/tests/repo_info_binding.mjs +248 -0
  473. package/tests/reward_gate.mjs +11 -2
  474. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  475. package/tests/reward_mode_surfacing.mjs +168 -0
  476. package/tests/route_permission_check.mjs +301 -0
  477. package/tests/route_rank_check.mjs +254 -12
  478. package/tests/route_validation.mjs +30 -0
  479. package/tests/sequence.mjs +267 -0
  480. package/tests/session_digest.mjs +44 -0
  481. package/tests/session_index.mjs +18 -6
  482. package/tests/session_records.mjs +39 -1
  483. package/tests/session_reward_on_land.mjs +381 -0
  484. package/tests/session_reward_reconcile.mjs +229 -0
  485. package/tests/ship_api_client_pathspec.mjs +100 -0
  486. package/tests/ship_base_flag.mjs +93 -0
  487. package/tests/ship_ci_deploy.mjs +140 -3
  488. package/tests/ship_upstream_deletion.mjs +57 -0
  489. package/tests/smoke_target.mjs +233 -0
  490. package/tests/start_env_guard.mjs +87 -0
  491. package/tests/start_rebase_warning.mjs +103 -0
  492. package/tests/status_mirror_sync.mjs +122 -0
  493. package/tests/stop_refcheck.mjs +19 -1
  494. package/tests/task_goal_link.mjs +15 -6
  495. package/tests/upgrade.mjs +393 -5
  496. package/tests/web_admission_status.mjs +162 -0
  497. package/tests/write_validation_gaps.mjs +110 -0
  498. package/.gitignore +0 -91
  499. package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
  500. package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
  501. package/scripts/hall-preview/fixtures/access-requests.json +0 -20
  502. package/scripts/hall-preview/fixtures/achievements.json +0 -76
  503. package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
  504. package/scripts/hall-preview/fixtures/audit-log.json +0 -68
  505. package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
  506. package/scripts/hall-preview/fixtures/box__me.json +0 -5
  507. package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
  508. package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
  509. package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
  510. package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
  511. package/scripts/hall-preview/fixtures/goals.json +0 -502
  512. package/scripts/hall-preview/fixtures/goals__25.json +0 -96
  513. package/scripts/hall-preview/fixtures/goals__26.json +0 -67
  514. package/scripts/hall-preview/fixtures/goals__3.json +0 -103
  515. package/scripts/hall-preview/fixtures/goals__35.json +0 -49
  516. package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
  517. package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
  518. package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
  519. package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
  520. package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
  521. package/scripts/hall-preview/fixtures/inbox.json +0 -330
  522. package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
  523. package/scripts/hall-preview/fixtures/instance.json +0 -124
  524. package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
  525. package/scripts/hall-preview/fixtures/me.json +0 -476
  526. package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
  527. package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
  528. package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
  529. package/scripts/hall-preview/fixtures/me__render.json +0 -5
  530. package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
  531. package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
  532. package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
  533. package/scripts/hall-preview/fixtures/override-requests.json +0 -15
  534. package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
  535. package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
  536. package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
  537. package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
  538. package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
  539. package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
  540. package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
  541. package/scripts/hall-preview/fixtures/public__grades.json +0 -50
  542. package/scripts/hall-preview/fixtures/public__progress.json +0 -124
  543. package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
  544. package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
  545. package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
  546. package/scripts/hall-preview/fixtures/security__docs.json +0 -10
  547. package/scripts/hall-preview/fixtures/security__reports.json +0 -31
  548. package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
  549. package/scripts/hall-preview/fixtures/tasks.json +0 -2420
  550. package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
  551. package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
  552. package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
  553. package/scripts/hall-preview/fixtures/versions.json +0 -89
@@ -29,6 +29,13 @@
29
29
  --font-mono:"Fira Code",ui-monospace,SFMono-Regular,Menlo,monospace;
30
30
  }
31
31
  *{box-sizing:border-box;margin:0;padding:0;}
32
+ /* The `hidden` attribute must always win. Several blocks below set an explicit
33
+ `display` via a class (.field→block, .row2→grid); an author rule like that
34
+ silently defeats the user-agent [hidden] rule, so `el.hidden = true` fails to
35
+ hide them. The Home step (task 1002228) toggles el.hidden to switch between the
36
+ adopt-picker / fresh-name / manual sub-panels — without this they ALL render at
37
+ once. Make hidden authoritative for the whole page. */
38
+ [hidden]{display:none !important;}
32
39
  html{scroll-behavior:smooth;}
33
40
  body{
34
41
  font-family:var(--font-body); color:var(--ink);
@@ -75,11 +82,6 @@ body{
75
82
  .exit-wizard kbd{font-family:var(--font-mono);font-size:10px;line-height:1;background:rgba(42,31,23,0.08);border-radius:4px;padding:2px 5px;color:var(--ink-faint);}
76
83
  .who{display:flex;align-items:center;gap:10px;font-size:14px;font-weight:600;color:var(--ink-soft);min-height:34px;}
77
84
  .who img{width:30px;height:30px;border-radius:50%;border:2px solid #fff;box-shadow:0 2px 8px rgba(42,31,23,0.12);}
78
- .who a{color:var(--accent-deep);text-decoration:none;font-weight:700;}
79
- .who a:hover{text-decoration:underline;}
80
- /* switch to a different GitHub account from inside the wizard (task 2098) */
81
- .who .switch-acct{font-size:12px;font-weight:600;color:var(--ink-faint);padding-left:10px;margin-left:2px;border-left:1px solid rgba(42,31,23,0.16);}
82
- .who .switch-acct:hover{color:var(--accent-deep);}
83
85
 
84
86
  /* ── layout ── */
85
87
  .wrap{
@@ -195,10 +197,6 @@ input[type=text].bad{border-color:var(--err);}
195
197
  .ghbtn:hover{background:#1b1f24;transform:translateY(-1px);}
196
198
  .ghbtn:active{transform:translateY(0) scale(.98);}
197
199
  .ghbtn .gh{width:18px;height:18px;fill:currentColor;}
198
- .ghcreate{display:none;margin-bottom:18px;}
199
- .ghcreate.on{display:block;}
200
- .ghcreate-note{font-size:13px;font-weight:500;color:var(--ink-faint);line-height:1.5;margin-top:9px;max-width:52ch;}
201
- .ghcreate-note strong{color:var(--ink-soft);}
202
200
  .steps .ghbtn{font-size:14px;padding:9px 16px;margin-top:10px;}
203
201
 
204
202
  /* buttons */
@@ -327,6 +325,16 @@ input[type=text].bad{border-color:var(--err);}
327
325
  @media (prefers-reduced-motion:reduce){
328
326
  *,*::before,*::after{animation:none !important;transition:none !important;}
329
327
  }
328
+ /* GitHub repo picker (task 1002099) */
329
+ .linklike{background:none;border:0;padding:0;margin-left:12px;color:var(--accent-deep);font:inherit;font-size:13px;cursor:pointer;text-decoration:underline;}
330
+ #ghConnect .btn{margin-top:4px;}
331
+ .ghlist{margin-top:8px;max-height:220px;overflow-y:auto;border:1px solid var(--line,#e7d8c4);border-radius:10px;background:var(--bg-deep,#fff);}
332
+ .ghlist:empty{display:none;}
333
+ .ghrow{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:0;border-bottom:1px solid rgba(42,31,23,0.06);padding:9px 12px;font:inherit;font-size:13.5px;color:var(--ink,#2a1f17);cursor:pointer;}
334
+ .ghrow:last-child{border-bottom:0;}
335
+ .ghrow:hover{background:#fff9f2;}
336
+ .ghrow[aria-selected=true]{background:#fff3e6;box-shadow:inset 3px 0 0 var(--accent,#f97316);font-weight:600;}
337
+ .ghrow .priv{margin-left:auto;font-size:11px;font-weight:600;color:var(--ink-faint,#9b8a78);background:rgba(42,31,23,0.06);border-radius:5px;padding:1px 6px;}
330
338
  </style>
331
339
  </head>
332
340
  <body>
@@ -363,7 +371,11 @@ input[type=text].bad{border-color:var(--err);}
363
371
  <section class="panel" id="panel-gate" aria-labelledby="gate-h">
364
372
  <h1 id="gate-h">Create a project</h1>
365
373
  <p class="lede">Your own <strong data-brand-product-inline>Cloud Bongos</strong> project: its own repository, its own home on the web, and a hall of AI builders who do the building. You set the direction; agents lay down the code. Sign in with GitHub so the project belongs to <em>you</em> from the very first beat.</p>
366
- <a class="btn big" id="signin" href="/api/bongos/auth/web/start?return=/create">
374
+ <!-- The wizard's own sign-in opts into repo scope (read:user + public_repo) up
375
+ front (&repos=1) — anyone here intends to create a project, which needs repo
376
+ access, so we ask once at sign-in instead of a second "Connect" step at Home.
377
+ Hall + CLI sign-in stay identity-only (ADR 0143 least-privilege default). -->
378
+ <a class="btn big" id="signin" href="/api/bongos/auth/web/start?return=/create&amp;repos=1">
367
379
  <svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
368
380
  Sign in with GitHub
369
381
  </a>
@@ -405,7 +417,31 @@ input[type=text].bad{border-color:var(--err);}
405
417
  </button>
406
418
  </div>
407
419
  <div class="reassure" id="adoptNote">Adopting never overwrites your files: you get a conflict pre-flight first, nothing is written until you accept, and your existing backlog (issues, TODOs) is imported as real tasks.</div>
408
- <div class="row2">
420
+
421
+ <!-- GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099.
422
+ Progressive enhancement: when the signed-in user grants repo access we let
423
+ them PICK a repo (adopt) or NAME one we auto-create (fresh). Without that
424
+ access — not granted yet, or the server can't store the token — we fall back
425
+ to the manual owner/repo inputs below (#manualHome), which stay authoritative. -->
426
+ <div id="ghConnect" class="reassure">
427
+ <p id="ghConnectMsg">&nbsp;</p>
428
+ <button type="button" class="btn" id="ghConnectBtn">Allow repo access</button>
429
+ <button type="button" class="linklike" id="ghManualToggle">or enter it manually</button>
430
+ </div>
431
+ <div id="ghBusy" class="hint" hidden>Reading your GitHub repositories…</div>
432
+ <div id="ghAdopt" class="field" hidden>
433
+ <span>Pick one of your repositories</span>
434
+ <input type="text" id="ghSearch" placeholder="Search your repos…" autocomplete="off">
435
+ <div id="ghList" class="ghlist" role="listbox" aria-label="Your repositories"></div>
436
+ <div class="hint" id="ghAdoptHint">&nbsp;</div>
437
+ </div>
438
+ <div id="ghFresh" class="field" hidden>
439
+ <span>New repository name</span>
440
+ <input type="text" id="ghNewName" placeholder="mercury" autocomplete="off" maxlength="100">
441
+ <div class="hint" id="ghFreshHint">We’ll create an empty repo at <code id="ghFreshPreview" style="font-family:var(--font-mono)">github.com/you/mercury</code>.</div>
442
+ </div>
443
+
444
+ <div class="row2" id="manualHome">
409
445
  <label class="field"><span>GitHub owner <small>(user or org)</small></span>
410
446
  <input type="text" id="f-owner" placeholder="acme" autocomplete="off" maxlength="60">
411
447
  </label>
@@ -413,13 +449,6 @@ input[type=text].bad{border-color:var(--err);}
413
449
  <input type="text" id="f-repo" placeholder="mercury" autocomplete="off" maxlength="120">
414
450
  </label>
415
451
  </div>
416
- <div class="ghcreate" id="ghCreate">
417
- <a class="ghbtn" id="ghCreateBtn" href="https://github.com/new" target="_blank" rel="noopener">
418
- <svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
419
- Create this repo on GitHub
420
- </a>
421
- <p class="ghcreate-note">Opens GitHub with the name already filled in — just click <strong>Create repository</strong> and leave it empty, then come back and press Next. Already made it? Just press Next.</p>
422
- </div>
423
452
  <div class="hint" id="h-home">&nbsp;</div>
424
453
  <div class="nav">
425
454
  <button type="button" class="btn ghost" data-back="1">← Back</button>
@@ -566,10 +595,6 @@ input[type=text].bad{border-color:var(--err);}
566
595
  if (owner) u.searchParams.set('owner', owner);
567
596
  return u.toString();
568
597
  }
569
- function updateGhCreateHref() {
570
- var a = $('ghCreateBtn');
571
- if (a) a.href = githubNewRepoUrl(state.owner, state.repo);
572
- }
573
598
  function esc(s) {
574
599
  return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
575
600
  return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
@@ -595,6 +620,7 @@ input[type=text].bad{border-color:var(--err);}
595
620
  saveDraft();
596
621
  var h = $(panels[step]).querySelector('h1');
597
622
  if (h) { h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false }); }
623
+ if (step === 2) { syncHome(); ensureRepoAccess(); }
598
624
  if (step === 4) renderReview();
599
625
  }
600
626
 
@@ -622,7 +648,6 @@ input[type=text].bad{border-color:var(--err);}
622
648
  if (!state.repoTouched) {
623
649
  state.repo = deriveRepo(state.name);
624
650
  $('f-repo').value = state.repo;
625
- updateGhCreateHref();
626
651
  }
627
652
  var h = $('h-name');
628
653
  if (state.slug) { h.innerHTML = 'its handle on the platform: <code>' + esc(state.slug) + '</code>'; h.className = 'hint'; }
@@ -636,18 +661,143 @@ input[type=text].bad{border-color:var(--err);}
636
661
  $('c-green').setAttribute('aria-pressed', String(mode === 'greenfield'));
637
662
  $('c-adopt').setAttribute('aria-pressed', String(mode === 'adopt'));
638
663
  $('adoptNote').classList.toggle('on', mode === 'adopt');
639
- // The "create the repo on GitHub" button only makes sense when starting fresh —
640
- // an adopted repo already exists.
641
- $('ghCreate').classList.toggle('on', mode === 'greenfield');
642
- updateGhCreateHref();
643
664
  saveDraft();
665
+ syncHome();
644
666
  }
645
667
  $('c-green').addEventListener('click', function () { setMode('greenfield'); });
646
668
  $('c-adopt').addEventListener('click', function () { setMode('adopt'); });
647
669
  setMode(state.mode);
648
670
 
649
- $('f-owner').addEventListener('input', function () { state.owner = this.value.trim(); state.ownerTouched = true; updateGhCreateHref(); saveDraft(); });
650
- $('f-repo').addEventListener('input', function () { state.repo = this.value.trim(); state.repoTouched = true; updateGhCreateHref(); saveDraft(); });
671
+ /* ── GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099 / ADR 0143.
672
+ Progressive enhancement: probe GET /github/repos once we reach step 2.
673
+ 200 → we hold repo access → show the picker (adopt) / name field (fresh);
674
+ 403 → offer to grant repo access (a ?repos=1 re-sign-in);
675
+ 503 (server can't store the token) or any error → the manual inputs.
676
+ #manualHome (the classic owner/repo fields) is the always-safe fallback. ── */
677
+ var repoAccess = null; // null=unknown/loading · true · 'need-connect' · 'unavailable' · 'manual'
678
+ var repoList = []; // the user's repos (from the probe) — feeds the picker + the fresh-name clash hint
679
+ var ghProbed = false;
680
+
681
+ function isManualHome() { return repoAccess === 'manual' || repoAccess === 'unavailable'; }
682
+ function validRepoName(s) { return /^[A-Za-z0-9._-]{1,100}$/.test(String(s || '')); }
683
+ function myLogin() { return (me && me.github_login) || 'you'; }
684
+
685
+ function connectRepos(e) {
686
+ if (e) e.preventDefault();
687
+ // Same-tab navigation so the sessionStorage draft round-trips back to step 2.
688
+ window.location.assign(API + '/auth/web/start?return=/create&repos=1');
689
+ }
690
+ $('ghConnectBtn').addEventListener('click', connectRepos);
691
+ $('ghManualToggle').addEventListener('click', function () { repoAccess = 'manual'; syncHome(); $('f-owner').focus(); });
692
+
693
+ function syncHome() {
694
+ var need = repoAccess === 'need-connect';
695
+ var ok = repoAccess === true;
696
+ var adopt = state.mode === 'adopt';
697
+ $('ghBusy').hidden = !!repoAccess; // busy only while still probing (null)
698
+ $('ghConnect').classList.toggle('on', need); // .reassure box: shown via .on (not hidden — .reassure sets display:none)
699
+ $('ghAdopt').hidden = !(ok && adopt);
700
+ $('ghFresh').hidden = !(ok && !adopt);
701
+ $('manualHome').hidden = !isManualHome();
702
+ if (need) {
703
+ $('ghConnectMsg').textContent = adopt
704
+ ? 'You’re signed in — creating your project needs one more GitHub permission. Allow repo access to pick the repository to adopt.'
705
+ : 'You’re signed in — creating your project needs one more GitHub permission. Allow repo access and we’ll create the empty repo for you.';
706
+ }
707
+ if (ok && adopt) initAdoptPicker();
708
+ if (ok && !adopt) syncFresh();
709
+ }
710
+
711
+ function syncFresh() {
712
+ // Fresh + access: the repo is created under the signed-in account. Seed owner for
713
+ // the review/target now; the create response confirms the real full_name.
714
+ state.owner = myLogin();
715
+ $('ghNewName').value = state.repo || '';
716
+ updateFreshPreview();
717
+ }
718
+ function updateFreshPreview() {
719
+ var hint = $('ghFreshHint');
720
+ var clash = state.repo && repoList.some(function (r) { return String(r.name).toLowerCase() === state.repo.toLowerCase(); });
721
+ if (state.repo && !validRepoName(state.repo)) {
722
+ hint.className = 'hint err'; hint.textContent = 'Use letters, numbers, and . _ - (1–100 chars).';
723
+ } else if (clash) {
724
+ hint.className = 'hint err'; hint.innerHTML = 'You already have a repo named <strong>' + esc(state.repo) + '</strong> — pick another name, or adopt it instead.';
725
+ } else {
726
+ hint.className = 'hint'; hint.innerHTML = 'We’ll create an empty repo at <code style="font-family:var(--font-mono)">github.com/' + esc(myLogin()) + '/' + esc(state.repo || 'name') + '</code>.';
727
+ }
728
+ }
729
+ $('ghNewName').addEventListener('input', function () { state.repo = this.value.trim(); updateFreshPreview(); saveDraft(); });
730
+
731
+ // The adopt picker behaves like a standard combobox (owner design review, task
732
+ // 1002247): the dropdown is open only while the visitor is choosing. Selecting a
733
+ // repo fills the search box with its full_name and collapses the list; focusing or
734
+ // typing reopens it.
735
+ function openRepoList() { $('ghList').hidden = false; renderRepoList(); }
736
+ function closeRepoList() { $('ghList').hidden = true; }
737
+ function renderRepoList() {
738
+ var q = ($('ghSearch').value || '').trim().toLowerCase();
739
+ var sel = state.owner && state.repo ? (state.owner + '/' + state.repo) : '';
740
+ // When the box just shows the current selection verbatim, list everything (so the
741
+ // visitor can switch) instead of filtering down to the one selected repo.
742
+ var effectiveQ = q === sel.toLowerCase() ? '' : q;
743
+ var rows = repoList.filter(function (r) { return !effectiveQ || String(r.full_name).toLowerCase().indexOf(effectiveQ) >= 0; }).slice(0, 100);
744
+ $('ghList').innerHTML = rows.length
745
+ ? rows.map(function (r) {
746
+ return '<button type="button" class="ghrow" role="option" data-full="' + esc(r.full_name) + '" aria-selected="' + (r.full_name === sel) + '">' +
747
+ '<span>' + esc(r.full_name) + '</span>' + (r.private ? '<span class="priv">Private</span>' : '') + '</button>';
748
+ }).join('')
749
+ : '<div class="hint" style="padding:10px 12px">No repositories match “' + esc(effectiveQ) + '”.</div>';
750
+ }
751
+ // Fill the search box with the current selection (if any) and collapse the list;
752
+ // with nothing chosen yet, open it so the visitor sees their repos right away.
753
+ function initAdoptPicker() {
754
+ var sel = state.owner && state.repo ? state.owner + '/' + state.repo : '';
755
+ // Only treat it as a confirmed pick if it's actually one of the visitor's repos.
756
+ // owner/repo are auto-derived from the project name, so a bare derived value must
757
+ // NOT masquerade as a chosen repo — that would collapse the list onto a repo that
758
+ // may not even exist.
759
+ var picked = sel && repoList.some(function (r) { return String(r.full_name).toLowerCase() === sel.toLowerCase(); });
760
+ var h = $('ghAdoptHint');
761
+ if (picked) {
762
+ $('ghSearch').value = sel;
763
+ h.className = 'hint ok'; h.innerHTML = 'Adopting <strong>' + esc(sel) + '</strong>.';
764
+ closeRepoList();
765
+ } else {
766
+ $('ghSearch').value = '';
767
+ h.className = 'hint'; h.innerHTML = '&nbsp;';
768
+ openRepoList();
769
+ }
770
+ }
771
+ $('ghSearch').addEventListener('focus', function () { this.select(); openRepoList(); });
772
+ $('ghSearch').addEventListener('input', openRepoList);
773
+ $('ghList').addEventListener('click', function (ev) {
774
+ var row = ev.target.closest('.ghrow'); if (!row) return;
775
+ var full = row.getAttribute('data-full'); var slash = full.indexOf('/');
776
+ state.owner = full.slice(0, slash); state.repo = full.slice(slash + 1);
777
+ state.ownerTouched = true; state.repoTouched = true; // a real pick — don't let name-derive clobber it
778
+ saveDraft();
779
+ $('ghSearch').value = full; // pull the choice into the search box …
780
+ var h = $('ghAdoptHint'); h.className = 'hint ok'; h.innerHTML = 'Adopting <strong>' + esc(full) + '</strong>.';
781
+ closeRepoList(); // … and collapse the dropdown (typical UX)
782
+ });
783
+
784
+ function ensureRepoAccess() {
785
+ if (ghProbed && repoAccess !== null) return; // already known this session
786
+ if (isManualHome()) return; // user chose manual — don't nag
787
+ repoAccess = null; syncHome(); // show "reading your repos…"
788
+ fetch(API + '/github/repos', { credentials: 'same-origin' })
789
+ .then(function (res) {
790
+ ghProbed = true;
791
+ if (res.status === 200) return res.json().then(function (d) { repoList = (d && d.repos) || []; repoAccess = true; });
792
+ if (res.status === 403 || res.status === 401) { repoAccess = 'need-connect'; return; }
793
+ repoAccess = 'unavailable'; return; // 503 or anything else → manual fallback
794
+ })
795
+ .catch(function () { ghProbed = true; repoAccess = 'unavailable'; })
796
+ .then(syncHome);
797
+ }
798
+
799
+ $('f-owner').addEventListener('input', function () { state.owner = this.value.trim(); state.ownerTouched = true; saveDraft(); });
800
+ $('f-repo').addEventListener('input', function () { state.repo = this.value.trim(); state.repoTouched = true; saveDraft(); });
651
801
 
652
802
  $('f-slug').addEventListener('input', function () {
653
803
  state.slugTouched = true;
@@ -675,7 +825,17 @@ input[type=text].bad{border-color:var(--err);}
675
825
  return true;
676
826
  }
677
827
  if (step === 2) {
678
- if (!state.owner || !state.repo) { $('h-home').textContent = 'Both owner and repository name are needed.'; $('h-home').className = 'hint err'; return false; }
828
+ var homeErr = function (msg) { $('h-home').textContent = msg; $('h-home').className = 'hint err'; return false; };
829
+ if (repoAccess === 'need-connect') return homeErr('Allow GitHub repo access to pick or create your repository — or choose “enter it manually”.');
830
+ if (repoAccess === true && state.mode !== 'adopt') {
831
+ // Fresh with access: just the new repo NAME (owner is the signed-in account).
832
+ if (!validRepoName(state.repo)) return homeErr('Name the new repository (letters, numbers, and . _ -).');
833
+ } else {
834
+ // Adopt (picker or manual) and the manual fallback: need a concrete owner + repo.
835
+ if (!state.owner || !state.repo) {
836
+ return homeErr(state.mode === 'adopt' && repoAccess === true ? 'Pick one of your repositories.' : 'Both owner and repository name are needed.');
837
+ }
838
+ }
679
839
  $('h-home').innerHTML = '&nbsp;'; $('h-home').className = 'hint';
680
840
  return true;
681
841
  }
@@ -725,20 +885,58 @@ input[type=text].bad{border-color:var(--err);}
725
885
  return 'Couldn’t file the request: ' + (err && err.message ? err.message : 'something went sideways. Try again.');
726
886
  }
727
887
 
888
+ /* Fresh + repo access: auto-create the empty GitHub repo NOW (the commit moment),
889
+ then hand its real full_name to provisioning. No-op for adopt / manual / no-access.
890
+ Idempotent across a retry — once created we reuse the stashed full_name so a second
891
+ click doesn't trip GitHub's 422 "name already exists". Errors set err.gotoHome so
892
+ the caller bounces the user back to the Home step to fix the name / reconnect. */
893
+ function ensureFreshRepo() {
894
+ if (state.mode === 'adopt' || repoAccess !== true) return Promise.resolve();
895
+ if (state.repoAutoCreated && state.owner && state.repo) return Promise.resolve();
896
+ return fetch(API + '/github/repos', {
897
+ method: 'POST', credentials: 'same-origin',
898
+ headers: { 'Content-Type': 'application/json' },
899
+ body: JSON.stringify({ name: state.repo })
900
+ }).then(function (res) {
901
+ return res.json().catch(function () { return {}; }).then(function (data) {
902
+ if (res.status === 401 || res.status === 403) {
903
+ repoAccess = 'need-connect';
904
+ var e0 = new Error('reconnect'); e0.gotoHome = true;
905
+ e0.homeMsg = 'Your GitHub authorization expired — reconnect on the Home step to create the repo.';
906
+ throw e0;
907
+ }
908
+ if (!res.ok) {
909
+ var e = new Error((data.error && (data.error.message || data.error.code)) || ('HTTP ' + res.status));
910
+ e.data = data;
911
+ if (data.error && data.error.code === 'name_taken') { e.gotoHome = true; e.homeMsg = data.error.message; }
912
+ throw e;
913
+ }
914
+ var full = data.repo && data.repo.full_name;
915
+ if (full && full.indexOf('/') > 0) {
916
+ state.owner = full.slice(0, full.indexOf('/'));
917
+ state.repo = full.slice(full.indexOf('/') + 1);
918
+ }
919
+ state.repoAutoCreated = true; saveDraft();
920
+ });
921
+ });
922
+ }
923
+
728
924
  $('doCreate').addEventListener('click', function () {
729
925
  var btn = this;
730
926
  var alertBox = $('createAlert');
731
927
  alertBox.classList.remove('on');
732
928
  btn.disabled = true; btn.textContent = 'Raising it into the sky…';
733
- fetch(API + '/provisioning/instances', {
734
- method: 'POST', credentials: 'same-origin',
735
- headers: { 'Content-Type': 'application/json' },
736
- body: JSON.stringify({
737
- slug: state.slug,
738
- hosting_shape: 'standalone',
739
- domain: state.domain || undefined,
740
- target_ref: state.owner + '/' + state.repo
741
- })
929
+ ensureFreshRepo().then(function () {
930
+ return fetch(API + '/provisioning/instances', {
931
+ method: 'POST', credentials: 'same-origin',
932
+ headers: { 'Content-Type': 'application/json' },
933
+ body: JSON.stringify({
934
+ slug: state.slug,
935
+ hosting_shape: 'standalone',
936
+ domain: state.domain || undefined,
937
+ target_ref: state.owner + '/' + state.repo
938
+ })
939
+ });
742
940
  }).then(function (res) {
743
941
  if (res.status === 401) { window.location.assign(API + '/auth/web/start?return=/create'); throw new Error('signing in'); }
744
942
  return res.json().catch(function () { return {}; }).then(function (data) {
@@ -761,9 +959,17 @@ input[type=text].bad{border-color:var(--err);}
761
959
  startProgress(data.instance && data.instance.id);
762
960
  }).catch(function (err) {
763
961
  if (String(err && err.message) === 'signing in') return;
962
+ btn.disabled = false; btn.textContent = 'Create my project';
963
+ if (err && err.gotoHome) {
964
+ // A repo-creation problem (name taken / reconnect) — bounce back to the Home
965
+ // step with the reason, so the user fixes it where the repo is chosen.
966
+ show(2);
967
+ $('h-home').textContent = err.homeMsg || 'Please revisit the repository step.';
968
+ $('h-home').className = 'hint err';
969
+ return;
970
+ }
764
971
  alertBox.textContent = friendlyCreateError(err);
765
972
  alertBox.classList.add('on');
766
- btn.disabled = false; btn.textContent = 'Create my project';
767
973
  });
768
974
  });
769
975
 
@@ -786,8 +992,10 @@ input[type=text].bad{border-color:var(--err);}
786
992
  var ghIcon = '<svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>';
787
993
  steps.push(adopt
788
994
  ? '<li data-onboard-step="github-repo"><b>Layer onto your existing repo</b><p>In your repo run <code style="font-family:var(--font-mono)">bongos init --adopt</code>. It shows a conflict pre-flight and writes <em>nothing</em> until you accept — your files and history stay yours, and your existing backlog is imported. Commit and push.</p></li>'
789
- : '<li data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>Do this first — the build needs the repo to exist. Click below (GitHub opens with the name already filled in), press <strong>Create repository</strong>, and leave it completely empty. Seeing “repository not found” above? That just means the repo isn’t created yet — make it here.</p>' +
790
- '<a class="ghbtn" href="' + esc(ghNewUrl) + '" target="_blank" rel="noopener">' + ghIcon + 'Create ' + repoLabel + ' on GitHub</a></li>');
995
+ : (state.repoAutoCreated
996
+ ? '<li data-onboard-step="github-repo"><b>Your empty repo is ready</b><p>We created ' + repoLabel + ' on GitHub for you — empty and waiting. The scaffold lands there when the build runs; nothing for you to do here.</p></li>'
997
+ : '<li data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>Do this first — the build needs the repo to exist. Click below (GitHub opens with the name already filled in), press <strong>Create repository</strong>, and leave it completely empty. Seeing “repository not found” above? That just means the repo isn’t created yet — make it here.</p>' +
998
+ '<a class="ghbtn" href="' + esc(ghNewUrl) + '" target="_blank" rel="noopener">' + ghIcon + 'Create ' + repoLabel + ' on GitHub</a></li>'));
791
999
  if (!state.domain) {
792
1000
  steps.push('<li><b>Add a domain you own</b><p>Sign-in on your new instance needs a real domain. When you have one, come back here or ask your hall — the address gets wired in minutes.</p></li>');
793
1001
  }
@@ -917,31 +1125,14 @@ input[type=text].bad{border-color:var(--err);}
917
1125
  if (e.key === 'Escape') { e.preventDefault(); exitWizard(); }
918
1126
  });
919
1127
 
920
- /* ── switch GitHub account from inside the wizard (task 2098) ── you are not
921
- locked to the account you entered with: sign out of the Cloud Bongos session,
922
- then restart GitHub sign-in so the wizard reloads under whichever account you
923
- pick. (To land on a *different* GitHub account, switch accounts on GitHub's
924
- side; this un-locks our session so the next sign-in is honored.) */
925
- function switchAccount(e) {
926
- if (e) e.preventDefault();
927
- var back = API + '/auth/web/start?return=/create';
928
- fetch(API + '/auth/logout', { method: 'POST', credentials: 'same-origin' })
929
- .then(function () { window.location.assign(back); })
930
- .catch(function () { window.location.assign(back); });
931
- }
932
-
933
1128
  /* ── boot: who am I? ── (task 2094: the wizard shows the signed-in identity
934
1129
  ONLY — never a link into cloud bongos's own builders hall. A project
935
- creator enters a hall from their own project's landing, not from here.
936
- task 2098: + a "switch account" action so you're not locked in.) ── */
1130
+ creator enters a hall from their own project's landing, not from here.) ── */
937
1131
  function renderWho() {
938
1132
  var w = $('who');
939
1133
  if (me) {
940
1134
  w.innerHTML = (me.avatar_url ? '<img src="' + esc(me.avatar_url) + '" alt="">' : '') +
941
- '<span>' + esc(me.github_login || 'signed in') + '</span>' +
942
- '<a href="#" class="switch-acct" id="switchAcct">switch account</a>';
943
- var sw = $('switchAcct');
944
- if (sw) sw.addEventListener('click', switchAccount);
1135
+ '<span>' + esc(me.github_login || 'signed in') + '</span>';
945
1136
  } else {
946
1137
  w.innerHTML = '';
947
1138
  }
@@ -957,7 +1148,6 @@ input[type=text].bad{border-color:var(--err);}
957
1148
  if (!state.ownerTouched && !state.owner && me && me.github_login) {
958
1149
  state.owner = me.github_login;
959
1150
  $('f-owner').value = state.owner;
960
- updateGhCreateHref();
961
1151
  saveDraft();
962
1152
  }
963
1153
  // returning after a create? resume watching that instance instead of a blank wizard
@@ -0,0 +1,137 @@
1
+ # Cloud Bongos — landing gate (interactive)
2
+
3
+ `index.html` is the Cloud Bongos landing experience — a small state machine over one
4
+ self-contained page (`space.css` + `space.js` for the space side; the download scene + its
5
+ drag physics live in the inline `<script>`), built from **drawn ATLA-style sprites**
6
+ (`sprites/*.png` — a `> cloud bongos` terminal, an enter key, a search bar, and download tiles)
7
+ refracting the backdrop through SVG displacement maps.
8
+
9
+ ## The flow (tasks 1721, 1722)
10
+
11
+ Four body-class views: `view-entry` → `view-space` → `view-download` / `view-detail`.
12
+
13
+ 1. **Entry — type `bongos`, in space** (`#entry`, `view-entry`). Every visit lands here: the
14
+ `> cloud ▊` terminal floating in the **space sky** (deep-blue → sunrise gradient, drifting
15
+ clouds), *not* the temple world. Typing `bongos` is the ritual on every visit; the enter key
16
+ lights, and pressing enter flashes through into the circles. Driven by `space.js`
17
+ (`entryKey` / `enterSite`), reusing the terminal + enter CSS.
18
+ 2. **Space — the circles** (`#space` / `#space-canvas`, `view-space`). A pannable, zoomable sky
19
+ of project orbs, sized by number of builders, moving as a **living gravity cluster** — every
20
+ orb attracts every other by its mass (∝ size³), so the field swirls, bodies sling past one
21
+ another, and a small orb is sometimes briefly caught circling a big one like a moon (task
22
+ 1733; see "The gravity" below). Project data is **inlined** in `space.js` (`PROJECTS`) — Caddy
23
+ 404s `*.json` on staging — so swap in a real registry by changing `PROJECTS` + `layout()`.
24
+ - **Drag** empty sky to pan · **scroll** to zoom (toward the cursor).
25
+ - **Hover** an orb for an info card; **click** it to dive in.
26
+ - **Space bar** opens a search scoped to the orbs **currently in view** (client-side match
27
+ over name/tagline/tags now; the Claude-backed answer is a dormant seam, `aiAnswer()`, per
28
+ the "interaction now, AI wired next" decision).
29
+ - **Click empty sky** drops a new project circle.
30
+ 3. **Download — Cloud Bongos' install page** (`#land` in `.stage`, `view-download`). Clicking the
31
+ Cloud Bongos orb zooms **straight** into the search + download-tiles scene over the
32
+ temple-world backdrop — **no type-`bongos` gate in between** (that already happened at entry).
33
+ `space.js` fires `cb:show-download`; the inline script reveals `#land` and runs its drag.
34
+ 4. **Detail — other projects** (`#detail`, `view-detail`). Clicking any non-self orb opens a
35
+ themed "full story — coming soon" page with a link out to its live site.
36
+
37
+ **ESC** rises from the download or detail page back to the circles (`space.js` → `cb:hide-download`).
38
+
39
+ ## The download scene's elastic-tether drag (task 1534)
40
+
41
+ Every download sprite (search bar, tiles, wordmark) is draggable. Grab one and it resists like
42
+ it's anchored; pull past the threshold and it snaps free, hovers, then drifts slowly home and
43
+ re-docks, carrying a faint idle bob at rest. The physics is scoped to `.stage`, so it never
44
+ grabs the entry terminal (which `space.js` owns). **This scene is separate from the space
45
+ gravity below — the download interaction is unchanged by task 1733.**
46
+
47
+ ## The space gravity — a living n-body cluster (task 1733)
48
+
49
+ The project orbs on the **space** view (`view-space`) are a real gravitational simulation, all
50
+ in `space.js`. Each orb is a body with **mass ∝ size³** (more builders → bigger → heavier →
51
+ pulls harder), so the sky behaves like planets, suns and the occasional fleeting moon rather
52
+ than a fixed scatter. Under the hood:
53
+
54
+ - **Softened Newtonian gravity**, every pair, so a close pass *slings* instead of blowing up
55
+ (no singularities).
56
+ - A **fixed-timestep leapfrog integrator** (`physStep`) for accurate, energy-stable motion; the
57
+ frame loop (`tick` → `render`) interpolates between steps for smoothness.
58
+ - **Soft collisions** so discs bounce rather than overlap; a **soft containment well** plus a
59
+ **band thermostat** keep the swirl bounded and lively *forever* — energy is free to ebb and
60
+ flow inside a band, and only a runaway collapse or blow-up gets a gentle nudge back.
61
+ - Net drift is removed each step, so the cluster stays centred on screen (and stays put when a
62
+ visitor adds a new orb, which is flung into the swirl).
63
+
64
+ Verified stable over a 60-minute headless simulation (no NaN, never off-screen, never
65
+ interpenetrating). It replaced the earlier "drift around fixed anchors" motion.
66
+
67
+ ### Tuning the gravity
68
+
69
+ All knobs live in the `GRAV` block near the top of `space.js`:
70
+
71
+ | You want… | Edit (in `GRAV`) |
72
+ |--------------------------------------|---------------------------------------------------|
73
+ | Livelier / faster swirl | `SWIRL` ↑ (or `THERMO_LO` ↑) |
74
+ | Calmer, slower motion | `SWIRL` ↓ / `THERMO_LO` ↓ |
75
+ | Tighter cluster (less panning) | `CONTAIN_R` ↓ |
76
+ | Stronger pull between orbs | `G` ↑ |
77
+ | Fewer hard slingshots on near-misses | `SOFTEN` ↑ |
78
+ | Bouncier contacts (→ elastic) | `REST` → 1 |
79
+ | Sharper size→mass contrast | lower `R_REF`, or raise the `massFor` exponent |
80
+
81
+ Turn it off (freeze the orbs): comment out `initOrbits()` in `layout()` and the
82
+ `requestAnimationFrame(tick)` at the bottom of the IIFE — pan, zoom, hover, dive, search and the
83
+ table all keep working.
84
+
85
+ > **Live:** password-locked at **staging.cloudbongos.com** (the future landing page, parked
86
+ > for review). Setup + the password live in [`docs/recipes/cloudbongos-staging-gate.md`](../../../docs/recipes/cloudbongos-staging-gate.md).
87
+ > Branding (logo coin / wordmark / mascot / code-glyphs) is still being finished and is
88
+ > intentionally untouched here.
89
+
90
+ ## Preview it locally
91
+
92
+ The page links to art one folder up (`../world/assets/…`), so it must be served from the
93
+ **`brand/cloud-bongos/`** folder — not from `gate/` itself, or those paths break.
94
+
95
+ **Easiest (Claude Code preview):** start the `gate-preview` server in `.claude/launch.json`,
96
+ then open `http://127.0.0.1:8137/gate/index.html`.
97
+
98
+ **From a terminal** (repo root): `python3 -m http.server --directory brand/cloud-bongos 8137`,
99
+ then open <http://127.0.0.1:8137/gate/index.html>.
100
+
101
+ Use a Chromium browser (Chrome/Edge) — the live-glass refraction relies on `backdrop-filter`
102
+ with an SVG displacement map. Safari ignores the displacement and falls back to a plain
103
+ frost, which still looks fine.
104
+
105
+ ## Tuning the drag
106
+
107
+ All behaviour is in the single `<script>` at the bottom of `index.html`, which opens with a
108
+ commented **TUNING** block. Common tweaks:
109
+
110
+ | You want… | Edit (in the TUNING block) |
111
+ |------------------------------------------|-----------------------------------|
112
+ | Harder / easier to pull a pane free | `THRESHOLD` — ↑ harder, ↓ easier |
113
+ | How far a tap can move before it's a drag | `DEADZONE` — px |
114
+ | Freed panes wait longer before returning | `HOVER` — milliseconds |
115
+ | Slower, more drifty return | `DRIFT_MAXV` — ↓ slower |
116
+ | Wider, more wandering return path | `WMAG` ↑ and/or `TURN` ↓ |
117
+ | More / less idle "breathing" | `BOB_X`, `BOB_Y` (px) — `0` = off |
118
+
119
+ ### Which elements are draggable
120
+
121
+ The drag targets **`.mark`** (the wordmark) + every **`.el`** sprite pane (terminal, enter,
122
+ search, tiles). To add one, give it the `el` class (or push it into the `els` array); to
123
+ exclude one, filter it out of `els`.
124
+
125
+ ### Why it does not fight the other animations
126
+
127
+ The drag only ever applies `transform: translate(…)` to each pane, and it owns that property
128
+ exclusively — so the float/reveal animations were replaced by the drag's own idle bob, and
129
+ the enter key's reveal uses opacity + blur only (no transform). The **scene** transforms
130
+ (the dive's scale/translate) live on the parent `.gate`/`.land` containers, so they compose
131
+ with the per-pane drag instead of clobbering it. When a scene settles, `measure()` re-runs
132
+ so a freshly-revealed pane drags from the right home.
133
+
134
+ ### Turn the drag off
135
+
136
+ Delete the second `<script>` IIFE (the drag) — the type→dive interaction and all the art
137
+ keep working untouched.