@bongos/core 1.19.328 → 1.19.329

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.
package/.bongos-core.json CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.328",
6
- "core_contract": "1.19.328",
7
- "source_commit": "6897b2516a4a7f0b138ea4fb2c7477ec70dd1383",
5
+ "core_version": "1.19.329",
6
+ "core_contract": "1.19.329",
7
+ "source_commit": "ed313bf56c84b4802561c1a22a7b1f13552d3297",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-24T22:47:11.897Z",
9
+ "built_at": "2026-08-24T23:19:59.592Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 324,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1811,
20
- "tree_sha256": "89e32bb1ea6b184d5fa7d3e224f40a10fe95675f1bdded8750985042f9942349",
20
+ "tree_sha256": "2e52a9d82898b6470557b59fc6ca710346a09b410a7f47f88fc037ad493df397",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -4667,12 +4667,12 @@
4667
4667
  {
4668
4668
  "path": "package-lock.json",
4669
4669
  "mode": "0000644",
4670
- "sha256": "af956f74c63054468aa30a91c2996ef93e636903e823c30d141fb12214a8127d"
4670
+ "sha256": "454fbe2af15bdbe1cdd60d5eb558c601062ee8efb13b21805f0644e07a125984"
4671
4671
  },
4672
4672
  {
4673
4673
  "path": "package.json",
4674
4674
  "mode": "0000644",
4675
- "sha256": "709ecfd08e6bf78726409f18c5fcf175aa73da3d0e68db9c7faf73cf59f78f6e"
4675
+ "sha256": "40a3c39811c23c665f9710599982e4964d6f58d405c6e2fcf24b96e4aab36820"
4676
4676
  },
