@bongos/core 1.19.1015 → 1.19.1017
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 +159 -94
- package/.claude/skills/planning-session/SKILL.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +7 -2
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0331-the-sky-is-the-ideators-home.md +12 -0
- package/docs/adr/0334-the-recruiting-directory-lists-a-hidden-builder-by-name.md +47 -0
- package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +113 -0
- package/docs/adr/0336-a-guilds-record-is-the-sum-of-what-its-members-already-show.md +201 -0
- package/docs/adr/README.md +3 -0
- package/docs/api/openapi.json +60 -5
- package/docs/api-reference.md +8 -2
- package/docs/architecture.md +4 -1
- package/docs/copy-inventory.md +5 -3
- package/docs/copy-registry.json +22 -4
- package/docs/file-map.md +1 -2
- package/docs/module-api-changelog.md +5 -1
- package/migrations/core_253_idea_filed_via.sql +59 -0
- package/modules/agents/pollers/event-router.js +2 -0
- package/modules/discord/discord-bugs.js +2 -0
- package/modules/discord/discord-inbound.js +3 -0
- package/modules/hall-ui/public/people.html +11 -0
- package/modules/hall-ui/public/people.js +77 -11
- package/modules/hall-ui/public/people.states.json +1 -1
- package/modules/hall-ui/public/sky-data.js +22 -8
- package/modules/hall-ui/public/sky-draw.js +3 -2
- package/modules/hall-ui/public/sky-panel.js +1 -1
- package/modules/ideas/born-of.js +55 -0
- package/modules/ideas/inbox.js +23 -8
- package/modules/ideas/projection.js +48 -0
- package/modules/ideas/routes/inbox.js +20 -0
- package/modules/lifecycle/sky.js +25 -4
- package/modules/platform-identity/account-visibility.js +33 -2
- package/modules/platform-identity/activity-snapshots.js +47 -0
- package/modules/platform-identity/builder-search.js +153 -0
- package/modules/platform-identity/migrations/platform_identity_022_activity_snapshots.sql +46 -0
- package/modules/platform-identity/migrations/platform_identity_023_builder_search_trgm.sql +40 -0
- package/modules/platform-identity/module.json +1 -1
- package/modules/platform-identity/platform-identity.js +13 -0
- package/modules/platform-identity/routes/community-search.js +120 -0
- package/modules/platform-identity/routes/scouting.js +14 -7
- package/modules/platform-identity/scouting-name-only.js +47 -0
- package/modules/platform-identity/tests/activity-snapshots.mjs +68 -0
- package/modules/platform-identity/tests/platform-identity.mjs +21 -0
- package/modules/public-landing/public/projects-floor.states.json +9 -2
- package/modules/public-landing/public/projects.html +69 -4
- package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
- package/modules/ui-design/styles/expedition/mock.html +16 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/capture.js +11 -0
- package/scripts/gds/run-unit-tests.js +4 -0
- package/src/module-api.js +1 -1
- package/tests/account_privacy_flags.mjs +4 -1
- package/tests/activity_snapshots_db.mjs +97 -0
- package/tests/card_apply_ui.mjs +297 -0
- package/tests/community_search_route.mjs +385 -0
- package/tests/discord_bugs.mjs +1 -0
- package/tests/discord_inbound.mjs +1 -0
- package/tests/effective_visibility_predicate.mjs +49 -1
- package/tests/goal_map_page.mjs +14 -0
- package/tests/guild_namespace_reserved.mjs +76 -0
- package/tests/hall_sky.mjs +33 -16
- package/tests/idea_filed_via.mjs +185 -0
- package/tests/landing_page.mjs +8 -0
- package/tests/people_name_only.mjs +262 -0
- package/tests/profile_rollup_consent.mjs +5 -1
- package/tests/profile_route.mjs +23 -5
- package/tests/project_admin_console_model.mjs +105 -0
- package/tests/recruiter_sliver_boundary.mjs +3 -0
- package/tests/scouting_owner_access_and_reach.mjs +112 -2
- package/tests/sky_read.mjs +19 -2
- package/tests/ui_design_skills.mjs +9 -0
- package/tests/ui_design_styles.mjs +13 -0
- package/modules/public-landing/public/world/assets/clouds/cloud-island-large.png +0 -0
- package/modules/public-landing/public/world/assets/clouds/cloud-puff.png +0 -0
- package/modules/public-landing/public/world/assets/creatures/creature-whale-cargo.png +0 -0
- package/modules/public-landing/public/world/assets/creatures/creature-whale-glide.png +0 -0
- package/modules/public-landing/public/world/assets/plates/plate-clouds-near.png +0 -0
- package/modules/public-landing/public/world/assets/plates/scene-establishing.png +0 -0
|
@@ -271,6 +271,9 @@ function accountFilter(where, params) {
|
|
|
271
271
|
take(/\b(?:[a-z_]+\.)?terms_accepted_at IS NOT NULL/, () => (r) => r.terms_accepted_at != null);
|
|
272
272
|
take(/\b(?:[a-z_]+\.)?account_visibility = 'public'/, () => (r) => r.account_visibility === 'public');
|
|
273
273
|
take(/\bNOT (?:[a-z_]+\.)?hide_stats\b/, () => (r) => !r.hide_stats);
|
|
274
|
+
// The directory's name-only tier asks the hide flag POSITIVELY (task 1002291).
|
|
275
|
+
// After the NOT form, which consumes its own text first.
|
|
276
|
+
take(/\b(?:[a-z_]+\.)?hide_stats\b/, () => (r) => !!r.hide_stats);
|
|
274
277
|
|
|
275
278
|
take(/\b(?:[a-z_]+\.)?github_id = \$(\d+)/, (m) => {
|
|
276
279
|
consumed.add(m[1]);
|
|
@@ -70,7 +70,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
70
70
|
// importing them here safe.
|
|
71
71
|
import {
|
|
72
72
|
scoutingListableSql,
|
|
73
|
+
scoutingNameOnlySql,
|
|
73
74
|
rollupVisibleSql,
|
|
75
|
+
accountVisibleSql,
|
|
76
|
+
accountActiveSql,
|
|
77
|
+
recruiterReachSql,
|
|
74
78
|
isRecruiterDiscoverable,
|
|
75
79
|
} from '../modules/platform-identity/account-visibility.js';
|
|
76
80
|
import {
|
|
@@ -130,8 +134,21 @@ const ACCOUNTS = [
|
|
|
130
134
|
// presence in the SQL provable here rather than merely asserted.
|
|
131
135
|
{ github_id: '3030', github_login: 'unaccepted', display_name: 'Unaccepted',
|
|
132
136
|
profile_state: 'active', terms_accepted_at: null, account_visibility: 'public', hide_stats: false, recruiter_discoverable: null },
|
|
137
|
+
// Task 1002291's two controls for the NAME-ONLY tier, each differing from `shy`
|
|
138
|
+
// in the one axis that must keep them out of it: `shyout` opted out of
|
|
139
|
+
// recruiting, `shypriv` is private (and opted in, which never buys existence).
|
|
140
|
+
{ github_id: '6161', github_login: 'shyout', display_name: 'Shy Out',
|
|
141
|
+
profile_state: 'active', terms_accepted_at: ACCEPTED_AT, account_visibility: 'public', hide_stats: true, recruiter_discoverable: false },
|
|
142
|
+
{ github_id: '6262', github_login: 'shypriv', display_name: 'Shy Private',
|
|
143
|
+
profile_state: 'active', terms_accepted_at: ACCEPTED_AT, account_visibility: 'private', hide_stats: true, recruiter_discoverable: true },
|
|
133
144
|
];
|
|
134
145
|
|
|
146
|
+
// The record a hidden builder keeps private. Planted on every row the name-only
|
|
147
|
+
// read hands back — a real row grows columns — so a projection that spread the
|
|
148
|
+
// row instead of naming its keys would carry these into the body and fail below.
|
|
149
|
+
const HIDDEN_RECORD = { github_id_echo: '6060-ID-ECHO', total_credits: 773311, total_tasks_shipped: 773322,
|
|
150
|
+
total_karma: 773333, project_count: 773344, last_active: 'shy-last-active-773355', disciplines: ['shy-craft-773366'] };
|
|
151
|
+
|
|
135
152
|
// Project memberships, as the hub holds them. `membership_kind` is what the
|
|
136
153
|
// PROJECT reported at check-in; `signin_verified_at` is the hub's OWN witness
|
|
137
154
|
// that the person really signed in (ADR 0205 D1/D2). Owner needs both.
|
|
@@ -233,6 +250,9 @@ function accountFilter(where, params) {
|
|
|
233
250
|
take(/\b(?:[a-z_]+\.)?terms_accepted_at IS NOT NULL/, () => (r) => r.terms_accepted_at != null);
|
|
234
251
|
take(/\b(?:[a-z_]+\.)?account_visibility = 'public'/, () => (r) => r.account_visibility === 'public');
|
|
235
252
|
take(/\bNOT (?:[a-z_]+\.)?hide_stats\b/, () => (r) => !r.hide_stats);
|
|
253
|
+
// The name-only tier's term (task 1002291): the hide flag asked POSITIVELY.
|
|
254
|
+
// Tried after the NOT form, which consumes its own text first.
|
|
255
|
+
take(/\b(?:[a-z_]+\.)?hide_stats\b/, () => (r) => !!r.hide_stats);
|
|
236
256
|
|
|
237
257
|
// The TERM guards run before the STRUCTURE guard, so an unreadable term is
|
|
238
258
|
// reported as the unreadable term it is rather than as leftover text.
|
|
@@ -318,6 +338,22 @@ const pool = {
|
|
|
318
338
|
return { rows, rowCount: rows.length };
|
|
319
339
|
}
|
|
320
340
|
|
|
341
|
+
// The directory's NAME-ONLY tier (task 1002291): a plain read of the accounts
|
|
342
|
+
// table, judged by its own WHERE. Each row comes back WHOLE, plus the record
|
|
343
|
+
// the builder hid, so the route's projection is what has to drop them.
|
|
344
|
+
if (/^SELECT .+ FROM platform_identity_accounts WHERE /.test(sql)) {
|
|
345
|
+
assert.doesNotMatch(sql, /platform_identity_builder_project_activity/,
|
|
346
|
+
'the name-only read must not consult the rollup — presence there would say who has built');
|
|
347
|
+
const where = sql.slice(sql.indexOf(' WHERE ') + 7).replace(/ ORDER BY .*| LIMIT .*/g, '');
|
|
348
|
+
const limit = Number(params[Number((/ LIMIT \$(\d+)/.exec(sql) || [, 0])[1]) - 1]);
|
|
349
|
+
assert.ok(limit > 0, 'the name-only read is bounded like the ranked one');
|
|
350
|
+
const rows = ACCOUNTS.filter(accountFilter(where, params))
|
|
351
|
+
.sort((a, b) => a.github_login.localeCompare(b.github_login))
|
|
352
|
+
.slice(0, limit)
|
|
353
|
+
.map((a) => ({ ...a, ...HIDDEN_RECORD }));
|
|
354
|
+
return { rows, rowCount: rows.length };
|
|
355
|
+
}
|
|
356
|
+
|
|
321
357
|
throw new Error(`fake pool: unhandled statement — ${sql.slice(0, 120)}`);
|
|
322
358
|
},
|
|
323
359
|
};
|
|
@@ -391,6 +427,7 @@ function getScouting(caller, query = '') {
|
|
|
391
427
|
});
|
|
392
428
|
}
|
|
393
429
|
const logins = (res) => JSON.parse(res.text).builders.map((b) => b.github_login).sort();
|
|
430
|
+
const namedLogins = (res) => (JSON.parse(res.text).name_only || []).map((b) => b.github_login).sort();
|
|
394
431
|
|
|
395
432
|
// The cast. `rank` is the caller's rank on the HUB; `github_id` is the hub
|
|
396
433
|
// account whose project memberships decide the ownership door.
|
|
@@ -521,8 +558,8 @@ await t('and the whole matrix lands exactly where the specs put it', async () =>
|
|
|
521
558
|
assert.deepEqual(shown, ['octo', 'optin'],
|
|
522
559
|
'in: reach-unset-and-public (octo) + explicitly-opted-in (optin). '
|
|
523
560
|
+ 'out: optout (opted out), scout (private, and reach never buys addressability), '
|
|
524
|
-
+ 'shy (hidden), newbie (provisional),
|
|
525
|
-
+ `terms — ADR 0213) — got ${JSON.stringify(shown)}`);
|
|
561
|
+
+ 'shy (hidden — ranked out, listed by name in the second tier below), newbie (provisional), '
|
|
562
|
+
+ `unaccepted (never accepted the platform terms — ADR 0213) — got ${JSON.stringify(shown)}`);
|
|
526
563
|
});
|
|
527
564
|
|
|
528
565
|
await t('opting back IN restores them — the toggle is a toggle, not a tombstone', async () => {
|
|
@@ -613,6 +650,77 @@ await t('/people is the SAME endpoint, so "absent from /people" is true by const
|
|
|
613
650
|
'and listScoutingRollup — the one reader behind both surfaces — joins the composed predicate');
|
|
614
651
|
});
|
|
615
652
|
|
|
653
|
+
// ===========================================================================
|
|
654
|
+
// 2b. HIDE CHANGES THE SHAPE, NOT THE PRESENCE (task 1002291, R15)
|
|
655
|
+
// ===========================================================================
|
|
656
|
+
//
|
|
657
|
+
// Privacy spec D2, the "active · public · hide_stats" row, recruiter column:
|
|
658
|
+
// "listed name-only (if not opted out)". Until R15 the directory read hide as a
|
|
659
|
+
// second opt-out and dropped the builder entirely, while the Settings row told
|
|
660
|
+
// that same builder recruiters would see their name and picture. The directory
|
|
661
|
+
// now has two tiers: the ranked record, and a name-only list.
|
|
662
|
+
|
|
663
|
+
console.log('\nhide — a hidden builder stays findable, by name alone:');
|
|
664
|
+
|
|
665
|
+
await t('a hidden, reachable builder is listed NAME-ONLY — and never in the ranked tier', async () => {
|
|
666
|
+
const res = await getScouting(OPERATOR);
|
|
667
|
+
assert.equal(res.status, 200);
|
|
668
|
+
assert.deepEqual(namedLogins(res), ['shy'],
|
|
669
|
+
'in: shy (public, hidden, reach unset). out: shyout (hidden but opted out), shypriv (hidden and private — '
|
|
670
|
+
+ `reach never buys existence), and every unhidden builder, who is ranked instead — got ${JSON.stringify(namedLogins(res))}`);
|
|
671
|
+
assert.ok(!logins(res).includes('shy'), 'a hidden builder is never RANKED — rank position is derived from the record');
|
|
672
|
+
const read = calls.find((c) => /^SELECT .+ FROM platform_identity_accounts WHERE /.test(c.sql));
|
|
673
|
+
assert.ok(read, 'the name-only tier is a read of its own');
|
|
674
|
+
assert.ok(read.sql.includes(scoutingNameOnlySql()),
|
|
675
|
+
'and it joins the SHARED predicate, not a copy of it');
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
await t('the name-only row is identity and nothing else — the record the builder hid never reaches the body', async () => {
|
|
679
|
+
const res = await getScouting(OWNER);
|
|
680
|
+
const row = JSON.parse(res.text).name_only[0];
|
|
681
|
+
assert.deepEqual(Object.keys(row).sort(), ['avatar_url', 'display_name', 'github_login'],
|
|
682
|
+
'a literal whitelist: what a hidden builder\'s own public page already shows');
|
|
683
|
+
for (const [key, value] of Object.entries(HIDDEN_RECORD)) {
|
|
684
|
+
for (const v of [].concat(value)) {
|
|
685
|
+
assert.ok(!res.text.includes(String(v)), `${key} (${v}) leaked into the directory — the read spread a row it should have named`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
assert.ok(!res.text.includes('Shy Out') && !res.text.includes('Shy Private'),
|
|
689
|
+
'and the two hidden builders the tier refuses leave no trace either');
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
await t('un-hiding moves a builder between the tiers; opting out removes them from both', async () => {
|
|
693
|
+
const shy = ACCOUNTS.find((a) => a.github_login === 'shy');
|
|
694
|
+
try {
|
|
695
|
+
shy.hide_stats = false;
|
|
696
|
+
let res = await getScouting(OPERATOR);
|
|
697
|
+
assert.ok(logins(res).includes('shy') && !namedLogins(res).includes('shy'),
|
|
698
|
+
'shown again, the builder is ranked on their record and gone from the name-only list — never in both');
|
|
699
|
+
shy.hide_stats = true;
|
|
700
|
+
shy.recruiter_discoverable = false;
|
|
701
|
+
res = await getScouting(OPERATOR);
|
|
702
|
+
assert.ok(!logins(res).includes('shy') && !namedLogins(res).includes('shy'),
|
|
703
|
+
'hidden AND opted out: absent from the directory entirely — the opt-out is still the only thing that removes a builder');
|
|
704
|
+
} finally {
|
|
705
|
+
shy.hide_stats = true;
|
|
706
|
+
shy.recruiter_discoverable = null;
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
await t('MUTATION CHECK: drop the reach or the existence half and the wrong hidden builder walks in', async () => {
|
|
711
|
+
const admits = (clause) => ACCOUNTS.filter(accountFilter(clause, [])).map((a) => a.github_login).sort();
|
|
712
|
+
const tier = scoutingNameOnlySql();
|
|
713
|
+
assert.deepEqual(admits(tier), ['shy'], 'the real predicate');
|
|
714
|
+
const withoutReach = tier.replace(` AND ${recruiterReachSql()}`, '');
|
|
715
|
+
assert.notEqual(withoutReach, tier, 'fixture check: the reach term was really removed');
|
|
716
|
+
assert.deepEqual(admits(withoutReach), ['shy', 'shyout'],
|
|
717
|
+
'WITHOUT reach, the builder who opted out is listed — so the opt-out assertion above is load-bearing');
|
|
718
|
+
const withoutExistence = tier.replace(accountVisibleSql(), accountActiveSql());
|
|
719
|
+
assert.notEqual(withoutExistence, tier, 'fixture check: the existence axis was really removed');
|
|
720
|
+
assert.deepEqual(admits(withoutExistence), ['shy', 'shypriv'],
|
|
721
|
+
'WITHOUT the existence axis, a PRIVATE account is listed by name — the D3 sliver\'s subject, which the directory must never carry');
|
|
722
|
+
});
|
|
723
|
+
|
|
616
724
|
// ===========================================================================
|
|
617
725
|
// 3. THE TWO AXES NEVER TOUCH (ADR 0016)
|
|
618
726
|
// ===========================================================================
|
|
@@ -631,6 +739,8 @@ await t('opting out of recruiting grants and denies NO permission', async () =>
|
|
|
631
739
|
+ 'so a reader can never infer a privacy setting from whose list they got');
|
|
632
740
|
assert.deepEqual(shown, logins(await getScouting(OPERATOR)),
|
|
633
741
|
'byte-for-byte the same population an operator sees');
|
|
742
|
+
assert.deepEqual(namedLogins(res), namedLogins(await getScouting(OPERATOR)),
|
|
743
|
+
'and the same name-only list — neither tier varies by viewer');
|
|
634
744
|
});
|
|
635
745
|
|
|
636
746
|
await t('being scoutable buys nothing either — the response is the same descriptive whitelist', async () => {
|
package/tests/sky_read.mjs
CHANGED
|
@@ -47,7 +47,7 @@ await test('the shape: wells carry their criteria and their compact tasks; edges
|
|
|
47
47
|
criteria: [cr(10, 1, 100, 'shipped'), cr(10, 1, 101, 'ready'), cr(11, 2, null)],
|
|
48
48
|
tasks: [t(100, 1, { status: 'shipped', idea_id: '555' }), t(101, 1), t(200, 2)],
|
|
49
49
|
});
|
|
50
|
-
assert.deepEqual(Object.keys(out), ['goals', 'edges', 'counts']);
|
|
50
|
+
assert.deepEqual(Object.keys(out), ['goals', 'edges', 'born', 'counts']);
|
|
51
51
|
assert.deepEqual(out.edges, [{ from: '1', to: '2', count: 3 }]);
|
|
52
52
|
assert.deepEqual(out.counts, { goals: 2, edges: 1, criteria: 2, tasks: 3 });
|
|
53
53
|
const w1 = out.goals[0];
|
|
@@ -108,7 +108,7 @@ await test('a PRIVATE goal the viewer cannot see stays a well with its counts, r
|
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
await test('an empty corpus is a valid, empty sky', () => {
|
|
111
|
-
assert.deepEqual(shapeSky({}), { goals: [], edges: [], counts: { goals: 0, edges: 0, criteria: 0, tasks: 0 } });
|
|
111
|
+
assert.deepEqual(shapeSky({}), { goals: [], edges: [], born: { goals: {}, criteria: {} }, counts: { goals: 0, edges: 0, criteria: 0, tasks: 0 } });
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
// ---- the criterion→task link agrees with the progress rollup ------------------------
|
|
@@ -198,6 +198,23 @@ await test('the payload is the shaped sky, categories included, from one fake re
|
|
|
198
198
|
assert.equal(out.goals[0].tasks[0].title, 'Task 100');
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
+
// ---- born of a thought (task 1004233, sky part 5) ---------------------------------------
|
|
202
|
+
|
|
203
|
+
await test('born rides on the sky from the ideas port, normalised to string ids; an absent or failing port is an EMPTY map — never a guess', async () => {
|
|
204
|
+
const pool = fakePool({ goalRows: [{ goal_id: '1', title: 'A', status: 'open', version_id: 'V1', category_id: null, visible: true, task_total: 1, task_done: 0 }], extraRows: [x(1)] });
|
|
205
|
+
const out = await sky({ viewerId: 1 }, { pool, categoryIndex: async () => CATS, readBornOf: async () => ({ goals: { 1: 55 }, criteria: { 10: '56', 11: null } }) });
|
|
206
|
+
assert.deepEqual(out.born, { goals: { 1: '55' }, criteria: { 10: '56' } });
|
|
207
|
+
assert.equal(pool.calls.length, 5, 'the born read is the ideas module\'s own — not a sixth query here');
|
|
208
|
+
const none = await sky({ viewerId: 1 }, { pool: fakePool(), categoryIndex: async () => CATS, readBornOf: null });
|
|
209
|
+
assert.deepEqual(none.born, { goals: {}, criteria: {} });
|
|
210
|
+
const broken = await sky({ viewerId: 1 }, { pool: fakePool(), categoryIndex: async () => CATS, readBornOf: async () => { throw new Error('port down'); } });
|
|
211
|
+
assert.deepEqual(broken.born, { goals: {}, criteria: {} }, 'a failing port empties the map and the sky still draws');
|
|
212
|
+
assert.deepEqual(skyLib.normalizeBorn({ goals: 'nope' }), { goals: {}, criteria: {} });
|
|
213
|
+
const SKY_SRC = fs.readFileSync(path.join(ROOT, 'modules/lifecycle/sky.js'), 'utf8');
|
|
214
|
+
assert.match(SKY_SRC, /resolveOptional\('ideas\.bornOf'\)/, 'production resolves the port');
|
|
215
|
+
assert.equal(/ideas_goal_ratifications|criterion_proposal_audit/.test(SKY_SRC), false, 'lifecycle reads no ideas table itself');
|
|
216
|
+
});
|
|
217
|
+
|
|
201
218
|
// ---- the cache and the handler -------------------------------------------------------
|
|
202
219
|
|
|
203
220
|
function run(handler, req) {
|
|
@@ -145,6 +145,15 @@ test('impeccable is one in-house skill whose sixteen sub-commands each own exact
|
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
test('impeccable menu names the kit-report temp dir OS-generically, not Windows-only %TMP% (task 1003636)', () => {
|
|
149
|
+
const skill = read(path.join(IMPECCABLE, 'SKILL.md'));
|
|
150
|
+
const signalLine = skill.split('\n').find((l) => l.includes('The last kit report'));
|
|
151
|
+
assert.ok(signalLine, 'the kit-report menu signal line exists');
|
|
152
|
+
assert.doesNotMatch(signalLine, /`%TMP%\/ui-design-kit/, 'the signal does not lead with a bare Windows %TMP% path');
|
|
153
|
+
assert.match(signalLine, /os\.tmpdir\(\)/, 'points at os.tmpdir(), which resolves per-OS');
|
|
154
|
+
assert.match(signalLine, /\$TMPDIR/, 'names the mac/linux env var too, not just %TMP%');
|
|
155
|
+
});
|
|
156
|
+
|
|
148
157
|
test('impeccable carries none of the behaviour that made its origin dangerous: no hooks, no bypass agent, no telemetry, no remote bundle, no live-server token', () => {
|
|
149
158
|
const files = [path.join(IMPECCABLE, 'SKILL.md'), ...fs.readdirSync(path.join(IMPECCABLE, 'reference')).map((f) => path.join(IMPECCABLE, 'reference', f))];
|
|
150
159
|
for (const f of files) {
|
|
@@ -120,6 +120,19 @@ test('every mock passes the platform rules under its own colours and its own DES
|
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
test("the expedition's halftone screen renders: it sits on a fixed body::before layer, never body's own background-image (task 1003633)", () => {
|
|
124
|
+
const html = read(path.join(STYLES_DIR, 'expedition', 'mock.html'));
|
|
125
|
+
const bodyRule = html.match(/\bbody\s*\{[^}]*\}/);
|
|
126
|
+
assert.ok(bodyRule, 'the mock declares a body rule');
|
|
127
|
+
assert.doesNotMatch(bodyRule[0], /background-image/, "body's own rule carries no background-image — the branding pack's backdrop emits body{background:...} after the page's sheet (themeMarkup, serve-internal.js) and that shorthand resets it to none");
|
|
128
|
+
const before = html.match(/body::before\s*\{[^}]*\}/);
|
|
129
|
+
assert.ok(before, 'the halftone moved onto a body::before layer');
|
|
130
|
+
assert.match(before[0], /position:\s*fixed/, 'the layer is fixed, not in flow');
|
|
131
|
+
assert.match(before[0], /pointer-events:\s*none/, 'the layer never intercepts input');
|
|
132
|
+
assert.match(before[0], /background-image:\s*radial-gradient\(var\(--dots\)/, 'the halftone dots ride the layer, keyed to the pack-derived --dots var');
|
|
133
|
+
assert.match(before[0], /background-size:\s*var\(--dot-pitch\)/, 'the dot pitch moved with it');
|
|
134
|
+
});
|
|
135
|
+
|
|
123
136
|
test('the world, hub and hall sheets clear AA under every alternate in both modes: the three token suites run once per pack', () => {
|
|
124
137
|
// relative, forward-slash paths: `node --test <path>` reads its positionals as globs, and a
|
|
125
138
|
// Windows absolute path matches nothing (zero tests, exit 0 — a silent pass)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|