@bongos/core 1.24.44 → 1.24.46

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 (145) hide show
  1. package/.bongos-core.json +179 -144
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +4 -0
  4. package/clients/bongos-client/index.cjs +4 -0
  5. package/clients/bongos-client/index.d.ts +8 -0
  6. package/clients/bongos-client/index.mjs +4 -0
  7. package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +15 -0
  8. package/docs/api/openapi.json +152 -3
  9. package/docs/api-reference.md +4 -2
  10. package/docs/copy-inventory.md +1111 -1111
  11. package/docs/copy-registry.json +1547 -1547
  12. package/docs/module-api-changelog.md +4 -0
  13. package/docs/onboarding/diagrams/assertions.json +2 -2
  14. package/docs/page-inventory.json +11 -0
  15. package/docs/page-readings.json +823 -823
  16. package/migrations/core_295_runner_logins.sql +50 -0
  17. package/modules/government/session-scopes.json +5 -0
  18. package/modules/hall-ui/public/ai-studio.html +1 -1
  19. package/modules/hall-ui/public/ai-studio.states.json +2 -0
  20. package/modules/hall-ui/public/atlas.html +1 -1
  21. package/modules/hall-ui/public/atlas.states.json +2 -0
  22. package/modules/hall-ui/public/blockers.html +1 -1
  23. package/modules/hall-ui/public/board-room.html +1 -1
  24. package/modules/hall-ui/public/board-room.states.json +2 -0
  25. package/modules/hall-ui/public/city.html +1 -1
  26. package/modules/hall-ui/public/city.states.json +2 -0
  27. package/modules/hall-ui/public/collab.html +1 -1
  28. package/modules/hall-ui/public/collab.states.json +2 -0
  29. package/modules/hall-ui/public/commands.html +1 -1
  30. package/modules/hall-ui/public/commands.states.json +2 -0
  31. package/modules/hall-ui/public/copy-desk.html +1 -1
  32. package/modules/hall-ui/public/copy-desk.states.json +2 -0
  33. package/modules/hall-ui/public/craft-mode.js +4 -1
  34. package/modules/hall-ui/public/deploy.html +1 -1
  35. package/modules/hall-ui/public/deploy.states.json +2 -0
  36. package/modules/hall-ui/public/goal-map.html +1 -1
  37. package/modules/hall-ui/public/goal-map.states.json +2 -0
  38. package/modules/hall-ui/public/goals.css +1 -0
  39. package/modules/hall-ui/public/goals.html +1 -1
  40. package/modules/hall-ui/public/goals.states.json +2 -0
  41. package/modules/hall-ui/public/government.html +1 -1
  42. package/modules/hall-ui/public/government.states.json +2 -0
  43. package/modules/hall-ui/public/idea.html +1 -1
  44. package/modules/hall-ui/public/idea.states.json +2 -0
  45. package/modules/hall-ui/public/ideas.html +1 -1
  46. package/modules/hall-ui/public/ideas.states.json +2 -0
  47. package/modules/hall-ui/public/index-founding.states.json +2 -0
  48. package/modules/hall-ui/public/index-genesis-decide.states.json +2 -0
  49. package/modules/hall-ui/public/index-genesis-demo.states.json +2 -0
  50. package/modules/hall-ui/public/index-genesis.states.json +2 -0
  51. package/modules/hall-ui/public/index.html +1 -1
  52. package/modules/hall-ui/public/index.states.json +5 -0
  53. package/modules/hall-ui/public/market-watch-item.html +1 -1
  54. package/modules/hall-ui/public/market-watch-item.states.json +2 -0
  55. package/modules/hall-ui/public/market-watch-rules.html +1 -1
  56. package/modules/hall-ui/public/market-watch-rules.states.json +2 -0
  57. package/modules/hall-ui/public/market-watch.html +1 -1
  58. package/modules/hall-ui/public/market-watch.states.json +2 -0
  59. package/modules/hall-ui/public/modules.html +1 -1
  60. package/modules/hall-ui/public/modules.states.json +2 -0
  61. package/modules/hall-ui/public/money.html +1 -1
  62. package/modules/hall-ui/public/money.states.json +2 -0
  63. package/modules/hall-ui/public/my-agents.html +1 -1
  64. package/modules/hall-ui/public/my-agents.states.json +2 -0
  65. package/modules/hall-ui/public/notes-panel.css +1 -1
  66. package/modules/hall-ui/public/primer.html +1 -1
  67. package/modules/hall-ui/public/primer.states.json +2 -0
  68. package/modules/hall-ui/public/profile.html +1 -1
  69. package/modules/hall-ui/public/profile.states.json +2 -0
  70. package/modules/hall-ui/public/project-settings.html +1 -1
  71. package/modules/hall-ui/public/roster.html +1 -1
  72. package/modules/hall-ui/public/roster.states.json +2 -0
  73. package/modules/hall-ui/public/settings.html +1 -1
  74. package/modules/hall-ui/public/settings.states.json +2 -0
  75. package/modules/hall-ui/public/shell.js +21 -1
  76. package/modules/hall-ui/public/street-card.states.json +2 -0
  77. package/modules/hall-ui/public/street.html +1 -1
  78. package/modules/hall-ui/public/street.states.json +2 -0
  79. package/modules/hall-ui/public/studio.html +1 -1
  80. package/modules/hall-ui/public/studio.states.json +2 -0
  81. package/modules/hall-ui/public/style.css +27 -24
  82. package/modules/hall-ui/public/task.html +1 -1
  83. package/modules/hall-ui/public/task.states.json +2 -0
  84. package/modules/hall-ui/public/thinking.html +1 -1
  85. package/modules/hall-ui/public/thinking.states.json +2 -0
  86. package/modules/hall-ui/public/tweak-editor.html +1 -1
  87. package/modules/hall-ui/public/tweak-editor.states.json +2 -0
  88. package/modules/hall-ui/public/watch.html +1 -1
  89. package/modules/hall-ui/public/watch.states.json +2 -0
  90. package/modules/hall-ui/public/welcome-boot.js +6 -2
  91. package/modules/hall-ui/public/work-founding.states.json +2 -0
  92. package/modules/hall-ui/public/work.html +1 -1
  93. package/modules/hall-ui/public/work.states.json +3 -1
  94. package/modules/hall-ui/records/hall-navigation.md +5 -0
  95. package/modules/lifecycle/db-claims.js +6 -3
  96. package/modules/lifecycle/routes/claims.js +10 -0
  97. package/modules/lifecycle/routes/tasks.js +1 -1
  98. package/modules/lifecycle/runner-self-guard.js +19 -10
  99. package/modules/platform-identity/platform-identity.js +1 -1
  100. package/modules/platform-identity/tests/platform-identity.mjs +13 -2
  101. package/modules/provisioning/core-upgrade-authz.js +8 -0
  102. package/modules/public-landing/public/account.html +1 -0
  103. package/modules/public-landing/public/community.html +1 -0
  104. package/modules/public-landing/public/connections.html +1 -0
  105. package/modules/public-landing/public/contact.html +1 -0
  106. package/modules/public-landing/public/guild.html +1 -0
  107. package/modules/public-landing/public/index.html +1 -0
  108. package/modules/public-landing/public/invitations.html +1 -0
  109. package/modules/public-landing/public/privacy.html +1 -0
  110. package/modules/public-landing/public/projects.html +1 -0
  111. package/modules/public-landing/public/terms.html +1 -0
  112. package/modules/raise/views/gate.html +1 -0
  113. package/modules/raise/views/nda.html +1 -0
  114. package/modules/raise/views/page.html +1 -0
  115. package/modules/status-ui/public/index.html +1 -0
  116. package/modules/ui-design/kit/README.md +1 -1
  117. package/modules/ui-design/kit/fixtures/versions.json +1 -0
  118. package/modules/ui-design/kit/lib.js +48 -3
  119. package/modules/ui-design/kit/probe.js +1 -0
  120. package/modules/ui-design/kit/render.js +2 -0
  121. package/modules/ui-design/kit/serve.js +1 -0
  122. package/modules/ui-design/module.json +1 -1
  123. package/modules/ui-design/public/tokens.css +125 -0
  124. package/package-lock.json +2 -2
  125. package/package.json +1 -1
  126. package/release-notes.json +20 -0
  127. package/scripts/bongos/fitness.js +1 -1
  128. package/scripts/bongos/gen-hall-sitemap.js +3 -1
  129. package/scripts/bongos/run-unit-tests.js +3 -0
  130. package/src/bongos/auth.js +25 -4
  131. package/src/bongos/route-rank-check.js +5 -0
  132. package/src/bongos/routes/auth.js +48 -0
  133. package/src/bongos/runner-login.js +151 -0
  134. package/src/bongos/serve-internal.js +6 -1
  135. package/src/module-api.js +1 -1
  136. package/tests/autobongos_runner_login.mjs +296 -0
  137. package/tests/autobongos_runner_login_db.mjs +105 -0
  138. package/tests/craft_mode_nav.mjs +51 -3
  139. package/tests/design_tokens_shared.mjs +183 -0
  140. package/tests/government_session_scope.mjs +2 -1
  141. package/tests/hall_reading_world.mjs +5 -1
  142. package/tests/hall_tokens.mjs +22 -3
  143. package/tests/nav_permission_atoms.mjs +2 -1
  144. package/tests/ui_design_kit.mjs +30 -0
  145. package/tests/welcome_boot_browser.mjs +4 -0
