@bongos/core 1.21.91 → 1.21.92

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.
@@ -283,36 +283,28 @@ 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
- // 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']) {
286
+ // the one page (task 1004648): the gate, then the page and its three parts, all real
287
+ // markup, and the screens they folded in are gone rather than left behind as dead panels
288
+ for (const id of ['panel-gate', 'panel-page', 'part-project', 'part-run', 'panel-done']) {
289
289
  assert.match(HUB, new RegExp(`id="${id}"`), `the wizard's ${id} ships as real markup (C7)`);
290
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)`);
291
+ for (const id of ['panel-terms', 'panel-physics', 'panel-name', 'panel-type', 'panel-detail', 'panel-modules', 'panel-home', 'panel-address', 'panel-app',
292
+ 'panel-fork', 'panel-what', 'panel-checks', 'panel-review', 'panel-look']) {
293
+ assert.doesNotMatch(HUB, new RegExp(`id="${id}"`), `the retired ${id} is gone (tasks 1004504, 1004648)`);
293
294
  }
294
295
  });
295
296
 
296
- test('the wizard step indices stay coupled — data-next lands one past its own panel (task 1002772)', () => {
297
- // The whole renumbering turns on validateStep(next - 1): a form's data-next
298
- // must sit inside panels[next - 1], or the validator guards the wrong step.
297
+ test('the wizard has no steps left to couple: one page, no data-next forms, no per-panel Back (task 1004648)', () => {
298
+ // The old renumbering risk turned on validateStep(next - 1) and data-next forms; the one
299
+ // page has neither, so the map is the gate, the page, and the same page created.
299
300
  const pm = SCRIPT.match(/var panels = \{([^}]*)\}/);
300
301
  assert.ok(pm, 'the panels map must exist');
301
302
  const map = {};
302
303
  for (const m of pm[1].matchAll(/(\d+):\s*'([a-z-]+)'/g)) map[Number(m[1])] = m[2];
303
- const nexts = [...HUB.matchAll(/data-next="(\d+)"/g)];
304
- assert.ok(nexts.length >= 4, 'every wizard form declares its next step');
305
- for (const m of nexts) {
306
- const before = HUB.slice(0, m.index);
307
- const sec = before.lastIndexOf('id="panel-');
308
- assert.ok(sec !== -1, 'a data-next form lives inside a panel');
309
- const id = HUB.slice(sec + 4).match(/^(panel-[a-z]+)/)[1];
310
- assert.equal(id, map[Number(m[1]) - 1],
311
- `data-next="${m[1]}" must live in panels[${Number(m[1]) - 1}] — validateStep(next-1) guards that panel`);
312
- }
313
- for (const m of HUB.matchAll(/data-back="(\d+)"/g)) {
314
- assert.ok(map[Number(m[1])], `data-back="${m[1]}" must point at a mapped panel`);
315
- }
304
+ assert.deepEqual(map, { 0: 'panel-gate', 1: 'panel-page', 2: 'panel-page' });
305
+ assert.deepEqual([...HUB.matchAll(/data-next="(\d+)"/g)], [], 'no form walks to a next step');
306
+ assert.deepEqual([...HUB.matchAll(/data-back="(\d+)"/g)], [], 'and nothing walks back');
307
+ assert.doesNotMatch(SCRIPT, /function validateStep\(/, 'no per-step validator is left');
316
308
  // the numbered rail is gone (task 1004504): no beat is left to drift from the map
317
309
  const beats = [...HUB.matchAll(/data-step="(\d+)"/g)].map((x) => Number(x[1]));
318
310
  assert.deepEqual(beats, [], 'no rail beat is left on the page');
@@ -662,59 +654,49 @@ test('every per-step hook in show() still points at the panel it belongs to (ren
662
654
  const STEP = {};
663
655
  for (const m of map.matchAll(/(\d+):\s*'([a-z-]+)'/g)) STEP[m[2]] = Number(m[1]);
664
656
  const show = SCRIPT.slice(SCRIPT.indexOf('function show(step, moveFocus)'));
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');
657
+ const endAt = show.search(/\r?\n {4}\/\* ── the room's sky/);
658
+ assert.ok(endAt > 0, 'show() and paintPage() end where the room\'s sky begins — re-point this slice if it moved');
667
659
  const body = show.slice(0, endAt);
668
660
 
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
661
+ // task 1004648: every old step's entry hook lives in ONE block, paintPage, run on every
662
+ // entry to the page — the rows host the old steps' controls, all on one page
663
+ assert.ok(body.includes('if (step === 1) paintPage();'), 'the page (step 1) paints on entry');
664
+ const page = (body.match(/function paintPage\(\) \{([\s\S]*?)\r?\n {4}\}/) || [, ''])[1];
665
+ assert.ok(page, 'the page has its own entry block');
666
+ assert.match(page, /physLoadAccountOnce\(\);/, 'the quick checks offer the account\'s last answers');
667
+ assert.match(page, /paintChecks\(\);/, 'and paint');
668
+ assert.match(page, /syncHome\(\);/, 'the code row repaints its repo controls');
669
+ assert.match(page, /if \(authKnown\) ensureRepoAccess\(\);/, 'the repo probe fires on the page, and never before /me settles');
670
+ assert.match(page, /checkSlugAvailable\(\); renderAddrPanel\(\);/, 'the handle re-check fires on entry (N10)');
671
+ assert.match(page, /syncAppStep\(\); syncRenderSetup\(\);/, 'the hosting row repaints its choice');
672
+ assert.match(page, /renderModules\(\);/, 'the extras row paints the picker');
673
+ assert.match(page, /renderLook\(\);/, 'the look row builds its picker');
674
+ assert.match(page, /paintSetup\(\);\s*paintCreate\(\);/, 'and the rows, then Create, are painted');
675
+ // the room behind the wizard follows the page, through one map keyed like panels
686
676
  assert.match(body, /\$\('wizRoom'\)\.setAttribute\('data-screen', SCREEN\[step\]\);/, 'show() writes the room\'s data-screen');
687
677
  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');
678
+ assert.deepEqual([...screens.matchAll(/(\d+):/g)].map((m) => Number(m[1])), [0, 1, 2], 'SCREEN names every step the map holds, and nothing else');
679
+ // and part 03 claims no paint of its own here — renderDone runs from the create
680
+ assert.doesNotMatch(body, /renderDone\(\)/, 'part 03 is painted by the create, not by show()');
693
681
  });
694
682
 
695
683
  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
684
+ // the About step is gone (task 1004504): its sentence is the optional field of part 01
685
+ // (task 1004648), and its team shape is a choice on part 02's Team row
698
686
  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');
687
+ const what = (HUB.match(/<div class="part" id="part-project"[\s\S]*?\n {14}<\/div>\n/) || [''])[0];
688
+ assert.ok(what, 'part 01 ships as real markup');
701
689
  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>/,
690
+ // optional, and what it is for is stated on the field itself
691
+ assert.match(what, /<span>In a sentence <small>\(optional, it goes on the public map\)<\/small><\/span>/,
704
692
  'the field says it is optional and names what it is for (ADR 0182 D4), before it asks');
705
693
  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
708
- const validate = SCRIPT.slice(SCRIPT.indexOf('function validateStep('));
709
- const map = SCRIPT.match(/var panels = \{([^}]*)\}/)[1];
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
+ assert.match(team, /id="teamCards"/, 'and who is building it — what the recommendations read — on the Team row');
695
+ // Create waits only for a name and the tick: nothing may gate the sentence or the team
696
+ const why = (SCRIPT.match(/function createWhy\(\) \{([\s\S]*?)\r?\n {4}\}/) || [, ''])[1];
697
+ assert.match(why, /state\.name/, 'the name is required');
698
+ assert.match(why, /state\.termsAck/, 'and the tick');
699
+ assert.doesNotMatch(why, /description|teamShape|projType/, 'the optional answers — the kind included — never hold Create');
718
700
  });
719
701
 
720
702
  test('the About answers travel on the create request, and an empty one is OMITTED not blanked', () => {
@@ -725,12 +707,11 @@ test('the About answers travel on the create request, and an empty one is OMITTE
725
707
  assert.match(SCRIPT, /detail: createDetail\(\),/);
726
708
  assert.match(SCRIPT, /if \(state\.teamShape\) d\.team_shape = state\.teamShape;\r?\n\s*return Object\.keys\(d\)\.length \? d : undefined;/,
727
709
  'and an unanswered team shape is never guessed into the request');
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)
710
+ // task 1004648: part 02 says only the answers the page PREFILLED, and the sentence is never
711
+ // prefilled, so it has no row there — what it is for is stated on its own field (pinned above)
731
712
  assert.doesNotMatch(SCRIPT, /function renderReview\(|\$\('reviewList'\)/, 'no ledger review is left');
732
- assert.match(SCRIPT, /var ROWS = \['code', 'address', 'app', 'extras', 'team', 'rewards', 'run', 'ship'\];/,
733
- 'the review rows are the seven prefilled answers');
713
+ assert.match(SCRIPT, /var ROWS = \['code', 'address', 'team', 'extras', 'rewards', 'app', 'run', 'ship', 'look'\];/,
714
+ 'part 02\'s rows are the prefilled answers, in the mockup\'s order');
734
715
  });
735
716
 
736
717
  test('a renumbering of the wizard invalidates old drafts — the key carries the version', () => {
@@ -746,8 +727,9 @@ test('a renumbering of the wizard invalidates old drafts — the key carries the
746
727
  // task 1004421 put the Look step between App and Review: the seventh
747
728
  // task 1004418 put planet physics right after the fork: the eighth
748
729
  // 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');
730
+ // task 1004648 put the whole flow on one page: the tenth
731
+ assert.equal(count, 3, 'gate, the page, and the page created — bump DRAFT_KEY when this changes');
732
+ assert.equal(key, 'cb-create-draft-v10', 'and the key moved with the one page');
751
733
  });
752
734
 
753
735
  test('every row lays the same five parts — the ledger has no ragged row', () => {
@@ -2201,7 +2183,7 @@ test('the wizard folds at one width: no rail fold is left, and no 900px rule is
2201
2183
  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
2184
  // the ledger review is gone (task 1004504); its rows stack at their own, narrower width
2203
2185
  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');
2186
+ assert.match(STYLE, /@media \(max-width:560px\)\{\s*\.setRow\{grid-template-columns:minmax\(0,1fr\) auto;grid-template-areas:"k acts" "v v";/, 'part 02\'s rows stack under 560, Change beside the label');
2205
2187
  });
2206
2188
 
2207
2189
  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)', () => {
@@ -2214,13 +2196,14 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
2214
2196
  assert.match(block, /\.tpill\[aria-pressed=true\]::before\{background:var\(--accent\);/, 'the chosen cell carries the dot');
2215
2197
  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');
2216
2198
  assert.match(block, /\.choice svg\{display:none;\}/, 'the dot is the mark, the icon leaves');
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');
2199
+ // task 1004648: part 02 is one hairline column of rows to skim, not a ledger of li
2200
+ assert.match(block, /\.setRows\{margin:14px 0 0;border-top:1px solid var\(--rule\);\}/, 'part 02\'s rows hang from one hairline');
2201
+ assert.match(block, /\.setRow\{display:grid;grid-template-columns:108px minmax\(0,1fr\) auto;[^}]*border-bottom:1px solid var\(--rule\);\}/, 'each row is label · value · Change on its own hairline');
2220
2202
  assert.doesNotMatch(block, /\.review( li)?\{/, 'the ledger\'s rules are gone with renderReview');
2221
- assert.match(block, /\.humanSteps>li\{\s*display:grid;grid-template-columns:44px 1fr;/, 'the human steps are a numbered ledger');
2203
+ assert.match(block, /\.humanSteps>li\{\s*display:grid;grid-template-columns:44px 1fr;/, 'the human steps keep the ledger grid');
2204
+ assert.match(block, /#panel-done \.humanSteps>li\{grid-template-columns:24px 1fr;/, 'which part 03 draws as the mockup\'s checklist: a dot, then the step');
2222
2205
  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)');
2223
- assert.match(block, /\.humanSteps>li\.sdone::before\{content:"done";/, 'a finished step reads DONE in the ok ink');
2206
+ assert.match(block, /#panel-done \.humanSteps>li\.sdone::before\{content:"✓";/, 'a verified step wears the tick in the ok ink');
2224
2207
  assert.match(block, /\.prog\{background:none;border:0;border-radius:0;padding:0;/, 'the standup strip is drawn as the rail');
2225
2208
  assert.match(block, /\.prog \.stages li\.now \.sdot::after\{content:none;animation:none;\}/, 'no pulse on the rail');
2226
2209
  assert.match(block, /\.doneHead \.tick\{display:none;\}/, 'the tick leaves; the rail says filed');
@@ -2229,13 +2212,12 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
2229
2212
  // the one exception is drawn, not chrome: the night room's moon glows in the window
2230
2213
  // (task 1004504), and a control's halo is still an outline
2231
2214
  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');
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)');
2215
+ assert.deepEqual(shadows, ['#view-new .rmMoon', '#view-new[data-sky="day"] .rmMoon'], 'no box shadow in the wizard block but the room\'s moon, and its sun by day');
2216
+ // no Back at all any more (task 1004648): one page, and Create with its recap beside it
2217
+ assert.doesNotMatch(HUB, /data-back="|id="wizBack"/, 'no back door is left');
2218
+ assert.match(HUB, /<div class="wizTop">\s*<span class="wizEyebrow" id="wizEyebrow">New project<\/span>\s*<\/div>/, 'the eyebrow alone in the top row');
2219
+ assert.match(HUB, /<div class="createBar">\s*<button type="button" class="btn big" id="doCreate">Create my project<\/button>\s*<span class="wizRecap" id="wizRecap"><\/span>/, 'Create, with the one-line recap beside it');
2220
+ assert.match(HUB, /<h2 id="done-part-h"><span class="n">03<\/span>Connect GitHub <span class="state" id="ghState">opens once your project exists<\/span><\/h2>/, 'part 03 names itself and where it is');
2239
2221
  // what is enqueued is unchanged (ADR 0111): the filing body the control plane reads
2240
2222
  assert.match(SCRIPT, /body: JSON\.stringify\(\{\s*slug: state\.slug,/, 'the filing still carries the slug first');
2241
2223
  assert.match(SCRIPT, /hosting_shape: 'cloud-host',/, 'the shape is unchanged');
@@ -2243,9 +2225,9 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
2243
2225
  assert.match(SCRIPT, /onboard_mode: state\.mode === 'adopt' \? 'adopt' : 'greenfield'/, 'the onboard mode is unchanged');
2244
2226
  // the kit walks every step and files against the stub
2245
2227
  const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8')).states;
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`);
2228
+ for (const s of ['named', 'ready', 'demo', 'checks-filled', 'row-code', 'row-extras', 'look', 'create-blocked', 'done']) assert.ok(states['in-new-' + s], `the ${s} state has a kit state`);
2247
2229
  const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
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');
2230
+ assert.ok(probes.some((p) => /the wizard driver \(task 1004648\b/.test(p.name) && p.steps.some((st) => st[1] === '#doCreate')), 'the wizard driver files a project end to end');
2249
2231
  // a walk that clicks a panel the page no longer has stops dead at that click: every panel a
2250
2232
  // state or probe names must be one of the screens (task 1004504 retired seven of them)
2251
2233
  const live = new Set([...HUB.matchAll(/<section class="panel(?: on)?" id="(panel-[a-z]+)"/g)].map((m) => m[1]));
@@ -2316,20 +2298,17 @@ test('the extras picker ships as real markup on the review\'s Extras row and is
2316
2298
  assert.match(EXTRAS_ROW, /id="modCards"/, 'the band the picker paints its rows into');
2317
2299
  assert.match(EXTRAS_ROW, /class="typeCards"/, 'and it is the SAME band the type and team questions use — no new grammar');
2318
2300
  assert.match(EXTRAS_ROW, /id="modCoreNote"/, 'the always-on core is stated on the row');
2319
- // the suggested set is accepted by a control — the page's "Looks right, continue", or
2320
- // Done on the open row (task 1004550) — never inferred
2321
- assert.match(HUB, /<div class="setRow" data-row="extras">[\s\S]*?<button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit<\/button>/,
2301
+ // the suggestion is the server's own until the owner toggles (task 1004648): no row is
2302
+ // confirmed, so an untouched picker files no answer and the server applies the same bundle
2303
+ assert.match(HUB, /<div class="setRow" data-row="extras">[\s\S]*?<button type="button" class="rowChg" data-row-chg aria-expanded="false">Change<\/button>/,
2322
2304
  'the row can be opened and changed');
2323
- assert.match(HUB, /<button type="submit" class="btn" id="reviewGo">Looks right, continue<\/button>/,
2324
- 'accepting the suggested set is a control, not an inference');
2325
- // confirming turns the shown set into an answer given; the row's validity never refuses it
2326
- const confirm = (SCRIPT.match(/function confirmRow\(key\) \{([\s\S]*?)\n \}/) || [, ''])[1];
2327
- 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');
2305
+ assert.doesNotMatch(HUB, /id="reviewGo"/, 'and there is no confirm control to accept it with');
2306
+ assert.doesNotMatch(SCRIPT, /state\.modules = pickedModules\(\);/, 'nothing turns the suggestion into an answer the owner did not give');
2328
2307
  const valid = (SCRIPT.match(/function rowValid\(key\) \{([\s\S]*?)\n \}/) || [, ''])[1];
2329
2308
  assert.ok(valid, 'rowValid() must exist');
2330
2309
  assert.doesNotMatch(valid, /'extras'/, 'the extras row has no check that can refuse it — any set, even none, is an answer');
2331
- // and the kind is what the suggestion follows, so changing it asks again
2332
- assert.match(SCRIPT, /\/\* the suggested extras follow the kind, so the review asks about them again \*\/\s*unconfirm\(\['extras'\]\);/);
2310
+ // and the kind is what the suggestion follows, so changing it repaints the row
2311
+ assert.match(SCRIPT, /\/\* the suggested extras follow the kind, so their row repaints with it \*\/\s*renderModules\(\);\s*paintSetup\(\);/);
2333
2312
  });
2334
2313
 
2335
2314
  test('the picker\'s search ships as real markup, in the wizard\'s own field grammar (task 1002340)', () => {
@@ -2399,7 +2378,7 @@ test('the kit stub answers the picker\'s read, so the panel renders in the desig
2399
2378
  const stub = fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'serve.js'), 'utf8');
2400
2379
  assert.match(stub, /rest === '\/provisioning\/starter-bundles'/, 'the stub serves the picker\'s read');
2401
2380
  const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
2402
- assert.ok(probes.some((p) => /the wizard driver \(tasks? 1003346\b/.test(p.name)
2403
- && p.steps.some((st) => st[1] === '#reviewGo')),
2404
- 'and the end-to-end driver confirms the review rather than stopping at it (tasks 1004504, 1004550)');
2381
+ assert.ok(probes.some((p) => /the wizard driver \(task 1004648\b/.test(p.name)
2382
+ && p.steps.some((st) => st[1] === '#doCreate')),
2383
+ 'and the end-to-end driver walks the page through to Create rather than stopping at it');
2405
2384
  });
@@ -257,9 +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
- // 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\);/);
260
+ // part 03 goes live on the created page, step 2 of the one-page flow (task 1004648)
261
+ assert.match(HUB, /var SCREEN = \{ 0: 'signin', 1: 'page', 2: 'done' \};/);
262
+ assert.match(HUB, /if \(box\) watchApp\(instanceId, box, function \(\) \{ return state\.step === 2; \}, 5000\);/);
263
263
  assert.match(HUB, /<div id="doneApp" hidden><\/div>/);
264
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>/);
265
265
  assert.match(HUB, /loadAppStatus\(inst\.id \|\| asked, inst\);/);
@@ -157,22 +157,22 @@ const ANSWERS = { [LOOKUP]: TWO, [LOOKUP + '#' + TEA_A]: SERVICES };
157
157
 
158
158
  test('the row is real markup: Render and Decide later, Decide later the default', () => {
159
159
  assert.ok(PANEL, 'the review carries a data-row="app" row');
160
- assert.match(PANEL, /<span class="k">App hosting<\/span>/, 'the row is named for the app alone');
160
+ assert.match(PANEL, /<span class="k">Hosting<\/span>/, 'the row is the mockup\'s Hosting (task 1004648)');
161
161
  assert.match(PANEL, /<input type="radio" name="appPlatform" value="render" aria-describedby="appRenderWhat">/, 'Render can be picked (task 1004352)');
162
162
  assert.doesNotMatch(PANEL, /Coming soon/, 'and is no longer marked soon');
163
163
  assert.match(PANEL, /<input type="radio" name="appPlatform" value="later" checked>/, 'Decide later is the default');
164
164
  assert.ok(PANEL.indexOf('value="later"') < PANEL.indexOf('value="render"'), 'Decide later comes first');
165
- assert.match(HUB.slice(HUB.indexOf('<div class="setRow" data-row="app">')), /^[\s\S]*?<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit<\/button>/, 'Edit opens the row, and reads Done to confirm it (task 1004550)');
165
+ assert.match(HUB.slice(HUB.indexOf('<div class="setRow" data-row="app">')), /^[\s\S]*?<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change<\/button>/, 'Change opens the row in place, and reads Done while open (task 1004648)');
166
166
  assert.doesNotMatch(HUB, /id="panel-app"/, 'the old App panel is gone');
167
167
  });
168
168
 
169
- test("the row sits in the review, between Address and Extras, and its checks are the old step's (task 1004504)", () => {
170
- assert.match(HUB, /var panels = \{ 0: 'panel-gate', 1: 'panel-fork', 2: 'panel-what', 3: 'panel-checks', 4: 'panel-review', 5: 'panel-look', 6: 'panel-done' \};/);
171
- assert.match(HUB, /var ROWS = \['code', 'address', 'app', 'extras', 'team', 'rewards', 'run', 'ship'\];/);
169
+ test("the row sits in part 02, between Rewards and We run, and its checks are the old step's (task 1004648)", () => {
170
+ assert.match(HUB, /var panels = \{ 0: 'panel-gate', 1: 'panel-page', 2: 'panel-page' \};/);
171
+ assert.match(HUB, /var ROWS = \['code', 'address', 'team', 'extras', 'rewards', 'app', 'run', 'ship', 'look'\];/);
172
172
  assert.doesNotMatch(HUB, /<li[^>]*data-step="\d+"/, 'no rail beat names the step any more');
173
- assert.match(HUB, /if \(step === 4\) \{[^}]*syncAppStep\(\); syncRenderSetup\(\);\r?\n\s*paintSetup\(\);\r?\n\s*\}/, 'entering the review repaints the row, Render setup included');
173
+ assert.match(HUB, /function paintPage\(\) \{[^}]*syncAppStep\(\); syncRenderSetup\(\); syncShipStep\(\);/, 'entering the page repaints the row, Render setup included');
174
174
  assert.match(HUB, /if \(key === 'app'\) \{ syncAppStep\(\); syncRenderSetup\(\); \}/, 'opening the row repaints it too');
175
- assert.match(HUB, /if \(key === 'app'\) return rowFixed\('app'\) \|\| validateRender\(\);/, 'confirming the row runs the Render check');
175
+ assert.match(HUB, /if \(key === 'app'\) return rowFixed\('app'\) \|\| validateRender\(\);/, 'filing the row runs the Render check');
176
176
  });
177
177
 
178
178
  test('a fresh draft reads "decide later", and the Render setup stays hidden', () => {
@@ -517,5 +517,5 @@ test('the row names exactly what was chosen, prices included', () => {
517
517
  });
518
518
 
519
519
  test('Create hands the app to Render straight after the project is filed', () => {
520
- assert.match(HUB, /show\(6, true\);\r?\n\s*sendRenderSetup\(data\.instance && data\.instance\.id\);/);
520
+ assert.match(HUB, /show\(2, true\);\r?\n\s*sendRenderSetup\(data\.instance && data\.instance\.id\);/);
521
521
  });
@@ -3,13 +3,13 @@
3
3
  // The create wizard's done panel in two moments (task 1004578, from idea 1001205):
4
4
  // WAIT — one status line, a thin bar and the cloud hop, a little game with one
5
5
  // cloud per PUBLIC project on the platform — then ARRIVE — the steps only the
6
- // founder can play, one at a time. The owner set the game's rules on a played
6
+ // founder can play, all on show as one checklist (part 03, task 1004648). The owner set the game's rules on a played
7
7
  // prototype, and the nausea ones are the ones a refactor would quietly lose: the
8
8
  // clouds stand still, the sky stands still, the view does not follow a jump.
9
9
  //
10
10
  // EXECUTED by the vm-slice idiom: momentFor/setMoment ride the same region the DNS
11
11
  // test runs (renderProgress); the physics (hopStep), the score line (hopScore),
12
- // the status sync (hopSync) and the pager (stepsPaint) are sliced function by
12
+ // the status sync (hopSync) and the checklist's state line (stepsPaint) are sliced function by
13
13
  // function and run under narrow stubs. Every slice is asserted to exist, so a
14
14
  // rename fails loudly instead of testing nothing. vm-born values are compared as
15
15
  // primitives or through JSON.
@@ -238,66 +238,42 @@ test('the status line and the bar read what the strip says', () => {
238
238
  assert.match(fn('setProgress'), /hopSync\(el\)/, 'every strip write syncs the line');
239
239
  });
240
240
 
241
- function bootSteps(items, signin) {
242
- const list = { children: items, querySelector: (q) => items.find((li) => q.includes(li.key) || (q === 'li.cur' && li.classList.contains('cur'))) || null };
243
- const panel = el(signin ? ['hopSignin'] : []);
244
- const nodes = { 'panel-done': panel, humanSteps: list, hopWhile: el(), stepCount: el(), stepBack: el(), stepNext: el() };
245
- const sandbox = { $: (id) => nodes[id] || null, Math, Array };
241
+ // ── part 03's checklist (task 1004648): every step on show, the state line from it ──
242
+ function bootSteps(items, { step = 2, inFlight = false } = {}) {
243
+ const list = { children: items };
244
+ const ghState = { textContent: '' };
245
+ const sandbox = { $: (id) => ({ humanSteps: list, ghState })[id] || null, Array, state: { step }, manifestInFlight: () => inFlight };
246
246
  vm.createContext(sandbox);
247
- vm.runInContext('var stepIdx = -1, stepAuto = true;' + fn('stepsPaint') + fn('stepGo'), sandbox);
248
- return { sandbox, nodes };
249
- }
250
- function li(key, cls = []) { const e = el(cls); e.key = key; e.focus = () => {}; return e; }
251
-
252
- test('the arrival shows one step at a time, opening on the first not yet done', () => {
253
- const items = [li('github-repo', ['sdone']), li('oauth-app'), li('device-flow', ['slocked']), li('enter-hall', ['slocked'])];
254
- const { sandbox, nodes } = bootSteps(items);
255
- sandbox.stepsPaint(true);
256
- assert.deepEqual(items.map((i) => i.classList.contains('cur')), [false, true, false, false]);
257
- assert.equal(nodes.stepCount.textContent, 'Step 2 of 4');
258
- assert.equal(items[1].attrs['data-n'], '02', 'the step on show carries its own number — a hidden step never counts');
259
- assert.equal(nodes.stepBack.hidden, false);
260
- assert.equal(nodes.stepNext.hidden, false);
261
- // the checklist ticks itself: the step on show finishing moves the founder on
262
- items[1].classList.add('sdone');
263
- sandbox.stepsPaint();
264
- assert.equal(nodes.stepCount.textContent, 'Step 3 of 4');
265
- // once they page for themselves, their choice holds
266
- sandbox.stepGo(-1);
267
- items[2].classList.add('sdone');
247
+ vm.runInContext(fn('stepsPaint'), sandbox);
268
248
  sandbox.stepsPaint();
269
- assert.equal(nodes.stepCount.textContent, 'Step 2 of 4');
270
- sandbox.stepGo(1); sandbox.stepGo(1);
271
- assert.equal(nodes.stepNext.hidden, true, 'no Continue past the last step');
272
- });
249
+ return ghState.textContent;
250
+ }
251
+ function li(key, cls = []) { const e = el(cls); e.key = key; return e; }
273
252
 
274
- test('while waiting, the sign-in step is offered only when it can be done', () => {
275
- const open = bootSteps([li('github-repo'), li('oauth-app')]);
276
- open.sandbox.stepsPaint();
277
- assert.equal(open.nodes.hopWhile.hidden, false, 'a domain and a public platform: offer it');
278
- const locked = bootSteps([li('github-repo'), li('oauth-app', ['slocked'])]);
279
- locked.sandbox.stepsPaint();
280
- assert.equal(locked.nodes.hopWhile.hidden, true, 'locked: nothing to offer');
281
- const done = bootSteps([li('github-repo'), li('oauth-app', ['sdone'])]);
282
- done.sandbox.stepsPaint();
283
- assert.equal(done.nodes.hopWhile.hidden, true, 'already wired: nothing to offer');
284
- const opened = bootSteps([li('github-repo'), li('oauth-app', ['sdone'])], true);
285
- opened.sandbox.stepsPaint();
286
- assert.equal(opened.nodes.hopWhile.hidden, false, 'but an open one keeps its door so it can be closed');
253
+ test('part 03\'s state line reads the checklist: ready, how many done, waiting on GitHub, connected', () => {
254
+ assert.equal(bootSteps([li('github-repo'), li('oauth-app')], { step: 1 }), 'opens once your project exists', 'before Create');
255
+ assert.equal(bootSteps([li('github-repo'), li('oauth-app')]), 'ready');
256
+ assert.equal(bootSteps([li('github-repo', ['sdone']), li('oauth-app'), li('enter-hall', ['slocked'])]), '1 of 3 done');
257
+ assert.equal(bootSteps([li('github-repo', ['sdone']), li('oauth-app')], { inFlight: true }), 'waiting on GitHub…', 'a GitHub leg in flight says so');
258
+ assert.equal(bootSteps([li('github-repo', ['sdone']), li('oauth-app', ['sdone'])]), 'connected ✓');
259
+ assert.doesNotMatch(fn('stepsPaint'), /innerHTML|\.cur|data-n/, 'no paging, and no markup from strings');
287
260
  });
288
261
 
289
262
  // ── the markup and the sheet ────────────────────────────────────────────────────
290
- test('the wait stands the strip, the locked checklist and the later lines down; the arrival pages', () => {
291
- for (const id of ['hop', 'hopStatus', 'hopBarFill', 'hopCanvas', 'hopLeft', 'hopJump', 'hopRight', 'hopScore', 'hopWhile', 'hopSignin', 'stepPager', 'stepCount', 'stepBack', 'stepNext']) {
292
- assert.ok(HUB.includes(`id="${id}"`), `#${id} is baked into the panel`);
263
+ test('the wait stands the strip and the later lines down, the checklist stays on show, and nothing is paged', () => {
264
+ for (const id of ['hop', 'hopStatus', 'hopBarFill', 'hopCanvas', 'hopLeft', 'hopJump', 'hopRight', 'hopScore', 'humanSteps', 'ghState']) {
265
+ assert.ok(HUB.includes(`id="${id}"`), `#${id} is baked into part 03`);
266
+ }
267
+ for (const id of ['hopWhile', 'hopSignin', 'stepPager', 'stepCount', 'stepBack', 'stepNext']) {
268
+ assert.ok(!HUB.includes(`id="${id}"`), `#${id} is gone: the checklist is on show the whole time (task 1004648)`);
293
269
  }
294
270
  const panel = HUB.slice(HUB.indexOf('id="panel-done"'), HUB.indexOf('</section>', HUB.indexOf('id="panel-done"')));
295
- assert.ok(panel.indexOf('id="liveProgress"') < panel.indexOf('id="hop"') && panel.indexOf('id="hop"') < panel.indexOf('id="wizInvite"'),
296
- 'the hop sits where the strip was, above the invite');
271
+ assert.ok(panel.indexOf('id="humanSteps"') < panel.indexOf('id="hop"') && panel.indexOf('id="hop"') < panel.indexOf('id="wizInvite"'),
272
+ 'the checklist first, then the hop, then the invite');
297
273
  assert.match(panel, /id="hopStatus" role="status" aria-live="polite"/, 'the line is the live region while the strip is hidden');
298
274
  assert.match(STYLE, /#panel-done\[data-moment="wait"\] #liveProgress,/);
299
- assert.match(STYLE, /#panel-done\[data-moment="wait"\]:not\(\.hopSignin\) #humanSteps\{display:none;\}/);
300
- assert.match(STYLE, /#panel-done\[data-moment="arrive"\] \.humanSteps>li:not\(\.cur\)\{display:none;\}/);
275
+ assert.doesNotMatch(STYLE, /#panel-done\[data-moment="wait"\][^{]*#humanSteps\{display:none;\}/, 'the wait no longer hides the checklist');
276
+ assert.doesNotMatch(STYLE, /humanSteps>li:not\(\.cur\)/, 'and the arrival does not page it');
301
277
  assert.match(STYLE, /#panel-done\[data-moment="arrive"\] #liveProgress \.stages\{display:none;\}/, 'the arrival keeps the note with the address, not the dots');
302
- assert.match(STYLE, /@media \(prefers-reduced-motion:no-preference\)\{\n {2}#panel-done\[data-moment="arrive"\] \.humanSteps>li\.cur,/, 'the arrival\'s fade-in is motion-gated');
278
+ assert.match(STYLE, /#panel-done \.humanSteps>li\.sdone::before\{content:"✓";/, 'a verified step wears the tick');
303
279
  });
@@ -5,8 +5,8 @@
5
5
  // • the previews paint the REAL looks: every colour lookKit carries equals its
6
6
  // pack.json, and the night shares equal src/night-tint.js — a preview that drifted
7
7
  // from the hall would promise a look the hall does not wear;
8
- // • the screen sits after the review and holds Create (review 4, look 5, done 6,
9
- // draft v9 since task 1004504; no rail), and every kit walk files only from it;
8
+ // • the look is part 02's Look row on the one page (task 1004648), above Create,
9
+ // draft v10, no rail, and the kit pictures the row and "make your own";
10
10
  // • EXECUTED by the vm-slice idiom: the picker offers the four looks and "make your
11
11
  // own", shows the accent only for your own, shows the ADJUSTED colour in the
12
12
  // preview and says so, paints a black night when the tint is off, refuses an SVG
@@ -105,41 +105,31 @@ test('every colour the previews paint is its pack\'s, and the night shares are t
105
105
  assert.equal(kit.NIGHT.card, '#101012');
106
106
  });
107
107
 
108
- test('the screen sits after the review and holds Create: review 4, look 5, done 6, draft v9, no rail (task 1004504)', () => {
109
- // task 1004504 blended the wizard into six screens; the look moved AFTER the review and
110
- // became the screen Create stands on, so the look is the last thing chosen
111
- assert.match(HUB, /4: 'panel-review', 5: 'panel-look', 6: 'panel-done' \};/);
112
- assert.match(HUB, /var DRAFT_KEY = 'cb-create-draft-v9';/);
108
+ test('the look is part 02\'s Look row: Change opens the picker in place, above Create on the one page (task 1004648)', () => {
109
+ // task 1004648 put the whole flow on one page; the look is the summary's last row, so it
110
+ // is chosen beside everything else, and Create stands under it
111
+ assert.match(HUB, /var panels = \{ 0: 'panel-gate', 1: 'panel-page', 2: 'panel-page' \};/);
112
+ assert.match(HUB, /var DRAFT_KEY = 'cb-create-draft-v10';/);
113
113
  assert.doesNotMatch(HUB, /<li[^>]*data-step="\d+"/, 'no rail beat is left on the page');
114
- assert.doesNotMatch(HUB, /id="panel-app"/, 'the App step is the review\'s App hosting row now');
115
- const panel = (HUB.match(/<section class="panel" id="panel-look"[\s\S]*?<\/section>/) || [''])[0];
116
- assert.match(panel, /<h1 id="look-h">Give it a look\.<\/h1>/);
117
- assert.doesNotMatch(panel, /<form|data-next=|data-back=/, 'no form and no per-panel Back: the one door is Create, the one Back is the wizard\'s');
118
- assert.match(panel, /<div class="lookPick" id="wizLook"><\/div>/);
119
- assert.match(panel, /<p class="wizRecap" id="wizRecap"><\/p>/, 'the one-line recap of what Create makes');
120
- assert.match(panel, /<button type="button" class="btn big" id="doCreate">Create my project<\/button>/, 'Create stands on this screen');
121
- assert.ok(HUB.indexOf('id="panel-review"') < HUB.indexOf('id="panel-look"') && HUB.indexOf('id="panel-look"') < HUB.indexOf('id="panel-done"'), 'in document order too');
122
- assert.match(HUB, /if \(step === 5\) \{ renderLook\(\); renderRecap\(\); \}/);
114
+ assert.doesNotMatch(HUB, /id="panel-look"|id="look-h"/, 'no Look screen any more');
115
+ const row = (HUB.match(/<div class="setRow" data-row="look">[\s\S]*?\n {18}<\/div>\n/) || [''])[0];
116
+ assert.match(row, /<span class="k">Look<\/span>/);
117
+ assert.match(row, /<div class="opts" hidden>\s*<div class="lookPick" id="wizLook"><\/div>/, 'Change opens the shared picker in place');
118
+ assert.match(row, /<span class="swDot" id="lookDot" aria-hidden="true"><\/span>/, 'the row wears the look\'s own accent');
119
+ assert.ok(HUB.indexOf('id="wizLook"') < HUB.indexOf('id="doCreate"'), 'the picker sits above Create');
120
+ assert.match(HUB, /if \(key === 'look'\) renderLook\(\);/, 'opening the row builds or repaints the picker');
121
+ assert.match(HUB, /lookKit\.buildLookPicker\(\$\('wizLook'\), state\.look, function \(\) \{ saveDraft\(\); paintSetup\(\); \}, 'wiz',/, 'a pick saves and repaints the row');
123
122
  assert.doesNotMatch(fs.readFileSync(path.join(LANDING, 'assets', 'cosmos.css'), 'utf8'), /#view-new \.steps/, 'and no rail grid in cosmos.css');
124
123
  });
125
124
 
126
- test('every kit walk that files a project passes the review and the Look screen first, and the kit has a state on it', () => {
125
+ test('the kit has states on the Look row, the picker and "make your own"', () => {
127
126
  const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8')).states;
128
- const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
129
- const walks = [...Object.entries(states).map(([k, v]) => [k, v.actions]), ...probes.map((p) => [p.name, p.steps])];
130
- let past = 0;
131
- for (const [name, steps] of walks) {
132
- const at = (steps || []).findIndex((a) => a[1] === '#reviewGo');
133
- if (at < 0) continue;
134
- past++;
135
- // the retired App and Look submits must not linger in a walk (they would stop it dead)
136
- assert.ok(!steps.some((a) => a[1] === '#panel-app button[type=submit]' || a[1] === '#panel-look button[type=submit]'), `${name}: walks no retired step`);
137
- const create = steps.findIndex((a) => a[1] === '#doCreate');
138
- if (create >= 0) assert.ok(create > at, `${name}: files only after the review's Continue lands it on the Look screen`);
139
- }
140
- assert.ok(past >= 4, `sanity: the walks that leave the review were found (${past})`);
141
- assert.deepEqual(states['in-new-look'].expect.visible.slice(0, 2), ['#panel-look', '#wizLook']);
127
+ assert.ok(states['in-new-look'].expect.visible.includes('#wizLook'));
128
+ assert.ok(states['in-new-look'].actions.some((a) => a[1] === '#reviewRows .setRow[data-row="look"] [data-row-chg]'), 'opened by its own Change');
142
129
  assert.ok(states['in-new-look-custom'].actions.some((a) => a[1] === '#wizLook-custom'), 'a state pictures "make your own"');
130
+ for (const [name, s] of Object.entries(states)) {
131
+ for (const a of s.actions || []) assert.ok(!/panel-look|reviewGo|panel-app/.test(String(a[1])), `${name}: walks no retired screen`);
132
+ }
143
133
  });
144
134
 
145
135
  test('executed: the picker offers the four looks and your own, and the accent shows only for your own', () => {
@@ -239,17 +229,17 @@ test('executed: sendLook sends the look, then the logo — and a removed logo is
239
229
  assert.deepEqual(calls.map((c) => [c.method, c.url.split('/').pop(), c.body]), [['PATCH', 'look', { look: 'grove', night_tint: 'on' }], ['DELETE', 'logo', null]]);
240
230
  });
241
231
 
242
- test('Create sends the look right after the project is filed, and the look is chosen on the screen Create stands on', () => {
232
+ test('Create sends the look right after the project is filed, and the look is chosen on the page Create stands on', () => {
243
233
  assert.match(HUB, /sendRenderSetup\(data\.instance && data\.instance\.id\);[^\n]*\n\s*sendLookSetup\(data\.instance && data\.instance\.id\);/);
244
234
  assert.match(HUB, /lookKit\.sendLook\(instanceId, state\.look \|\| defaultLook\(\)\)/);
245
235
  assert.match(HUB, /function defaultLook\(\) \{ return \{ look: 'chrome-world', nightTint: true, accent: '#b48cf2', logo: null \}; \}/, 'the default is the platform\'s look with the tint ON (owner D2)');
246
- // the ledger review and its Look row are gone (task 1004504): the picker and Create share
247
- // one screen, so the look being filed is the one on screen beside the button
248
- assert.doesNotMatch(HUB, /function lookRow\(|\['Look', lookRow\(\)\]/, 'no review row restates the look');
249
- const panel = (HUB.match(/<section class="panel" id="panel-look"[\s\S]*?<\/section>/) || [''])[0];
250
- assert.ok(panel.indexOf('id="wizLook"') > -1 && panel.indexOf('id="wizLook"') < panel.indexOf('id="doCreate"'), 'the picker sits above Create on the same screen');
251
- assert.match(HUB, /<p class="hint" id="doneLook" role="status" hidden><\/p>/, 'a refused look is said on the done panel');
252
- assert.equal((HUB.match(/look: null, termsAck: false, confirmed: \{\} \};/g) || []).length, 2, 'the draft carries the look (and its reset clears it)');
236
+ // the picker and Create share one page (task 1004648), so the look being filed is the one
237
+ // the Look row says, above the button
238
+ assert.doesNotMatch(HUB, /function lookRow\(|\['Look', lookRow\(\)\]/, 'no second copy restates the look');
239
+ const page = (HUB.match(/<section class="panel on" id="panel-page"[\s\S]*?\n {10}<\/section>/) || [''])[0];
240
+ assert.ok(page.indexOf('id="wizLook"') > -1 && page.indexOf('id="wizLook"') < page.indexOf('id="doCreate"'), 'the picker sits above Create on the same page');
241
+ assert.match(HUB, /<p class="hint" id="doneLook" role="status" hidden><\/p>/, 'a refused look is said in part 03');
242
+ assert.equal((HUB.match(/look: null, termsAck: false \};/g) || []).length, 2, 'the draft carries the look (and its reset clears it)');
253
243
  });
254
244
 
255
245
  test('the manage page has "Its look", read once per project — never on the minute tick', () => {