@bongos/core 1.21.105 → 1.21.106

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.
Files changed (36) hide show
  1. package/.bongos-core.json +53 -43
  2. package/docs/copy-inventory.md +368 -366
  3. package/docs/copy-registry.json +475 -457
  4. package/docs/design/project-startup-direction.md +14 -0
  5. package/docs/module-api-changelog.md +2 -0
  6. package/docs/page-inventory.json +10 -3
  7. package/docs/page-readings.json +1023 -977
  8. package/modules/public-landing/public/projects-hall-live.states.json +3 -2
  9. package/modules/public-landing/public/projects-setup.states.json +91 -0
  10. package/modules/public-landing/public/projects.html +535 -804
  11. package/modules/public-landing/public/projects.probes.json +3 -3
  12. package/modules/public-landing/public/projects.states.json +1 -1
  13. package/modules/ui-design/kit/fixtures/provisioning-instances-hall-live.json +22 -2
  14. package/modules/ui-design/kit/fixtures/provisioning-instances-setup.json +195 -0
  15. package/modules/ui-design/kit/serve.js +22 -4
  16. package/package-lock.json +2 -2
  17. package/package.json +1 -1
  18. package/release-notes.json +6 -0
  19. package/src/module-api.js +1 -1
  20. package/tests/cosmos_token_contrast.mjs +10 -18
  21. package/tests/github_handback_any_device.mjs +4 -3
  22. package/tests/github_one_trip.mjs +45 -47
  23. package/tests/github_org_account.mjs +2 -2
  24. package/tests/github_pick_up.mjs +40 -29
  25. package/tests/onboard.mjs +13 -7
  26. package/tests/project_invite_ui.mjs +6 -3
  27. package/tests/project_modules_ui.mjs +3 -3
  28. package/tests/projects_hub.mjs +8 -9
  29. package/tests/projects_hub_cloud_hop.mjs +77 -125
  30. package/tests/projects_hub_dns_ready.mjs +42 -51
  31. package/tests/projects_hub_pre_uat.mjs +68 -69
  32. package/tests/public_landing_projects.mjs +45 -102
  33. package/tests/setup_status_facts.mjs +400 -0
  34. package/tests/wizard_github_feedback.mjs +90 -87
  35. package/tests/wizard_one_page.mjs +11 -9
  36. package/tests/wizard_auto_open.mjs +0 -94
@@ -13,7 +13,8 @@
13
13
  //
14
14
  // Four layers, all DB-free and network-free:
15
15
  // 1. manifest-truth.js — the effective status and how far a failure got (PURE).
16
- // 2. renderStepOauthApp, EXECUTED by the vm-slice idiom: the words for each stage.
16
+ // 2. part 03's GitHub facts (ghFacts, task 1004683), EXECUTED by the vm-slice idiom:
17
+ // the words and the one action for each stage.
17
18
  // 3. the reload path, EXECUTED: the seed from the instance read and the wait timer.
18
19
  // 4. the project's own sign-in door with nothing configured: a page in words.
19
20
  //