@@ -26,11 +26,18 @@
26
26
  // paths the registry has always protected, and one that does not is treated as
27
27
  // unreadable, which refuses.
28
28
  //
29
- // WHICH CLAIM IS A RUNNER'S. Its worktree name (runner-self.js RUNNER_TREE_RE),
30
- // read from the claim row. If that read FAILS, the branch name stands in (the
31
- // runner's branch is its worktree name), so a database hiccup does not switch the
32
- // wall off for a runner while an ordinary publish still never depends on the read.
33
- // Telling a runner's session from its owner's on the server is task 1004547.
29
+ // WHICH PUBLISH IS A RUNNER'S (task 1004547). Any one of three says so:
30
+ // - the claim row's `autonomous` mark, which the claims route sets from the
31
+ // claiming SESSION (a runner's own login, src/bongos/runner-login.js) and which
32
+ // no request body can set or clear. This is the reliable one: a worker that
33
+ // claims under a hand-picked worktree name is still recognised;
34
+ // - the publishing session is itself a runner's (`publisherIsRunner`), whoever
35
+ // made the claim;
36
+ // - the claim's worktree name (runner-self.js RUNNER_TREE_RE), kept for runners
37
+ // still running under their builder's CLI session until they are re-set-up.
38
+ // If the claim read FAILS, the branch name stands in (the runner's branch is its
39
+ // worktree name), so a database hiccup does not switch the wall off for a runner
40
+ // while an ordinary publish still never depends on the read.
34
41
  //
35
42
  // FAILS CLOSED on the files: a read that throws, a list at GitHub's 300-file cap,
36
43
  // or a matcher that fails its check all refuse. An unattended change that cannot
@@ -49,7 +56,7 @@ const runnerSelf = require('./runner-self');
49
56
  // resolved it. Both names are read because the server may store a disambiguated
50
57
  // worktree_name beside the one the runner asked for.
51
58
  const CLAIM_SQL = `
52
- SELECT c.worktree_name, c.requested_worktree_name
59
+ SELECT c.worktree_name, c.requested_worktree_name, c.autonomous
53
60
  FROM claims c WHERE c.task_id = $1
54
61
  ORDER BY c.claimed_at DESC, c.id DESC LIMIT 1`;
55
62
 
@@ -66,9 +73,11 @@ async function readLatestClaim(taskId, exec = api.pool) {
66
73
  }
67
74
  }
68
75
 