4677
4677
  {
4678
4678
  "path": "public-docs/index.html",
@@ -4912,7 +4912,7 @@
4912
4912
  {
4913
4913
  "path": "public-landing/assets/cosmos.css",
4914
4914
  "mode": "0000644",
4915
- "sha256": "32b7b1c69e01531170143bd55a0a23aebb076e3bce5e8775da55e26307bc5c2d"
4915
+ "sha256": "d39de81b2de333eb82eb5486bf975b56a99d24cf56bc84815fdf4bb96ae9fa0e"
4916
4916
  },
4917
4917
  {
4918
4918
  "path": "public-landing/assets/verro-medallion.png",
@@ -4932,7 +4932,7 @@
4932
4932
  {
4933
4933
  "path": "public-landing/projects.html",
4934
4934
  "mode": "0000644",
4935
- "sha256": "df56b665c742287ea4387730fa7f981472a63c6d761b757115c06e5dcdea8c5f"
4935
+ "sha256": "98e234b47865c9f84650d2b1a9408610965ef780492fcc9b70d74adee57ad039"
4936
4936
  },
4937
4937
  {
4938
4938
  "path": "public-landing/world/assets/clouds/cloud-island-large.png",
@@ -6452,7 +6452,7 @@
6452
6452
  {
6453
6453
  "path": "src/module-api.js",
6454
6454
  "mode": "0000644",
6455
- "sha256": "94d24f1fc6ba8fe13ea3f04ade9e3f18191392911804ef03839e335c1116a3f8"
6455
+ "sha256": "a06153efe33e5d5729c4c6a86e1889441808217fa7fe6c9b07bd6b5ebe86114c"
6456
6456
  },
6457
6457
  {
6458
6458
  "path": "src/module-loader/catalog.js",
@@ -8332,7 +8332,7 @@
8332
8332
  {
8333
8333
  "path": "tests/projects_hub_pre_uat.mjs",
8334
8334
  "mode": "0000644",
8335
- "sha256": "d56e9f8ed2a964b59ec5893399a52b1c9536caa3cc1b95967a7711fed2e5fa4a"
8335
+ "sha256": "566428702a778df50caf55d7050adbe18d3005eecf8f63aa6a758437ab2934a7"
8336
8336
  },
8337
8337
  {
8338
8338
  "path": "tests/promote_goal_id.mjs",
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.328",
3
+ "version": "1.19.329",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.328",
9
+ "version": "1.19.329",
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.19.328",
3
+ "version": "1.19.329",
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",
@@ -321,6 +321,12 @@ h1{font-family:var(--font-display);font-weight:700;font-size:clamp(24px,3.4vw,32
321
321
  .attach input[type=text]{flex:1;min-width:220px;font-family:var(--font-body);font-size:15px;font-weight:600;color:var(--ink);background:var(--bg-card);border:2px solid var(--ctl-line);border-radius:12px;padding:9px 14px;outline:none;transition:border-color .15s ease, box-shadow .15s ease;}
322
322
  .attach input[type=text]::placeholder{color:var(--ink-faint);font-weight:500;}
323
323
  .attach input[type=text]:focus{border-color:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 13%,transparent);}
324
+ /* the bring-your-own-domain fold (owner decision 2, 2026-08-24): the wizard's
325
+ address-step disclosure, closed by default and after the card's doors — a
326
+ domain the founder can't buy here must not be the card's loudest control */
327
+ .attachFold{margin-top:12px;}
328
+ .attachFold summary{cursor:pointer;width:fit-content;font-size:13px;font-weight:700;color:var(--ink-faint);}
329
+ .attachFold summary:hover,.attachFold[open] summary{color:var(--ink-soft);}
324
330
  .projMsg{font-size:13.5px;font-weight:600;margin-top:10px;min-height:18px;color:var(--ink-soft);line-height:1.5;}
325
331
  .projMsg.ok{color:var(--ok);}
326
332
  .projMsg.err{color:var(--err);}
@@ -3813,6 +3813,32 @@ h1[tabindex="-1"]:focus{outline:none;}
3813
3813
  return '<span class="none">No address yet</span> — sign-in on this project can’t be set up until it has one.';
3814
3814
  }
3815
3815
 
3816
+ /* ── the bring-your-own-domain fold (owner decision 2, 2026-08-24). A live
3817
+ card's loudest control used to be a full-width domain field + filled
3818
+ button — for a domain the founder can't buy here — while the success
3819
+ moment had only "Open site →". The field now sits behind the disclosure
3820
+ the wizard's address step uses (direction §6): closed, after the card's
3821
+ doors, and only where something is or will be answering — live and
3822
+ queued. A snagged or torn-down card has nothing to point a domain at, so
3823
+ it carries no fold (canAttachDomain stays the server's mirror; this is
3824
+ the card's own call). The re-kick door (F19) keeps its shape — open and
3825
+ pre-filled, one button: Try the address again. ── */
3826
+ function attachFold(inst, iid) {
3827
+ if (!canAttachDomain(inst)) return '';
3828
+ if (inst.status !== 'active' && inst.status !== 'requested') return '';
3829
+ /* the strip already says "Wiring the address" — a second attach would only
3830
+ be refused (intent_conflict), so the fold waits for the run to land */
3831
+ if (inst.open_intent && inst.open_intent.action === 'domain-attach') return '';
3832
+ var rekick = inst.status === 'active' && inst.domain && inst.error_note;
3833
+ return '<details class="attachFold"' + (rekick ? ' open' : '') + '>' +
3834
+ '<summary>' + (rekick ? 'Try the address again' : inst.domain ? 'Use a domain I own' : 'Give it an address') + '</summary>' +
3835
+ '<div class="attach">' +
3836
+ '<input type="text" id="dom-' + iid + '" placeholder="myproject.example.com" autocomplete="off" spellcheck="false" maxlength="253" aria-label="A domain you own, for ' + esc(inst.slug) + '"' +
3837
+ (rekick ? ' value="' + esc(inst.domain) + '"' : '') + '>' +
3838
+ '<button type="button" class="btn small" data-attach="' + iid + '">' +
3839
+ (rekick ? 'Try the address again' : 'Use this domain') + '</button></div></details>';
3840
+ }
3841
+
3816
3842
  function renderCard(inst) {
3817
3843
  /* iid is for MARKUP interpolation only (esc() = HTML escape); the DOM
3818
3844
  lookup goes through liFor(), which CSS-escapes — the two escapes are
@@ -3835,14 +3861,6 @@ h1[tabindex="-1"]:focus{outline:none;}
3835
3861
  (meta.length ? '<div class="projMeta">' + meta.map(function (m) { return '<span>' + m + '</span>'; }).join('') + '</div>' : '');
3836
3862
  var addr = addressRow(inst);
3837
3863
  if (addr) body += '<div class="addr">' + addr + '</div>';
3838
- if (canAttachDomain(inst)) {
3839
- var rekick = inst.status === 'active' && inst.domain && inst.error_note;
3840
- body += '<div class="attach">' +
3841
- '<input type="text" id="dom-' + iid + '" placeholder="myproject.example.com" autocomplete="off" spellcheck="false" maxlength="253" aria-label="A domain you own, for ' + esc(inst.slug) + '"' +
3842
- (rekick ? ' value="' + esc(inst.domain) + '"' : '') + '>' +
3843
- '<button type="button" class="btn small" data-attach="' + iid + '">' +
3844
- (rekick ? 'Try the address again' : inst.domain ? 'Use your own domain' : 'Give it an address') + '</button></div>';
3845
- }
3846
3864
  if ((inst.status === 'error' || inst.error_note) && !inst.open_intent) {
3847
3865
  /* once a retry is queued the strip tells the story — the stale error
3848
3866
  paragraph would just contradict it (owner walk, 2026-08-12). Also
@@ -3871,7 +3889,22 @@ h1[tabindex="-1"]:focus{outline:none;}
3871
3889
  if (canRevive(inst)) acts.push('<button type="button" class="btn small" data-revive="' + iid + '">Bring it back</button>');
3872
3890
  if (canTeardown(inst)) acts.push('<button type="button" class="btn small danger" data-teardown="' + iid + '">Tear it down</button>');
3873
3891
  if (acts.length) body += '<div class="actions" id="act-' + iid + '">' + acts.join('') + '</div>';
3892
+ body += attachFold(inst, iid);
3893
+ /* sig() buckets recency per minute, so a live card re-renders on the
3894
+ heartbeat — that must not snap an open fold shut, eat a half-typed
3895
+ domain or drop the caret. defaultValue tells a founder's typing from
3896
+ the re-kick pre-fill. */
3897
+ var oldFold = li.querySelector('details.attachFold');
3898
+ var oldInput = oldFold ? oldFold.querySelector('input') : null;
3899
+ var keepOpen = !!(oldFold && oldFold.open);
3900
+ var typed = oldInput && oldInput.value !== oldInput.defaultValue ? oldInput.value : '';
3901
+ var hadFocus = !!oldInput && document.activeElement === oldInput;
3874
3902
  li.innerHTML = orbFor(inst) + '<div class="projBody">' + body + '</div>';
3903
+ var fold = li.querySelector('details.attachFold');
3904
+ var input = fold ? fold.querySelector('input') : null;
3905
+ if (fold && keepOpen) fold.open = true;
3906
+ if (input && typed) input.value = typed;
3907
+ if (input && hadFocus) input.focus();
3875
3908
  }
3876
3909
 
3877
3910
  function render(instances) {
package/src/module-api.js CHANGED
@@ -1083,8 +1083,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1083
1083
  // landed since 1.19.326 with no explicit bump. run 32782353048. (task 1002620)
1084
1084
  // 1.19.328 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1085
1085
  // landed since 1.19.327 with no explicit bump. run 32786284063. (task 1002620)
1086
+ // 1.19.329 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1087
+ // landed since 1.19.328 with no explicit bump. run 32788827293. (task 1002620)
1086
1088
  // ---------------------------------------------------------------------------
1087
- const CORE_VERSION = '1.19.328'; // CI auto-patch carrier (ADR 0161); see changelog above
1089
+ const CORE_VERSION = '1.19.329'; // CI auto-patch carrier (ADR 0161); see changelog above
1088
1090
 
1089
1091
  // A namespaced logger so a module's log lines are attributable + consistent.
1090
1092
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -221,3 +221,46 @@ test('the inputs a stranger types into are labelled', () => {
221
221
  assert.match(HUB, /id="ghNewName"[^>]*aria-label="New repository name"/);
222
222
  assert.match(SCRIPT, /aria-label="A domain you own, for ' \+ esc\(inst\.slug\) \+ '"/);
223
223
  });
224
+
225
+ /* ── the custom-domain field sits behind a fold (owner decision 2, 2026-08-24) ── */
226
+ test('the domain field is a closed disclosure after the card’s doors — never the card’s loudest control', () => {
227
+ const fold = fn('attachFold');
228
+ assert.match(fold, /<details class="attachFold"/, 'the wizard’s address-step disclosure shape');
229
+ assert.match(fold, /Use a domain I own/, 'labelled the way the wizard labels it');
230
+ assert.match(fold, /inst\.status !== 'active' && inst\.status !== 'requested'\) return ''/, 'live + queued only — snagged and torn-down cards carry no fold');
231
+ assert.match(fold, /if \(!canAttachDomain\(inst\)\) return ''/, 'the server-mirror gate still comes first');
232
+ assert.match(fold, /inst\.open_intent\.action === 'domain-attach'\) return ''/, 'no second attach control while one is wiring');
233
+ assert.doesNotMatch(fold, /Use your own domain/, 'the old filled-button label is gone');
234
+ const card = fn('renderCard');
235
+ assert.doesNotMatch(card, /<div class="attach">/, 'renderCard no longer inlines the bare field');
236
+ const actions = card.indexOf('<div class="actions" id="act-');
237
+ const foldCall = card.indexOf('body += attachFold(inst, iid);');
238
+ assert.ok(actions >= 0 && foldCall > actions, 'Open site → Manage → (fold): the fold renders after the actions row');
239
+ assert.match(COSMOS, /\.attachFold summary\{cursor:pointer;/, 'the summary reads as a control');
240
+ // the 390px capture: the fold stacks field over button because the row wraps
241
+ // and the field's minimum (220px) is narrower than a phone card's body; the
242
+ // fold itself sets no width, so it can never be the thing that overflows
243
+ assert.match(COSMOS, /\.attach\{display:flex;gap:10px;margin-top:10px;flex-wrap:wrap;\}/, 'the row wraps at phone width');
244
+ assert.match(COSMOS, /\.attach input\[type=text\]\{flex:1;min-width:220px;/, 'the field fits a 390px card and grows on a laptop');
245
+ assert.doesNotMatch(COSMOS, /\.attachFold[^{]*\{[^}]*(width|min-width):(?!fit-content)/, 'the fold sets no width of its own');
246
+ // and in both themes: the summary reads from tokens, never a literal colour
247
+ assert.doesNotMatch((COSMOS.match(/\.attachFold[^\n]*/g) || []).join('\n'), /#[0-9a-f]{3,8}\b/i, 'the fold paints from --ink-faint / --ink-soft, so dark mode needs no twin');
248
+ });
249
+
250
+ test('the F19 re-kick door survives the fold: open, pre-filled, one button', () => {
251
+ const fold = fn('attachFold');
252
+ assert.match(fold, /var rekick = inst\.status === 'active' && inst\.domain && inst\.error_note;/);
253
+ assert.match(fold, /\(rekick \? ' open' : ''\)/, 'the fold opens on its own for a stalled address');
254
+ assert.match(fold, /\(rekick \? ' value="' \+ esc\(inst\.domain\) \+ '"' : ''\)/, 'pre-filled with the address that stalled');
255
+ assert.match(fold, /rekick \? 'Try the address again' : 'Use this domain'/, 'and the button says what it does');
256
+ assert.match(fn('canAttachDomain'), /inst\.status === 'active' && inst\.domain && inst\.error_note && !inst\.open_intent\) return true;/, 'the route-side re-kick gate is untouched');
257
+ });
258
+
259
+ test('a heartbeat re-render keeps an open fold open and a half-typed domain typed', () => {
260
+ const card = fn('renderCard');
261
+ assert.match(card, /var keepOpen = !!\(oldFold && oldFold\.open\);/);
262
+ assert.match(card, /oldInput\.value !== oldInput\.defaultValue \? oldInput\.value : ''/, 'typing is told apart from the re-kick pre-fill');
263
+ assert.match(card, /document\.activeElement === oldInput/, 'the caret comes back too');
264
+ const set = card.indexOf('li.innerHTML = orbFor(inst)');
265
+ assert.ok(card.indexOf('var keepOpen') < set && card.indexOf('if (fold && keepOpen) fold.open = true;') > set, 'read before the swap, restore after it');
266
+ });