@bongos/core 1.19.95 → 1.19.96

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.95",
6
- "core_contract": "1.19.95",
7
- "source_commit": "e6e718ed6edb6fc01565339760fd0fd14cffa2fc",
5
+ "core_version": "1.19.96",
6
+ "core_contract": "1.19.96",
7
+ "source_commit": "de62c4bfbfbe46d6479fdfbfb97dc0873611c6a5",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-13T21:44:20.853Z",
9
+ "built_at": "2026-08-13T22:10:53.979Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 298,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1549,
20
- "tree_sha256": "cb89e58c1034e1681a97ac75f367935e745215ceb83b1aa393639904a0d861db",
20
+ "tree_sha256": "fc9aca20647400c8086abd620cf8832fdc20b5695b0dc56bb6cae9c303d41182",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -4127,12 +4127,12 @@
4127
4127
  {
4128
4128
  "path": "package-lock.json",
4129
4129
  "mode": "0000644",
4130
- "sha256": "0ebfc1c5594ff344281bfc92013bb8cf8f871c7f02152c91ae847cde36f2de3d"
4130
+ "sha256": "5e745806419d6f06345f86548e6dab1997086a56087f87e06172fab999df9203"
4131
4131
  },
4132
4132
  {
4133
4133
  "path": "package.json",
4134
4134
  "mode": "0000644",
4135
- "sha256": "b192fbde61e9428fff6a9b7077f341abe1f6d930e009496b93c6fb44eca2a45c"
4135
+ "sha256": "5f0a6df46c69e1584b8ed6a9023858fbb769114c5912dcb87448effd645fe6b4"
4136
4136
  },
