@bongos/core 1.19.420 → 1.19.422
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 +68 -33
- package/docs/copy-inventory.md +282 -281
- package/docs/copy-registry.json +375 -366
- package/docs/design/mocks/hub/mine.html +377 -0
- package/docs/design/projects-hub-direction-v2.md +25 -1
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/settings.html +3 -2
- package/modules/hall-ui/public/settings.js +10 -6
- package/modules/platform-identity/account-visibility.js +94 -3
- package/modules/platform-identity/platform-identity.js +45 -50
- package/modules/platform-identity/routes/connections.js +3 -3
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -0
- package/modules/ui-design/kit/fixtures/provisioning-instances-empty.json +4 -0
- package/modules/ui-design/kit/fixtures/provisioning-instances.json +99 -0
- package/modules/ui-design/kit/lib.js +6 -2
- package/modules/ui-design/kit/probe.js +1 -1
- package/modules/ui-design/kit/render.js +1 -1
- package/modules/ui-design/kit/serve.js +63 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +246 -26
- package/public-landing/projects-empty.states.json +49 -0
- package/public-landing/projects-floor.states.json +63 -0
- package/public-landing/projects.html +113 -52
- package/public-landing/projects.probes.json +32 -7
- package/public-landing/projects.states.json +10 -6
- package/src/module-api.js +1 -1
- package/tests/account_privacy_flags.mjs +17 -10
- package/tests/effective_visibility_predicate.mjs +891 -0
- package/tests/profile_route.mjs +22 -11
- package/tests/projects_hub.mjs +54 -5
- package/tests/projects_hub_pre_uat.mjs +19 -7
- package/tests/ui_design_kit.mjs +32 -2
package/tests/profile_route.mjs
CHANGED
|
@@ -341,6 +341,7 @@ await t('hub port returns null (hidden/provisional/none) → cross_project: null
|
|
|
341
341
|
const HUB_GITHUB_ID = FULL_ROW.github_id;
|
|
342
342
|
const HIDDEN_LOGIN = 'hidden-DO-NOT-LEAK-r06';
|
|
343
343
|
const PROV_LOGIN = 'provisional-DO-NOT-LEAK-r06';
|
|
344
|
+
const PRIVATE_LOGIN = 'private-DO-NOT-LEAK-r06';
|
|
344
345
|
|
|
345
346
|
// TWO projects — one publicly catalogued, one not. Distinctive digits so the
|
|
346
347
|
// "no partial numbers" scan below cannot pass by coincidence.
|
|
@@ -353,22 +354,28 @@ const PROOF_PROJECTS = [
|
|
|
353
354
|
const sumOf = (k) => PROOF_PROJECTS.reduce((n, p) => n + p[k], 0);
|
|
354
355
|
|
|
355
356
|
// The one account row the reads consult. setAccountHideStats mutates it.
|
|
356
|
-
const hubAccount = { profile_state: 'active', hide_stats: false, handle: 'r06-handle' };
|
|
357
|
+
const hubAccount = { profile_state: 'active', account_visibility: 'public', hide_stats: false, handle: 'r06-handle' };
|
|
357
358
|
|
|
358
|
-
// The scouting "table": one visible builder plus the
|
|
359
|
+
// The scouting "table": one visible builder plus the THREE that consent and
|
|
360
|
+
// visibility exclude (the private row joined at task 1002967 — the same axis,
|
|
361
|
+
// enforced by the same shared predicate).
|
|
359
362
|
const SCOUTING_TABLE = [
|
|
360
|
-
{ profile_state: 'active', hide_stats: false,
|
|
363
|
+
{ profile_state: 'active', account_visibility: 'public', hide_stats: false,
|
|
361
364
|
row: { github_id: '11', github_login: 'r06-visible', display_name: 'Visible', avatar_url: null,
|
|
362
365
|
project_count: 2, total_credits: 5, total_tasks_shipped: 1, total_karma: 0,
|
|
363
366
|
last_active: '2026-08-01T00:00:00.000Z', disciplines: ['engineer'] } },
|
|
364
|
-
{ profile_state: 'active', hide_stats: true,
|
|
367
|
+
{ profile_state: 'active', account_visibility: 'public', hide_stats: true,
|
|
365
368
|
row: { github_id: '12', github_login: HIDDEN_LOGIN, display_name: 'Hidden', avatar_url: null,
|
|
366
369
|
project_count: 7, total_credits: 88817, total_tasks_shipped: 6613, total_karma: 5519,
|
|
367
370
|
last_active: '2026-08-02T00:00:00.000Z', disciplines: ['ui'] } },
|
|
368
|
-
{ profile_state: 'provisional', hide_stats: false,
|
|
371
|
+
{ profile_state: 'provisional', account_visibility: 'public', hide_stats: false,
|
|
369
372
|
row: { github_id: '13', github_login: PROV_LOGIN, display_name: 'Provisional', avatar_url: null,
|
|
370
373
|
project_count: 1, total_credits: 71713, total_tasks_shipped: 1, total_karma: 0,
|
|
371
374
|
last_active: '2026-08-03T00:00:00.000Z', disciplines: [] } },
|
|
375
|
+
{ profile_state: 'active', account_visibility: 'private', hide_stats: false,
|
|
376
|
+
row: { github_id: '14', github_login: PRIVATE_LOGIN, display_name: 'Private', avatar_url: null,
|
|
377
|
+
project_count: 3, total_credits: 63127, total_tasks_shipped: 4409, total_karma: 2287,
|
|
378
|
+
last_active: '2026-08-04T00:00:00.000Z', disciplines: ['engineer'] } },
|
|
372
379
|
];
|
|
373
380
|
|
|
374
381
|
const engine = {
|
|
@@ -382,6 +389,7 @@ const engine = {
|
|
|
382
389
|
if (/AS project_count/.test(sql)) {
|
|
383
390
|
let rows = SCOUTING_TABLE;
|
|
384
391
|
if (/pa\.profile_state = 'active'/.test(sql)) rows = rows.filter((r) => r.profile_state === 'active');
|
|
392
|
+
if (/pa\.account_visibility = 'public'/.test(sql)) rows = rows.filter((r) => r.account_visibility === 'public');
|
|
385
393
|
if (/NOT pa\.hide_stats/.test(sql)) rows = rows.filter((r) => !r.hide_stats);
|
|
386
394
|
return { rows: rows.map((r) => ({ ...r.row })) };
|
|
387
395
|
}
|
|
@@ -390,9 +398,11 @@ const engine = {
|
|
|
390
398
|
hubAccount.hide_stats = !!params[4];
|
|
391
399
|
return { rows: [{ github_id: String(HUB_GITHUB_ID), github_login: 'jaxri', ...hubAccount }] };
|
|
392
400
|
}
|
|
393
|
-
// getPublicProfileExtras' consent read
|
|
394
|
-
|
|
395
|
-
|
|
401
|
+
// getPublicProfileExtras' consent read — three columns since task 1002967
|
|
402
|
+
// wired the existence axis in (the shared rollupIsVisible port reads them).
|
|
403
|
+
if (/SELECT profile_state, account_visibility, hide_stats/.test(sql)) {
|
|
404
|
+
return { rows: [{ profile_state: hubAccount.profile_state,
|
|
405
|
+
account_visibility: hubAccount.account_visibility, hide_stats: hubAccount.hide_stats }] };
|
|
396
406
|
}
|
|
397
407
|
// getRollupForBuilder's per-project rows (the client_id → origin two-hop).
|
|
398
408
|
if (/platform_identity_sso_clients/.test(sql)) {
|
|
@@ -537,14 +547,15 @@ scoutApp.use(attachFail);
|
|
|
537
547
|
scoutApp.use('/api/gds', buildScoutingRoutes());
|
|
538
548
|
const scout = await listen(scoutApp);
|
|
539
549
|
|
|
540
|
-
await t('R06.3 GET /scouting drops the hidden AND the
|
|
550
|
+
await t('R06.3 GET /scouting drops the hidden, the provisional AND the private builder', async () => {
|
|
541
551
|
engine.calls.length = 0;
|
|
542
552
|
const r = await get(scout.port, '/api/gds/scouting');
|
|
543
553
|
assert.equal(r.status, 200);
|
|
544
554
|
const logins = r.json.builders.map((b) => b.github_login);
|
|
545
|
-
assert.deepEqual(logins, ['r06-visible'],
|
|
555
|
+
assert.deepEqual(logins, ['r06-visible'],
|
|
556
|
+
'only the consented, publicly-visible, non-hidden builder is listed (task 1002967 added the visibility axis)');
|
|
546
557
|
assert.equal(engine.calls.length, 1, 'exactly ONE read — nothing can re-enter via a second, unfiltered query');
|
|
547
|
-
for (const s of [HIDDEN_LOGIN, PROV_LOGIN, '88817', '6613', '5519', '71713']) {
|
|
558
|
+
for (const s of [HIDDEN_LOGIN, PROV_LOGIN, PRIVATE_LOGIN, '88817', '6613', '5519', '71713', '63127', '4409', '2287']) {
|
|
548
559
|
assert.ok(!r.text.includes(s), `the excluded builder leaks nothing — found "${s}"`);
|
|
549
560
|
}
|
|
550
561
|
});
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -172,12 +172,40 @@ test('orb accents are tokens, never literal hexes (§6)', () => {
|
|
|
172
172
|
for (const t of ['--sea', '--terracotta', '--accent', '--sun-lit', '--gold-soft']) {
|
|
173
173
|
assert.match(accents, new RegExp(`var\\(${t}\\)`), `${t} must be one of the orb accents`);
|
|
174
174
|
}
|
|
175
|
-
|
|
176
|
-
assert.doesNotMatch(
|
|
175
|
+
// the sphere's glow left with the sphere (task 1003345): a planet carries its accent only
|
|
176
|
+
assert.doesNotMatch(SCRIPT, /ORB_GLOWS|--glow:/, 'no glow — the row draws the sky\'s planet');
|
|
177
177
|
assert.equal((SCRIPT.match(/var ORB_ACCENTS/g) || []).length, 1,
|
|
178
178
|
'one palette, not two — the map and the manage cards must not drift apart');
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
+
/* ── the row's planet is the sky's object (task 1003345, direction record §14: v1 the ledger row) ── */
|
|
182
|
+
test('the mine row draws the project\'s planet — the catalog\'s template when matched, the slug walk otherwise', () => {
|
|
183
|
+
const orb = (SCRIPT.match(/function orbFor\(inst\) \{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
184
|
+
assert.match(orb, /var pl = planetOf\(inst\);/, 'orbFor asks planetOf');
|
|
185
|
+
assert.match(orb, /class="' \+ cls \+ '"' \+ attrs/, 'the planet is a .worldOrb with the template attributes');
|
|
186
|
+
assert.match(orb, /<span class="disc"><\/span>/, 'the disc cosmos.css draws');
|
|
187
|
+
assert.doesNotMatch(orb, /class="orb/, 'the sphere is gone');
|
|
188
|
+
const planet = (SCRIPT.match(/function planetOf\(inst\) \{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
189
|
+
assert.match(planet, /hostOf\(catalog\[i\]\.origin\) === h/, 'matched by host, the same join the map makes');
|
|
190
|
+
assert.match(planet, /tpl: templateOf\(catalog\[i\]\), acc: accentOf\(catalog\[i\]\)/, 'a catalog match takes the catalog\'s template + accent');
|
|
191
|
+
assert.match(planet, /tpl: slugTemplate\(inst\.slug\), acc: ORB_ACCENTS\[slugAccent\(inst\.slug\)\]/, 'owned-only walks by slug');
|
|
192
|
+
assert.match(SCRIPT, /assignAccents\(\); applyMapFilter\(\); repaintPlanets\(\);/, 'a catalog that lands late repaints the rows');
|
|
193
|
+
assert.doesNotMatch(COSMOS, /\n\.orb\{/, 'cosmos no longer carries the sphere');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('the mine list lays every row on one set of FIXED tracks (task 1003348) and the manage h1 stands outside #mMain', () => {
|
|
197
|
+
assert.match(COSMOS, /#view-mine \.projBody\{display:grid;grid-template-columns:minmax\(0,1\.1fr\) minmax\(0,1\.3fr\) minmax\(0,1fr\) 232px;/, 'no content minimum, so the tracks are the same on every row');
|
|
198
|
+
assert.match(COSMOS, /#view-mine \.proj\{display:grid;grid-template-columns:72px minmax\(0,1fr\);/, 'the planet column is a width');
|
|
199
|
+
assert.match(COSMOS, /#view-mine \.card,#view-manage \.card\{background:none;/, 'no card ground on either view');
|
|
200
|
+
assert.match(COSMOS, /#view-mine \.strip,#view-manage \.strip\{border:0;border-radius:0;padding:0;background:none;/, 'the strip is drawn as the rail');
|
|
201
|
+
assert.doesNotMatch(COSMOS.slice(COSMOS.indexOf('YOUR PROJECTS + THE MANAGE PAGE')), /box-shadow:(?!none)/, 'halos are outlines in the world, never box-shadow');
|
|
202
|
+
const title = HUB.indexOf('<h1 id="mTitle">');
|
|
203
|
+
const main = HUB.indexOf('<section id="mMain"');
|
|
204
|
+
assert.ok(title > -1 && main > -1 && title < main, 'the h1 renders in every manage state — signed out, not found, loading');
|
|
205
|
+
assert.match(HUB, /<h1 id="mTitle"><span id="mName">Manage<\/span><\/h1>/, '#mName is the heading\'s text, so renderManage names the page by filling it');
|
|
206
|
+
for (const id of ['mOrb', 'mFacts', 'mPlanet']) assert.match(HUB, new RegExp(`id="${id}"`), `#${id} is real markup`);
|
|
207
|
+
});
|
|
208
|
+
|
|
181
209
|
test('accents are assigned by canonical position, not by hashing the name', () => {
|
|
182
210
|
// a name hash collided: with five tokens and four projects it put three of
|
|
183
211
|
// them on the same ember and the sky stopped being readable
|
|
@@ -1242,7 +1270,7 @@ test('the delete section is live markup with its body slot (C7)', () => {
|
|
|
1242
1270
|
assert.match(HUB, /id="mDel"/, 'the volatile slot is real markup, not JS-built');
|
|
1243
1271
|
assert.match(HUB, /id="mDelWhat"/, 'the teardown-vs-delete fold ships with the page');
|
|
1244
1272
|
// the fold is a SIBLING of the volatile slot, so no re-render can drop it
|
|
1245
|
-
const card = (HUB.match(/<div class="card" id="mDelete">[\s\S]*?\n <\/div>/) || [''])[0];
|
|
1273
|
+
const card = (HUB.match(/<div class="card mSec" id="mDelete">[\s\S]*?\n <\/div>/) || [''])[0];
|
|
1246
1274
|
assert.ok(card, 'the delete card must be extractable');
|
|
1247
1275
|
const slotAt = card.indexOf('id="mDel"');
|
|
1248
1276
|
const foldAt = card.indexOf('id="mDelWhat"');
|
|
@@ -1322,7 +1350,7 @@ const fnHub = (name) => {
|
|
|
1322
1350
|
test('the visibility section is live markup with its body slot and a sibling fold (C7)', () => {
|
|
1323
1351
|
assert.match(HUB, /id="mVis"/, 'the volatile slot is real markup, not JS-built');
|
|
1324
1352
|
assert.match(HUB, /id="mVisWhat"/, 'the what-changes fold ships with the page');
|
|
1325
|
-
const card = (HUB.match(/<div class="card" id="mVisibility">[\s\S]*?\n <\/div>/) || [''])[0];
|
|
1353
|
+
const card = (HUB.match(/<div class="card mSec" id="mVisibility">[\s\S]*?\n <\/div>/) || [''])[0];
|
|
1326
1354
|
assert.ok(card, 'the visibility card must be extractable');
|
|
1327
1355
|
const slotAt = card.indexOf('id="mVis"');
|
|
1328
1356
|
const foldAt = card.indexOf('id="mVisWhat"');
|
|
@@ -1404,7 +1432,7 @@ test('the settings reads fire on entry + tab return only — never on the in-fli
|
|
|
1404
1432
|
test('the joinability section is live markup with its body slot and a sibling fold', () => {
|
|
1405
1433
|
assert.match(HUB, /id="mJoin"/, 'the volatile slot is real markup, not JS-built');
|
|
1406
1434
|
assert.match(HUB, /id="mJoinWhat"/, 'the what-changes fold ships with the page');
|
|
1407
|
-
const card = (HUB.match(/<div class="card" id="mJoinability">[\s\S]*?\n <\/div>/) || [''])[0];
|
|
1435
|
+
const card = (HUB.match(/<div class="card mSec" id="mJoinability">[\s\S]*?\n <\/div>/) || [''])[0];
|
|
1408
1436
|
assert.ok(card, 'the joinability card must be extractable');
|
|
1409
1437
|
const slotAt = card.indexOf('id="mJoin"');
|
|
1410
1438
|
const foldAt = card.indexOf('id="mJoinWhat"');
|
|
@@ -1627,3 +1655,24 @@ test('the kit proves the field path: the fixture carries a curated template and
|
|
|
1627
1655
|
assert.ok(attrs.some((a) => a.includes('amazonprimea.com') && a.endsWith('data-template bead-wash')), 'the curated template reaches the orb');
|
|
1628
1656
|
assert.ok(attrs.some((a) => a.includes('demo.cloudbongos.com') && a.endsWith('data-material gold')), 'the material reaches the orb as its own attribute');
|
|
1629
1657
|
});
|
|
1658
|
+
|
|
1659
|
+
/* ── the table's columns on FIXED tracks (task 1003348; the owner, 2026-08-29: "the
|
|
1660
|
+
columns in that projects table view dont look aligned") ── */
|
|
1661
|
+
test('one column grammar: the table lays every row on one set of fixed tracks (each header over its cells), and the mine list shares the rule', () => {
|
|
1662
|
+
assert.match(COSMOS, /\.tbl\{table-layout:fixed;min-width:860px;\}/, 'the header row\'s widths are the table\'s — no per-row content sizing');
|
|
1663
|
+
// the task's other half — the Your projects list — landed with task 1003345 and is pinned above
|
|
1664
|
+
// ('the mine list lays every row on one set of FIXED tracks'); it is asserted here too so the two
|
|
1665
|
+
// halves of the grammar can never drift apart: fixed planet + doors, ratio tracks with no content minimum
|
|
1666
|
+
assert.match(COSMOS, /#view-mine \.proj\{display:grid;grid-template-columns:72px minmax\(0,1fr\);/, 'the mine row: the planet track is a width');
|
|
1667
|
+
assert.match(COSMOS, /#view-mine \.projBody\{display:grid;grid-template-columns:minmax\(0,1\.1fr\) minmax\(0,1\.3fr\) minmax\(0,1fr\) 232px;/, 'the mine row: name, facts and rail by ratio with no content minimum, the doors a width');
|
|
1668
|
+
assert.match(COSMOS, /the same column grammar the every-project table takes/, 'the mine block names the table as its twin');
|
|
1669
|
+
for (const [col, w] of [['colType', 88], ['colBuilders', 96], ['colTags', 168], ['colStatus', 224], ['colGo', 112]]) {
|
|
1670
|
+
assert.match(COSMOS, new RegExp(`\\.tbl th\\.${col}\\{width:${w}px;\\}`), `${col} is a width, not a 1% hug`);
|
|
1671
|
+
}
|
|
1672
|
+
assert.doesNotMatch(COSMOS, /\.tbl \.col\w+\{width:1%/, 'no column hugs its widest cell any more');
|
|
1673
|
+
assert.match(COSMOS, /\.tbl th\.colName\{padding-left:22px;\}/, 'the Project header sits over the name, past the dot and its gap');
|
|
1674
|
+
assert.match(COSMOS, /\.tbl th\.right\{text-align:right;\}/, 'a right-aligned column\'s header is right-aligned too');
|
|
1675
|
+
assert.match(HUB, /<th class="right colBuilders" data-sort="builders">/, 'the builders header names its column so its width can be set');
|
|
1676
|
+
assert.match(COSMOS, /@media \(max-width:780px\)\{\.tbl \.colTags,\.tbl \.colType\{display:none;\}\.tbl\{min-width:600px;\}\}/, 'a phone scrolls the table inside its wrap');
|
|
1677
|
+
assert.match(COSMOS, /#sub-table-body\{overflow-x:auto;\}/, 'the wrap scrolls, never the page');
|
|
1678
|
+
});
|
|
@@ -304,9 +304,19 @@ test('the bar is the page ground closed by a hairline, holds one row on a phone,
|
|
|
304
304
|
assert.match(HUB, /#view-mine \.hero,#view-manage \.hero\{max-width:none;\}/, 'titles align with their cards');
|
|
305
305
|
});
|
|
306
306
|
|
|
307
|
-
test('card metadata is span-wrapped so a separator can never strand', () => {
|
|
307
|
+
test('card metadata is span-wrapped so a separator can never strand; the manage page lays its facts as a ledger', () => {
|
|
308
308
|
assert.match(fn('renderCard'), /meta\.map\(function \(m\) \{ return '<span>' \+ m \+ '<\/span>'; \}\)\.join\(''\)/);
|
|
309
|
-
|
|
309
|
+
/* task 1003345: the manage page's facts are label · value rows on hairlines — one
|
|
310
|
+
pair per fact, each fact keeping its id so a missing answer still hides */
|
|
311
|
+
assert.match(SCRIPT, /function ledgerRow\(r\) \{ return '<span class="k">' \+ r\[0\] \+ '<\/span><span class="v">' \+ r\[1\] \+ '<\/span>'; \}/);
|
|
312
|
+
const rm = fn('renderManage');
|
|
313
|
+
assert.match(rm, /\$\('mMeta'\)\.innerHTML = rows\.map\(ledgerRow\)\.join\(''\);/);
|
|
314
|
+
assert.match(rm, /\$\('mAddr'\)\.innerHTML = addr \? ledgerRow\(\['address', addr\]\) : '';/);
|
|
315
|
+
assert.match(rm, /paintManagePlanet\(inst\);/, 'the head and the planet row paint through one fn');
|
|
316
|
+
const pm = fn('paintManagePlanet');
|
|
317
|
+
assert.match(pm, /\$\('mPlanet'\)\.innerHTML = ledgerRow\(\['planet', planetWords\(pl\)/, 'the planet row is the slot the picker (task 1003347) fills');
|
|
318
|
+
assert.match(pm, /\$\('mOrb'\)\.innerHTML = orbFor\(inst\);/, 'the same planet the row shows, at the head');
|
|
319
|
+
assert.match(fn('repaintPlanets'), /if \(manageInst\) paintManagePlanet\(manageInst\);/, 'a catalog that lands after the manage page rendered repaints its head too');
|
|
310
320
|
});
|
|
311
321
|
|
|
312
322
|
test('an open teardown confirm survives the minute re-render', () => {
|
|
@@ -393,7 +403,7 @@ test('a heartbeat re-render keeps an open fold open and a half-typed domain type
|
|
|
393
403
|
|
|
394
404
|
/* ── the owner's five calls of 2026-08-24 (task 1003250) ── */
|
|
395
405
|
test('decision 6 — the manage section is "Take it offline", and the teardown truth ends by saying nothing erases', () => {
|
|
396
|
-
const del = (HUB.match(/<div class="card" id="mDelete">[\s\S]*?<\/details>\s*<\/div>/) || [''])[0];
|
|
406
|
+
const del = (HUB.match(/<div class="card mSec" id="mDelete">[\s\S]*?<\/details>\s*<\/div>/) || [''])[0];
|
|
397
407
|
assert.ok(del, '#mDelete exists');
|
|
398
408
|
assert.match(del, /<h2[^>]*>Take it offline<\/h2>/, 'the heading says what the control does');
|
|
399
409
|
assert.doesNotMatch(HUB, /Delete the project/, 'the word "Delete" no longer promises an erase anywhere on the page');
|
|
@@ -550,7 +560,7 @@ test('the done panel’s progress region is stable, change-only, and says each s
|
|
|
550
560
|
|
|
551
561
|
test('the map table sorts from the keyboard and an owned row’s name is a real link', () => {
|
|
552
562
|
assert.match(HUB, /<th class="colName" data-sort="name" aria-sort="ascending"><button type="button" class="thSort">Project<\/button><\/th>/);
|
|
553
|
-
assert.match(HUB, /<th class="right" data-sort="builders"><button type="button" class="thSort">Builders<\/button><\/th>/);
|
|
563
|
+
assert.match(HUB, /<th class="right colBuilders" data-sort="builders"><button type="button" class="thSort">Builders<\/button><\/th>/);
|
|
554
564
|
assert.match(SCRIPT, /var btn = th\.querySelector\('button'\);\n\s*if \(!btn\) return;\n\s*btn\.addEventListener\('click'/, 'the listener sits on the button, not the th');
|
|
555
565
|
assert.doesNotMatch(SCRIPT, /th\.style\.cursor/, 'no JS-set cursor on the th');
|
|
556
566
|
assert.match(COSMOS, /\.tbl th button\{all:unset;cursor:pointer;\}/);
|
|
@@ -604,9 +614,11 @@ test('completed rail steps are a way back, the repo picker is honest ARIA, the c
|
|
|
604
614
|
});
|
|
605
615
|
|
|
606
616
|
test('control nits: orb cursor, caret under reduced motion, no mode switch, home label, held reduce-motion query, target sizes', () => {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
assert.
|
|
617
|
+
/* task 1003345: the row's object is the planet (.worldOrb, docked); only the live
|
|
618
|
+
project's, a link to its site, is a pointer */
|
|
619
|
+
assert.match(COSMOS, /#view-mine \.worldOrb,#view-manage \.worldOrb\{[^}]*cursor:default/, 'the docked planet is not a pointer');
|
|
620
|
+
assert.match(COSMOS, /\n#view-mine a\.worldOrb,#view-manage a\.worldOrb\{cursor:pointer;\}/, 'only the linked one is');
|
|
621
|
+
assert.doesNotMatch(COSMOS, /\n\.orb\{/, 'the sphere is gone');
|
|
610
622
|
assert.doesNotMatch(HUB, /\.shell \.caret/, 'no caret to hide — the hub mark is static (task 1003300)');
|
|
611
623
|
assert.doesNotMatch(SCRIPT, /modeToggle|paintModeToggle|resolvedMode|cb-color-mode/, 'the mode switch left the hub — the apex is dark only (task 1003336)');
|
|
612
624
|
assert.doesNotMatch(HUB, /id="modeToggle"|class="modeBtn"/);
|
package/tests/ui_design_kit.mjs
CHANGED
|
@@ -33,11 +33,12 @@ test('the platform floors are a pinned list — a floor cannot vanish quietly',
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
test('the shipped states files validate and sit beside the page they name', () => {
|
|
36
|
-
for (const rel of ['public-landing/projects.states.json', 'public-landing/index.states.json', 'modules/hall-ui/public/ranks.states.json']) {
|
|
36
|
+
for (const rel of ['public-landing/projects.states.json', 'public-landing/projects-empty.states.json', 'public-landing/index.states.json', 'modules/hall-ui/public/ranks.states.json']) {
|
|
37
37
|
const json = readJson(rel);
|
|
38
38
|
const v = lib.validateStatesFile(json);
|
|
39
39
|
assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
|
|
40
|
-
assert.equal(path.basename(rel), `${lib.pageName(json.page)}.states.json`, `${rel} is named for its page`);
|
|
40
|
+
if (!json.stub || !json.stub.fixtures) assert.equal(path.basename(rel), `${lib.pageName(json.page)}.states.json`, `${rel} is named for its page`);
|
|
41
|
+
else assert.ok(path.basename(rel).startsWith(`${lib.pageName(json.page)}-`), `${rel}: a fixture-aliased sibling is named <page>-<variant>.states.json`);
|
|
41
42
|
}
|
|
42
43
|
// the apex is dark only (task 1003336): its states files name the one mode, and
|
|
43
44
|
// render.js reads it as the default so a plain `--page /` never renders light
|
|
@@ -66,6 +67,8 @@ test('a states file that breaks the contract is refused with the reason named',
|
|
|
66
67
|
assert.ok(bad.errors.some((e) => /states\.ok\.auth/.test(e)));
|
|
67
68
|
const stub = lib.validateStatesFile({ page: '/x', stub: { prefix: 'builders' }, states: { a: {} } });
|
|
68
69
|
assert.ok(stub.errors.some((e) => /stub\.prefix/.test(e)));
|
|
70
|
+
assert.ok(lib.validateStatesFile({ page: '/x', stub: { fixtures: { a: 'Not Kebab' } }, states: { a: {} } }).errors.some((e) => /stub\.fixtures/.test(e)), 'a fixture alias names a kebab-case file');
|
|
71
|
+
assert.deepEqual(lib.validateStatesFile({ page: '/x', stub: { fixtures: { 'provisioning-instances': 'provisioning-instances-empty' } }, states: { a: {} } }).errors, [], 'the empty-list alias is the shape');
|
|
69
72
|
const modes = lib.validateStatesFile({ page: '/x', modes: ['sepia'], states: { a: {} } });
|
|
70
73
|
assert.ok(modes.errors.some((e) => e.startsWith('modes:')), 'an unknown mode is refused');
|
|
71
74
|
assert.ok(lib.validateStatesFile({ page: '/x', modes: [], states: { a: {} } }).errors.some((e) => e.startsWith('modes:')), 'an empty modes list is refused');
|
|
@@ -146,6 +149,33 @@ test('checkMock: the platform rules catch a page :root, a literal colour, a pinn
|
|
|
146
149
|
assert.ok(f5.some((r) => r.startsWith('every dark rule written twice')), f5.join(' | '));
|
|
147
150
|
});
|
|
148
151
|
|
|
152
|
+
test('serve.js answers the manage flows with the routes\' receipts, signed in only (task 1003345)', async () => {
|
|
153
|
+
const s = await lib.startStub({ surface: 'public-landing', auth: true });
|
|
154
|
+
try {
|
|
155
|
+
const post = (p, method = 'POST', body = '{}') => fetch(s.base + p, { method, headers: { 'content-type': 'application/json' }, body });
|
|
156
|
+
for (const [p, msg] of [['/api/bongos/provisioning/instances/101/teardown', 'Teardown queued.'], ['/api/bongos/provisioning/instances/105/restart', 'Restart queued.'],
|
|
157
|
+
['/api/bongos/provisioning/instances/101/domain', 'Address recorded.'], ['/api/bongos/provisioning/instances', 'Queued. The platform picks it up from here.']]) {
|
|
158
|
+
const r = await post(p); assert.equal(r.status, 200, p); assert.equal((await r.json()).message, msg, p);
|
|
159
|
+
}
|
|
160
|
+
const patched = await post('/api/bongos/provisioning/instances/101/settings', 'PATCH', '{"joinability":"apply"}');
|
|
161
|
+
assert.equal(patched.status, 200); assert.equal((await patched.json()).push.message, 'Saved. The project restarts to apply it.');
|
|
162
|
+
assert.equal((await post('/api/bongos/provisioning/instances/101/settings')).status, 404, 'settings take PATCH, not POST');
|
|
163
|
+
assert.equal((await post('/api/bongos/provisioning/instances/101/nope')).status, 404, 'no invented flow');
|
|
164
|
+
const asked = await (await fetch(s.base + '/api/bongos/provisioning/instances/101')).json();
|
|
165
|
+
assert.deepEqual(asked.open_intent && { action: asked.open_intent.action, state: asked.open_intent.state }, { action: 'teardown', state: 'queued' }, 'a teardown ask is remembered for the run — the page re-reads the project as queued');
|
|
166
|
+
assert.match(String(asked.open_intent.created_at), /^\d{4}-\d{2}-\d{2}T/, 'stamped like the route stamps it');
|
|
167
|
+
const untouched = await (await fetch(s.base + '/api/bongos/provisioning/instances/104')).json();
|
|
168
|
+
assert.equal(untouched.open_intent, null, 'an instance nobody asked about is unchanged');
|
|
169
|
+
const list = await (await fetch(s.base + '/api/bongos/provisioning/instances')).json();
|
|
170
|
+
assert.equal(list.instances.length, 5, 'the fifth row (Mooring) is served');
|
|
171
|
+
assert.equal(list.instances.find((i) => i.id === 105).open_intent.action, 'restart', 'the list carries the asks too');
|
|
172
|
+
} finally { s.stop(); }
|
|
173
|
+
const out = await lib.startStub({ surface: 'public-landing', auth: false });
|
|
174
|
+
try {
|
|
175
|
+
assert.equal((await fetch(out.base + '/api/bongos/provisioning/instances/101/teardown', { method: 'POST' })).status, 401, 'signed out, every ask is refused');
|
|
176
|
+
} finally { out.stop(); }
|
|
177
|
+
});
|
|
178
|
+
|
|
149
179
|
test('checkMock: a taste ban applies only when the instance declared it', () => {
|
|
150
180
|
const dashed = GOOD_MOCK.replace('Plain copy.', 'Plain — copy.');
|
|
151
181
|
const withBan = mock.checkMock(dashed, { bans: new Set(['em-dash']) });
|