@@ -69,130 +70,132 @@ test('publicGithubApp: effective status, failure only on error, started_at, and
69
70
 
70
71
  // ── 2. the words for each stage ─────────────────────────────────────────────
71
72
 
72
- // from the shared helpers of the four GitHub steps (task 1004649) to the renderer table
73
- const RS = SCRIPT.indexOf(' /* The four GitHub steps of part 03');
74
- const RE = SCRIPT.indexOf(' var STEP_RENDERERS = {', RS);
75
- const RENDER = SCRIPT.slice(RS, RE);
73
+ // part 03's facts (task 1004683): the pure half of the facts region, run as is
74
+ const FS = SCRIPT.indexOf(' function ghStep(key) {');
75
+ const FE = SCRIPT.indexOf(' /* ── drawing it: nodes and textContent only', FS);
76
+ const RENDER = SCRIPT.slice(FS, FE);
76
77
 
77
- test('the GitHub step region is still findable (a rename must fail loudly)', () => {
78
- assert.ok(RS > 0 && RE > RS, 'both slice boundaries found');
79
- assert.match(RENDER, /function renderStepOauthApp\(\)/);
78
+ test('the GitHub facts region is still findable (a rename must fail loudly)', () => {
79
+ assert.ok(FS > 0 && FE > FS, 'both slice boundaries found');
80
+ assert.match(RENDER, /function ghFacts\(now\)/);
80
81
  });
81
82
 
82
- function stage(live, { tab = null, fn = 'renderStepOauthApp' } = {}) {
83
+ // every GitHub fact for one reading of the attempt, as the founder reads it
84
+ function stageAll(live, { tab = null } = {}) {
83
85
  const sandbox = {
84
- live: { manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, setup: null, installUrl: null, instStatus: 'active', addrReady: true, pickUp: null, verifying: false, askedPlatform: false, ...live },
86
+ live: { manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, setup: null, installUrl: null, instStatus: 'active', addrReady: true, pickUp: null, verifying: false, askedPlatform: false, inst: null, readAt: Date.now(), gone: '', repo: null, ...live },
85
87
  state: { name: 'Mercury', slug: 'mercury' },
86
- autoOpenArmed: false,
87
88
  resolvedDomain: () => 'mercury.example.com',
88
89
  apexBase: () => 'example.com',
89
- esc: (x) => String(x).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])),
90
- GH_ICON: '',
91
- manualOauthDetails: () => '<details>manual</details>',
92
- projectPageLink: (t) => '<a>' + t + '</a>',
93
- autoOpenTick: () => '<label>auto</label>',
94
- progressMarkup: (now, done, note, cls) => `<prog now=${now} done=${done} cls="${cls}">${note}</prog>`,
90
+ addressReady: () => true,
95
91
  };
96
92
  vm.createContext(sandbox);
97
- vm.runInContext(`${RENDER}\nghTabTrouble = ${JSON.stringify(tab)};\nvar out = ${fn}();`, sandbox);
98
- return sandbox.out;
93
+ vm.runInContext(`${RENDER}\nghTabTrouble = ${JSON.stringify(tab)};\nvar out = JSON.stringify(ghFacts(Date.now()));`, sandbox);
94
+ return JSON.parse(sandbox.out).map((r) => ({
95
+ ...r,
96
+ words: [r.title, [].concat(r.sub || []).map((p) => (typeof p === 'string' ? p : p.t)).join('')].join(' — '),
97
+ acts: [].concat(r.act || []),
98
+ }));
99
+ }
100
+ function stage(live, { tab = null, key = 'approve' } = {}) {
101
+ return stageAll(live, { tab }).find((r) => r.key === key);
99
102
  }
100
- const startButton = /data-action="manifest-start"/;
101
103
  const NEVER_A_CHORE = /\/contact|Contact us|delete it first|Developer settings → GitHub Apps\) so|Try again anyway|Start the setup again/;
102
104
 