4137
4137
  {
4138
4138
  "path": "public-docs/index.html",
@@ -4392,7 +4392,7 @@
4392
4392
  {
4393
4393
  "path": "public-landing/projects.html",
4394
4394
  "mode": "0000644",
4395
- "sha256": "6edf67839bd80ba22672ebfb9f5c38a2bbce980157024b50d71ce9bae211c8db"
4395
+ "sha256": "a79a9a26b3e33b539d3731c3629a6e4302352f9e89559e42df6bf5801dd86ef6"
4396
4396
  },
4397
4397
  {
4398
4398
  "path": "public-landing/world/assets/clouds/cloud-island-large.png",
@@ -5702,7 +5702,7 @@
5702
5702
  {
5703
5703
  "path": "src/module-api.js",
5704
5704
  "mode": "0000644",
5705
- "sha256": "401742f230ab25aa8dbb410a1b8bf1bfbe02257a360362fa7488f3965a88c5ca"
5705
+ "sha256": "aae1ee2bdf9de2e173d26cfa7c1ad4af1b2f14e8be763903d70af840a08b1465"
5706
5706
  },
5707
5707
  {
5708
5708
  "path": "src/module-loader/loader.js",
@@ -7212,7 +7212,7 @@
7212
7212
  {
7213
7213
  "path": "tests/projects_hub.mjs",
7214
7214
  "mode": "0000644",
7215
- "sha256": "e3e3e3f8164da3f897166dca2b0502fac2519483b36282e91ef5645cfef6c18c"
7215
+ "sha256": "59a6bcd44d374802b31e8ccf487b8fbbf6322d6bfac7f079d69f39d7bd8a02db"
7216
7216
  },
7217
7217
  {
7218
7218
  "path": "tests/provision.mjs",
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.95",
3
+ "version": "1.19.96",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.95",
9
+ "version": "1.19.96",
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.95",
3
+ "version": "1.19.96",
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",
@@ -173,7 +173,24 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
173
173
  <th class="colGo"></th>
174
174
  </tr>
175
175
  </thead>
176
- <tbody id="mapRows"></tbody>
176
+ <tbody id="mapRows">
177
+ <!-- The platform's own row. Static and apex-only for exactly the reason
178
+ the orb above is (stripApexOnly() is a string transform that cannot
179
+ reach anything a script renders later), and preserved across
180
+ re-renders by the same data-generated contract renderOrbs uses. -->
181
+ <!--apex-only:start-->
182
+ <tr id="selfRow">
183
+ <td class="colName"><span class="pname"><span class="pdot" style="--acc:var(--accent)"></span>
184
+ <span><span class="ptitle" data-brand-product-inline>Cloud Bongos</span><span id="selfPill"></span><br>
185
+ <span class="ptag">this platform</span></span></span></td>
186
+ <td class="right num">—</td>
187
+ <td class="colType">—</td>
188
+ <td class="colTags">—</td>
189
+ <td class="right colStatus"><span class="chip calm"><span class="cdot"></span>—</span></td>
190
+ <td class="colGo"><a class="btn small ghost" href="#" data-self-open>Open →</a></td>
191
+ </tr>
192
+ <!--apex-only:end-->
193
+ </tbody>
177
194
  </table>
178
195
  <p class="finePrint" style="text-align:center">Click a column to sort.</p>
179
196
  </div>
@@ -472,6 +489,13 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
472
489
  var ORB_GLOWS = ORB_ACCENTS.map(function (t) { return 'color-mix(in srgb,' + t + ' 45%,transparent)'; });
473
490
  var catalog = [];
474
491
  var mineOrigins = {}; /* normalized host → 'owner' | 'builder' */
492
+ /* What the platform's own entry is FOR SEARCH. It is deliberately not pushed
493
+ into `catalog`: the catalog is the federated directory of other instances,
494
+ and a synthetic member of it would flow into accent assignment, orb slots
495
+ and the sort comparator as if a row existed. This keeps the self entry a
496
+ view-level concern (the owner's call, 2026-08-13) while still letting the
497
+ search box match it by the same predicate every other project uses. */
498
+ var SELF_PROJECT = { name: productName, tagline: 'this platform' };
475
499
  var mapSort = { key: 'name', dir: 1 };
476
500
 
477
501
  /* Catalog origins become hrefs, so they are scheme-checked before they are
@@ -500,6 +524,23 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
500
524
  }
501
525
  function accentOf(p) { return p._acc || ORB_ACCENTS[0]; }
502
526
  function relOf(p) { return mineOrigins[hostOf(p.origin)] || 'none'; }
527
+ /* The platform's own relationship cannot come from mineOrigins, and not
528
+ because the data is merely missing: platform_identity_project_memberships
529
+ is written ONLY by the server-to-server check-in a FEDERATED instance posts
530
+ on sign-in (ADR 0141 §3), and the apex is not a federated client of itself
531
+ — reportMembershipCheckIn() returns early on the instance that IS the hub,
532
+ so no amount of signing in here will ever produce a row. Waiting on that
533
+ table for this one project waits forever.
534
+
535
+ The local session is the authority instead, and it is a stronger one: a
536
+ session on this instance EXISTS because sign-in created a builder row here,
537
+ so "signed in here" IS "builder here" — not a guess standing in for one.
538
+ Archon is the instance's owner. Signed out stays 'none' so a stranger's map
539
+ claims nothing. Membership stays the cross-instance federated view. */
540
+ function selfRel() {
541
+ if (!me) return 'none';
542
+ return me.rank === 'archon' ? 'owner' : 'builder';
543
+ }
503
544
  function matchesMapQuery(p, q) {
504
545
  if (!q) return true;
505
546
  return (String(p.name || '') + ' ' + String(p.tagline || '')).toLowerCase().indexOf(q) !== -1;
@@ -534,11 +575,17 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
534
575
  }
535
576
 
536
577
  function renderRows(list) {
537
- $('mapRows').innerHTML = list.map(function (p) {
578
+ var body = $('mapRows');
579
+ /* replace only the generated rows — the platform's own row is static
580
+ apex-only markup and must survive every re-render, for the same reason
581
+ #selfOrb does in the sky. It stays pinned first: it is "this platform",
582
+ not a peer in the catalog's sort order. */
583
+ body.querySelectorAll('tr[data-generated]').forEach(function (n) { n.remove(); });
584
+ body.insertAdjacentHTML('beforeend', list.map(function (p) {
538
585
  var rel = relOf(p);
539
586
  var pill = rel === 'owner' ? ' <span class="pill">yours</span>'
540
587
  : rel === 'builder' ? ' <span class="pill">you build here</span>' : '';
541
- return '<tr>' +
588
+ return '<tr data-generated>' +
542
589
  '<td class="colName"><span class="pname"><span class="pdot" style="--acc:' +
543
590
  accentOf(p) + '"></span>' +
544
591
  '<span><span class="ptitle">' + esc(p.name) + '</span>' + pill + '<br>' +
@@ -556,7 +603,7 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
556
603
  ? '<a class="btn small ghost" href="' + esc(safeHttpUrl(p.origin)) + '" target="_blank" rel="noopener">Open →</a>'
557
604
  : '') + '</td>' +
558
605
  '</tr>';
559
- }).join('');
606
+ }).join(''));
560
607
  }
561
608
 
562
609
  function applyMapFilter() {
@@ -571,13 +618,34 @@ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')do
571
618
  });
572
619
  renderOrbs(list);
573
620
  renderRows(list);
574
- var self = $('selfOrb');
575
- if (self) self.hidden = onlyMine; /* the platform is nobody's project */
576
- $('orbField').classList.toggle('isEmpty', list.length === 0 && (onlyMine || !!q));
621
+
622
+ /* The platform's own entry, in BOTH views from one predicate — it used to
623
+ be hidden outright under "only mine" ("the platform is nobody's project"),
624
+ which is what dropped Cloud Bongos out of a signed-in archon's own filter.
625
+ It is somebody's: see selfRel(). It still answers the search box, and it
626
+ still vanishes for a signed-out visitor, who owns nothing here. */
627
+ var rel = selfRel();
628
+ var selfShown = (rel !== 'none' || !onlyMine) && matchesMapQuery(SELF_PROJECT, q);
629
+ var badge = rel === 'owner' ? 'yours' : rel === 'builder' ? 'you build here' : '';
630
+ var orb = $('selfOrb'), row = $('selfRow');
631
+ if (orb) {
632
+ orb.hidden = !selfShown;
633
+ orb.setAttribute('data-rel', rel);
634
+ var b = orb.querySelector('.badge');
635
+ if (badge && !b) { b = document.createElement('span'); b.className = 'badge'; orb.insertBefore(b, orb.firstChild); }
636
+ if (b) { b.textContent = badge; b.hidden = !badge; }
637
+ }
638
+ if (row) {
639
+ row.hidden = !selfShown;
640
+ $('selfPill').innerHTML = badge ? ' <span class="pill">' + badge + '</span>' : '';
641
+ }
642
+
643
+ var shown = list.length + (selfShown ? 1 : 0);
644
+ $('orbField').classList.toggle('isEmpty', shown === 0 && (onlyMine || !!q));
577
645
  $('emptyMsg').textContent = onlyMine
578
646
  ? 'You don’t own or build on anything here yet.'
579
647
  : 'Nothing here matches that.';
580
- $('mapCount').textContent = list.length + (list.length === 1 ? ' project' : ' projects');
648
+ $('mapCount').textContent = shown + (shown === 1 ? ' project' : ' projects');
581
649
  }
582
650
 
583
651
  function loadCatalog() {
package/src/module-api.js CHANGED
@@ -617,8 +617,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
617
617
  // landed since 1.19.93 with no explicit bump. run 31742308532. (task 1002620)
618
618
  // 1.19.95 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
619
619
  // landed since 1.19.94 with no explicit bump. run 31746921919. (task 1002620)
620
+ // 1.19.96 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
621
+ // landed since 1.19.95 with no explicit bump. run 31748829423. (task 1002620)
620
622
  // ---------------------------------------------------------------------------
621
- const CORE_VERSION = '1.19.95'; // CI auto-patch carrier (ADR 0161); see changelog above
623
+ const CORE_VERSION = '1.19.96'; // CI auto-patch carrier (ADR 0161); see changelog above
622
624
 
623
625
  // A namespaced logger so a module's log lines are attributable + consistent.
624
626
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -203,3 +203,71 @@ test('/create still answers — the hub links it rather than absorbing it', () =
203
203
  assert.match(SCRIPT, /location\.assign\('\/create'/);
204
204
  assert.match(HUB, /href="\/create"/, 'the empty state must still point at the wizard');
205
205
  });
206
+
207
+ /* ── The platform's own entry (task 1002760) ────────────────────────────────
208
+ The apex had an orb for itself but no table row and no membership, so a
209
+ signed-in archon's own "only mine" filter hid the very project they were
210
+ standing on. These pin the shape of the fix: the self entry is a VIEW-level
211
+ concern present in both views, and its relationship comes from the local
212
+ session because the membership table structurally cannot answer for it. ── */
213
+
214
+ test('the platform has a self entry in the TABLE, not only in the sky', () => {
215
+ // the sky's orb and the table's row are both static markup, for the same
216
+ // reason: stripApexOnly() is a string transform and cannot reach JS output
217
+ assert.match(HUB, /id="selfOrb"/, 'the sky keeps its self orb');
218
+ assert.match(HUB, /id="selfRow"/, 'the table needs one too — this is the bug');
219
+ // and the row must sit INSIDE the tbody the renderer owns
220
+ const tbody = (HUB.match(/<tbody id="mapRows">([\s\S]*?)<\/tbody>/) || [, ''])[1];
221
+ assert.match(tbody, /id="selfRow"/, 'the self row belongs in #mapRows');
222
+ });
223
+
224
+ test('both self entries are apex-only — a child instance has no "this platform"', () => {
225
+ for (const id of ['selfOrb', 'selfRow']) {
226
+ const at = HUB.indexOf(`id="${id}"`);
227
+ const before = HUB.slice(0, at);
228
+ const opens = (before.match(/<!--apex-only:start-->/g) || []).length;
229
+ const closes = (before.match(/<!--apex-only:end-->/g) || []).length;
230
+ assert.equal(opens - closes, 1, `${id} must sit inside an apex-only sentinel pair`);
231
+ }
232
+ });
233
+
234
+ test('the renderer preserves the self row exactly as it preserves the self orb', () => {
235
+ // renderRows used to blow the whole tbody away with innerHTML=, which would
236
+ // delete a static row on the first catalog paint
237
+ assert.doesNotMatch(SCRIPT, /\$\('mapRows'\)\.innerHTML\s*=/,
238
+ 'a wholesale innerHTML= on #mapRows destroys the static self row');
239
+ assert.match(SCRIPT, /querySelectorAll\('tr\[data-generated\]'\)/,
240
+ 'generated rows must be removed selectively, like .worldOrb[data-generated]');
241
+ assert.match(SCRIPT, /<tr data-generated>/, 'generated rows must be markable');
242
+ });
243
+
244
+ test('"only mine" no longer hides the platform from a signed-in builder', () => {
245
+ // the exact regression: `if (self) self.hidden = onlyMine;`
246
+ assert.doesNotMatch(SCRIPT, /self\.hidden\s*=\s*onlyMine/,
247
+ 'hiding the self entry whenever the filter is on was the bug');
248
+ assert.match(SCRIPT, /selfRel\s*\(\s*\)/, 'a single predicate decides both views');
249
+ assert.match(SCRIPT, /selfShown/, 'and both views read the same computed visibility');
250
+ });
251
+
252
+ test('the self relationship comes from the SESSION, never from mineOrigins', () => {
253
+ const fn = (SCRIPT.match(/function selfRel\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
254
+ assert.ok(fn, 'selfRel() must exist');
255
+ assert.doesNotMatch(fn, /mineOrigins/,
256
+ 'memberships are written only by federated check-in; the apex is not a client of itself');
257
+ assert.match(fn, /if \(!me\) return 'none'/, 'a signed-out stranger owns nothing here');
258
+ assert.match(fn, /archon/, 'the instance archon is its owner');
259
+ });
260
+
261
+ test('the self entry is not pushed into the catalog array', () => {
262
+ // it would flow into accent assignment, orb slots and the sort comparator as
263
+ // though a federated row existed
264
+ assert.doesNotMatch(SCRIPT, /catalog\.push|catalog\.unshift|catalog\s*=\s*\[SELF/,
265
+ 'the catalog is the federated directory of OTHER instances');
266
+ assert.match(SCRIPT, /var SELF_PROJECT\s*=/, 'the self entry is a view-level constant');
267
+ });
268
+
269
+ test('the count includes the platform when it is shown', () => {
270
+ assert.doesNotMatch(SCRIPT, /mapCount'\)\.textContent = list\.length/,
271
+ 'the count must not ignore the self entry it is rendering beside');
272
+ assert.match(SCRIPT, /var shown = list\.length \+ \(selfShown \? 1 : 0\)/);
273
+ });