@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 +10 -10
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/projects.html +76 -8
- package/src/module-api.js +3 -1
- package/tests/projects_hub.mjs +68 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
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-
|
|
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": "
|
|
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": "
|
|
4130
|
+
"sha256": "5e745806419d6f06345f86548e6dab1997086a56087f87e06172fab999df9203"
|
|
4131
4131
|
},
|
|
4132
4132
|
{
|
|
4133
4133
|
"path": "package.json",
|
|
4134
4134
|
"mode": "0000644",
|
|
4135
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|
|
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')
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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 =
|
|
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.
|
|
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');
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -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
|
+
});
|