103
- test('pending: waiting for approval, says where a missing GitHub tab went, and reopens the SAME setup', () => {
104
- const html = stage({ manifest: 'pending' });
105
- assert.match(html, /Waiting for your approval on GitHub/);
106
- assert.match(html, /No GitHub tab\? It may have opened behind this window or in another one/);
107
- assert.doesNotMatch(html, startButton, 'no new-link button while GitHub holds this one (task 1004652)');
108
- assert.match(html, /class="linklike" data-action="reopen-github">No GitHub tab\? Reopen GitHub/, 'the way back is there from the first second, not after 90');
109
- // the tab this page opened, seen from here (task 1004652)
105
+ test('pending: the founder\'s turn, says where a missing GitHub tab went, and reopens the SAME setup', () => {
106
+ const f = stage({ manifest: 'pending' });
107
+ assert.equal(f.state, 'you');
108
+ assert.match(f.words, /Waiting for you in the GitHub tab\./);
109
+ assert.deepEqual(f.acts, ['reopen'], 'no new-link button while GitHub holds this one (task 1004652); the way back is there from the first second');
110
110
  const behind = stage({ manifest: 'pending' }, { tab: 'behind' });
111
- assert.match(behind, /Your GitHub tab opened behind this window/);
112
- assert.match(behind, /class="ghbtn" data-action="reopen-github">Reopen GitHub ↗/);
111
+ assert.match(behind.words, /Your GitHub tab opened behind this window\./);
112
+ assert.deepEqual(behind.acts, ['reopen']);
113
113
  const closed = stage({ manifest: 'pending' }, { tab: 'closed' });
114
- assert.match(closed, /The GitHub tab closed before the app was approved, so nothing was made on GitHub/);
115
- assert.match(closed, /data-action="reopen-github"/);
116
- assert.doesNotMatch(behind + closed, startButton);
114
+ assert.match(closed.words, /The GitHub tab closed before you approved, so nothing was made\. Reopen it to carry on\./);
115
+ assert.deepEqual(closed.acts, ['reopen']);
117
116
  });
118
117
 
119
- test('converting is the SAME stage as code_received — approved, placing credentials — never "waiting for approval"', () => {
118
+ test('converting is the SAME fact as code_received — approved, keys being collected — never "waiting for you"', () => {
120
119
  for (const m of ['code_received', 'converting']) {
121
- const html = stage({ manifest: m });
122
- assert.match(html, /Approved on GitHub/, m);
123
- assert.match(html, /goes on to installing it on your repo/, `${m}: the trip carries on to the install (task 1004649)`);
124
- assert.match(html, /<prog now=1 done=1/, `${m}: the strip is past the approval stage`);
125
- assert.doesNotMatch(html, /Waiting for your approval/, m);
120
+ const f = stage({ manifest: m });
121
+ assert.equal(f.state, 'now', `${m}: under way, not the founder's turn`);
122
+ assert.match(f.words, /Approved\. The platform is collecting the app’s keys\./, m);
123
+ assert.doesNotMatch(f.words, /Waiting for you/, m);
124
+ assert.deepEqual(f.acts, [], `${m}: nothing to press`);
126
125
  }
127
126
  });
128
127
 
129
- test('sign-in unconfirmed: the app stays ticked, sign-in is the stopped step, and picking it up makes no second app', () => {
128
+ test('sign-in unconfirmed: the app stays ticked, sign-in is a stopped fact, and picking it up makes no second app', () => {
130
129
  const live = { manifest: 'error', manifestFailure: 'sign_in_unconfirmed', appSlug: 'mercury-cloudbongos-com', pickUp: 'check' };
131
- const approve = stage(live);
132
- assert.match(approve, /^<li class="sdone"/, 'GitHub made the app and its keys are placed: that much is true');
133
- const signIn = stage(live, { fn: 'renderStepFirstSignIn' });
134
- assert.match(signIn, /^<li class="sstopped"/);
135
- assert.match(signIn, /Your project didn’t confirm that sign-in works\. Nothing is lost, and no second app will be made\./);
136
- assert.match(signIn, /data-action="pick-up">Pick up where it stopped/);
137
- assert.match(signIn, /data-action="ask-platform">Send it to the platform team/, 'a project that still will not answer goes to the platform team from here');
138
- assert.doesNotMatch(approve + signIn, startButton, 'no new link: it would make a second app');
139
- assert.doesNotMatch(approve + signIn, NEVER_A_CHORE);
140
- // while the runner is asking again, the step says so and offers no button that repeats it
141
- const checking = stage({ ...live, pickUp: null, verifying: true }, { fn: 'renderStepFirstSignIn' });
142
- assert.match(checking, /We’re asking it again, with the app GitHub already made/);
143
- assert.doesNotMatch(checking, /data-action="pick-up"/);
144
- // sent: the step says the platform team has it
145
- assert.match(stage({ ...live, askedPlatform: true }, { fn: 'renderStepFirstSignIn' }), /Sent\. The platform team has it/);
146
- assert.match(stage({ ...live, askedPlatform: 'failed' }, { fn: 'renderStepFirstSignIn' }), /That didn’t send\. Try again\.[\s\S]*data-action="ask-platform"/);
130
+ const all = stageAll(live);
131
+ const [approve, , signIn] = all;
132
+ assert.equal(approve.state, 'done', 'GitHub made the app and its keys are placed: that much is true');
133
+ assert.equal(signIn.state, 'bad');
134
+ assert.match(signIn.words, /Your project didn’t confirm that sign-in works\./);
135
+ assert.ok(signIn.acts.includes('ask-platform'), 'a project that still will not answer goes to the platform team from here');
136
+ assert.equal(all.flatMap((r) => r.acts).filter((a) => /^pick-up/.test(a)).length, 1, 'one "Pick up where it stopped", on the first stopped fact');
137
+ assert.ok(!all.some((r) => r.acts.includes('connect')), 'no new link: it would make a second app');
138
+ assert.doesNotMatch(all.map((r) => r.words).join('\n'), NEVER_A_CHORE);
139
+ // while the runner is asking again, the fact says so and offers no button that repeats it
140
+ const checking = stage({ ...live, pickUp: null, verifying: true }, { key: 'sign_in' });
141
+ assert.equal(checking.state, 'now');
142
+ assert.match(checking.words, /Asking your project again, with the app GitHub already made\./);
143
+ assert.deepEqual(checking.acts, []);
144
+ // sent: the fact says the platform team has it
145
+ const sent = stage({ ...live, askedPlatform: true }, { key: 'sign_in' });
146
+ assert.match(sent.words, /Sent\. The platform team has it and finishes it from here\./);
147
+ assert.ok(!sent.acts.includes('ask-platform'), 'sent once');
148
+ const failed = stage({ ...live, askedPlatform: 'failed' }, { key: 'sign_in' });
149
+ assert.match(failed.words, /That didn’t send\. Try again\./);
150
+ assert.ok(failed.acts.includes('ask-platform'));
147
151
  });
148
152
 
149
153
  test('approved, conversion stopped with its code still young: pick it up, no second app', () => {
150
- const html = stage({ manifest: 'error', manifestFailure: 'finish_failed', pickUp: 'convert' });
151
- assert.match(html, /^<li class="sstopped"/);
152
- assert.match(html, /GitHub approved the app, and the platform stopped before it finished collecting the app’s keys\. Nothing is lost, and no second app will be made\./);
153
- assert.match(html, /data-action="pick-up">Pick up where it stopped/);
154
- assert.doesNotMatch(html, startButton);
155
- assert.doesNotMatch(html, NEVER_A_CHORE);
154
+ const f = stage({ manifest: 'error', manifestFailure: 'finish_failed', pickUp: 'convert' });
155
+ assert.equal(f.state, 'bad');
156
+ assert.match(f.words, /GitHub approved the app, and the platform stopped before it finished collecting the app’s keys\./);
157
+ assert.deepEqual(f.acts, ['pick-up']);
158
+ assert.doesNotMatch(f.words, NEVER_A_CHORE);
156
159
  });
157
160
 
158
161
  test('approved but the keys never reached us: the platform team, sent from the page — never a GitHub chore', () => {
159
- const html = stage({ manifest: 'error', manifestFailure: 'finish_failed', pickUp: 'operator' });
160
- assert.match(html, /GitHub made the app, but its keys never reached your project/);
161
- assert.match(html, /data-action="ask-platform">Send it to the platform team/);
162
- assert.doesNotMatch(html, startButton, 'a new link would leave the founder with two apps');
163
- assert.doesNotMatch(html, NEVER_A_CHORE);
164
- assert.doesNotMatch(html, /Nothing was changed on GitHub/);
162
+ const f = stage({ manifest: 'error', manifestFailure: 'finish_failed', pickUp: 'operator' });
163
+ assert.match(f.words, /GitHub made the app, but its keys never reached your project/);
164
+ assert.deepEqual(f.acts, ['ask-platform'], 'a new link would leave the founder with two apps');
165
+ assert.doesNotMatch(f.words, NEVER_A_CHORE);
166
+ assert.doesNotMatch(f.words, /Nothing was changed on GitHub/);
165
167
  });
166
168
 
167
- test('the install the runner stopped checking for: the mockup’s sentence, and a pick-up that opens the SAME app’s install', () => {
168
- const html = stage({ manifest: 'done', appSlug: 'mercury-app', installUrl: 'https://github.com/apps/mercury-app/installations/new', pickUp: 'check' }, { fn: 'renderStepAppInstall' });
169
- assert.match(html, /^<li class="sstopped" data-onboard-step="app-install">/);
170
- assert.match(html, /GitHub stopped before the app was installed on your repo\. Nothing is lost, and no second app will be made\./);
171
- assert.match(html, /<a class="ghbtn" href="https:\/\/github\.com\/apps\/mercury-app\/installations\/new" target="_blank" rel="noopener" data-action="pick-up">Pick up where it stopped ↗<\/a>/);
172
- // still being checked: the ordinary step, no stop
173
- assert.doesNotMatch(stage({ manifest: 'done', appSlug: 'mercury-app', pickUp: null, verifying: true }, { fn: 'renderStepAppInstall' }), /sstopped/);
169
+ test('the install the runner stopped checking for: the mockup’s words, and a pick-up that opens the SAME app’s install', () => {
170
+ const f = stage({ manifest: 'done', appSlug: 'mercury-app', installUrl: 'https://github.com/apps/mercury-app/installations/new', pickUp: 'check' }, { key: 'install' });
171
+ assert.equal(f.state, 'bad');
172
+ assert.match(f.words, /GitHub stopped before the app was on your repo\./);
173
+ assert.deepEqual(f.acts, ['pick-up-link']);
174
+ assert.equal(f.href, 'https://github.com/apps/mercury-app/installations/new');
175
+ // still being checked: the founder's turn on GitHub, no stop
176
+ assert.equal(stage({ manifest: 'done', appSlug: 'mercury-app', pickUp: null, verifying: true }, { key: 'install' }).state, 'you');
174
177
  });
175
178
 
176
179
  test('device flow after the runner stopped checking: "Check again", once the install has ticked', () => {
177
180
  const setup = { approve: 'done', install: 'done', sign_in: 'done', device_flow: 'later', connected: true };
178
- const again = stage({ manifest: 'done', appSlug: 'mercury-app', setup, pickUp: 'check' }, { fn: 'renderStepDeviceFlow' });
179
- assert.match(again, /data-action="pick-up">Turned it on\? Check again/);
180
- assert.doesNotMatch(stage({ manifest: 'done', appSlug: 'mercury-app', setup, pickUp: null, verifying: true }, { fn: 'renderStepDeviceFlow' }), /Check again/);
181
- assert.doesNotMatch(stage({ manifest: 'done', appSlug: 'mercury-app', setup: { ...setup, install: 'now', connected: false }, pickUp: 'check' }, { fn: 'renderStepDeviceFlow' }), /Check again/, 'the install step carries the action until it ticks');
181
+ assert.deepEqual(stage({ manifest: 'done', appSlug: 'mercury-app', setup, pickUp: 'check' }, { key: 'device_flow' }).acts, ['check-again']);
182
+ assert.deepEqual(stage({ manifest: 'done', appSlug: 'mercury-app', setup, pickUp: null, verifying: true }, { key: 'device_flow' }).acts, []);
183
+ assert.deepEqual(stage({ manifest: 'done', appSlug: 'mercury-app', setup: { ...setup, install: 'now', connected: false }, pickUp: 'check' }, { key: 'device_flow' }).acts, [],
184
+ 'the install fact carries the action until it ticks');
182
185
  });
183
186
 
184
187
  test('a failure before GitHub (or a refused start) keeps the honest "nothing changed" line', () => {
185
- assert.match(stage({ manifest: 'error', manifestFailure: 'not_approved' }), /Nothing was changed on GitHub/);
188
+ assert.match(stage({ manifest: 'error', manifestFailure: 'not_approved' }).words, /Nothing was changed on GitHub/);
186
189
  const refused = stage({ manifest: 'error', manifestMsg: 'HTTP 500' });
187
- assert.match(refused, /Nothing was changed on GitHub/);
188
- assert.match(refused, /Technical details/);
189
- assert.match(refused, startButton);
190
+ assert.match(refused.words, /Nothing was changed on GitHub/);
191
+ assert.equal(refused.tech, 'HTTP 500', 'the route text folds under Technical details');
192
+ assert.deepEqual(refused.acts, ['connect']);
190
193
  });
191
194
 
192
195
  test('an expired link offers a fresh one', () => {
193
- const html = stage({ manifest: 'expired' });
194
- assert.match(html, /That setup link expired/);
195
- assert.match(html, startButton);
196
+ const f = stage({ manifest: 'expired' });
197
+ assert.match(f.words, /That setup link expired/);
198
+ assert.deepEqual(f.acts, ['connect']);
196
199
  });
197
200
 
198
201
  // ── 3. the reload path ──────────────────────────────────────────────────────
@@ -201,7 +204,7 @@ const WS = SCRIPT.indexOf(' function manifestInFlight() {');
201
204
  const WE = SCRIPT.indexOf(' function pollManifest(stateToken) {', WS);
202
205
  const WAIT = SCRIPT.slice(WS, WE);
203
206
  const SS = SCRIPT.indexOf(' var ga = data.github_app;');
204
- const SE = SCRIPT.indexOf(' /* the checklist reflects live state (task 1002694)', SS);
207
+ const SE = SCRIPT.indexOf(' /* the server-verified checklist rides the same read (task 1004649)', SS);
205
208
  const SEED = SCRIPT.slice(SS, SE);
206
209
 
207
210
  test('the reload regions are still findable (a rename must fail loudly)', () => {
@@ -220,7 +223,7 @@ function reloaded(githubApp, now) {
220
223
  live: { manifest: null, manifestFailure: null, appSlug: null },
221
224
  manifestPollTimer: null, manifestStartedAt: 0, ghTabTrouble: null,
222
225
  document: { hidden: false, hasFocus: () => true },
223
- rememberManifest: () => {}, renderSteps: () => renders.push(1),
226
+ rememberManifest: () => {}, paintFacts: () => renders.push(1),
224
227
  Date: { now: () => now, parse: Date.parse },
225
228
  };
226
229
  vm.createContext(sandbox);
@@ -112,7 +112,7 @@ test('nothing the live wizard does is lost: every capability in the direction ta
112
112
  assert.match(row('run'), /data-val="hybrid" aria-pressed="false">Some of it<\/button>/, 'We run: "Some of it" reveals the service checklist');
113
113
  has('the Shipping row', row('ship'), ['shipOpts']);
114
114
  has('the Look row', row('look'), ['wizLook', 'lookDot']);
115
- has('the page', PAGE, ['wizResume', 'termsAck', 'doCreate', 'wizRecap', 'createWhy', 'createAlert', 'humanSteps', 'liveProgress', 'wizInvite', 'done-h']);
115
+ has('the page', PAGE, ['wizResume', 'termsAck', 'doCreate', 'wizRecap', 'createWhy', 'createAlert', 'setupNow', 'homeFacts', 'ghFacts', 'wizInvite', 'done-h', 'enterGenesis']);
116
116
  const ids = [...VIEW.matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]);
117
117
  assert.deepEqual(ids.filter((id, i) => ids.indexOf(id) !== i), [], 'moved, never copied: every id in the view is unique');
118
118
  });
@@ -143,7 +143,7 @@ test('Create waits for a name and the terms tick only, and says why in place (ex
143
143
  });
144
144
 
145
145
  test('Create runs the screening stop before anything exists, and a row that cannot be filed opens with its reason', () => {
146
- const create = slice('$(\'doCreate\').addEventListener(\'click\'', '/* ── done panel');
146
+ const create = slice('$(\'doCreate\').addEventListener(\'click\'', '/* ── part 03 after Create (task 1004683');
147
147
  const order = ['createWhy()', 'firstInvalidRow()', 'physScreen()', 'ensureFreshRepo()', "fetch(API + '/provisioning/instances'"].map((s) => create.indexOf(s));
148
148
  assert.ok(order.every((i, k) => i > 0 && (k === 0 || i > order[k - 1])), 'name and tick, then the rows, then the screen, then the repo, then the create');
149
149
  assert.match(create, /if \(!ok\) \{ var stop = new Error\('stopped'\); stop\.stopped = true; throw stop; \}/, 'a stop files nothing');
@@ -181,16 +181,18 @@ test('the day room is the mockup\'s, drawn from the fifteen, and its motion sits
181
181
  assert.doesNotMatch(STYLE.replace(calm, ''), /rmDrift 60s|animation:rmTwinkle|animation:rmBreathe/, 'no room motion outside the no-preference block');
182
182
  });
183
183
 
184
- test('part 03 is the checklist, every step on show, with the state line from the server\'s own record', () => {
184
+ test('part 03 waits for the project, then is one list of checked facts with its count, all on show', () => {
185
185
  assert.match(part('panel-done') || PAGE, /id="panel-done"/);
186
186
  assert.match(PAGE, /<div class="part waiting" id="panel-done" aria-labelledby="done-part-h">/, 'part 03 waits until the project exists');
187
+ assert.match(PAGE, /<h2 id="done-part-h"><span class="n">03<\/span><span id="donePartT">Connect GitHub<\/span> <span class="state" id="ghState">opens once your project exists<\/span><\/h2>/,
188
+ 'before Create it is Connect GitHub; the script names it "Getting <name> ready" after');
187
189
  assert.match(SCRIPT, /done\.classList\.toggle\('waiting', !created\);/);
188
- const steps = slice('function stepsPaint()', '(function hopInput()');
189
- assert.match(steps, /'connected ✓'/);
190
- assert.match(steps, /'waiting on GitHub…'/);
191
- assert.match(steps, /'ready'/);
192
- assert.doesNotMatch(steps, /innerHTML/, 'no markup built from strings');
193
- assert.doesNotMatch(STYLE, /humanSteps>li:not\(\.cur\)/, 'the checklist is no longer paged one step at a time');
190
+ const paint = slice('function paintFacts()', 'function repaintFacts()');
191
+ assert.match(paint, /title\.textContent = 'Getting ' \+ projectName\(\) \+ ' ready';/);
192
+ assert.match(paint, /count\.textContent = setupCount\(home, gh\);/);
193
+ assert.match(slice('function setupCount(home, gh)', 'function setupSentence('), /'ready ✓' : done \+ ' of ' \+ req\.length \+ ' done'/);
194
+ assert.doesNotMatch(slice('/* ── part 03 after Create (task 1004683', '/* ── C8: the wizard'), /innerHTML\s*=/, 'no markup built from strings: the facts are nodes');
195
+ assert.doesNotMatch(STYLE, /humanSteps/, 'the old checklist and its sheet are gone');
194
196
  });
195
197
 
196
198
  test('the name is typed, so the handle under it is built from nodes, never markup', () => {
@@ -1,94 +0,0 @@
1
- // tests/wizard_auto_open.mjs — the checklist's "Open my project automatically when it's
2
- // ready" tick (task 1004548).
3
- //
4
- // WHY. The GitHub-App callback page used to hold the founder on its own stepper and open
5
- // the instance itself. Since task 1004548 it hands them straight back to the checklist,
6
- // so the auto-open moved there. The rule it must keep: open ONCE, only when sign-in is
7
- // wired AND the project answers, only after a wait this page actually watched (so a
8
- // revisit to a long-finished checklist never jumps away), never when the founder
9
- // unticked it — and prefer a new tab, falling back to this one when the browser blocks
10
- // an unprompted tab.
11
- //
12
- // Executed, not read: the real AUTO_OPEN_KEY…maybeAutoOpen region is sliced out of the
13
- // page's inline script and run in a node:vm under stubs (the vm-slice idiom of
14
- // tests/wizard_intent_resume.mjs). The slice boundaries are asserted, so a rename fails
15
- // loudly instead of testing nothing.
16
- //
17
- // Run: node --test tests/wizard_auto_open.mjs
18
-
19
- import assert from 'node:assert/strict';
20
- import { test } from 'node:test';
21
- import fs from 'node:fs';
22
- import path from 'node:path';
23
- import vm from 'node:vm';
24
- import { fileURLToPath } from 'node:url';
25
-
26
- const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
27
- const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
28
- const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
29
- const SCRIPT = HUB.slice(HUB.lastIndexOf('<script>'), HUB.lastIndexOf('</script>'));
30
-
31
- const START = SCRIPT.indexOf(" var AUTO_OPEN_KEY = 'cb-open-when-ready';");
32
- const END = SCRIPT.indexOf(' /* oauth-app — the ADR 0133 one-click GitHub-App manifest flow', START);
33
- const SLICE = SCRIPT.slice(START, END);
34
-
35
- test('the auto-open region is still findable (a rename must fail loudly)', () => {
36
- assert.ok(START > 0 && END > START, 'both slice boundaries found');
37
- assert.match(SLICE, /function maybeAutoOpen\(\)/);
38
- assert.match(SLICE, /function autoOpenTick\(\)/);
39
- });
40
-
41
- const READY = { manifest: 'done', instStatus: 'active', addrReady: true };
42
-
43
- function run({ live = READY, armed = true, pref = null, popupBlocked = false, ticks = 1 } = {}) {
44
- const store = new Map();
45
- if (pref !== null) store.set('cb-open-when-ready', pref);
46
- const calls = { opened: [], assigned: [] };
47
- const sandbox = {
48
- live: { ...live },
49
- resolvedDomain: () => 'mercury.example.com',
50
- localStorage: { getItem: (k) => (store.has(k) ? store.get(k) : null), setItem: (k, v) => store.set(k, v) },
51
- window: {
52
- open: (u, t) => { calls.opened.push([u, t]); return popupBlocked ? null : { opener: 'checklist' }; },
53
- location: { assign: (u) => { calls.assigned.push(u); } },
54
- },
55
- };
56
- sandbox.globalThis = sandbox;
57
- vm.createContext(sandbox);
58
- vm.runInContext(`${SLICE}\nautoOpenArmed = ${armed ? 'true' : 'false'};\nfor (var i = 0; i < ${ticks}; i++) maybeAutoOpen();\nvar tickHtml = autoOpenTick();`, sandbox);
59
- return { calls, sandbox };
60
- }
61
-
62
- test('ready + armed + ticked (the default) → opens the project ONCE, in a new tab', () => {
63
- const { calls } = run({ ticks: 3 });
64
- assert.deepEqual(calls.opened, [['https://mercury.example.com', '_blank']], 'one new tab, however many renders follow');
65
- assert.deepEqual(calls.assigned, [], 'this checklist stays put');
66
- });
67
-
68
- test('a browser that blocks the unprompted tab sends THIS tab instead — the founder asked', () => {
69
- const { calls } = run({ popupBlocked: true });
70
- assert.deepEqual(calls.assigned, ['https://mercury.example.com']);
71
- });
72
-
73
- test('nothing opens until sign-in is wired AND the project answers', () => {
74
- for (const live of [
75
- { ...READY, manifest: 'code_received' },
76
- { ...READY, instStatus: 'provisioning' },
77
- { ...READY, addrReady: false },
78
- ]) {
79
- const { calls } = run({ live });
80
- assert.deepEqual([calls.opened, calls.assigned], [[], []], JSON.stringify(live));
81
- }
82
- });
83
-
84
- test('unticked → never opens, and the tick renders unticked', () => {
85
- const { calls, sandbox } = run({ pref: '0' });
86
- assert.deepEqual([calls.opened, calls.assigned], [[], []]);
87
- assert.doesNotMatch(sandbox.tickHtml, / checked/);
88
- assert.match(run().sandbox.tickHtml, / checked>/, 'the default is ticked');
89
- });
90
-
91
- test('a checklist that never watched the wait (a revisit) does not jump away', () => {
92
- const { calls } = run({ armed: false });
93
- assert.deepEqual([calls.opened, calls.assigned], [[], []]);
94
- });