@bongos/core 1.18.3 → 1.18.19

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 (471) hide show
  1. package/.bongos-core.json +1576 -286
  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 +4 -2
  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 +6 -1
  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/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  27. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  28. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  29. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  30. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  31. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  32. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  33. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  34. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  35. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  36. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  37. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  38. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  39. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  40. package/docs/adr/README.md +10 -0
  41. package/docs/api/openapi.json +843 -17
  42. package/docs/api-reference.md +23 -10
  43. package/docs/architecture.md +4 -2
  44. package/docs/branding-contract.md +3 -1
  45. package/docs/canonical-permissions.md +21 -8
  46. package/docs/file-map.md +8 -0
  47. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  48. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  49. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  50. package/docs/onboarding/diagrams/assertions.json +4 -7
  51. package/docs/onboarding/drachmae-and-karma.md +3 -3
  52. package/docs/onboarding/primer.md +17 -16
  53. package/docs/onboarding/slash-commands.md +124 -0
  54. package/docs/recipes/builder-box-lifecycle.md +23 -0
  55. package/docs/recipes/devbox-app-release.md +14 -4
  56. package/docs/recipes/instance-first-run.md +16 -0
  57. package/docs/recipes/local-dev.md +81 -0
  58. package/docs/recipes/ops-gotchas.md +19 -1
  59. package/docs/recipes/overnight-builder.md +1 -1
  60. package/docs/recipes/parallel-goal-waves.md +174 -0
  61. package/docs/recipes/ship-and-regrade.md +2 -2
  62. package/docs/recipes/standalone-live-docs.md +48 -0
  63. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  64. package/migrations/003_pms.sql +6 -7
  65. package/migrations/006_pms_v2.sql +3 -31
  66. package/migrations/core_193_task_needs_rebase.sql +28 -0
  67. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  68. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  69. package/migrations/core_196_session_reward_topup.sql +69 -0
  70. package/modules/autonomy/pollers/routine-timers.js +9 -8
  71. package/modules/builder-settings/wandering-prefs.js +61 -18
  72. package/modules/dev-box/app/README.md +73 -0
  73. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  74. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  75. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  76. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  77. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  78. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  79. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  80. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  81. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  82. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  83. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  84. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  85. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  86. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  87. package/modules/dev-box/app/build-resources/icon.png +0 -0
  88. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  89. package/modules/dev-box/app/electron-builder.yml +95 -0
  90. package/modules/dev-box/app/package-lock.json +5291 -0
  91. package/modules/dev-box/app/package.json +30 -0
  92. package/modules/dev-box/app/renderer/app.js +319 -0
  93. package/modules/dev-box/app/renderer/index.html +45 -0
  94. package/modules/dev-box/app/renderer/styles.css +246 -0
  95. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  96. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  97. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  98. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  99. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  100. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  101. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  102. package/modules/dev-box/app/src/box-controller.js +287 -0
  103. package/modules/dev-box/app/src/brand.generated.json +13 -0
  104. package/modules/dev-box/app/src/brand.js +16 -0
  105. package/modules/dev-box/app/src/config.js +19 -0
  106. package/modules/dev-box/app/src/gds-client.js +90 -0
  107. package/modules/dev-box/app/src/main.js +610 -0
  108. package/modules/dev-box/app/src/pairing.js +57 -0
  109. package/modules/dev-box/app/src/preload.js +29 -0
  110. package/modules/dev-box/app/src/session-store.js +58 -0
  111. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  112. package/modules/dev-box/app/src/task-picker.js +70 -0
  113. package/modules/dev-box/app/src/tray.js +99 -0
  114. package/modules/dev-box/app/src/updater.js +55 -0
  115. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  116. package/modules/dev-box/app/test/unit.mjs +526 -0
  117. package/modules/dev-box/box-access.js +121 -4
  118. package/modules/dev-box/boxes.js +20 -0
  119. package/modules/dev-box/module.json +1 -1
  120. package/modules/dev-box/routes/box.js +87 -3
  121. package/modules/economy/credits.js +116 -25
  122. package/modules/economy/reward.js +8 -0
  123. package/modules/economy/streak.js +2 -1
  124. package/modules/governance/CLAUDE.md +15 -0
  125. package/modules/governance/catalog.js +419 -0
  126. package/modules/governance/config.js +174 -0
  127. package/modules/governance/db.js +125 -0
  128. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  129. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  130. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  131. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  132. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  133. package/modules/governance/module.json +10 -0
  134. package/modules/governance/protected-surfaces.js +88 -0
  135. package/modules/governance/protected-surfaces.json +80 -0
  136. package/modules/governance/rank-roles.js +83 -0
  137. package/modules/governance/resolver.js +221 -0
  138. package/modules/governance/routes/governance.js +89 -0
  139. package/modules/governance/routes/role-sync.js +49 -0
  140. package/modules/governance/session-scopes.js +127 -0
  141. package/modules/governance/session-scopes.json +38 -0
  142. package/modules/hall-ui/public/atlas.css +117 -0
  143. package/modules/hall-ui/public/atlas.html +3 -114
  144. package/modules/hall-ui/public/brand-apply.js +34 -6
  145. package/modules/hall-ui/public/builders.js +187 -219
  146. package/modules/hall-ui/public/diagrams.html +2 -0
  147. package/modules/hall-ui/public/dom-utils.js +27 -0
  148. package/modules/hall-ui/public/drachmae.html +2 -0
  149. package/modules/hall-ui/public/drachmae.js +54 -1
  150. package/modules/hall-ui/public/gate.css +98 -0
  151. package/modules/hall-ui/public/gate.html +3 -95
  152. package/modules/hall-ui/public/goal-inbox.js +4 -1
  153. package/modules/hall-ui/public/goals-page.js +626 -114
  154. package/modules/hall-ui/public/goals.css +79 -1
  155. package/modules/hall-ui/public/goals.html +38 -11
  156. package/modules/hall-ui/public/hall-widgets.js +62 -52
  157. package/modules/hall-ui/public/harbor.css +123 -0
  158. package/modules/hall-ui/public/harbor.html +3 -120
  159. package/modules/hall-ui/public/index.html +11 -4
  160. package/modules/hall-ui/public/instances-panel.js +4 -1
  161. package/modules/hall-ui/public/modules.css +63 -0
  162. package/modules/hall-ui/public/modules.html +3 -60
  163. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  164. package/modules/hall-ui/public/not-ready.html +2 -0
  165. package/modules/hall-ui/public/pair.css +73 -0
  166. package/modules/hall-ui/public/pair.html +3 -70
  167. package/modules/hall-ui/public/palette.js +302 -0
  168. package/modules/hall-ui/public/primer.css +13 -0
  169. package/modules/hall-ui/public/primer.html +2 -0
  170. package/modules/hall-ui/public/profile.html +2 -0
  171. package/modules/hall-ui/public/ranks.html +2 -0
  172. package/modules/hall-ui/public/roadmap.css +179 -0
  173. package/modules/hall-ui/public/roadmap.html +9 -172
  174. package/modules/hall-ui/public/roadmap.js +329 -316
  175. package/modules/hall-ui/public/sessions.css +16 -0
  176. package/modules/hall-ui/public/sessions.html +3 -13
  177. package/modules/hall-ui/public/settings.html +2 -0
  178. package/modules/hall-ui/public/settings.js +4 -4
  179. package/modules/hall-ui/public/shell.js +143 -18
  180. package/modules/hall-ui/public/style.css +231 -39
  181. package/modules/hall-ui/public/task-detail.js +347 -26
  182. package/modules/hall-ui/public/task.html +6 -1
  183. package/modules/hall-ui/public/watch.html +2 -0
  184. package/modules/hall-ui/public/work.html +8 -6
  185. package/modules/hall-ui/public/work.js +1 -1
  186. package/modules/ideas/inbox.js +61 -11
  187. package/modules/ideas/routes/inbox.js +23 -0
  188. package/modules/lifecycle/claim-eligibility.js +72 -0
  189. package/modules/lifecycle/db.js +519 -139
  190. package/modules/lifecycle/done-when.js +18 -3
  191. package/modules/lifecycle/github-push.js +19 -2
  192. package/modules/lifecycle/goal-advisory.js +114 -0
  193. package/modules/lifecycle/goal-authz.js +62 -0
  194. package/modules/lifecycle/lifecycle.js +9 -0
  195. package/modules/lifecycle/publish-reconciler.js +109 -6
  196. package/modules/lifecycle/routes/claims.js +68 -23
  197. package/modules/lifecycle/routes/done-when.js +110 -15
  198. package/modules/lifecycle/routes/goals.js +8 -38
  199. package/modules/lifecycle/routes/tasks.js +38 -6
  200. package/modules/lifecycle/ship-card.js +6 -0
  201. package/modules/onboarding/onboarding-state.js +1 -1
  202. package/modules/platform-identity/CLAUDE.md +15 -0
  203. package/modules/platform-identity/hub-device.js +147 -0
  204. package/modules/platform-identity/hub-keys.js +151 -0
  205. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  206. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  207. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  208. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  209. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  210. package/modules/platform-identity/module.json +18 -0
  211. package/modules/platform-identity/platform-identity.js +531 -0
  212. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  213. package/modules/platform-identity/routes/my-projects.js +140 -0
  214. package/modules/platform-identity/routes/projects.js +107 -0
  215. package/modules/platform-identity/routes/scouting.js +35 -0
  216. package/modules/platform-identity/routes/sso.js +402 -0
  217. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  218. package/modules/platform-identity/ui/.gitkeep +0 -0
  219. package/modules/security/routes/reports.js +10 -0
  220. package/modules/sessions/db.js +166 -21
  221. package/modules/sessions/module.json +1 -1
  222. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  223. package/modules/sessions/routes/sessions.js +33 -0
  224. package/package-lock.json +2 -2
  225. package/package.json +1 -1
  226. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  227. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  228. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  229. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  230. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  231. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  232. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  233. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  234. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  235. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  236. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  237. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  238. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  239. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  270. package/public-landing/create.html +51 -58
  271. package/public-landing/gate/README.md +137 -0
  272. package/public-landing/gate/dispmaps/bar.png +0 -0
  273. package/public-landing/gate/dispmaps/flat.png +0 -0
  274. package/public-landing/gate/dispmaps/key.png +0 -0
  275. package/public-landing/gate/index.html +587 -0
  276. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  277. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  278. package/public-landing/gate/space.css +799 -0
  279. package/public-landing/gate/space.js +1826 -0
  280. package/public-landing/gate/sprites/enter.png +0 -0
  281. package/public-landing/gate/sprites/search.png +0 -0
  282. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  283. package/public-landing/gate/sprites/tile.png +0 -0
  284. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  285. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  286. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  287. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  288. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  289. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  290. package/scripts/gds/abandon.js +6 -4
  291. package/scripts/gds/api.js +10 -5
  292. package/scripts/gds/api.ps1 +26 -0
  293. package/scripts/gds/api.sh +6 -0
  294. package/scripts/gds/audit-ships.js +7 -1
  295. package/scripts/gds/backfill-sessions.js +78 -18
  296. package/scripts/gds/backup.js +23 -1
  297. package/scripts/gds/box-connect-lib.js +124 -31
  298. package/scripts/gds/box-sync.js +66 -0
  299. package/scripts/gds/box.js +225 -53
  300. package/scripts/gds/cf-tunnel.js +145 -6
  301. package/scripts/gds/claim.js +34 -3
  302. package/scripts/gds/cli-lib.js +161 -27
  303. package/scripts/gds/code.js +6 -2
  304. package/scripts/gds/deploy-divergence.js +209 -0
  305. package/scripts/gds/dev-lib.js +309 -0
  306. package/scripts/gds/dev.js +322 -0
  307. package/scripts/gds/doctor.js +90 -61
  308. package/scripts/gds/fetch-art-key.js +1 -1
  309. package/scripts/gds/fetch-sound-prefs.js +2 -2
  310. package/scripts/gds/fitness.js +228 -12
  311. package/scripts/gds/gds-db-backup.service +16 -1
  312. package/scripts/gds/gds-db-offsite.service +9 -1
  313. package/scripts/gds/gen-api-client.js +1 -1
  314. package/scripts/gds/gen-api-docs.js +36 -14
  315. package/scripts/gds/gen-diagrams.js +45 -28
  316. package/scripts/gds/gen-file-map.js +2 -1
  317. package/scripts/gds/gen-freshness.js +32 -0
  318. package/scripts/gds/gen-repo-map.js +3 -2
  319. package/scripts/gds/gen-session-index.js +14 -5
  320. package/scripts/gds/hooks/pre-push.js +2 -2
  321. package/scripts/gds/init.js +60 -3
  322. package/scripts/gds/install-git-hooks.js +1 -1
  323. package/scripts/gds/key-card.js +15 -6
  324. package/scripts/gds/key-status.js +1 -1
  325. package/scripts/gds/linkify-refs.js +35 -9
  326. package/scripts/gds/login.js +16 -1
  327. package/scripts/gds/paste-token.js +7 -1
  328. package/scripts/gds/preflight.js +126 -0
  329. package/scripts/gds/provision.js +123 -12
  330. package/scripts/gds/publish-manifest.js +7 -1
  331. package/scripts/gds/reauth.js +7 -3
  332. package/scripts/gds/regen-instance-docs.js +47 -0
  333. package/scripts/gds/run-unit-tests.js +4 -2
  334. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  335. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  336. package/scripts/gds/sequence.js +425 -0
  337. package/scripts/gds/session-cost.js +10 -1
  338. package/scripts/gds/setup.js +31 -5
  339. package/scripts/gds/shell.js +5 -3
  340. package/scripts/gds/ship.js +319 -29
  341. package/scripts/gds/smoke-gds.js +139 -8
  342. package/scripts/gds/start.js +171 -22
  343. package/scripts/gds/status.js +1 -1
  344. package/scripts/gds/triage.js +10 -9
  345. package/scripts/gds/upgrade.js +124 -2
  346. package/scripts/migrate.sh +99 -14
  347. package/scripts/status-mirror-sync.js +114 -84
  348. package/src/bongos/api-errors.js +1 -0
  349. package/src/bongos/auth.js +447 -102
  350. package/src/bongos/db-kernel.js +27 -0
  351. package/src/bongos/db.js +39 -0
  352. package/src/bongos/module-scope-map.js +27 -1
  353. package/src/bongos/permission-path-check.js +136 -102
  354. package/src/bongos/repo-info.js +47 -11
  355. package/src/bongos/route-rank-check.js +429 -46
  356. package/src/bongos/routes/_helpers.js +12 -1
  357. package/src/bongos/routes/auth.js +219 -2
  358. package/src/bongos/routes/backup.js +58 -2
  359. package/src/bongos/routes/me.js +35 -1
  360. package/src/bongos/serve-internal.js +84 -15
  361. package/src/branding.js +32 -0
  362. package/src/instance-config.js +30 -0
  363. package/src/module-api.js +124 -1
  364. package/src/platform-server.js +86 -9
  365. package/tests/abandon_task.mjs +59 -3
  366. package/tests/api_cli_flags.mjs +53 -0
  367. package/tests/auth_page_gate.mjs +13 -0
  368. package/tests/backfill_sessions.mjs +127 -1
  369. package/tests/backup_cli.mjs +54 -0
  370. package/tests/bongos_box_connect.mjs +77 -0
  371. package/tests/box_access.mjs +130 -1
  372. package/tests/box_credential.mjs +125 -0
  373. package/tests/box_host_keys.mjs +24 -0
  374. package/tests/box_infra_resolve.mjs +149 -0
  375. package/tests/box_scope_session.mjs +6 -2
  376. package/tests/box_sync.mjs +55 -0
  377. package/tests/box_task_scope.mjs +99 -0
  378. package/tests/branding.mjs +12 -0
  379. package/tests/cf_ruleset_preflight.mjs +142 -0
  380. package/tests/cf_tunnel.mjs +37 -0
  381. package/tests/claim_gate_rebase.mjs +98 -0
  382. package/tests/clawback_confirmed_backlog.mjs +103 -0
  383. package/tests/cli_lib_formatters.mjs +20 -0
  384. package/tests/confirm_attribution.mjs +30 -46
  385. package/tests/credit_grant.mjs +198 -10
  386. package/tests/criterion_authoring_gate.mjs +272 -0
  387. package/tests/criterion_progress.mjs +33 -1
  388. package/tests/dev_lib.mjs +253 -0
  389. package/tests/devbox_artifact_gate.mjs +75 -0
  390. package/tests/devbox_dmg_backdrop.mjs +137 -0
  391. package/tests/devbox_icons.mjs +304 -0
  392. package/tests/devbox_installer_brand.mjs +232 -0
  393. package/tests/devbox_key_register.mjs +234 -0
  394. package/tests/docs_openapi_route.mjs +110 -0
  395. package/tests/doctor_hooks.mjs +141 -0
  396. package/tests/extract_json.mjs +140 -0
  397. package/tests/fitness.mjs +89 -0
  398. package/tests/gen_freshness.mjs +96 -0
  399. package/tests/goal_advisory.mjs +251 -0
  400. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  401. package/tests/goal_routes.mjs +59 -4
  402. package/tests/goal_tier.mjs +12 -3
  403. package/tests/governance_catalog.mjs +129 -0
  404. package/tests/governance_claim_eligibility.mjs +262 -0
  405. package/tests/governance_config.mjs +187 -0
  406. package/tests/governance_ownership_scope.mjs +410 -0
  407. package/tests/governance_page_gates.mjs +324 -0
  408. package/tests/governance_parity.mjs +336 -0
  409. package/tests/governance_principals.mjs +232 -0
  410. package/tests/governance_protected_surfaces.mjs +208 -0
  411. package/tests/governance_require_permission.mjs +223 -0
  412. package/tests/governance_resolver.mjs +84 -0
  413. package/tests/governance_role_sync.mjs +231 -0
  414. package/tests/governance_seed.mjs +127 -0
  415. package/tests/governance_session_scope.mjs +434 -0
  416. package/tests/governance_vanilla_boot.mjs +203 -0
  417. package/tests/grade_attribution.mjs +7 -7
  418. package/tests/hall_audit.mjs +223 -0
  419. package/tests/hall_nav.mjs +191 -0
  420. package/tests/hall_palette.mjs +206 -0
  421. package/tests/hall_signout.mjs +124 -0
  422. package/tests/hook_user_agents.mjs +10 -0
  423. package/tests/idea_triage.mjs +162 -0
  424. package/tests/idp_device_exchange.mjs +93 -0
  425. package/tests/init.mjs +51 -0
  426. package/tests/instance_config.mjs +65 -0
  427. package/tests/instance_mismatch_guard.mjs +23 -6
  428. package/tests/instance_release_config.mjs +104 -0
  429. package/tests/linkify_refs.mjs +15 -2
  430. package/tests/migrate_root_resolution.mjs +124 -1
  431. package/tests/module_contributions.mjs +55 -0
  432. package/tests/module_loader.mjs +10 -5
  433. package/tests/module_scope_active_claims.mjs +94 -0
  434. package/tests/needs_rebase.mjs +92 -0
  435. package/tests/one_active_claim_per_session.mjs +76 -0
  436. package/tests/override_attribution.mjs +10 -8
  437. package/tests/override_request_ownership.mjs +8 -4
  438. package/tests/parse_cookie_decode.mjs +42 -0
  439. package/tests/pay_on_land.mjs +129 -0
  440. package/tests/platform_boot.mjs +100 -1
  441. package/tests/post_login_landing.mjs +42 -0
  442. package/tests/preflight_checks.mjs +72 -0
  443. package/tests/provision.mjs +132 -7
  444. package/tests/publish_branch_route.mjs +53 -0
  445. package/tests/publish_reconciler.mjs +187 -2
  446. package/tests/repo_info_binding.mjs +248 -0
  447. package/tests/reward_gate.mjs +11 -2
  448. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  449. package/tests/reward_mode_surfacing.mjs +168 -0
  450. package/tests/route_permission_check.mjs +301 -0
  451. package/tests/route_rank_check.mjs +254 -12
  452. package/tests/route_validation.mjs +30 -0
  453. package/tests/sequence.mjs +267 -0
  454. package/tests/session_digest.mjs +44 -0
  455. package/tests/session_index.mjs +18 -6
  456. package/tests/session_records.mjs +39 -1
  457. package/tests/session_reward_on_land.mjs +381 -0
  458. package/tests/session_reward_reconcile.mjs +229 -0
  459. package/tests/ship_api_client_pathspec.mjs +100 -0
  460. package/tests/ship_base_flag.mjs +93 -0
  461. package/tests/ship_ci_deploy.mjs +80 -3
  462. package/tests/ship_upstream_deletion.mjs +57 -0
  463. package/tests/smoke_target.mjs +233 -0
  464. package/tests/start_env_guard.mjs +87 -0
  465. package/tests/start_rebase_warning.mjs +103 -0
  466. package/tests/status_mirror_sync.mjs +122 -0
  467. package/tests/stop_refcheck.mjs +19 -1
  468. package/tests/task_goal_link.mjs +15 -6
  469. package/tests/upgrade.mjs +139 -0
  470. package/tests/web_admission_status.mjs +162 -0
  471. package/tests/write_validation_gaps.mjs +110 -0
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "bongos-devbox-app",
3
+ "version": "1.0.10",
4
+ "description": "Menu-bar / system-tray switch for a builder's hosted dev box. Self-contained subproject (own deps, never installed on the droplet) — see ADR 0045.",
5
+ "private": true,
6
+ "main": "src/main.js",
7
+ "author": "Cloud Bongos",
8
+ "license": "UNLICENSED",
9
+ "engines": {
10
+ "node": ">=22"
11
+ },
12
+ "scripts": {
13
+ "vendor": "node scripts/vendor-client.js",
14
+ "gen-brand": "node scripts/gen-brand.js",
15
+ "prep": "node scripts/vendor-client.js && node scripts/gen-brand.js",
16
+ "prestart": "npm run prep",
17
+ "pretest": "npm run prep",
18
+ "start": "electron .",
19
+ "test": "node test/unit.mjs",
20
+ "icons": "node scripts/gen-icons.js",
21
+ "predist:mac": "npm run prep",
22
+ "predist:win": "npm run prep",
23
+ "dist:mac": "electron-builder --mac --arm64 --x64",
24
+ "dist:win": "electron-builder --win --x64 --arm64"
25
+ },
26
+ "devDependencies": {
27
+ "electron": "^33.3.1",
28
+ "electron-builder": "^25.1.8"
29
+ }
30
+ }
@@ -0,0 +1,319 @@
1
+ // devbox-app/renderer/app.js — the panel UI. Pure view over the snapshots the
2
+ // main process pushes; every action is a named verb on window.otb (preload).
3
+ (() => {
4
+ 'use strict';
5
+
6
+ const $ = (sel) => document.querySelector(sel);
7
+ let last = null; // { snapshot, meta }
8
+
9
+ function escapeHtml(s) {
10
+ return String(s ?? '').replace(/[&<>"']/g, (c) => ({
11
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
12
+ }[c]));
13
+ }
14
+
15
+ const STATUS_LABEL = {
16
+ none: ['Off', ''],
17
+ provisioning: ['Starting your box…', 'busy'],
18
+ active: ['On', 'on'],
19
+ closing: ['Stopping your box…', 'busy'],
20
+ error: ['Error', 'err'],
21
+ loading: ['Checking…', 'busy'],
22
+ };
23
+
24
+ function header(meta, snapshot) {
25
+ $('#hd-title').textContent = 'Dev Box';
26
+ if (meta.tagline) $('#hd-epigraph').textContent = `— ${meta.tagline} —`;
27
+ if (snapshot.status === 'signed_out') {
28
+ $('#hd-sub').textContent = `Your ${meta.civName} dev box, one switch away.`;
29
+ } else if (meta.builder) {
30
+ $('#hd-sub').textContent = `@${meta.builder.login} · ${meta.civName}`;
31
+ } else {
32
+ $('#hd-sub').textContent = '';
33
+ }
34
+ }
35
+
36
+ function signedOutView(meta) {
37
+ if (meta.pairing && meta.pairing.phase === 'waiting') {
38
+ return `
39
+ <section class="scroll">
40
+ <p class="lede">A page just opened in your browser. Check the code matches, then click
41
+ <strong>Approve</strong> there — this panel updates itself.</p>
42
+ <div class="paircode">${escapeHtml(meta.pairing.pairCode)}</div>
43
+ <button type="button" class="btn btn--ghost" id="act-reopen">Re-open the approval page</button>
44
+ <button type="button" class="btn btn--danger" id="act-cancel">Cancel</button>
45
+ </section>`;
46
+ }
47
+ const failed = meta.pairing && meta.pairing.phase === 'failed';
48
+ return `
49
+ <section class="scroll">
50
+ <p class="lede">This app starts and stops your hosted dev box — the machine your
51
+ Claude works on. Sign in once with browser clicks; no terminal, no pasting.</p>
52
+ ${failed ? `<p class="detail" style="color:var(--terracotta)">The pairing ${escapeHtml(meta.pairing.reason === 'expired' ? 'expired — try again.' : 'did not complete.')}</p>` : ''}
53
+ <button type="button" class="btn" id="act-signin">Sign in through the hall</button>
54
+ <p class="note">Opens your browser at the builders’ hall: sign in with GitHub if asked,
55
+ then click one Approve button.</p>
56
+ </section>`;
57
+ }
58
+
59
+ function boxView(meta, s) {
60
+ const [label, tone] = STATUS_LABEL[s.status] || STATUS_LABEL.none;
61
+ const on = s.status === 'active';
62
+ const busy = s.busy || s.status === 'provisioning' || s.status === 'closing';
63
+ const switchDisabled = busy || s.status === 'loading' || s.status === 'error' && s.busy;
64
+
65
+ let detail = '';
66
+ if (s.status === 'provisioning') {
67
+ detail = 'About a minute or two. The app will chime when it’s ready.';
68
+ } else if (s.status === 'closing') {
69
+ detail = 'Shutting down and clearing away.';
70
+ } else if (s.status === 'active') {
71
+ detail = 'Boxes rest on their own after ~10 idle minutes — nothing is lost; flip it back anytime.';
72
+ } else if (s.status === 'none') {
73
+ detail = 'Flip the switch and a fresh box starts up for you.';
74
+ } else if (s.status === 'error') {
75
+ detail = s.lastError || 'Something went wrong — flip the switch to try again.';
76
+ }
77
+ if (s.lastError && s.status !== 'error') detail = s.lastError;
78
+
79
+ // [label, value, copyKey?] — a copyKey makes the value click-to-copy via
80
+ // the main process (named verbs only; the renderer never copies raw text).
81
+ const rows = [];
82
+ if (on && s.box) {
83
+ if (s.box.hostname) rows.push(['Host', s.box.hostname]);
84
+ if (s.box.size) rows.push(['Size', s.box.size]);
85
+ if (s.terminal && s.terminal.credential) rows.push(['Terminal user', 'otb', 'terminal-user']);
86
+ if (s.terminal && s.terminal.credential) rows.push(['Terminal pass', s.terminal.credential, 'terminal-credential']);
87
+ }
88
+
89
+ return `
90
+ <section class="scroll">
91
+ <div class="switchrow">
92
+ <button type="button" class="switch" id="act-toggle" data-on="${on ? 1 : 0}" data-busy="${busy ? 1 : 0}"
93
+ ${switchDisabled ? 'disabled' : ''} aria-label="Turn the dev box ${on ? 'off' : 'on'}">
94
+ <span class="switch__knob"></span>
95
+ </button>
96
+ <div class="status status--${tone || 'off'}">${escapeHtml(label)}</div>
97
+ <div class="detail">${escapeHtml(detail)}</div>
98
+ </div>
99
+ ${rows.length ? `<div class="rows">${rows.map(([k, v, copyKey]) => `
100
+ <div class="row"><span class="row__k">${escapeHtml(k)}</span>${copyKey
101
+ ? `<span class="row__v row__v--copy" data-copy="${escapeHtml(copyKey)}" tabindex="0" role="button"
102
+ title="Click to copy" aria-label="Copy ${escapeHtml(k.toLowerCase())}">${escapeHtml(v)}</span>`
103
+ : `<span class="row__v">${escapeHtml(v)}</span>`}</div>`).join('')}
104
+ </div>` : ''}
105
+ ${on ? `
106
+ <button type="button" class="btn" id="act-terminal" ${s.terminal && s.terminal.url ? '' : 'disabled'}>
107
+ ${s.terminal && s.terminal.url ? 'Open the browser terminal' : 'Terminal warming up…'}
108
+ </button>
109
+ <button type="button" class="btn btn--ghost" id="act-copyssh">Copy SSH command</button>
110
+ ` : ''}
111
+ ${keyRow(s)}
112
+ ${hostKeysRow(s)}
113
+ ${desktopLinkRow(meta, s)}
114
+ </section>`;
115
+ }
116
+
117
+ // task 1002509: SSH-key registration status. Silence here is what stranded
118
+ // builders — the key never reached their account and SSH just said "denied".
119
+ // Only the failure is shown; a verified key needs no chrome.
120
+ function keyRow(s) {
121
+ if (!s.keyRegistered || s.keyRegistered.ok) return '';
122
+ const why = s.keyRegistered.reason === 'verify_failed'
123
+ ? "The dev system didn't record it."
124
+ : s.keyRegistered.reason === 'list_failed'
125
+ ? "Couldn't check whether it arrived."
126
+ : s.keyRegistered.reason === 'key_setup_failed'
127
+ ? "This machine couldn't create the key file."
128
+ : 'The dev system could not be reached.';
129
+ return `<div class="dlink"><span class="dlink__warn">!</span>
130
+ <span>Your SSH key isn't registered yet — ${why} Retrying by itself.</span>
131
+ <button type="button" class="btn btn--ghost" id="act-key">Retry now</button></div>`;
132
+ }
133
+
134
+ // #909: host-key pinning status — visible, never silent. Only meaningful
135
+ // while the box is on (keys re-pin on every activation).
136
+ function hostKeysRow(s) {
137
+ if (s.status !== 'active' || !s.hostKeys) return '';
138
+ if (s.hostKeys.ok) {
139
+ // No action button here on purpose: the self-verify (task 1292) re-checks the
140
+ // pin every poll, so a green "pinned ✓" is trustworthy — and a Repair button
141
+ // next to a healthy status reads as "something's wrong" (owner UX feedback,
142
+ // task 1324). Recovery lives in the failure branch below, where it's earned.
143
+ return `<div class="dlink"><span class="dlink__ok">✓</span>
144
+ <span>Box identity pinned — SSH connects without warnings.</span></div>`;
145
+ }
146
+ const why = s.hostKeys.reason === 'tool_missing'
147
+ ? 'OpenSSH tools not found on this machine.'
148
+ : s.hostKeys.reason === 'stale_pin'
149
+ ? 'The box was rebuilt with a new identity — re-pinning now.'
150
+ : s.hostKeys.reason === 'partial_scan'
151
+ ? `Got some of the box's identity keys but not the one Claude Desktop checks (attempt ${escapeHtml(String(s.hostKeys.attempt || 1))}) — retrying by itself.`
152
+ : `Still reaching the box (attempt ${escapeHtml(String(s.hostKeys.attempt || 1))}) — retrying by itself.`;
153
+ return `<div class="dlink"><span class="dlink__warn">!</span>
154
+ <span>Box identity not pinned yet. ${why}</span>
155
+ <button type="button" class="btn btn--ghost" id="act-hostkeys">Retry now</button></div>`;
156
+ }
157
+
158
+ function desktopLinkRow(meta, s) {
159
+ if (!meta.settingsPath) return '';
160
+ if (s.settingsInstalled) {
161
+ return `<div class="dlink"><span class="dlink__ok">✓</span>
162
+ <span>Claude Desktop is linked — restart it once, then choose
163
+ <strong>${escapeHtml(meta.envName)}</strong> under Code → Environment.</span></div>`;
164
+ }
165
+ return `<div class="dlink"><span class="dlink__warn">!</span>
166
+ <span>Claude Desktop link not installed.</span>
167
+ <button type="button" class="btn btn--ghost" id="act-install">Install</button></div>`;
168
+ }
169
+
170
+ function render() {
171
+ if (!last) return;
172
+ const { snapshot: s, meta } = last;
173
+ header(meta, s);
174
+ $('#main').innerHTML = s.status === 'signed_out' ? signedOutView(meta) : boxView(meta, s);
175
+ $('#ft-auth').textContent = s.status === 'signed_out' ? '' : 'Sign out';
176
+ $('#ft-version').textContent = `${meta.productName} v${meta.version}`;
177
+ if (meta.update) {
178
+ $('#update').hidden = false;
179
+ // #924: the banner text is computed in main (updater.updateBannerText) so
180
+ // the copy lives with the version logic; fall back if an older main omits it.
181
+ $('#update-text').textContent = meta.update.banner || `Update available — v${meta.update.version}`;
182
+ } else {
183
+ $('#update').hidden = true;
184
+ }
185
+ wire();
186
+ renderPicker(s);
187
+ }
188
+
189
+ function wire() {
190
+ const on = (id, fn) => { const el = $(id); if (el) el.addEventListener('click', fn); };
191
+ on('#act-signin', () => window.otb.signIn());
192
+ on('#act-cancel', () => window.otb.cancelPairing());
193
+ on('#act-reopen', () => window.otb.openPairPage());
194
+ on('#act-toggle', async () => {
195
+ const s = last.snapshot;
196
+ if (s.status === 'active') await window.otb.boxOff();
197
+ else await window.otb.boxOn();
198
+ });
199
+ on('#act-terminal', () => window.otb.openTerminal());
200
+ on('#act-copyssh', async (e) => {
201
+ await window.otb.copy('ssh');
202
+ e.target.textContent = 'Copied ✓';
203
+ setTimeout(() => { e.target.textContent = 'Copy SSH command'; }, 1500);
204
+ });
205
+ on('#act-install', () => window.otb.installSettings());
206
+ on('#act-hostkeys', () => window.otb.retryHostKeys());
207
+ on('#act-key', () => window.otb.retryKey());
208
+
209
+ // Click-to-copy credential rows. Brief "Copied ✓" feedback in place; the
210
+ // original value is restored unless a re-render replaced the node anyway.
211
+ document.querySelectorAll('.row__v--copy[data-copy]').forEach((el) => {
212
+ const copy = async () => {
213
+ const ok = await window.otb.copy(el.getAttribute('data-copy'));
214
+ if (!ok) return;
215
+ const orig = el.textContent;
216
+ el.textContent = 'Copied ✓';
217
+ el.classList.add('row__v--copied');
218
+ setTimeout(() => {
219
+ if (el.isConnected) { el.textContent = orig; el.classList.remove('row__v--copied'); }
220
+ }, 1200);
221
+ };
222
+ el.addEventListener('click', copy);
223
+ el.addEventListener('keydown', (e) => {
224
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); copy(); }
225
+ });
226
+ });
227
+ }
228
+
229
+ // --- BV1.R100: "What are you working on?" task picker ---------------------
230
+ // Local state, held OUTSIDE the snapshot so it survives the state-push
231
+ // re-renders that rewrite #main. Rendered into #picker, shown only when active.
232
+ let picker = { open: false, loading: false, tasks: null, error: null, result: null, claimingId: null };
233
+ const resetPicker = () => { picker = { open: false, loading: false, tasks: null, error: null, result: null, claimingId: null }; };
234
+ const snap = () => (last ? last.snapshot : null);
235
+
236
+ function pickerHtml() {
237
+ if (picker.result) {
238
+ return `<div class="pick pick--done">
239
+ <div class="pick__msg">${escapeHtml(picker.result)}</div>
240
+ <button type="button" class="btn btn--ghost" id="pick-again">Pick another task</button>
241
+ </div>`;
242
+ }
243
+ if (!picker.open) {
244
+ return `<button type="button" class="btn btn--ghost pick__open" id="pick-open">What are you working on? — pick a task</button>`;
245
+ }
246
+ if (picker.loading) {
247
+ return `<div class="pick"><div class="pick__hd">What are you working on?</div>
248
+ <div class="detail">Finding your tasks…</div></div>`;
249
+ }
250
+ if (picker.error) {
251
+ return `<div class="pick"><div class="pick__hd">What are you working on?</div>
252
+ <div class="detail" style="color:var(--terracotta)">${escapeHtml(picker.error)}</div>
253
+ <button type="button" class="btn btn--ghost" id="pick-refresh">Try again</button>
254
+ <button type="button" class="btn btn--ghost" id="pick-close">Close</button></div>`;
255
+ }
256
+ const tasks = picker.tasks || [];
257
+ const list = tasks.length
258
+ ? tasks.map((t) => `<div class="pick__row">
259
+ <div class="pick__row-main">
260
+ <div class="pick__title">${escapeHtml(t.title)}</div>
261
+ <div class="pick__meta">${escapeHtml(t.kind)}${t.estMinutes ? ` · ~${escapeHtml(String(t.estMinutes))} min` : ''} · #${escapeHtml(t.id)}</div>
262
+ </div>
263
+ <button type="button" class="btn btn--sm" data-claim="${escapeHtml(t.id)}" ${picker.claimingId ? 'disabled' : ''}>${picker.claimingId === t.id ? 'Claiming…' : 'Claim'}</button>
264
+ </div>`).join('')
265
+ : `<div class="detail">Nothing claimable right now — everything’s in flight, blocked, or shipped.</div>`;
266
+ return `<div class="pick">
267
+ <div class="pick__hd">What are you working on?</div>
268
+ <p class="note">Pick one and your box pulls exactly that task’s code — nothing else.</p>
269
+ <div class="pick__list">${list}</div>
270
+ <button type="button" class="btn btn--ghost" id="pick-refresh">Refresh</button>
271
+ <button type="button" class="btn btn--ghost" id="pick-close">Close</button>
272
+ </div>`;
273
+ }
274
+
275
+ async function loadClaimable() {
276
+ picker.loading = true; picker.error = null; picker.tasks = null; renderPicker(snap());
277
+ const r = await window.otb.claimable();
278
+ picker.loading = false;
279
+ if (r && r.ok) picker.tasks = r.tasks; else picker.error = (r && r.error) || 'Could not load your tasks.';
280
+ renderPicker(snap());
281
+ }
282
+
283
+ async function claimPicked(id) {
284
+ picker.claimingId = id; renderPicker(snap());
285
+ const r = await window.otb.pickTask(id);
286
+ picker.claimingId = null;
287
+ if (r && r.ok) { picker.result = r.message; picker.open = false; }
288
+ else { picker.error = (r && r.error) || 'Could not claim it.'; }
289
+ renderPicker(snap());
290
+ }
291
+
292
+ function renderPicker(s) {
293
+ const el = $('#picker');
294
+ if (!el) return;
295
+ if (!s || s.status !== 'active') {
296
+ el.hidden = true;
297
+ // Reset when there's no live box, so the next session starts clean.
298
+ if (!s || s.status === 'signed_out' || s.status === 'none') resetPicker();
299
+ return;
300
+ }
301
+ el.hidden = false;
302
+ el.innerHTML = pickerHtml();
303
+ const on = (id, fn) => { const b = $(id); if (b) b.addEventListener('click', fn); };
304
+ on('#pick-open', () => { picker.open = true; renderPicker(s); loadClaimable(); });
305
+ on('#pick-refresh', () => loadClaimable());
306
+ on('#pick-close', () => { picker.open = false; picker.error = null; renderPicker(s); });
307
+ on('#pick-again', () => { picker.result = null; picker.open = true; renderPicker(s); loadClaimable(); });
308
+ el.querySelectorAll('[data-claim]').forEach((b) => b.addEventListener('click', () => claimPicked(b.getAttribute('data-claim'))));
309
+ }
310
+
311
+ $('#ft-hall').addEventListener('click', () => window.otb.openHall());
312
+ $('#ft-auth').addEventListener('click', () => {
313
+ if (last && last.snapshot.status !== 'signed_out') window.otb.signOut();
314
+ });
315
+ $('#update-btn').addEventListener('click', () => window.otb.openUpdate());
316
+
317
+ window.otb.onState((payload) => { last = payload; render(); });
318
+ window.otb.getState().then((payload) => { last = payload; render(); });
319
+ })();
@@ -0,0 +1,45 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="Content-Security-Policy"
6
+ content="default-src 'none'; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; script-src 'self'; img-src 'self' data:;">
7
+ <title>Dev Box</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700&family=Quicksand:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="theme.generated.css">
12
+ <link rel="stylesheet" href="styles.css">
13
+ </head>
14
+ <body>
15
+
16
+ <header class="hd">
17
+ <p class="hd__epigraph" id="hd-epigraph"></p>
18
+ <h1 class="hd__title" id="hd-title">Dev Box</h1>
19
+ <p class="hd__sub" id="hd-sub"></p>
20
+ </header>
21
+
22
+ <main id="main">
23
+ <!-- filled by app.js -->
24
+ </main>
25
+
26
+ <!-- BV1.R100: "What are you working on?" — lives OUTSIDE #main so its local
27
+ pick state survives the snapshot re-renders that rewrite #main. Shown only
28
+ while the box is on. -->
29
+ <section id="picker" hidden></section>
30
+
31
+ <footer class="ft">
32
+ <button type="button" class="ft__link" id="ft-hall">The builders’ hall ↗</button>
33
+ <span class="ft__dot" aria-hidden="true">·</span>
34
+ <button type="button" class="ft__link" id="ft-auth">Sign out</button>
35
+ <div class="ft__version" id="ft-version"></div>
36
+ </footer>
37
+
38
+ <div class="update" id="update" hidden>
39
+ <span id="update-text"></span>
40
+ <button type="button" class="update__btn" id="update-btn">Update</button>
41
+ </div>
42
+
43
+ <script src="app.js"></script>
44
+ </body>
45
+ </html>
@@ -0,0 +1,246 @@
1
+ /* Dev Box panel styles — layout/structure only. The :root design tokens
2
+ (palette + fonts) are generated from config/branding.json into
3
+ theme.generated.css by scripts/gen-brand.js and loaded BEFORE this file, so
4
+ the whole panel re-skins to the instance brand with no edit here. */
5
+
6
+ * { box-sizing: border-box; margin: 0; padding: 0; }
7
+
8
+ html, body { height: 100%; }
9
+ body {
10
+ background: var(--bg);
11
+ color: var(--ink);
12
+ font-family: var(--font-body);
13
+ font-size: 15px;
14
+ display: flex;
15
+ flex-direction: column;
16
+ user-select: none;
17
+ -webkit-user-select: none;
18
+ overflow: hidden;
19
+ }
20
+
21
+ /* header */
22
+ .hd { text-align: center; padding: 18px 16px 10px; }
23
+ .hd__epigraph {
24
+ font-style: italic;
25
+ font-size: 0.78rem;
26
+ color: var(--ink-faint);
27
+ letter-spacing: 0.04em;
28
+ }
29
+ .hd__title {
30
+ font-family: var(--font-display);
31
+ font-weight: 700;
32
+ font-size: 1.25rem;
33
+ letter-spacing: 0.14em;
34
+ text-transform: uppercase;
35
+ margin-top: 2px;
36
+ }
37
+ .hd__sub { font-size: 0.85rem; color: var(--ink-soft); min-height: 1.2em; }
38
+
39
+ /* main card area */
40
+ main {
41
+ flex: 1;
42
+ overflow-y: auto;
43
+ padding: 8px 16px 12px;
44
+ }
45
+ .scroll {
46
+ background: var(--bg-card);
47
+ border: 1px solid var(--rule);
48
+ outline: 1px solid var(--rule-soft);
49
+ outline-offset: 3px;
50
+ padding: 16px;
51
+ margin: 6px 2px 14px;
52
+ }
53
+ .lede { color: var(--ink-soft); font-size: 0.92rem; }
54
+ .lede strong { color: var(--ink); }
55
+
56
+ /* the switch */
57
+ .switchrow {
58
+ display: flex;
59
+ flex-direction: column;
60
+ align-items: center;
61
+ gap: 8px;
62
+ padding: 10px 0 6px;
63
+ }
64
+ .switch {
65
+ width: 96px;
66
+ height: 48px;
67
+ border: 2px solid var(--rule);
68
+ border-radius: 24px;
69
+ background: var(--bg-deep);
70
+ position: relative;
71
+ cursor: pointer;
72
+ transition: background 0.25s ease;
73
+ }
74
+ .switch:disabled { cursor: default; opacity: 0.6; }
75
+ .switch__knob {
76
+ position: absolute;
77
+ top: 3px; left: 4px;
78
+ width: 38px; height: 38px;
79
+ border-radius: 50%;
80
+ background: var(--bg-card);
81
+ border: 1px solid var(--rule-soft);
82
+ transition: left 0.25s ease;
83
+ display: flex; align-items: center; justify-content: center;
84
+ font-size: 0.9rem;
85
+ }
86
+ .switch[data-on="1"] { background: var(--accent-deep); border-color: var(--accent-deep); }
87
+ .switch[data-on="1"] .switch__knob { left: 50px; }
88
+ .switch[data-busy="1"] .switch__knob::after {
89
+ content: "";
90
+ width: 18px; height: 18px;
91
+ border: 2px solid var(--rule);
92
+ border-top-color: transparent;
93
+ border-radius: 50%;
94
+ animation: spin 0.9s linear infinite;
95
+ }
96
+ @keyframes spin { to { transform: rotate(360deg); } }
97
+
98
+ .status {
99
+ font-family: var(--font-display);
100
+ font-size: 0.86rem;
101
+ letter-spacing: 0.1em;
102
+ text-transform: uppercase;
103
+ }
104
+ .status--on { color: var(--accent-deep); }
105
+ .status--busy { color: var(--ink-soft); }
106
+ .status--err { color: var(--terracotta); }
107
+ .detail {
108
+ text-align: center;
109
+ font-size: 0.86rem;
110
+ color: var(--ink-soft);
111
+ min-height: 1.2em;
112
+ }
113
+
114
+ /* info rows */
115
+ .rows { margin-top: 12px; border-top: 1px solid var(--rule-soft); }
116
+ .row {
117
+ display: flex;
118
+ justify-content: space-between;
119
+ align-items: center;
120
+ gap: 8px;
121
+ padding: 7px 0;
122
+ border-bottom: 1px solid color-mix(in srgb, var(--rule-soft) 40%, transparent);
123
+ font-size: 0.86rem;
124
+ }
125
+ .row__k { color: var(--ink-faint); }
126
+ .row__v {
127
+ color: var(--ink-soft);
128
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
129
+ font-size: 0.78rem;
130
+ max-width: 220px;
131
+ overflow: hidden;
132
+ text-overflow: ellipsis;
133
+ white-space: nowrap;
134
+ }
135
+ .row__v--copy {
136
+ cursor: pointer;
137
+ text-decoration: underline dotted color-mix(in srgb, var(--ink-faint) 60%, transparent);
138
+ text-underline-offset: 3px;
139
+ }
140
+ .row__v--copy:hover, .row__v--copy:focus-visible { color: var(--accent-deep); }
141
+ .row__v--copied { color: var(--accent-deep); font-weight: 600; }
142
+
143
+ /* buttons */
144
+ .btn {
145
+ font-family: var(--font-display);
146
+ font-size: 0.78rem;
147
+ letter-spacing: 0.08em;
148
+ text-transform: uppercase;
149
+ padding: 9px 14px;
150
+ background: var(--accent-deep);
151
+ color: var(--bg-card);
152
+ border: 1px solid var(--accent-deep);
153
+ cursor: pointer;
154
+ width: 100%;
155
+ margin-top: 10px;
156
+ }
157
+ .btn:hover:not(:disabled) { background: var(--accent); border-color: var(--accent); }
158
+ .btn:disabled { opacity: 0.55; cursor: default; }
159
+ .btn--ghost {
160
+ background: transparent;
161
+ color: var(--accent-deep);
162
+ }
163
+ .btn--ghost:hover:not(:disabled) { background: var(--bg-deep); color: var(--ink); }
164
+ .btn--danger { background: transparent; color: var(--terracotta); border-color: var(--terracotta); }
165
+
166
+ /* pairing code */
167
+ .paircode {
168
+ text-align: center;
169
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
170
+ font-size: 1.3rem;
171
+ letter-spacing: 0.18em;
172
+ padding: 10px;
173
+ margin: 10px 0;
174
+ background: var(--bg-deep);
175
+ border: 1px solid var(--rule-soft);
176
+ }
177
+
178
+ /* desktop-link row */
179
+ .dlink { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 0.86rem; }
180
+ .dlink__ok { color: var(--accent-deep); font-weight: 600; }
181
+ .dlink__warn { color: var(--terracotta); }
182
+ .dlink .btn { width: auto; margin: 0; padding: 5px 10px; }
183
+
184
+ .note { font-style: italic; font-size: 0.8rem; color: var(--ink-faint); margin-top: 10px; }
185
+
186
+ /* footer */
187
+ .ft {
188
+ text-align: center;
189
+ padding: 8px 16px 12px;
190
+ border-top: 1px solid var(--rule-soft);
191
+ background: var(--bg);
192
+ }
193
+ .ft__link {
194
+ background: none; border: none; cursor: pointer;
195
+ font-family: var(--font-body);
196
+ font-size: 0.84rem;
197
+ color: var(--accent-deep);
198
+ text-decoration: underline;
199
+ }
200
+ .ft__dot { color: var(--ink-faint); margin: 0 6px; }
201
+ .ft__version { font-size: 0.72rem; color: var(--ink-faint); margin-top: 2px; }
202
+
203
+ /* update banner */
204
+ .update[hidden] { display: none; }
205
+ .update {
206
+ position: fixed;
207
+ left: 0; right: 0; bottom: 0;
208
+ background: var(--accent-deep);
209
+ color: var(--bg-card);
210
+ display: flex; align-items: center; justify-content: center; gap: 10px;
211
+ padding: 8px;
212
+ font-size: 0.84rem;
213
+ }
214
+ .update__btn {
215
+ font-family: var(--font-display);
216
+ font-size: 0.72rem;
217
+ text-transform: uppercase;
218
+ letter-spacing: 0.06em;
219
+ background: var(--bg-card);
220
+ color: var(--accent-deep);
221
+ border: none; padding: 5px 10px; cursor: pointer;
222
+ }
223
+
224
+ /* BV1.R100 — "What are you working on?" task picker (#picker, outside #main). */
225
+ #picker { padding: 0 18px 14px; }
226
+ .pick__open { margin-top: 0; }
227
+ .pick { border-top: 1px solid var(--rule-soft); padding-top: 12px; }
228
+ .pick__hd { font-family: var(--font-display); font-size: 0.9rem; color: var(--ink); }
229
+ .pick__msg { color: var(--ink-soft); font-size: 0.9rem; line-height: 1.5; }
230
+ .pick--done { border-top: 1px solid var(--rule-soft); padding-top: 12px; }
231
+ .pick__list { margin-top: 8px; }
232
+ .pick__row {
233
+ display: flex; align-items: center; gap: 10px;
234
+ padding: 8px 0; border-bottom: 1px solid var(--rule-soft);
235
+ }
236
+ .pick__row-main { flex: 1; min-width: 0; }
237
+ .pick__title {
238
+ color: var(--ink); font-size: 0.86rem; line-height: 1.35;
239
+ overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
240
+ -webkit-line-clamp: 2; -webkit-box-orient: vertical;
241
+ }
242
+ .pick__meta { color: var(--ink-faint); font-size: 0.74rem; margin-top: 2px; }
243
+ .btn--sm {
244
+ width: auto; margin-top: 0; flex: 0 0 auto;
245
+ padding: 6px 12px; font-size: 0.72rem;
246
+ }
@@ -0,0 +1,16 @@
1
+ /* GENERATED by scripts/gen-brand.js from config/branding.json — do not edit by hand. */
2
+ :root {
3
+ --bg: #fff7ed;
4
+ --bg-card: #ffffff;
5
+ --bg-deep: #ffedd5;
6
+ --ink: #2a1f17;
7
+ --ink-soft: #6b5d52;
8
+ --ink-faint: #857567;
9
+ --rule: #fdba74;
10
+ --rule-soft: #fed7aa;
11
+ --accent: #f97316;
12
+ --accent-deep: #ea580c;
13
+ --terracotta: #fb7185;
14
+ --font-display: "Baloo 2", "Quicksand", system-ui, sans-serif;
15
+ --font-body: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif;
16
+ }