69
- function isRunnerPublish(claim, branch) {
76
+ function isRunnerPublish(claim, branch, publisherIsRunner = false) {
77
+ if (publisherIsRunner === true) return true;
70
78
  if (claim === CLAIM_UNREADABLE) return runnerSelf.isRunnerTree(branch);
71
- return !!claim && (runnerSelf.isRunnerTree(claim.worktree_name) || runnerSelf.isRunnerTree(claim.requested_worktree_name));
79
+ return !!claim && (claim.autonomous === true
80
+ || runnerSelf.isRunnerTree(claim.worktree_name) || runnerSelf.isRunnerTree(claim.requested_worktree_name));
72
81
  }
73
82
 
74
83
  // Paths the registry has protected since ADR 0043; a matcher that misses either
@@ -86,9 +95,9 @@ const unreadable = () => ({ code: 'self_modification_unreadable', files: [], pro
86
95
  // The route's one call. Returns null (publish as usual) or the refusal
87
96
  // { code, files, protected }. `deps` is the test seam, and the route passes a
88
97
  // shared getPullRequestFiles so this and the auto-pickup check read GitHub once.
89
- async function runnerSelfEditRefusal({ taskId, prNumber, branch = null }, deps = {}) {
98
+ async function runnerSelfEditRefusal({ taskId, prNumber, branch = null, publisherIsRunner = false }, deps = {}) {
90
99
  const claim = await (deps.readLatestClaim || readLatestClaim)(taskId);
91
- if (!isRunnerPublish(claim, branch)) return null;
100
+ if (!isRunnerPublish(claim, branch, publisherIsRunner)) return null;
92
101
  const match = verifiedMatcher(deps.matchProtected || ((files) => api.permissionPathCheck.matchProtected(files)));
93
102
  if (!match) return unreadable();
94
103
  let rows;
@@ -96,7 +96,7 @@ function clientSecretValid(client, presentedSecret) {
96
96
  // FEDERATION_SOURCES mirrors modules/government/session-scopes.json — a module may
97
97
  // not read another module's files, and the doorway exports no lookup for it — and
98
98
  // tests/platform-identity.mjs pins the two lists equal, so a source declared or
99
- // retired there without the same edit here fails the build instead of drifting.
99
+ // retired there without the same edit here fails the build instead of drifting ('runner' is left out: task 1004547).
100
100
  const FEDERATION_SOURCES = Object.freeze(['cli', 'web', null]);
101
101
  function sessionUsableForFederation(session) {
102
102
  if (!session) return { ok: false, reason: 'no_session' };
@@ -130,12 +130,23 @@ t('a session of an undeclared source (a leftover dev-box token) may NOT federate
130
130
  }
131
131
  });
132
132
 
133
- t('FEDERATION_SOURCES is exactly the kernel session-source registry', () => {
133
+ // Sources the registry declares that must NOT federate. An Autobongos runner's own
134
+ // login (task 1004547) is a machine working for its builder on ONE project; it has no
135
+ // business signing that person in to other projects through the hub.
136
+ const NEVER_FEDERATES = ['runner'];
137
+
138
+ t('FEDERATION_SOURCES is exactly the kernel session-source registry, less the machine logins', () => {
134
139
  // platform-identity may not read the government module, so it mirrors the list;
135
140
  // this pin is what stops the copy drifting from modules/government/session-scopes.json.
136
141
  const reg = JSON.parse(readFileSync(new URL('../../government/session-scopes.json', import.meta.url), 'utf8'));
137
142
  const declared = reg.scopes.map((r) => (r.source === undefined ? null : r.source));
138
- assert.deepEqual([...P.FEDERATION_SOURCES].sort(), declared.sort());
143
+ for (const s of NEVER_FEDERATES) assert.ok(declared.includes(s), `${s} is still declared, so the exclusion is still needed`);
144
+ assert.deepEqual([...P.FEDERATION_SOURCES].sort(), declared.filter((s) => !NEVER_FEDERATES.includes(s)).sort());
145
+ });
146
+
147
+ t('an Autobongos runner login may NOT federate', () => {
148
+ const r = P.sessionUsableForFederation({ github_id: 1, status: 'active', source: 'runner' });
149
+ assert.deepEqual(r, { ok: false, reason: 'session_scope' });
139
150
  });
140
151
 
141
152
  t('a service principal (system_role set) may NOT federate', () => {
@@ -54,6 +54,14 @@ function ownsUpgradableProject(inst, builder) {
54
54
  function requireOwnProjectOrPin({ pinGate, provisioning, pool, log }) {
55
55
  return async function ownProjectOrPinGate(req, res, next) {
56
56
  const raw = req.params ? req.params.id : undefined;
57
+ // An Autobongos runner's own login never takes the owner door (task 1004547):
58
+ // an unattended machine does not move a live version, even its owner's. It falls
59
+ // to the permission gate, which refuses it, because a runner session holds no
60
+ // system permission (src/bongos/auth.js resolveHeldPermissions).
61
+ if (req.builder && req.builder.runner) {
62
+ req.coreUpgradeAuthority = 'pin';
63
+ return pinGate(req, res, next);
64
+ }
57
65
  if (typeof raw === 'string' && ID_RE.test(raw)) {
58
66
  try {
59
67
  const inst = await provisioning.getInstanceById(pool, Number(raw));
@@ -19,6 +19,7 @@
19
19
  <link rel="preconnect" href="https://fonts.googleapis.com">
20
20
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
21
21
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
22
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
22
23
  <link rel="stylesheet" href="/assets/cosmos.css">
23
24
  <!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
24
25
  <style>
@@ -36,6 +36,7 @@
36
36
  <link rel="preconnect" href="https://fonts.googleapis.com">
37
37
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
38
38
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
39
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
39
40
  <link rel="stylesheet" href="/assets/cosmos.css">
40
41
  <!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
41
42
  <style>
@@ -22,6 +22,7 @@
22
22
  <link rel="preconnect" href="https://fonts.googleapis.com">
23
23
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
24
24
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
25
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
25
26
  <link rel="stylesheet" href="/assets/cosmos.css">
26
27
  <!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
27
28
  <style>
@@ -16,6 +16,7 @@
16
16
  <link rel="preconnect" href="https://fonts.googleapis.com">
17
17
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
18
18
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
19
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
19
20
  <link rel="stylesheet" href="/assets/cosmos.css">
20
21
  <!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
21
22
  <style>
@@ -29,6 +29,7 @@
29
29
  <link rel="preconnect" href="https://fonts.googleapis.com">
30
30
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
31
31
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
32
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
32
33
  <link rel="stylesheet" href="/assets/cosmos.css">
33
34
  <!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
34
35
  <style>
@@ -41,6 +41,7 @@
41
41
  <!-- The world's token layer. Root-absolute so the server's ?v= content stamp
42
42
  applies (a relative href is never stamped, and a four-hour browser cache
43
43
  then styles new markup with an old sheet). -->
44
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
44
45
  <link rel="stylesheet" href="/assets/world.css">
45
46
  <style>
46
47
  /* ── PAGE-LOCAL SHEET ────────────────────────────────────────────────────
@@ -23,6 +23,7 @@
23
23
  <link rel="preconnect" href="https://fonts.googleapis.com">
24
24
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
25
25
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
26
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
26
27
  <link rel="stylesheet" href="/assets/cosmos.css">
27
28
  <!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
28
29
  <style>
@@ -14,6 +14,7 @@
14
14
  <link rel="preconnect" href="https://fonts.googleapis.com">
15
15
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
16
16
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
17
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
17
18
  <link rel="stylesheet" href="/assets/cosmos.css">
18
19
  <!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
19
20
  <style>
@@ -19,6 +19,7 @@
19
19
  space sky both modes ride on, light/dark governing the cards. The server
20
20
  re-injects the branding :root AFTER stylesheets, so the pack stays
21
21
  authoritative. Design contract: docs/design/projects-surfaces-redesign-direction.md -->
22
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
22
23
  <link rel="stylesheet" href="/assets/cosmos.css">
23
24
  <!-- The apex is dark only (task 1003336, ADR 0204): data-mode="dark" is fixed on
24
25
  <html> above — no pre-paint script, no stored choice, no OS preference. Light
@@ -14,6 +14,7 @@
14
14
  <link rel="preconnect" href="https://fonts.googleapis.com">
15
15
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
16
16
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
17
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
17
18
  <link rel="stylesheet" href="/assets/cosmos.css">
18
19
  <!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
19
20
  <style>
@@ -10,6 +10,7 @@
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
12
12
  <script src="{{base}}/assets/mode.js"></script>
13
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
13
14
  <link rel="stylesheet" href="{{base}}/assets/raise.css">
14
15
  </head>
15
16
  <body class="gate-body">
@@ -10,6 +10,7 @@
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
12
12
  <script src="{{base}}/assets/mode.js"></script>
13
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
13
14
  <link rel="stylesheet" href="{{base}}/assets/raise.css">
14
15
  </head>
15
16
  <body>
@@ -10,6 +10,7 @@
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
12
12
  <script src="{{base}}/assets/mode.js"></script>
13
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
13
14
  <link rel="stylesheet" href="{{base}}/assets/raise.css?v={{assetVersion}}">
14
15
  <script src="{{base}}/assets/raise.js?v={{assetVersion}}" defer></script>
15
16
  {{editorAssets}}
@@ -9,6 +9,7 @@
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700&family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
12
13
  <link rel="stylesheet" href="/style.css?v=2026-06-12-wip3">
13
14
  </head>
14
15
  <body>
@@ -14,7 +14,7 @@ A **look** is a branding pack from the module's style library (`modules/ui-desig
14
14
 
15
15
  Two of the floors are about fit ([task 1004203](https://cloudbongos.com/builders#/task/1004203), [ADR 0329](../../../docs/adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md)): `text-fit` (text past its box, SVG text past its card) and `sibling-overlap` (text-bearing siblings on top of each other). `scripts/bongos/render-check.js` runs just those two over the pages a branch changed, and `/ship-check` shows them as an advisory row.
16
16
 
17
- A surface describes itself in two JSON files beside it — `<page>.states.json` and `<page>.probes.json` — in the vocabularies `lib.js` defines and validates. The full loop, the file shapes and the failure table: [`docs/recipes/ui-look-before-you-ship.md`](../../../docs/recipes/ui-look-before-you-ship.md). Exit codes everywhere: 0 clean · 1 problems · 2 a broken file or usage · 3 no browser.
17
+ A surface describes itself in two JSON files beside it — `<page>.states.json` and `<page>.probes.json` — in the vocabularies `lib.js` defines and validates. A page served at the surface root (the hall's Home, `/builders/`) reads `index.states.json`. A file (or one state) may carry `"storage": { "session": {…}, "local": {…} }`, written into the browser before the page's own scripts run, so a first-visit question renders as a returning visitor sees it: every hall file seeds `hall-craft-asked` so the mode picker stays out of the shots ([task 1005080](https://cloudbongos.com/builders#/task/1005080)). A hall state that needs real data (a goal's detail, the Settings sections) passes against the hall-preview harness with `--base-in`/`--base-out`, one render at a time. The full loop, the file shapes and the failure table: [`docs/recipes/ui-look-before-you-ship.md`](../../../docs/recipes/ui-look-before-you-ship.md). Exit codes everywhere: 0 clean · 1 problems · 2 a broken file or usage · 3 no browser.
18
18
 
19
19
  ## The anti-pattern detector (`tells.js`, and the rendered twins in `lib.js`)
20
20
 
@@ -0,0 +1 @@
1
+ {"versions":[{"id":"BONGOS-V1","name":"Cloud Bongos — platform (V1)","status":"shipped","scope_doc_path":null,"started_at":"2026-06-01T00:00:00.000Z","shipped_at":"2026-09-01T00:00:00.000Z","done_when":"The platform builds itself."},{"id":"BONGOS-V2","name":"The ten working areas","status":"building","scope_doc_path":null,"started_at":"2026-09-01T00:00:00.000Z","shipped_at":null,"done_when":"Every working area has a goal."}],"page":{"limit":50,"offset":0,"total":2}}
@@ -79,7 +79,16 @@ function resolveSurfaceDir(surface, root = REPO_ROOT) {
79
79
  return null;
80
80
  }
81
81
  // The states/probes file for a page lives BESIDE the surface: <dir>/<page>.states.json.
82
- function sidecarPath(surfaceDir, urlPath, kind) { return path.join(surfaceDir, `${pageName(urlPath)}.${kind}.json`); }
82
+ function sidecarPath(surfaceDir, urlPath, kind) {
83
+ const named = path.join(surfaceDir, `${pageName(urlPath)}.${kind}.json`);
84
+ // A page served at the surface ROOT ("/builders/") has no last segment of its own — the
85
+ // segment is the surface's prefix — so its sidecar is index.<kind>.json (task 1005080).
86
+ if (/\/$/.test(String(urlPath || '').split('?')[0]) && !fs.existsSync(named)) {
87
+ const index = path.join(surfaceDir, `index.${kind}.json`);
88
+ if (fs.existsSync(index)) return index;
89
+ }
90
+ return named;
91
+ }
83
92
  function readJson(file) { return JSON.parse(fs.readFileSync(file, 'utf8')); }
84
93
 
85
94
  // ── the action vocabulary (states + probes) ───────────────────────────────────
@@ -332,6 +341,37 @@ function reducedMotionProblems({ running = [], frames = 0 } = {}) {
332
341
  // floor and not proof, and a reviewer verifies each tell in context (task 1003329, ADR 0223).
333
342
  function craftTells(facts) { return (facts && facts.tells ? facts.tells : []).map((t) => `tell: ${t}`); }
334
343
 
344
+ // ── storage seeded before a page's own scripts run (task 1005080) ─────────────
345
+ // { "storage"?: { "session"?: { key: "value" }, "local"?: { key: "value" } } } on a states or
346
+ // probes file — and the same object on one state — is written into the browser's storage by
347
+ // an init script, so a page that asks a one-time question on first visit (the hall's mode
348
+ // picker) renders as a returning visitor sees it. A state that MEANS the first visit leaves
349
+ // it out. A state's own storage is merged over the file's.
350
+ function validateStorage(st, at) {
351
+ if (st === undefined) return [];
352
+ if (!st || typeof st !== 'object' || Array.isArray(st)) return [`${at}: an object { session?, local? }`];
353
+ const errors = [];
354
+ for (const k of Object.keys(st)) {
355
+ if (k !== 'session' && k !== 'local') { errors.push(`${at}.${k}: unknown store (session | local)`); continue; }
356
+ const m = st[k];
357
+ if (!m || typeof m !== 'object' || Array.isArray(m) || !Object.values(m).every((v) => typeof v === 'string')) errors.push(`${at}.${k}: an object of string → string`);
358
+ }
359
+ return errors;
360
+ }
361
+ function mergeStorage(...layers) {
362
+ const out = { session: {}, local: {} };
363
+ for (const s of layers) if (s) for (const k of ['session', 'local']) Object.assign(out[k], s[k] || {});
364
+ return out;
365
+ }
366
+ async function seedStorage(ctx, ...layers) {
367
+ const st = mergeStorage(...layers);
368
+ if (!Object.keys(st.session).length && !Object.keys(st.local).length) return;
369
+ await ctx.addInitScript((s) => {
370
+ try { for (const [k, v] of Object.entries(s.session)) window.sessionStorage.setItem(k, v); } catch (_) { /* storage refused */ }
371
+ try { for (const [k, v] of Object.entries(s.local)) window.localStorage.setItem(k, v); } catch (_) { /* storage refused */ }
372
+ }, st);
373
+ }
374
+
335
375
  // ── the states file ───────────────────────────────────────────────────────────
336
376
  // { "page": "/projects", "surface"?: "public-landing" (a module key), "stub"?: { prefix?, child? },
337
377
  // "modes"?: ["dark" | "light", …] (the modes the page renders; default both — a page that pins
@@ -339,7 +379,8 @@ function craftTells(facts) { return (facts && facts.tells ? facts.tells : []).ma
339
379
  // stub.fixtures?: { "<fixture name>": "<other fixture file>" } (serve another file under a name — an empty list, a
340
380
  // failure shape — for the states in THIS file; task 1003345),
341
381
  // "ignoreControls"?: [selectors], "states": { name: { url?, auth?, actions?, expect? } },
342
- // "reducedMotion"?: { auth?, url?, actions? } }
382
+ // "reducedMotion"?: { auth?, url?, actions? },
383
+ // "storage"?: { session?, local? } (seeded before load; a state may carry its own — see seedStorage) }
343
384
  function validateStatesFile(json) {
344
385
  if (!json || typeof json !== 'object' || Array.isArray(json)) return { ok: false, errors: ['a states file is a JSON object'] };
345
386
  const errors = [];
@@ -349,6 +390,7 @@ function validateStatesFile(json) {
349
390
  if (json.modeQuery !== undefined && json.modeQuery !== false && typeof json.modeQuery !== 'string') errors.push('modeQuery: the query param the page reads for its colour mode (default "mode"), or false');
350
391
  if (json.modes !== undefined && !(Array.isArray(json.modes) && json.modes.length && json.modes.every((m) => MODES.includes(m)))) errors.push('modes: a non-empty array of "dark" | "light" — the modes the page renders (default both); a page that pins one mode declares it here');
351
392
  for (const k of ['ignoreRequests', 'ignoreConsole', 'ignoreControls']) if (json[k] !== undefined && !(Array.isArray(json[k]) && json[k].every((s) => typeof s === 'string'))) errors.push(`${k}: an array of strings`);
393
+ errors.push(...validateStorage(json.storage, 'storage'));
352
394
  const states = json.states;
353
395
  if (!states || typeof states !== 'object' || Array.isArray(states) || !Object.keys(states).length) errors.push('states: required — a non-empty object of name → { url?, auth?, actions?, expect? }');
354
396
  else {
@@ -359,6 +401,7 @@ function validateStatesFile(json) {
359
401
  if (s.auth !== undefined && typeof s.auth !== 'boolean') errors.push(`states.${name}.auth: true (signed-in stub) or false`);
360
402
  errors.push(...validateActions(s.actions, `states.${name}.actions`));
361
403
  errors.push(...validateExpect(s.expect, `states.${name}.expect`));
404
+ errors.push(...validateStorage(s.storage, `states.${name}.storage`));
362
405
  }
363
406
  }
364
407
  if (json.reducedMotion !== undefined) {
@@ -386,6 +429,7 @@ function validateProbesFile(json) {
386
429
  const errors = [];
387
430
  if (typeof json.page !== 'string' || !json.page.startsWith('/')) errors.push('page: required — the root-relative path the probes open');
388
431
  errors.push(...validateStub(json.stub, 'stub'));
432
+ errors.push(...validateStorage(json.storage, 'storage'));
389
433
  if (!Array.isArray(json.probes) || !json.probes.length) errors.push('probes: required — a non-empty array');
390
434
  else json.probes.forEach((pr, i) => {
391
435
  const at = `probes[${i}]`;
@@ -396,6 +440,7 @@ function validateProbesFile(json) {
396
440
  if (pr.url !== undefined && (typeof pr.url !== 'string' || !pr.url.startsWith('/'))) errors.push(`${at}.url: a root-relative path`);
397
441
  if (pr.continue !== undefined && typeof pr.continue !== 'boolean') errors.push(`${at}.continue: boolean (reuse the previous probe's page)`);
398
442
  if (pr.continue && i === 0) errors.push(`${at}.continue: the first probe has nothing to continue`);
443
+ errors.push(...validateStorage(pr.storage, `${at}.storage`));
399
444
  errors.push(...validateActions(pr.steps, `${at}.steps`));
400
445
  if (!pr.expect || typeof pr.expect !== 'object' || !Object.keys(pr.expect).length) errors.push(`${at}.expect: required — a probe with nothing to expect proves nothing`);
401
446
  else errors.push(...validateExpect(pr.expect, `${at}.expect`));
@@ -527,7 +572,7 @@ module.exports = {
527
572
  REPO_ROOT, KIT_DIR, STYLES_DIR, EXIT, FLOORS, SIZES, DEFAULT_SIZES, MODES, CHANNELS, SETTLE_MS, ACTIONS,
528
573
  argOf, hasFlag, listArg, pageName, normalizePageArg, resolveSurfaceDir, sidecarPath, readJson,
529
574
  runActions, factsInPage, expectProblems, auditFloors, reducedMotionProblems, craftTells,
530
- validateStatesFile, validateProbesFile, NoBrowserError, launchBrowser, attachCollectors, withMode,
575
+ validateStatesFile, validateProbesFile, mergeStorage, seedStorage, NoBrowserError, launchBrowser, attachCollectors, withMode,
531
576
  freePort, startStub, parseColor, composite, luminance, contrastRatio, instancePack, packColors,
532
577
  listStyles, resolveStyle, styleBrandingFile,
533
578
  };
@@ -72,6 +72,7 @@ async function main() {
72
72
  if (ctx) await ctx.close();
73
73
  const [W, H] = lib.SIZES[pr.width || 1440];
74
74
  ctx = await browser.newContext({ viewport: { width: W, height: H }, deviceScaleFactor: 1 });
75
+ await lib.seedStorage(ctx, json.storage, pr.storage);
75
76
  page = await ctx.newPage();
76
77
  errors = [];
77
78
  page.on('pageerror', (e) => errors.push(String(e.message).split('\n')[0]));
@@ -97,6 +97,7 @@ async function main() {
97
97
  const st = json.states[name];
98
98
  const [W, H] = lib.SIZES[w];
99
99
  const ctx = await browser.newContext({ viewport: { width: W, height: H }, deviceScaleFactor: 1, colorScheme: mode });
100
+ await lib.seedStorage(ctx, json.storage, st.storage);
100
101
  const page = await ctx.newPage();
101
102
  const logs = lib.attachCollectors(page, collectorOpts);
102
103
  const base = st.auth ? baseIn : baseOut;
@@ -121,6 +122,7 @@ async function main() {
121
122
  const rm = json.reducedMotion || {};
122
123
  for (const mode of lib.hasFlag(args, '--no-reduced-motion') ? [] : modes) {
123
124
  const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: mode, reducedMotion: 'reduce' });
125
+ await lib.seedStorage(ctx, json.storage, rm.storage);
124
126
  const page = await ctx.newPage();
125
127
  await page.goto((rm.auth === false ? baseOut : baseIn) + lib.withMode(rm.url || json.page, mode, json.modeQuery), { waitUntil: 'load' });
126
128
  await page.waitForTimeout(lib.SETTLE_MS);
@@ -106,6 +106,7 @@ const FEEDS = {
106
106
  '/public/cost-summary': 'cost-summary',
107
107
  '/public/recent-shipped': 'recent-shipped',
108
108
  '/public/leaderboard': 'leaderboard',
109
+ '/public/versions': 'versions', // the roadmap and goals pages' version dates (task 1005080)
109
110
  '/projects/featured': 'featured',
110
111
  };
111
112
  // /founding/builders (task 1004424) answers a never-founded project's empty list by default,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "key": "ui-design",
3
3
  "title": "UI design (the design contract, its adapters and sync, the /design playbook, the kit, the styles)",
4
- "description": "The platform capability that lets any builder on any instance create and design good-looking, functional UIs in line with that instance's world (scoped with the owner 2026-08-28; task 1003324, ADR 0197). It ships METHOD and never a world: the neutral design contract of ADR 0081 with its tool adapters (Claude Design, Figma), their sync CLIs and the validator; the /design playbook for ui-discipline work; the look-before-you-ship kit (kit/) and the style library (styles/). The thirteen design-style skills that lived in skills/ are the opt-in design-styles module since task 1004470; /design reaches them when that module is on. The instance's world — its branding pack, config/design-tokens.*, config/design-sources.json and its DESIGN.md — stays in config/ and at the repo root as the host boundary this module reads and never owns (ADR 0062 §7). Cloud Bongos's own front door is this module's first customer, not its owner. default-on because every instance has a face. No routes, no migrations, no port: declaration-only like character-anim, so disabling it withdraws only the skills it contributes and touches no served surface.",
4
+ "description": "The platform capability that lets any builder on any instance create and design good-looking, functional UIs in line with that instance's world (scoped with the owner 2026-08-28; task 1003324, ADR 0197). It ships METHOD and never a world: the neutral design contract of ADR 0081 with its tool adapters (Claude Design, Figma), their sync CLIs and the validator; the /design playbook for ui-discipline work; the look-before-you-ship kit (kit/) and the style library (styles/). The thirteen design-style skills that lived in skills/ are the opt-in design-styles module since task 1004470; /design reaches them when that module is on. The instance's world — its branding pack, config/design-tokens.*, config/design-sources.json and its DESIGN.md — stays in config/ and at the repo root as the host boundary this module reads and never owns (ADR 0062 §7). Cloud Bongos's own front door is this module's first customer, not its owner. default-on because every instance has a face. No routes, no migrations, no port. Since task 1005081 it also ships public/tokens.css, the shared token sheet (the scales: type, spacing, corners, shadows, motion, stacking) every surface loads at /modules/ui-design/public/tokens.css, so disabling it now also withdraws that sheet and the hall's scale aliases lose their values.",
5
5
  "version": "1.0.0",
6
6
  "coreVersion": "^1.19.0",
7
7
  "category": { "parent": "pages" },
@@ -0,0 +1,125 @@
1
+ /* modules/ui-design/public/tokens.css — THE SHARED TOKEN SHEET (task 1005081,
2
+ goal 1000138 "One Cloud Bongos design system").
3
+
4
+ One sheet every Cloud Bongos surface loads — the hall, the landing + the
5
+ projects hub, the status page and the raise page — served at the one stable
6
+ path /modules/ui-design/public/tokens.css on every host (serve-internal.js's
7
+ module-asset route; the kit's stub serves the same path).
8
+
9
+ WHAT IS HERE: the named SCALES — type sizes, spacing, corners, shadows,
10
+ motion and stacking layers. Every value was already in use (the hall's token
11
+ block, DESIGN.md's rhythm/shapes/motion), so loading this sheet moves nothing.
12
+
13
+ WHAT IS NOT HERE: the fifteen brand tokens (thirteen colours + two font
14
+ stacks). Those are the instance pack's API — serve-internal.js themeMarkup()
15
+ splices config/branding.*.json theme.ui as a plain :root at the END of <head>,
16
+ after every sheet, so the pack always wins. This sheet declares none of the
17
+ fifteen, so there is nothing for it to win against. No colour literal lives
18
+ here except black with alpha (the shadows) — the Fifteen Rule's one exception.
19
+
20
+ WHY --ds-*: the surfaces grew their own scale names, and some collide with a
21
+ DIFFERENT meaning (cosmos.css spends --line as a colour; raise.css sets
22
+ --radius to 10px). A shared name would silently change whichever surface did
23
+ not define it, so every scale is namespaced and each surface keeps its own
24
+ names as aliases until a later task migrates it (the hall's aliases live in
25
+ modules/hall-ui/public/style.css: --radius → --ds-radius-panel, …).
26
+
27
+ DARK TWINS: the one mode-dependent scale (the shadow) is written twice,
28
+ byte-identical — the switch rule (the hall's html[data-theme], the apex's
29
+ :root[data-mode]) and the prefers-color-scheme twin for first paint.
30
+
31
+ KILL SWITCH: each surface keeps its own `animation/transition: none
32
+ !important` rule; this sheet additionally zeroes its durations under
33
+ prefers-reduced-motion so a rule built on them stays still too. */
34
+
35
+ :root {
36
+ /* ---- type sizes: the steps the hall's 113 literal sizes cluster on, small
37
+ to large. The "-plus" half steps are the ones a later migration
38
+ should collapse into their neighbours. The four fluid roles are
39
+ DESIGN.md's display/headline/figure/title. ---- */
40
+ --ds-text-3xs: 10.5px;
41
+ --ds-text-2xs: 11px;
42
+ --ds-text-2xs-plus: 11.5px;
43
+ --ds-text-xs: 12px;
44
+ --ds-text-xs-plus: 12.5px;
45
+ --ds-text-sm: 13px;
46
+ --ds-text-sm-plus: 13.5px;
47
+ --ds-text-md: 14px;
48
+ --ds-text-md-plus: 14.5px;
49
+ --ds-text-lg: 15px;
50
+ --ds-text-xl: 16px;
51
+ --ds-text-2xl: 17px;
52
+ --ds-text-3xl: 20px;
53
+ --ds-text-4xl: 22px;
54
+ --ds-text-5xl: 26px;
55
+ --ds-text-title: clamp(23px, 2.4vw, 32px);
56
+ --ds-text-headline: clamp(26px, 3.1vw, 42px);
57
+ --ds-text-figure: clamp(30px, 3.7vw, 52px);
58
+ --ds-text-display: clamp(34px, 6.7vw, 96px);
59
+
60
+ /* ---- spacing: DESIGN.md's rhythm (6/10/14/22/34/52) + the band gutters ---- */
61
+ --ds-space-hair: 6px;
62
+ --ds-space-xs: 10px;
63
+ --ds-space-sm: 14px;
64
+ --ds-space-md: 22px;
65
+ --ds-space-lg: 34px;
66
+ --ds-space-xl: 52px;
67
+ --ds-space-gutter: 26px;
68
+ --ds-space-gutter-narrow: 20px;
69
+
70
+ /* ---- the hairline: a WIDTH, never a colour ---- */
71
+ --ds-line: 1px;
72
+
73
+ /* ---- corners: pill or nothing for controls; panels and tiles ---- */
74
+ --ds-radius-pill: 999px;
75
+ --ds-radius-panel: 16px;
76
+ --ds-radius-tile: 10px;
77
+
78
+ /* ---- shadows: the world has no UI shadow vocabulary; this is spent only
79
+ where an element floats over a scrim. Black with alpha. ---- */
80
+ --ds-shadow-pop: 0 18px 40px rgba(0, 0, 0, .28);
81
+
82
+ /* ---- motion: state changes take the one easing; an authored entrance
83
+ takes the exponential ease-out (DESIGN.md, Motion). ---- */
84
+ --ds-dur-fast: 150ms;
85
+ --ds-dur-base: 200ms;
86
+ --ds-dur-slow: 320ms;
87
+ --ds-dur-slower: 420ms;
88
+ --ds-dur-arrive: 700ms;
89
+ --ds-stagger: 70ms;
90
+ --ds-ease: cubic-bezier(.2, .7, .3, 1);
91
+ --ds-ease-arrive: cubic-bezier(.16, 1, .3, 1);
92
+
93
+ /* ---- stacking layers, low to high: the reading hairline under the top
94
+ bar, the chrome, the phone drawer over its scrim, a confirm dialog
95
+ over the drawer, the fullscreen diagram over everything. ---- */
96
+ --ds-z-progress: 25;
97
+ --ds-z-topbar: 30;
98
+ --ds-z-sidebar: 40;
99
+ --ds-z-scrim: 140;
100
+ --ds-z-drawer: 150;
101
+ --ds-z-modal: 160;
102
+ --ds-z-diagram-full: 9999;
103
+ }
104
+
105
+ /* ---------- dark: written twice, byte-identical ---------- */
106
+ html[data-theme="dark"], :root[data-mode="dark"] {
107
+ --ds-shadow-pop: 0 18px 40px rgba(0, 0, 0, .62);
108
+ }
109
+ @media (prefers-color-scheme: dark) {
110
+ html:not([data-theme]):not([data-mode="light"]) {
111
+ --ds-shadow-pop: 0 18px 40px rgba(0, 0, 0, .62);
112
+ }
113
+ }
114
+
115
+ /* ---------- the kill switch, for the motion scale ---------- */
116
+ @media (prefers-reduced-motion: reduce) {
117
+ :root {
118
+ --ds-dur-fast: 0ms;
119
+ --ds-dur-base: 0ms;
120
+ --ds-dur-slow: 0ms;
121
+ --ds-dur-slower: 0ms;
122
+ --ds-dur-arrive: 0ms;
123
+ --ds-stagger: 0ms;
124
+ }
125
+ }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.44",
3
+ "version": "1.24.46",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.44",
9
+ "version": "1.24.46",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.44",
3
+ "version": "1.24.46",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11227,5 +11227,25 @@
11227
11227
  "id": "1005051",
11228
11228
  "text": "Goals now opens with the version's progress and the timeline of every version, so the separate Roadmap page is gone from the sidebar. Old Roadmap links open Goals. The Tasks board's 'see the version' line points at Goals too."
11229
11229
  }
11230
+ ],
11231
+ "1.24.45": [
11232
+ {
11233
+ "id": "1005070",
11234
+ "text": "The hall's sidebar now appears instantly on every page instead of popping in late, and Building mode shows a single Home button."
11235
+ }
11236
+ ],
11237
+ "1.24.46": [
11238
+ {
11239
+ "id": "1005081",
11240
+ "text": "Cloud Bongos now has one shared set of design measurements (text sizes, spacing, corner shapes, shadows, animation timing and layering) that the builders hall, the landing page, the projects map, the status page and the invest"
11241
+ },
11242
+ {
11243
+ "id": "1004547",
11244
+ "text": "An Autobongos PC can now sign in with a login of its own, so the server always knows its work is automatic and it can never move a live version or flip its own off switch."
11245
+ },
11246
+ {
11247
+ "id": "1005080",
11248
+ "text": "The screenshot checker builders use before shipping a design change shows the real hall pages again, instead of a pop-up covering every page. Two small accessibility fixes came with it: a few tiny links are now big enough to t"
11249
+ }
11230
11250
  ]
11231
11251
  }
@@ -540,7 +540,7 @@ const KERNEL_FILES = [
540
540
  'src/bongos/auth-admission.js', // membership kind, admission gate, first-admin seating
541
541
  'src/bongos/auth-github.js', // GitHub device + web OAuth flows
542
542
  'src/bongos/auth-idp.js', // hub federation, assertion + logout-token verification
543
- 'src/bongos/auth-discord.js', // Discord OAuth link + guild membership
543
+ 'src/bongos/auth-discord.js', 'src/bongos/runner-login.js', // Discord OAuth link + guild membership; an Autobongos PC's own login + its limits (task 1004547; requires only pool/db-kernel)
544
544
  'src/bongos/route-rank-check.js', // deterministic route→rank classification
545
545
  'src/bongos/permission-path-check.js', // protected-path matcher (PROTECTED_GLOBS, ADR 0043)
546
546
  'src/bongos/goal-scope-check.js', // goal create-gate scope wall (composes the 3 matchers above; ADR 0086 §3, BV1.R60)
@@ -269,7 +269,9 @@ function build() {
269
269
  };
270
270
  });
271
271
 
272
- const inAnyCraft = new Set(CRAFTS.flatMap((c) => crafts.tabs[c]));
272
+ // 'home' is in every craft: there the plate under the brand IS the Home button
273
+ // (tasks 1005065, 1005070), so no mode lists the Home tab beside it.
274
+ const inAnyCraft = new Set(['home', ...CRAFTS.flatMap((c) => crafts.tabs[c])]);
273
275
  const findings = {
274
276
  broken: dedupe(broken).sort((a, b) => (a.from + a.href).localeCompare(b.from + b.href)),
275
277
  orphan: rows.filter((r) => r.url !== '/' && !r.nav.length && !r.in.length).map((r) => r.url),