@bongos/core 1.21.4 → 1.21.5
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 +118 -63
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0362-the-governor-docket-is-collected-from-contributed-sources.md +34 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +35 -1
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +389 -518
- package/docs/copy-registry.json +880 -2143
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +17 -25
- package/docs/page-readings.json +1008 -1246
- package/modules/autonomy/governor-docket.js +101 -0
- package/modules/autonomy/routes/autonomy.js +5 -0
- package/modules/discord/board-broadcast.js +3 -1
- package/modules/discord/craft-broadcast.js +107 -0
- package/modules/discord/routes/discord.js +10 -0
- package/modules/discord/ship-broadcast.js +30 -2
- package/modules/economy/reward.js +3 -0
- package/modules/government/board.js +20 -5
- package/modules/government/docket-vocab.js +82 -0
- package/modules/government/docket.js +458 -0
- package/modules/government/routes/government.js +34 -0
- package/modules/hall-ui/public/genesis-home.js +9 -10
- package/modules/ideas/governor-docket.js +93 -0
- package/modules/ideas/routes/blockers.js +5 -0
- package/modules/lifecycle/governor-docket.js +192 -0
- package/modules/lifecycle/routes/gate-approvals.js +78 -33
- package/modules/lifecycle/routes/lifecycle.js +3 -0
- package/modules/onboarding/routes/access-requests.js +68 -27
- package/modules/public-landing/public/assets/cosmos.css +6 -50
- package/modules/public-landing/public/projects.html +1170 -1659
- package/modules/public-landing/public/projects.probes.json +18 -18
- package/modules/public-landing/public/projects.states.json +23 -31
- package/modules/security/routes/reports.js +39 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +14 -0
- package/src/bongos/module-rechecks.js +77 -0
- package/src/bongos/route-rank-check.js +3 -0
- package/src/bongos/routes/core-update.js +41 -0
- package/src/bongos/routes/modules.js +17 -0
- package/src/module-api.js +1 -1
- package/tests/discord_craft_broadcast.mjs +162 -0
- package/tests/genesis_home.mjs +1 -1
- package/tests/government_routes.mjs +3 -1
- package/tests/governor_docket.mjs +552 -0
- package/tests/onboard.mjs +10 -4
- package/tests/platform_boot.mjs +1 -1
- package/tests/projects_hub.mjs +196 -89
- package/tests/projects_hub_app_status.mjs +3 -1
- package/tests/projects_hub_app_step.mjs +72 -48
- package/tests/projects_hub_dns_ready.mjs +10 -2
- package/tests/projects_hub_look.mjs +32 -22
- package/tests/projects_hub_module_picker.mjs +112 -62
- package/tests/projects_hub_pre_uat.mjs +48 -41
- package/tests/public_landing_projects.mjs +12 -6
- package/tests/wizard_demo.mjs +37 -11
- package/tests/wizard_draft_resume.mjs +34 -22
- package/tests/wizard_front_door.mjs +173 -103
- package/tests/wizard_intent_resume.mjs +28 -25
- package/tests/wizard_physics.mjs +94 -76
- package/tests/wizard_physics_more.mjs +409 -277
- package/tests/wizard_six_screens.mjs +126 -0
package/tests/projects_hub.mjs
CHANGED
|
@@ -283,9 +283,14 @@ test('/create is absorbed — the wizard lives here and the route only redirects
|
|
|
283
283
|
const server = fs.readFileSync(path.join(ROOT, 'src', 'platform-server.js'), 'utf8');
|
|
284
284
|
assert.match(server, /res\.redirect\(302, '\/projects\?view=new'/,
|
|
285
285
|
'/create must 302 into the hub (302, not 301: the URL is an OAuth return target in the wild)');
|
|
286
|
-
|
|
286
|
+
// the six blended screens (task 1004504): every one is real markup, and the steps they
|
|
287
|
+
// folded in are gone rather than left behind as dead panels
|
|
288
|
+
for (const id of ['panel-gate', 'panel-fork', 'panel-what', 'panel-checks', 'panel-review', 'panel-look', 'panel-done']) {
|
|
287
289
|
assert.match(HUB, new RegExp(`id="${id}"`), `the wizard's ${id} ships as real markup (C7)`);
|
|
288
290
|
}
|
|
291
|
+
for (const id of ['panel-terms', 'panel-physics', 'panel-name', 'panel-type', 'panel-detail', 'panel-modules', 'panel-home', 'panel-address', 'panel-app']) {
|
|
292
|
+
assert.doesNotMatch(HUB, new RegExp(`id="${id}"`), `the retired ${id} is gone (task 1004504)`);
|
|
293
|
+
}
|
|
289
294
|
});
|
|
290
295
|
|
|
291
296
|
test('the wizard step indices stay coupled — data-next lands one past its own panel (task 1002772)', () => {
|
|
@@ -308,10 +313,10 @@ test('the wizard step indices stay coupled — data-next lands one past its own
|
|
|
308
313
|
for (const m of HUB.matchAll(/data-back="(\d+)"/g)) {
|
|
309
314
|
assert.ok(map[Number(m[1])], `data-back="${m[1]}" must point at a mapped panel`);
|
|
310
315
|
}
|
|
311
|
-
// the rail
|
|
316
|
+
// the numbered rail is gone (task 1004504): no beat is left to drift from the map
|
|
312
317
|
const beats = [...HUB.matchAll(/data-step="(\d+)"/g)].map((x) => Number(x[1]));
|
|
313
|
-
assert.
|
|
314
|
-
|
|
318
|
+
assert.deepEqual(beats, [], 'no rail beat is left on the page');
|
|
319
|
+
assert.doesNotMatch(HUB, /class="steps"/, 'no <ol class="steps"> rail in the wizard');
|
|
315
320
|
});
|
|
316
321
|
|
|
317
322
|
test('the absorbed wizard sheds its two URL landmines — /gate/ and the whole-query strip', () => {
|
|
@@ -657,40 +662,59 @@ test('every per-step hook in show() still points at the panel it belongs to (ren
|
|
|
657
662
|
const STEP = {};
|
|
658
663
|
for (const m of map.matchAll(/(\d+):\s*'([a-z-]+)'/g)) STEP[m[2]] = Number(m[1]);
|
|
659
664
|
const show = SCRIPT.slice(SCRIPT.indexOf('function show(step, moveFocus)'));
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
assert.
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
assert.
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
assert.
|
|
679
|
-
|
|
680
|
-
// the
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
assert.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
665
|
+
const endAt = show.search(/\n\s*\$\('wizBack'\)\.addEventListener\('click'/);
|
|
666
|
+
assert.ok(endAt > 0, 'show() still ends where the one Back button is wired — re-point this slice if it moved');
|
|
667
|
+
const body = show.slice(0, endAt);
|
|
668
|
+
|
|
669
|
+
// task 1004504: the old Home / Address / App steps live as rows on the review, so their
|
|
670
|
+
// entry hooks moved there — one block, run on every entry to the review
|
|
671
|
+
const review = (body.match(new RegExp(`if \\(step === ${STEP['panel-review']}\\) \\{([\\s\\S]*?)\\n \\}`)) || [, ''])[1];
|
|
672
|
+
assert.ok(review, 'the review screen has its own entry block');
|
|
673
|
+
assert.match(review, /syncHome\(\);/, 'the code row repaints its repo controls on the review');
|
|
674
|
+
assert.match(review, /if \(authKnown\) ensureRepoAccess\(\);/, 'the repo probe fires on the review, and never before /me settles');
|
|
675
|
+
assert.match(review, /checkSlugAvailable\(\); renderAddrPanel\(\);/, 'the handle re-check fires on the review (N10)');
|
|
676
|
+
assert.match(review, /syncAppStep\(\); syncRenderSetup\(\);/, 'the app row repaints its choice');
|
|
677
|
+
assert.match(review, /renderModules\(\);/, 'the extras row paints the picker');
|
|
678
|
+
assert.match(review, /paintSetup\(\);/, 'and the rows are painted on the review');
|
|
679
|
+
assert.ok(body.includes(`if (step === ${STEP['panel-fork']}) paintDoors();`),
|
|
680
|
+
'the fork repaints its two doors against the tick');
|
|
681
|
+
assert.ok(body.includes(`if (step === ${STEP['panel-checks']}) { physLoadAccountOnce(); paintChecks(); }`),
|
|
682
|
+
'the quick checks paint on the checks screen');
|
|
683
|
+
assert.ok(body.includes(`if (step === ${STEP['panel-look']}) { renderLook(); renderRecap(); }`),
|
|
684
|
+
'the look and the recap paint on the look screen, the one Create sits on');
|
|
685
|
+
// the room behind the wizard follows the screen, through one map keyed like panels
|
|
686
|
+
assert.match(body, /\$\('wizRoom'\)\.setAttribute\('data-screen', SCREEN\[step\]\);/, 'show() writes the room\'s data-screen');
|
|
687
|
+
const screens = (SCRIPT.match(/var SCREEN = \{([^}]*)\}/) || [, ''])[1];
|
|
688
|
+
assert.deepEqual([...screens.matchAll(/(\d+):/g)].map((m) => Number(m[1])), Object.values(STEP).sort((a, b) => a - b),
|
|
689
|
+
'SCREEN names every panel the map holds, and nothing else');
|
|
690
|
+
// and the done panel claims no hook of its own — renderDone runs from the create
|
|
691
|
+
assert.ok(!body.includes(`if (step === ${STEP['panel-done']})`),
|
|
692
|
+
'the done panel is painted by the create, not by a show() hook');
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test('the About answers are OFFERED by every form and can be walked past (task 1002334; folded into the screens by task 1004504)', () => {
|
|
696
|
+
// the About step is gone (task 1004504): its sentence is the optional field on "What are
|
|
697
|
+
// you making?", and its team shape is a choice on the review's Team size row
|
|
698
|
+
assert.doesNotMatch(HUB, /id="panel-detail"|id="skipDetail"|id="detailSkipNote"/, 'no About panel and no skip button are left');
|
|
699
|
+
const what = (HUB.match(/<section class="panel" id="panel-what"[\s\S]*?<\/section>/) || [''])[0];
|
|
700
|
+
assert.ok(what, '#panel-what ships as real markup');
|
|
701
|
+
assert.match(what, /<input type="text" id="f-desc"/, 'it asks what the owner is building — the publish gate half');
|
|
702
|
+
// optional, and the consequence of filling it in is stated on the field itself
|
|
703
|
+
assert.match(what, /<span>In a sentence <small>\(optional — puts it on the public map\)<\/small><\/span>/,
|
|
704
|
+
'the field says it is optional and names what it is for (ADR 0182 D4), before it asks');
|
|
705
|
+
const team = (HUB.match(/<div class="setRow" data-row="team">[\s\S]*?<span class="acts">/) || [''])[0];
|
|
706
|
+
assert.match(team, /id="teamCards"/, 'and who is building it — what the recommendations read — on the Team size row');
|
|
707
|
+
// nothing in validateStep may gate the sentence: the screen asks only for the name and the kind
|
|
688
708
|
const validate = SCRIPT.slice(SCRIPT.indexOf('function validateStep('));
|
|
689
709
|
const map = SCRIPT.match(/var panels = \{([^}]*)\}/)[1];
|
|
690
|
-
const
|
|
691
|
-
assert.ok(
|
|
692
|
-
|
|
693
|
-
|
|
710
|
+
const whatStep = Number((map.match(/(\d+):\s*'panel-what'/) || [, 0])[1]);
|
|
711
|
+
assert.ok(whatStep > 0, 'the What panel is in the map');
|
|
712
|
+
const fn = validate.slice(0, validate.indexOf('\n function ', 1));
|
|
713
|
+
const clause = (fn.match(new RegExp(`if \\(step === ${whatStep}\\) \\{([\\s\\S]*?)\\n \\}`)) || [, ''])[1];
|
|
714
|
+
assert.ok(clause, 'the What screen has a validation clause (name + kind)');
|
|
715
|
+
assert.match(clause, /state\.name\.trim\(\)/, 'the name is required');
|
|
716
|
+
assert.match(clause, /state\.projType/, 'the kind is required');
|
|
717
|
+
assert.doesNotMatch(clause, /description|teamShape/, 'the optional answers have no validation — Continue past an empty one is the point');
|
|
694
718
|
});
|
|
695
719
|
|
|
696
720
|
test('the About answers travel on the create request, and an empty one is OMITTED not blanked', () => {
|
|
@@ -701,11 +725,12 @@ test('the About answers travel on the create request, and an empty one is OMITTE
|
|
|
701
725
|
assert.match(SCRIPT, /detail: createDetail\(\),/);
|
|
702
726
|
assert.match(SCRIPT, /if \(state\.teamShape\) d\.team_shape = state\.teamShape;\r?\n\s*return Object\.keys\(d\)\.length \? d : undefined;/,
|
|
703
727
|
'and an unanswered team shape is never guessed into the request');
|
|
704
|
-
// the review is the
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
assert.match(
|
|
728
|
+
// task 1004504: the ledger review is gone; the review now confirms only the answers the
|
|
729
|
+
// wizard PREFILLED, and the sentence is never prefilled, so it has no row there — its
|
|
730
|
+
// cost is stated on its own field (pinned above)
|
|
731
|
+
assert.doesNotMatch(SCRIPT, /function renderReview\(|\$\('reviewList'\)/, 'no ledger review is left');
|
|
732
|
+
assert.match(SCRIPT, /var ROWS = \['code', 'address', 'app', 'extras', 'team', 'rewards', 'run'\];/,
|
|
733
|
+
'the review rows are the seven prefilled answers');
|
|
709
734
|
});
|
|
710
735
|
|
|
711
736
|
test('a renumbering of the wizard invalidates old drafts — the key carries the version', () => {
|
|
@@ -720,8 +745,9 @@ test('a renumbering of the wizard invalidates old drafts — the key carries the
|
|
|
720
745
|
// task 1004417 put the front door's two screens (terms, fork) in front: the sixth renumbering
|
|
721
746
|
// task 1004421 put the Look step between App and Review: the seventh
|
|
722
747
|
// task 1004418 put planet physics right after the fork: the eighth
|
|
723
|
-
|
|
724
|
-
assert.equal(
|
|
748
|
+
// task 1004504 blended the thirteen into six screens: the ninth
|
|
749
|
+
assert.equal(count, 7, 'gate + the six blended screens (fork, what, checks, review, look, done) — bump DRAFT_KEY when this changes');
|
|
750
|
+
assert.equal(key, 'cb-create-draft-v9', 'and the key moved with the six screens');
|
|
725
751
|
});
|
|
726
752
|
|
|
727
753
|
test('every row lays the same five parts — the ledger has no ragged row', () => {
|
|
@@ -2135,23 +2161,28 @@ test("its card is the planet section's twin: its own heading, the same channel,
|
|
|
2135
2161
|
});
|
|
2136
2162
|
|
|
2137
2163
|
/* ── the create wizard in the world (task 1003346; direction record §15, the pick: v1 the
|
|
2138
|
-
rail above, the bongo pair kept
|
|
2139
|
-
(#view-new), projects.html's
|
|
2140
|
-
|
|
2164
|
+
rail above, the bongo pair kept; task 1004504 retired the rail and the pair for the night
|
|
2165
|
+
room). Two sheets carry it: cosmos.css draws the FRAME by scope (#view-new), projects.html's
|
|
2166
|
+
own wizard block draws the wizard's structures and the room behind them. ── */
|
|
2167
|
+
test('the wizard takes the world by scope: no card ground, no progress rail, the doors as pills (task 1003346; rail retired by task 1004504)', () => {
|
|
2141
2168
|
const at = COSMOS.indexOf('THE CREATE WIZARD, IN THE WORLD');
|
|
2142
2169
|
assert.ok(at > -1, 'the #view-new block exists in cosmos.css');
|
|
2143
2170
|
const block = COSMOS.slice(at);
|
|
2144
2171
|
assert.match(block, /#view-new \.card\{background:none;border-radius:0;box-shadow:none;/, 'no card ground');
|
|
2145
|
-
//
|
|
2146
|
-
//
|
|
2147
|
-
assert.
|
|
2148
|
-
assert.
|
|
2149
|
-
|
|
2150
|
-
assert.match(block, /#view-new \.panel h1\{font-weight:
|
|
2151
|
-
assert.match(block, /#view-new \.btn\{[^}]*text-transform:
|
|
2172
|
+
// the strip that used to be the rail is gone with the rail (task 1004504), and so is the
|
|
2173
|
+
// base .steps vocabulary it scoped over
|
|
2174
|
+
assert.doesNotMatch(block, /#view-new \.steps/, 'no #view-new .steps rule is left in the wizard block');
|
|
2175
|
+
assert.doesNotMatch(COSMOS, /(^|[\s,}])\.steps[\s{.:\[]/m, 'no .steps rule is left anywhere in cosmos.css');
|
|
2176
|
+
// task 1004504: the design of record's light display weight, in sentence case
|
|
2177
|
+
assert.match(block, /#view-new \.panel h1\{font-weight:200;[^}]*text-transform:none;/, 'the step title in the display voice, light and in sentence case');
|
|
2178
|
+
assert.match(block, /#view-new \.btn\{[^}]*text-transform:none;[^}]*box-shadow:none;/, 'the doors are pills without a shadow, in sentence case');
|
|
2152
2179
|
assert.match(block, /#view-new \.btn\.ghost\{[^}]*border:1px solid var\(--rule\);/, 'back and skip are hairline pills');
|
|
2153
2180
|
assert.match(block, /#view-new #wizSignin\{background:var\(--ink\);color:var\(--bg\);/, 'the GitHub sign-in is the inverted pill');
|
|
2154
|
-
|
|
2181
|
+
// the bongo pair left the wizard with the night room (task 1004504): no rule and no markup
|
|
2182
|
+
assert.doesNotMatch(block, /\.bongos\b/, 'no bongo-pair rule is left in the wizard block');
|
|
2183
|
+
assert.doesNotMatch(STYLE, /\.bongos\b/, 'nor in the page\'s own sheet');
|
|
2184
|
+
const wizMarkup = HUB.slice(HUB.indexOf('<section id="view-new"'), HUB.indexOf('<footer class="foot"'));
|
|
2185
|
+
assert.doesNotMatch(wizMarkup, /class="bongos/, 'and the wizard carries no bongo pair');
|
|
2155
2186
|
assert.match(block, /#view-new \.panel \.lede a\{display:inline-block;padding:3px 0;min-height:24px;/, 'the done clause\'s link clears the 24px floor (the sheet\'s first problem)');
|
|
2156
2187
|
assert.doesNotMatch(block, /box-shadow:(?!none)/, 'halos are outlines in the world, never box-shadow');
|
|
2157
2188
|
assert.doesNotMatch(block, /#[0-9a-f]{3,8}\b/i, 'every colour is a token');
|
|
@@ -2161,13 +2192,16 @@ test('the wizard takes the world by scope: no card ground, the six-step strip as
|
|
|
2161
2192
|
at the sheet's 900 while the wizard's fields, choice bands and review ledger folded at the
|
|
2162
2193
|
page's 840 (cosmos's createSplit collapse), so between the two the rail was already three-up
|
|
2163
2194
|
beside full-width bands. Both sheets now name the same width. ── */
|
|
2164
|
-
test('the wizard folds at one width:
|
|
2195
|
+
test('the wizard folds at one width: no rail fold is left, and no 900px rule is left in the #view-new block (task 1003538; rail retired by task 1004504)', () => {
|
|
2165
2196
|
const at = COSMOS.indexOf('THE CREATE WIZARD, IN THE WORLD');
|
|
2166
2197
|
assert.ok(at > -1, 'the #view-new block exists in cosmos.css');
|
|
2167
2198
|
const block = COSMOS.slice(at);
|
|
2168
|
-
assert.
|
|
2199
|
+
assert.doesNotMatch(block, /#view-new \.steps/, 'the rail and its 840 fold are gone (task 1004504)');
|
|
2169
2200
|
assert.doesNotMatch(block, /@media \(max-width:900px\)/, 'no 900px rule is left in the wizard block');
|
|
2170
|
-
assert.match(STYLE, /@media \(max-width:840px\)\{[\s\S]*?\.
|
|
2201
|
+
assert.match(STYLE, /@media \(max-width:840px\)\{\s*\.row2,\.choices\{grid-template-columns:1fr;\}[\s\S]*?\.typeCards\{grid-template-columns:1fr;\}/, 'the page\'s own wizard block still folds its bands at that same 840');
|
|
2202
|
+
// the ledger review is gone (task 1004504); its rows stack at their own, narrower width
|
|
2203
|
+
assert.doesNotMatch(STYLE, /\.review\{/, 'no .review ledger rule is left');
|
|
2204
|
+
assert.match(STYLE, /@media \(max-width:560px\)\{\s*\.setRow\{grid-template-columns:minmax\(0,1fr\);/, 'the review\'s rows stack under 560');
|
|
2171
2205
|
});
|
|
2172
2206
|
|
|
2173
2207
|
test('the wizard\'s own structures wear the world: hairline fields, cells of one band, the review as a ledger, the human steps as a numbered ledger, the standup strip as the rail (task 1003346)', () => {
|
|
@@ -2180,8 +2214,10 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
|
|
|
2180
2214
|
assert.match(block, /\.tpill\[aria-pressed=true\]::before\{background:var\(--accent\);/, 'the chosen cell carries the dot');
|
|
2181
2215
|
assert.match(block, /\.choices\{display:grid;grid-template-columns:1fr 1fr;gap:0;border-top:1px solid var\(--rule\);/, 'start fresh / adopt are two cells of one band');
|
|
2182
2216
|
assert.match(block, /\.choice svg\{display:none;\}/, 'the dot is the mark, the icon leaves');
|
|
2183
|
-
|
|
2184
|
-
assert.match(block, /\.
|
|
2217
|
+
// task 1004504: the review is one hairline column of rows to confirm, not a ledger of li
|
|
2218
|
+
assert.match(block, /\.setRows\{margin:24px 0 0;border-top:1px solid var\(--rule\);\}/, 'the review\'s rows hang from one hairline');
|
|
2219
|
+
assert.match(block, /\.setRow\{display:grid;grid-template-columns:120px minmax\(0,1fr\) auto;[^}]*border-bottom:1px solid var\(--rule\);\}/, 'each row is label · value · actions on its own hairline');
|
|
2220
|
+
assert.doesNotMatch(block, /\.review( li)?\{/, 'the ledger\'s rules are gone with renderReview');
|
|
2185
2221
|
assert.match(block, /\.humanSteps>li\{\s*display:grid;grid-template-columns:44px 1fr;/, 'the human steps are a numbered ledger');
|
|
2186
2222
|
assert.match(block, /\.humanSteps>li>\*\{grid-column:2;/, 'every child of a step sits in the text column, never under the counter (the sheet\'s bug)');
|
|
2187
2223
|
assert.match(block, /\.humanSteps>li\.sdone::before\{content:"done";/, 'a finished step reads DONE in the ok ink');
|
|
@@ -2190,8 +2226,15 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
|
|
|
2190
2226
|
assert.match(block, /\.doneHead \.tick\{display:none;\}/, 'the tick leaves; the rail says filed');
|
|
2191
2227
|
assert.match(block, /\.addrBig code\{display:block;font-family:var\(--font-display\);/, 'the address stands in the display voice');
|
|
2192
2228
|
assert.doesNotMatch(block, /linear-gradient/, 'no tinted plates');
|
|
2193
|
-
|
|
2194
|
-
|
|
2229
|
+
// the one exception is drawn, not chrome: the night room's moon glows in the window
|
|
2230
|
+
// (task 1004504), and a control's halo is still an outline
|
|
2231
|
+
const shadows = [...block.matchAll(/([^{}]*)\{[^}]*box-shadow:(?!none)/g)].map((m) => m[1].trim());
|
|
2232
|
+
assert.deepEqual(shadows, ['#view-new .rmMoon'], 'no box shadow in the wizard block but the room\'s moon');
|
|
2233
|
+
// no per-panel Back any more (task 1004504): one Back in the wizard's top row, and Create
|
|
2234
|
+
// stands alone on the look screen
|
|
2235
|
+
assert.doesNotMatch(HUB, /data-back="/, 'no per-panel back door is left');
|
|
2236
|
+
assert.match(HUB, /<div class="wizTop">\s*<span class="wizEyebrow" id="wizEyebrow">New project<\/span>\s*<button type="button" class="wizBack" id="wizBack" hidden>← Back<\/button>\s*<\/div>/, 'one Back, beside the eyebrow');
|
|
2237
|
+
assert.match(HUB, /<div class="nav">\s*<span><\/span>\s*<button type="button" class="btn big" id="doCreate">Create my project<\/button>/, 'the look screen\'s one door is Create');
|
|
2195
2238
|
assert.match(HUB, /<h2 class="stepsHead" id="doneStepsHead">Your steps — the parts only you can play<\/h2>/, 'the steps heading is the label face by class, not an inline style (the id is where a skipped invite step hands focus on, task 1002346)');
|
|
2196
2239
|
// what is enqueued is unchanged (ADR 0111): the filing body the control plane reads
|
|
2197
2240
|
assert.match(SCRIPT, /body: JSON\.stringify\(\{\s*slug: state\.slug,/, 'the filing still carries the slug first');
|
|
@@ -2200,32 +2243,94 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
|
|
|
2200
2243
|
assert.match(SCRIPT, /onboard_mode: state\.mode === 'adopt' \? 'adopt' : 'greenfield'/, 'the onboard mode is unchanged');
|
|
2201
2244
|
// the kit walks every step and files against the stub
|
|
2202
2245
|
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8')).states;
|
|
2203
|
-
for (const s of ['
|
|
2246
|
+
for (const s of ['fork-ticked', 'what', 'checks', 'review', 'review-extras', 'look', 'done']) assert.ok(states['in-new-' + s], `the ${s} screen has a kit state`);
|
|
2204
2247
|
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
|
|
2205
|
-
assert.ok(probes.some((p) => /the wizard driver \(
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
const
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2248
|
+
assert.ok(probes.some((p) => /the wizard driver \(tasks? 1003346\b/.test(p.name) && p.steps.some((st) => st[1] === '#doCreate')), 'the wizard driver files a project end to end');
|
|
2249
|
+
// a walk that clicks a panel the page no longer has stops dead at that click: every panel a
|
|
2250
|
+
// state or probe names must be one of the screens (task 1004504 retired seven of them)
|
|
2251
|
+
const live = new Set([...HUB.matchAll(/<section class="panel(?: on)?" id="(panel-[a-z]+)"/g)].map((m) => m[1]));
|
|
2252
|
+
const walks = [...Object.entries(states).map(([n, s]) => [n, s.actions || s.steps || []]), ...probes.map((p) => [p.name, p.steps || []])];
|
|
2253
|
+
for (const [name, steps] of walks) {
|
|
2254
|
+
for (const st of steps) {
|
|
2255
|
+
for (const m of String(st[1] || '').matchAll(/#(panel-[a-z]+)/g)) {
|
|
2256
|
+
assert.ok(live.has(m[1]), `"${name}" walks #${m[1]}, which the page no longer has`);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
});
|
|
2261
|
+
|
|
2262
|
+
/* ── the night room (task 1004504; the design of record docs/design/mocks/combined-onboarding/).
|
|
2263
|
+
The studio's lofi room behind the whole wizard, in place of the plate, the planet and the
|
|
2264
|
+
bongo pair. Decoration only, re-lit by the pack, and still under the kill switch. ── */
|
|
2265
|
+
test('the night room is decoration, lit only by the fifteen, and moves only when motion is welcome (task 1004504)', () => {
|
|
2266
|
+
// real markup, hidden from assistive tech, carrying the screen show() writes
|
|
2267
|
+
const tag = (HUB.match(/<div class="wizRoom" id="wizRoom"[^>]*>/) || [''])[0];
|
|
2268
|
+
assert.ok(tag, '#wizRoom ships as real markup inside the new view');
|
|
2269
|
+
assert.ok(HUB.indexOf('id="wizRoom"') > HUB.indexOf('<section id="view-new"'), 'and it lives inside #view-new');
|
|
2270
|
+
assert.match(tag, /aria-hidden="true"/, 'the room is decoration: aria-hidden');
|
|
2271
|
+
assert.match(tag, /data-screen="[a-z]+"/, 'it carries a data-screen for show() to move');
|
|
2272
|
+
// every colour of its own is a mix of the pack's tokens, never a literal: a re-branded
|
|
2273
|
+
// pack re-lights the room (C1/C2), and the tokens are scoped to the room, never :root
|
|
2274
|
+
const rule = (STYLE.match(/#view-new \.wizRoom\{([^}]*)\}/) || [, ''])[1];
|
|
2275
|
+
assert.ok(rule, 'the room\'s own rule exists in the page sheet');
|
|
2276
|
+
const tokens = [...rule.matchAll(/(--rm-[a-z0-9-]+):([^;]+);/g)];
|
|
2277
|
+
assert.ok(tokens.length >= 10, `the room declares its colours as --rm-* tokens, found ${tokens.length}`);
|
|
2278
|
+
for (const [, name, value] of tokens) {
|
|
2279
|
+
assert.ok(/^color-mix\(/.test(value.trim()) || /^rgba\(255,255,255,/.test(value.trim()),
|
|
2280
|
+
`${name} must be a color-mix( of the fifteen or a white wash, not ${value.trim()}`);
|
|
2281
|
+
assert.doesNotMatch(value.trim(), /^#/, `${name} has no hex literal of its own`);
|
|
2282
|
+
}
|
|
2283
|
+
// the room's other rules read those tokens (or a pack token), never a literal colour
|
|
2284
|
+
const roomRules = [...STYLE.matchAll(/(#view-new \.(?:rm[A-Za-z0-9]+|wizRoom\[[^\]]*\])[^{]*)\{([^}]*)\}/g)];
|
|
2285
|
+
assert.ok(roomRules.length > 10, 'the room\'s parts are styled in the page sheet');
|
|
2286
|
+
for (const [, sel, body] of roomRules) {
|
|
2287
|
+
assert.doesNotMatch(body.replace(/url\("data:[^"]*"\)/g, ''), /(?:background|color|border[a-z-]*|box-shadow|filter):[^;]*#[0-9a-f]{3,8}\b/i, `${sel.trim()} paints no literal colour`);
|
|
2288
|
+
}
|
|
2289
|
+
// the kill switch: the twinkle, the breathe, the float and the seed's growth all sit
|
|
2290
|
+
// inside the no-preference block, and nothing in the room moves outside it
|
|
2291
|
+
const mqAt = STYLE.indexOf('@media (prefers-reduced-motion:no-preference){');
|
|
2292
|
+
assert.ok(mqAt > -1, 'the room\'s motion has a no-preference block');
|
|
2293
|
+
let depth = 0, mqEnd = -1;
|
|
2294
|
+
for (let i = STYLE.indexOf('{', mqAt); i < STYLE.length; i++) {
|
|
2295
|
+
if (STYLE[i] === '{') depth++;
|
|
2296
|
+
else if (STYLE[i] === '}' && --depth === 0) { mqEnd = i; break; }
|
|
2297
|
+
}
|
|
2298
|
+
const motion = STYLE.slice(mqAt, mqEnd);
|
|
2299
|
+
for (const want of [/\.rmStar\{animation:rmTwinkle /, /\.rmGlow\{animation:rmBreathe /, /\.rmSeed img\{animation:rmFloat /, /@keyframes rmTwinkle\{/, /@keyframes rmBreathe\{/, /@keyframes rmFloat\{/]) {
|
|
2300
|
+
assert.match(motion, want, `${want} lives inside the no-preference block`);
|
|
2301
|
+
}
|
|
2302
|
+
const outside = STYLE.slice(0, mqAt) + STYLE.slice(mqEnd);
|
|
2303
|
+
assert.doesNotMatch(outside, /@keyframes rm|animation:rm/, 'no room animation outside the kill switch');
|
|
2304
|
+
for (const [, sel, body] of [...outside.matchAll(/(#view-new \.(?:rm[A-Za-z0-9]+|wizRoom)[^{]*)\{([^}]*)\}/g)]) {
|
|
2305
|
+
assert.doesNotMatch(body, /\b(?:animation|transition)\s*:/, `${sel.trim()} moves only inside the no-preference block`);
|
|
2306
|
+
}
|
|
2307
|
+
});
|
|
2308
|
+
|
|
2309
|
+
// the Modules step became the review's Extras row (task 1004504): the picker is the row's
|
|
2310
|
+
// own controls, moved in whole, so these pin the row as they pinned the panel
|
|
2311
|
+
const EXTRAS_ROW = (HUB.match(/<div class="setRow" data-row="extras">[\s\S]*?<span class="acts">/) || [''])[0];
|
|
2312
|
+
|
|
2313
|
+
test('the extras picker ships as real markup on the review\'s Extras row and is a default you confirm, never a gate (tasks 1002339, 1004504)', () => {
|
|
2314
|
+
assert.doesNotMatch(HUB, /id="panel-modules"|id="skipModules"/, 'no Modules panel and no skip button are left');
|
|
2315
|
+
assert.ok(EXTRAS_ROW, 'the Extras row ships as real markup, not built by script');
|
|
2316
|
+
assert.match(EXTRAS_ROW, /id="modCards"/, 'the band the picker paints its rows into');
|
|
2317
|
+
assert.match(EXTRAS_ROW, /class="typeCards"/, 'and it is the SAME band the type and team questions use — no new grammar');
|
|
2318
|
+
assert.match(EXTRAS_ROW, /id="modCoreNote"/, 'the always-on core is stated on the row');
|
|
2319
|
+
// the suggested set is accepted by its own control — "Looks right" — never inferred
|
|
2320
|
+
assert.match(HUB, /<div class="setRow" data-row="extras">[\s\S]*?<button type="button" class="rowOk" data-row-ok>Looks right<\/button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change<\/button>/,
|
|
2321
|
+
'accepting the suggested set is a control, not an inference');
|
|
2322
|
+
// confirming turns the shown set into an answer given; the row's validity never refuses it
|
|
2323
|
+
const confirm = (SCRIPT.match(/function confirmRow\(key\) \{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
2324
|
+
assert.match(confirm, /if \(key === 'extras' && starterBundles\) state\.modules = pickedModules\(\);/,'confirming the row is the answer — once the catalog has landed, never an empty list in its place');
|
|
2325
|
+
const valid = (SCRIPT.match(/function rowValid\(key\) \{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
2326
|
+
assert.ok(valid, 'rowValid() must exist');
|
|
2327
|
+
assert.doesNotMatch(valid, /'extras'/, 'the extras row has no check that can refuse it — any set, even none, is an answer');
|
|
2328
|
+
// and the kind is what the suggestion follows, so changing it asks again
|
|
2329
|
+
assert.match(SCRIPT, /\/\* the suggested extras follow the kind, so the review asks about them again \*\/\s*unconfirm\(\['extras'\]\);/);
|
|
2225
2330
|
});
|
|
2226
2331
|
|
|
2227
2332
|
test('the picker\'s search ships as real markup, in the wizard\'s own field grammar (task 1002340)', () => {
|
|
2228
|
-
const panel =
|
|
2333
|
+
const panel = EXTRAS_ROW;
|
|
2229
2334
|
assert.match(panel, /<input type="text" id="modQ"[^>]*>/, 'the search is a real field, not one built by script');
|
|
2230
2335
|
assert.match(panel, /id="modQ"[^>]*aria-label="Search the extras by name"/, 'and it is named for a screen reader');
|
|
2231
2336
|
assert.match(panel, /id="modFilterNote"[^>]*aria-live="polite"/, 'the count under it is announced when it changes');
|
|
@@ -2272,9 +2377,11 @@ test('the picker\'s answer rides the create request, and an untouched picker sen
|
|
|
2272
2377
|
test('the review names what the project will actually come with (task 1002339)', () => {
|
|
2273
2378
|
// The picker is optional, so the last screen before Create must show the set
|
|
2274
2379
|
// the owner is about to get — their picks, or the preselect they walked past.
|
|
2275
|
-
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2380
|
+
// task 1004504: the review is the row-by-row confirm, and its Extras row reads the picker
|
|
2381
|
+
const text = SCRIPT.slice(SCRIPT.indexOf('function rowText(key)'));
|
|
2382
|
+
const extras = text.slice(text.indexOf("if (key === 'extras')"), text.indexOf("if (key === 'team')"));
|
|
2383
|
+
assert.ok(text.length && extras, 'rowText() and its extras arm must exist');
|
|
2384
|
+
assert.match(extras, /return \{ v: lead, s: starterBundles \? extrasRow\(\) : '' \};/, 'the review carries an Extras row, its line the picker\'s own');
|
|
2278
2385
|
// What that row SAYS has three cases (picked / preselect / catalog not loaded)
|
|
2279
2386
|
// and they live with the picker's own rules, executed by
|
|
2280
2387
|
// tests/projects_hub_module_picker.mjs. This pins that the review defers to them.
|
|
@@ -2289,7 +2396,7 @@ test('the kit stub answers the picker\'s read, so the panel renders in the desig
|
|
|
2289
2396
|
const stub = fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'serve.js'), 'utf8');
|
|
2290
2397
|
assert.match(stub, /rest === '\/provisioning\/starter-bundles'/, 'the stub serves the picker\'s read');
|
|
2291
2398
|
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
|
|
2292
|
-
assert.ok(probes.some((p) => /the wizard driver \(
|
|
2293
|
-
&& p.steps.some((st) => st[1] === '#
|
|
2294
|
-
'and the end-to-end driver
|
|
2399
|
+
assert.ok(probes.some((p) => /the wizard driver \(tasks? 1003346\b/.test(p.name)
|
|
2400
|
+
&& p.steps.some((st) => st[1] === '#reviewRows .setRow[data-row="extras"] [data-row-ok]')),
|
|
2401
|
+
'and the end-to-end driver confirms the Extras row rather than stopping at it (task 1004504)');
|
|
2295
2402
|
});
|
|
@@ -257,7 +257,9 @@ test('a repaint never lands on top of an open Retry — from this watch or a fre
|
|
|
257
257
|
});
|
|
258
258
|
|
|
259
259
|
test('both surfaces use it: the done panel and the project page', () => {
|
|
260
|
-
|
|
260
|
+
// the done panel is screen 6 of the six blended screens (task 1004504)
|
|
261
|
+
assert.match(HUB, /6: 'panel-done' \};/);
|
|
262
|
+
assert.match(HUB, /if \(box\) watchApp\(instanceId, box, function \(\) \{ return state\.step === 6; \}, 5000\);/);
|
|
261
263
|
assert.match(HUB, /<div id="doneApp" hidden><\/div>/);
|
|
262
264
|
assert.match(HUB, /<div class="card mSec" id="mAppCard" hidden>\n\s*<h2>Where its app runs<\/h2>\n\s*<div id="mApp"><\/div>/);
|
|
263
265
|
assert.match(HUB, /loadAppStatus\(inst\.id \|\| asked, inst\);/);
|