@bongos/core 1.21.104 → 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 (40) hide show
  1. package/.bongos-core.json +57 -47
  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 +4 -0
  6. package/docs/page-inventory.json +10 -3
  7. package/docs/page-readings.json +1023 -977
  8. package/modules/lifecycle/check-rollup.js +11 -1
  9. package/modules/lifecycle/github-push.js +3 -7
  10. package/modules/public-landing/public/projects-hall-live.states.json +3 -2
  11. package/modules/public-landing/public/projects-setup.states.json +91 -0
  12. package/modules/public-landing/public/projects.html +535 -804
  13. package/modules/public-landing/public/projects.probes.json +3 -3
  14. package/modules/public-landing/public/projects.states.json +1 -1
  15. package/modules/ui-design/kit/fixtures/provisioning-instances-hall-live.json +22 -2
  16. package/modules/ui-design/kit/fixtures/provisioning-instances-setup.json +195 -0
  17. package/modules/ui-design/kit/serve.js +22 -4
  18. package/package-lock.json +2 -2
  19. package/package.json +1 -1
  20. package/release-notes.json +12 -0
  21. package/scripts/gds/ship-land.js +2 -2
  22. package/src/module-api.js +1 -1
  23. package/tests/cosmos_token_contrast.mjs +10 -18
  24. package/tests/github_handback_any_device.mjs +4 -3
  25. package/tests/github_one_trip.mjs +45 -47
  26. package/tests/github_org_account.mjs +2 -2
  27. package/tests/github_pick_up.mjs +40 -29
  28. package/tests/github_push_merge_driver.mjs +46 -0
  29. package/tests/onboard.mjs +13 -7
  30. package/tests/project_invite_ui.mjs +6 -3
  31. package/tests/project_modules_ui.mjs +3 -3
  32. package/tests/projects_hub.mjs +8 -9
  33. package/tests/projects_hub_cloud_hop.mjs +77 -125
  34. package/tests/projects_hub_dns_ready.mjs +42 -51
  35. package/tests/projects_hub_pre_uat.mjs +68 -69
  36. package/tests/public_landing_projects.mjs +45 -102
  37. package/tests/setup_status_facts.mjs +400 -0
  38. package/tests/wizard_github_feedback.mjs +90 -87
  39. package/tests/wizard_one_page.mjs +11 -9
  40. package/tests/wizard_auto_open.mjs +0 -94
@@ -169,52 +169,51 @@ test('"live" is ONE rule — chip, orb and address row all read freshUp()', () =
169
169
  });
170
170
 
171
171
  /* ── the queued strip is honest about which stage is current ── */
172
- test('a queued standup lights "Request filed", not "Standing it up" — on the card and on the done panel', () => {
172
+ test('a queued standup lights "Request filed", not "Standing it up" on the card; part 03 says one fact is under way', () => {
173
173
  const strip = fn('stripFor');
174
174
  assert.match(strip, /if \(s === 'requested'\) \{[\s\S]*?stripMarkup\(revive \? 'revive' : 'standup', 0, 0,/, 'card: requested → stage 0 current');
175
175
  assert.match(strip, /if \(s === 'provisioning'\) \{[\s\S]*?stripMarkup\(revive \? 'revive' : 'standup', 1, 1,/, 'card: provisioning → stage 1 current');
176
- assert.match(SCRIPT, /if \(s === 'requested'\) \{\n\s*setProgress\(progressMarkup\(0, 0,/, 'done panel: requested → stage 0 current');
176
+ // part 03 (task 1004683) has no stages: a filed, running or queued standup is one fact, starting
177
+ assert.match(fn('homeFacts'), /else if \(s === 'requested' \|\| s === 'provisioning' \|\| act === 'provision'\) server = \{ state: 'now', title: 'Server and database starting'/, 'part 03: one fact under way');
177
178
  assert.doesNotMatch(SCRIPT, /Waiting for a runner/, '"runner" is operator vocabulary');
178
179
  assert.doesNotMatch(SCRIPT, /a runner picks it up/, '"runner" is operator vocabulary (retry message)');
179
180
  });
180
181
 
181
182
  /* ── the done panel never lies at the end of its watch, and never opens a door early ── */
182
- test('the 33-minute hand-off keeps a snag honest and keeps its flow', () => {
183
- assert.match(SCRIPT, /if \(ticks > 400\) \{[\s\S]*?if \(live\.instStatus !== 'error'\) \{[\s\S]*?Still working[\s\S]*?watchFlow\)/, 'no "still building" over an error; the strip keeps its vocabulary');
184
- assert.doesNotMatch(SCRIPT, /Still building — this tab will stop watching now/);
185
- assert.match(SCRIPT, /progressMarkup\(1, 1, m\.slice\(5\), 'isErr', watchFlow\)/, 'the session-expiry message keeps its flow too');
183
+ test('part 03\'s watch never gives up on a timer, and a read that is gone is said once (task 1004683)', () => {
184
+ assert.doesNotMatch(SCRIPT, /ticks > 400|this tab will stop watching now/, 'no 33-minute hand-off: no timer stands in for a fact');
185
+ assert.match(SCRIPT, /if \(m\.indexOf\('gone:'\) === 0\) \{[\s\S]{0,120}live\.gone = m\.slice\(5\);\n\s*paintFacts\(\);\n\s*stopPolling\(\);/, 'signed out or gone: the sentence says it, and the watch stops');
186
+ assert.match(fn('setupSentence'), /if \(live\.gone === 'missing'\) return \[\{ t: 'This project can’t be seen any more\. It may have been switched off\.' \}\];/);
186
187
  });
187
188
 
188
- test('"Builders can sign in" ticks on verified sign-in, and its door opens only when the project answers (task 1004649)', () => {
189
- // the one-page checklist (task 1004649): the step's tick is the server's 'done';
190
- // the door to the project still waits for a resolving address (task 1004505)
191
- const first = fn('renderStepFirstSignIn');
192
- assert.match(first, /<b>Builders can sign in<\/b>/);
193
- assert.match(first, /if \(live\.manifest !== 'done'\) \{\n\s*return '<li class="slocked"/, 'locked until the platform has seen sign-in work');
194
- assert.match(first, /var open = live\.instStatus === 'active' && live\.addrReady;/, 'the door gates on status and a resolving address');
195
- assert.match(first, /\(open \? '<a class="ghbtn" href="https:\/\/' \+ esc\(domain\)/, 'and only then links the address');
196
- // the hall is the "is alive" block's way in; the step survives only for the local hall
197
- const hall = fn('renderStepEnterHall');
198
- assert.match(hall, /if \(domain\) return '';/);
199
- assert.match(hall, /bongos dev/);
189
+ test('"Builders can sign in" ticks on verified sign-in, and the door into the project opens only when it answers (tasks 1004649, 1004683)', () => {
190
+ // the fact's tick is the server's 'done'; the address is named, and the Enter Genesis door
191
+ // opened, only on a resolving address (task 1004505)
192
+ const gh = fn('ghFacts');
193
+ assert.match(gh, /var signIn = \{ key: 'sign_in', title: 'Builders can sign in' \};/);
194
+ assert.match(gh, /if \(live\.manifest === 'done' \|\| ghStep\('sign_in'\) === 'done'\) \{ signIn\.state = 'done'; signIn\.sub = dom && live\.addrReady \? 'Tested on ' \+ dom : ''; \}/, 'done only once the platform has seen sign-in work');
195
+ assert.match(fn('paintAlive'), /addressReady\(inst\) && inst\.last_verified_up === true/, 'the door gates on a resolving, answering address');
196
+ // the hall is entered through the Enter Genesis door; no local-hall one-liner is left on part 03
197
+ assert.doesNotMatch(SCRIPT, /bongos dev|Enter your builders hall/);
200
198
  });
201
199
 
202
- test('the domainless build is standing, not "Live"', () => {
203
- assert.match(SCRIPT, /progressMarkup\(-1, 2,\n\s*'The build finished — your project is standing, but it has no address yet/, 'two stages done, third unlit');
204
- assert.doesNotMatch(SCRIPT, /has no address yet, so sign-in can’t be wired up[^']*', 'isLive'/, 'no green skin on a project that answers nowhere');
200
+ test('a domainless project is standing, never "alive": GitHub waits for an address (task 1004683)', () => {
201
+ assert.match(fn('setupSentence'), /if \(!hostOk\(inst\.domain\)\) return \[\{ t: 'It has no address yet, so GitHub has to wait\. Attach one any time from ' \}, \{ t: 'its own page', href: manageHref\(\) \}/);
202
+ assert.match(fn('homeFacts'), /if \(dom && s === 'active' && act !== 'teardown'\) \{/, '"Address answers" is never reached without an address');
205
203
  });
206
204
 
207
- test('the fallback checklist carries every human step the canonical plan has', () => {
208
- assert.match(SCRIPT, /FALLBACK_HUMAN_KEYS = \['github-repo', 'oauth-app', 'device-flow', 'app-install', 'first-sign-in', 'enter-hall'\]/);
205
+ test('part 03\'s facts name every GitHub step the canonical plan has (task 1004683)', () => {
206
+ assert.match(SCRIPT, /var FACT_PLAN_STEP = \{ approve: 'oauth-app', install: 'app-install', sign_in: 'first-sign-in', device_flow: 'device-flow' \};/);
207
+ assert.match(HUB, /data-onboard-step="github-repo"/);
208
+ assert.match(HUB, /data-onboard-step="enter-hall"/);
209
209
  });
210
210
 
211
211
  /* ── the manifest leg: acknowledges GitHub, offers a way out, survives a reload ── */
212
212
  test('the manifest step acknowledges code_received, offers a restart after a stall, and rides the CREATED record', () => {
213
213
  // 'converting' is the same stage — it used to fall through to "waiting for approval" (task 1004572)
214
- assert.match(SCRIPT, /if \(live\.manifest === 'code_received' \|\| live\.manifest === 'converting'\)/, 'GitHub’s half is acknowledged');
215
- assert.match(SCRIPT, /Approved on GitHub\. The platform is placing the app’s keys on your project/);
214
+ assert.match(SCRIPT, /else if \(live\.manifest === 'code_received' \|\| live\.manifest === 'converting'\) \{ approve\.state = 'now'; approve\.sub = 'Approved\. The platform is collecting the app’s keys\.'; \}/, 'GitHub’s half is acknowledged');
216
215
  // the way back is the SAME attempt, on show from the start — never a new link after 90s (task 1004652)
217
- assert.match(SCRIPT, /data-action="reopen-github">No GitHub tab\? Reopen GitHub/, 'a waiting step can reopen GitHub');
216
+ assert.match(SCRIPT, /approve\.state = 'you'; approve\.act = 'reopen';/, 'a waiting fact can reopen GitHub');
218
217
  assert.doesNotMatch(SCRIPT, /manifestStale|> 90000/, 'no 90-second restart door left');
219
218
  assert.match(SCRIPT, /function rememberManifest\(stateToken, status, appSlug\)/);
220
219
  assert.match(SCRIPT, /rememberManifest\(d\.state, 'pending'\)/, 'the token is remembered when GitHub is opened');
@@ -231,7 +230,7 @@ test('the wizard’s 401 recoveries keep the repo scope they already granted', (
231
230
  const plain = (SCRIPT.match(/\/auth\/web\/start\?return=\/projects%3Fview%3Dnew'\)/g) || []).length;
232
231
  assert.equal(plain, 0, 'no wizard redirect drops the scope');
233
232
  assert.ok((SCRIPT.match(/\/auth\/web\/start\?return=\/projects%3Fview%3Dnew&repos=1'\); throw new Error\('signing in'\)/g) || []).length >= 2, 'create + manifest');
234
- assert.match(SCRIPT, /projects%3Fview%3Dnew&amp;repos=1">sign in again<\/a> to keep watching/, 'the progress watch');
233
+ assert.match(SCRIPT, /\{ t: 'Sign in again', href: API \+ '\/auth\/web\/start\?return=\/projects%3Fview%3Dnew&repos=1' \}, \{ t: ' to keep watching\.' \}/, 'the progress watch');
235
234
  // every hub sign-in door asks for repo access since task 1004549, so signInAgain does too
236
235
  assert.match(fn('signInAgain'), /\(currentView === 'new' \? '\/projects%3Fview%3Dnew' : '\/projects'\) \+ '&amp;repos=1'/, 'signInAgain from the wizard');
237
236
  });
@@ -544,21 +543,23 @@ test('an unmapped create failure gets a plain sentence with the raw text folded
544
543
  });
545
544
 
546
545
  test('a manifest error says a plain sentence, folds the route text, and sends a domainless project to its address', () => {
547
- const steps = fn('renderStepOauthApp');
548
- assert.doesNotMatch(steps, /'GitHub setup hit a snag' \+ \(live\.manifestMsg \? ': ' \+ esc\(live\.manifestMsg\)/, 'the raw route text is no longer the headline');
549
- assert.match(steps, /else if \(live\.manifestCode === 'domain_required'\) snag = 'This project needs an address first — attach one from ' \+ projectPageLink\('its own page'\)/,
546
+ const steps = fn('ghFacts');
547
+ assert.doesNotMatch(steps, /'GitHub setup hit a snag'/, 'the raw route text is no longer the headline');
548
+ assert.match(steps, /else if \(live\.manifestCode === 'domain_required'\) \{ approve\.sub = 'It needs an address first\. Attach one on its own page\.'; approve\.act = 'manage';/,
550
549
  'the address lives on the project’s own page since task 1003355 — the list would be a dead end');
551
- assert.match(fn('projectPageLink'), /instanceId \? '\?view=manage&id=' \+ encodeURIComponent\(instanceId\) : '\?view=mine'/,
550
+ assert.match(fn('manageHref'), /return instanceId \? '\?view=manage&id=' \+ encodeURIComponent\(instanceId\) : '\?view=mine';/,
552
551
  'and it is named by id whenever the flow has one');
553
- // every done-panel pointer that names a CONTROL goes the same way; the ones
554
- // that only say "watch it there" still name the list, which does show it
555
- assert.equal((SCRIPT.match(/projectPageLink\(/g) || []).length, 8,
556
- 'the helper and its seven callers: the locked step, the domain_required snag, the domainless standup, the snagged retry, the torn-down revive, the extras pointer (task 1002342), and the invite pointer (task 1002346)');
552
+ // every part 03 pointer that names a CONTROL goes the same way (task 1004683: drawn as
553
+ // nodes through manageHref); the invite pointer still rides projectPageLink's markup
554
+ assert.equal((SCRIPT.match(/projectPageLink\(/g) || []).length, 2, 'the helper and the invite pointer (task 1002346)');
555
+ assert.equal((SCRIPT.match(/manageHref\(\)/g) || []).length, 6,
556
+ 'its definition, projectPageLink, the switched-off sentence, the no-address sentence, the extras pointer, and a fact\'s manage door (a stopped standup\'s retry, the domain_required snag)');
557
557
  for (const dead of ['Retry it \\(and see the technical details\\) from <a href="\\?view=mine">',
558
558
  'Bring it back any time from <a href="\\?view=mine">']) {
559
559
  assert.doesNotMatch(SCRIPT, new RegExp(dead), 'a control the list no longer has must not be pointed at there');
560
560
  }
561
- assert.match(steps, /\(raw \? '<details><summary>Technical details<\/summary><p>' \+ esc\(raw\) \+ '<\/p><\/details>' : ''\)/);
561
+ assert.match(steps, /approve\.sub = 'GitHub setup didn’t start\. Nothing was changed on GitHub\.'; approve\.tech = live\.manifestMsg \|\| '';/, 'the route text folds under Technical details');
562
+ assert.match(fn('factNode'), /det\.appendChild\(textNode\('summary', null, 'Technical details'\)\);\n\s*det\.appendChild\(textNode\('p', null, r\.tech\)\);/, 'as text, never markup');
562
563
  assert.match(SCRIPT, /e\.code = \(d\.error && d\.error\.code\) \|\| '';/, 'the route code travels with the error');
563
564
  assert.match(SCRIPT, /live\.manifestCode = String\(\(err && err\.code\) \|\| ''\);/);
564
565
  assert.equal((SCRIPT.match(/manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null/g) || []).length, 2, 'both resets carry the code (and, since task 1004572, the failure stage)');
@@ -613,20 +614,15 @@ test('teardown copy is state-aware: a queued project is told nothing was built',
613
614
  assert.doesNotMatch(truth, /status === 'error'|status === 'provisioning'/, 'error and provisioning keep today’s text — a failed run may already have built things');
614
615
  });
615
616
 
616
- test('founder voice on the Device Flow step and the manual OAuth fallback', () => {
617
- const df = fn('renderStepDeviceFlow');
618
- // the mockup's words (task 1004649): one title, in every branch
619
- assert.match(df, /var title = '<b>Command-line sign-in is on<\/b>';/, 'the outcome-first title');
620
- assert.doesNotMatch(df, /<b>Enable Device Flow — one checkbox<\/b>/);
621
- assert.match(df, /the <code style="font-family:var\(--font-mono\)">bongos setup<\/code> command builders run on their own machine/, 'the command is glossed, not bare-coded');
622
- // GitHub has no manifest field and no API for device flow (task 1004649's spike), so the
623
- // step is OPTIONAL by design and never holds the project back
624
- assert.equal((df.match(/Optional — turn it on later\./g) || []).length, 2, 'the not-yet branches say it is optional');
625
- assert.equal((df.match(/class="[^"]*soptional/g) || []).length, 3, 'every branch is marked optional, so "connected" never waits on it');
626
- assert.equal((df.match(/<strong>Enable Device Flow<\/strong>/g) || []).length, 2, 'GitHub’s own checkbox label stays verbatim in the instructions');
627
- const manual = fn('manualOauthDetails');
628
- assert.match(manual, /The platform team places the generated credentials; this step ticks itself once they land\.<\/p><\/details>/);
629
- assert.doesNotMatch(manual, /auth_configured|healthz/, 'no JSON health check for a founder to perform');
617
+ test('founder voice on command-line sign-in, and no manual OAuth chore on the page (task 1004683)', () => {
618
+ const gh = fn('ghFacts');
619
+ // the mockup's words (setup-status.html): one title, marked optional
620
+ assert.match(gh, /var device = \{ key: 'device_flow', title: 'Command-line sign-in', opt: true \};/, 'the outcome-first title, optional by design');
621
+ assert.match(gh, /device\.sub = settings \? \['Turn it on later in ', \{ t: 'the app settings', href: settings \}, '\.'\] : 'Turn it on later in the app settings\.';/, 'one plain line, linking the app\'s own settings once GitHub has made it');
622
+ assert.match(fn('factRequired'), /return !r\.opt;/, 'the optional fact never holds "ready" back');
623
+ // the manual OAuth-app details live behind the platform docs now, not on this page
624
+ assert.doesNotMatch(SCRIPT, /function manualOauthDetails\(|Prefer to create an OAuth app by hand/);
625
+ assert.doesNotMatch(gh, /auth_configured|healthz/, 'no JSON health check for a founder to perform');
630
626
  });
631
627
 
632
628
  test('repo visibility: the public line stays one line, then the door or the reason — the mechanism lives in the fold', () => {
@@ -657,15 +653,13 @@ test('a failed manage read offers a retry; the no-address sentence offers a door
657
653
  });
658
654
 
659
655
  /* ── accessibility follow-ups (task 1003246) ── */
660
- test('the done panel’s progress region is stable, change-only, and says each stage’s state', () => {
661
- assert.match(HUB, /<div id="liveProgress" role="status" aria-live="polite"><\/div>/, 'the live region is the static wrapper');
662
- assert.doesNotMatch(fn('progressMarkup'), /aria-live/, 'the rebuilt strip carries no live-region attributes');
663
- assert.match(fn('progressMarkup'), /\(c === 'now' \? ' aria-current="step"' : ''\)/, 'the current stage is marked');
664
- assert.match(fn('progressMarkup'), /<span class="vh">, ' \+ word \+ '<\/span>/, 'each stage says its state to a screen reader');
665
- assert.match(fn('setProgress'), /if \(!el \|\| html === lastProgHtml\) return;/, 'an identical tick performs no DOM write');
666
- assert.doesNotMatch(SCRIPT, /\$\('liveProgress'\)\.innerHTML =/, 'every write goes through setProgress');
667
- assert.doesNotMatch(fn('renderProgress'), /el\.innerHTML =/, 'renderProgress included');
668
- assert.match(SCRIPT, /lastStepsHtml = '';\n\s*lastProgHtml = '';/, 'starting another project resets both guards');
656
+ test('part 03 speaks through one stable live region, change-only, and each fact says its state (task 1004683)', () => {
657
+ assert.match(HUB, /<p class="setNow" id="setupNow" role="status" aria-live="polite"><\/p>/, 'the live region is the static sentence');
658
+ assert.match(fn('paintFacts'), /if \(key !== sentenceSaid\) \{/, 'an identical read performs no write to it');
659
+ assert.match(fn('factNode'), /t\.appendChild\(textNode\('span', 'vh', ', ' \+ FACT_SAID\[r\.state\]\)\);/, 'each fact says its state to a screen reader');
660
+ assert.match(fn('factNode'), /d\.setAttribute\('aria-hidden', 'true'\);/, 'the dot is for the eye only');
661
+ assert.doesNotMatch(HUB, /id="liveProgress"|id="hopStatus"/, 'the old strip and its echo are gone');
662
+ assert.match(SCRIPT, /factsDrawn = \{\}; sentenceSaid = '';/, 'starting another project resets both guards');
669
663
  assert.match(COSMOS, /\.vh\{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset\(50%\);white-space:nowrap;\}/, 'the visually-hidden utility is in cosmos');
670
664
  assert.match(fn('stripMarkup'), /role="status" aria-live="polite"/, 'the mine-card strip is left alone this pass');
671
665
  });
@@ -747,25 +741,30 @@ test('the done panel re-learns the GitHub-App leg from the instance read, yieldi
747
741
  assert.match(sp, /var ga = data\.github_app;\n\s*if \(ga && ga\.status && !manifestPollTimer\) \{/, 'seeded from the same 5s read; a running manifest poll owns live.manifest');
748
742
  assert.match(sp, /var seeded = ga\.status === 'done' \? 'done' : ga\.status;/, 'the status route’s vocabulary, verbatim');
749
743
  assert.match(sp, /if \(ga\.app_slug\) live\.appSlug = ga\.app_slug;\n\s*live\.manifest = seeded;/);
750
- assert.match(sp, /if \(seedChanged\) \{ rememberManifest\(undefined, live\.manifest, live\.appSlug \|\| undefined\); renderSteps\(\); \}/, 'change-only: an identical tick neither rewrites the bridge nor re-renders');
744
+ assert.match(sp, /if \(seedChanged\) rememberManifest\(undefined, live\.manifest, live\.appSlug \|\| undefined\);/, 'change-only: an identical tick never rewrites the bridge');
751
745
  const seedAt = sp.indexOf('var ga = data.github_app;');
752
- const statusAt = sp.indexOf("if (inst && (inst.status !== live.instStatus || addressReady(inst) !== live.addrReady))");
753
- assert.ok(seedAt > 0 && seedAt < statusAt, 'the manifest seed lands before the status re-render, so one renderSteps() paints both');
746
+ const paintAt = sp.indexOf('live.inst = inst || null; live.readAt = Date.now();');
747
+ assert.ok(seedAt > 0 && seedAt < paintAt, 'the manifest seed lands before the facts are redrawn, so one paintFacts() paints both');
754
748
  /* the bridge moved from enter() to enterCreated() when task 1003503 split the
755
749
  created-record branch out — same seed, same place in the same flow */
756
750
  assert.match(fn('enterCreated'), /if \(created\.manifest\) \{ live\.manifest = created\.manifest; live\.appSlug = created\.app_slug \|\| null; \}/, 'the sessionStorage bridge stays as the pre-first-tick seed');
757
751
  });
758
752
 
759
753
  /* ── a settings push is named on both surfaces, by state, with no duration promise (task 1003140) ── */
760
- test('a queued or running settings push is named on the card strip AND the done panel — by state, no "briefly"', () => {
761
- assert.equal((SCRIPT.match(/settings: \{ names: \['Change saved', 'Restarting to apply it', 'Applied'\], cls: '' \}/g) || []).length, 2,
762
- 'FLOWS (card) and PROG_FLOWS (done panel) both carry the settings flow');
754
+ test('a queued or running settings push is named on the card strip AND part 03 — by state, no "briefly"', () => {
755
+ assert.equal((SCRIPT.match(/settings: \{ names: \['Change saved', 'Restarting to apply it', 'Applied'\], cls: '' \}/g) || []).length, 1,
756
+ 'FLOWS (card) carries the settings flow; part 03 has no stage strip (task 1004683)');
763
757
  const strip = fn('stripFor');
764
758
  assert.match(strip, /a === 'settings-apply'/, 'the card strip names the intent');
765
759
  assert.match(strip, /A settings change is queued — this card updates when it starts\./, 'pending: queued, not "restarting"');
766
760
  assert.match(strip, /Applying a settings change — the project restarts to pick it up\./, 'running: the restart is said plainly');
767
- const prog = fn('renderProgress');
768
- assert.match(prog, /oi\.action === 'settings-apply'/, 'the done panel has the same branch — a restarting project is not read as "isn’t answering"');
769
- assert.match(prog, /A settings change is queued — this panel updates when it starts\./);
770
- assert.doesNotMatch(strip + prog, /briefly|shortly|soon\b|minute|second/i, 'no wall-clock or duration promise (F16)');
761
+ const prog = fn('homeFacts');
762
+ assert.match(prog, /act === 'settings-apply'/, 'part 03 has the same branch — a restarting project is not read as "isn’t answering"');
763
+ assert.match(prog, /oi\.state === 'running'/, 'by state');
764
+ assert.match(prog, /'A settings change is queued\. It restarts to apply it\.'/);
765
+ // the settings branch only: part 03's "Usually a few minutes." for a fresh standup is the
766
+ // owner's approved mockup word (setup-status.html, 2026-10-09), not a settings promise
767
+ const settingsBranch = prog.slice(prog.indexOf("act === 'settings-apply'"), prog.indexOf("else if (s === 'active') server ="));
768
+ assert.ok(settingsBranch.length > 40, 'the settings branch is found');
769
+ assert.doesNotMatch(strip + settingsBranch, /briefly|shortly|soon\b|minute|second/i, 'no wall-clock or duration promise (F16)');
771
770
  });
@@ -178,19 +178,14 @@ test('the empty state points a new owner at the wizard view — never at a hall'
178
178
  assert.doesNotMatch(body, /Enter the Hall/i);
179
179
  });
180
180
 
181
- test('the manual-repo step states placement and visibility — the owner never has to ask', async () => {
182
- // QA walk F7 (task 1002702 → 1002721, owner firsthand): the "Create the empty
183
- // GitHub repo" step never said WHERE the repo lands, and the owner had to ask
184
- // "is there somewhere specific I should set it up?" mid-walk. The step must
185
- // say the repo belongs at the exact prefilled owner/name (changing GitHub's
186
- // Owner dropdown strands the build watching the wrong address) and must state
187
- // the visibility choice up front, before GitHub's own radio is the first
188
- // mention.
181
+ test('part 03 never asks the founder to make the repo by hand: the platform makes it at Create (task 1004683)', async () => {
182
+ // QA walk F7 (task 1002702 → 1002721) once needed the "Create the empty GitHub repo" step
183
+ // to say where the repo lands. Since GitHub in one trip (task 1004649) the platform makes
184
+ // the empty repo at Create (ensureFreshRepo), so that step and its adopt / auto-create
185
+ // variants are gone from part 03 (the design of record setup-status.html).
189
186
  const { body } = await request('/projects', 'game.test.local');
190
- assert.match(body, /Owner box/, 'the step must name GitHub’s Owner box and say to leave it as prefilled');
191
- assert.match(body, /exact address|exactly[^<]*repoLabel|land at exactly/,
192
- 'the step must say the build watches the exact owner/name');
193
- assert.match(body, /public or private/i, 'visibility must be stated in our copy, not first seen on GitHub');
187
+ assert.doesNotMatch(body, /Create the empty GitHub repo|function renderStepGithubRepo\(|Owner box/, 'the pre-one-trip repo step is gone');
188
+ assert.match(body, /<div id="ghFresh" class="wizField" data-onboard-step="github-repo" hidden>/, 'the new repo part 02 makes at Create is the plan\'s github-repo step');
194
189
  });
195
190
 
196
191
  test('teardown copy states what the runner actually does (F17)', async () => {
@@ -213,101 +208,47 @@ test('teardown copy states what the runner actually does (F17)', async () => {
213
208
  assert.doesNotMatch(body, /database (is|will be) (deleted|dropped|destroyed|removed)/i);
214
209
  });
215
210
 
216
- test('the done panel reads the open intent — a queued revive is never told to start another (F22)', async () => {
211
+ // part 03's Its home (task 1004683): three facts read off the owner's instance read
212
+ const homeFactsOf = (body) => (body.match(/function homeFacts\(inst, now\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
213
+
214
+ test('Its home reads the open intent — a queued revive is starting, never told to start another (F22)', async () => {
217
215
  const { body } = await request('/projects', 'game.test.local');
218
- const fn = (body.match(/function renderProgress\(inst\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
219
- assert.ok(fn, 'renderProgress() must be extractable');
220
- assert.match(fn, /open_intent/, 'the strip is chosen from the open intent, not status alone');
221
- assert.match(fn, /revive/, 'a queued revive on a torn-down instance gets the revive vocabulary');
216
+ const fn = homeFactsOf(body);
217
+ assert.ok(fn, 'homeFacts() must be extractable');
218
+ assert.match(fn, /open_intent/, 'the server fact is chosen from the open intent, not status alone');
219
+ assert.match(fn, /act === 'provision'\) server = \{ state: 'now', title: 'Server and database starting'/, 'a queued standup or revive is starting');
222
220
  // a requested instance ALWAYS has its provision intent open — the status
223
221
  // branch must win there or it goes dead and diverges from the mine card
224
- assert.match(fn, /!== 'requested'/, 'requested speaks its status, not its (always-present) intent');
222
+ assert.match(fn, /s !== 'requested'/, 'requested speaks its status, not its (always-present) intent');
225
223
  assert.match(body, /inst\.open_intent = data\.open_intent \|\| null/, 'the watch hands the intent through');
226
- // teardown speaks its own stage names in the done panel, in its own palette
227
- assert.match(fn, /'teardown'/, 'the teardown flow is used');
228
- assert.match(body, /\.prog\.flowDown/, 'the .prog strip has the teardown palette (transcribed from .strip.flowDown)');
229
- });
230
-
231
- test('the done panel lights Live on a verified answer, never on status alone (task 1002895)', async () => {
232
- const { body } = await request('/projects', 'game.test.local');
233
- const fn = (body.match(/function renderProgress\(inst\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
234
- assert.ok(fn, 'renderProgress() must be extractable');
235
- assert.match(fn, /last_verified_up/, 'the strip reads the verification overlay, not status alone');
236
- // the three honest rungs: verified, failed probe, and never-probed —
237
- // null is deliberately distinct from false and must not read as a failure
238
- assert.match(fn, /answering at/i, 'verified speaks the list view\'s language');
239
- assert.match(fn, /isn’t answering right now/, 'a failed probe is spoken, never painted green');
240
- assert.match(fn, /checking that/, 'the never-probed instance gets its own calm rung');
241
- // gap row 8: domainless is never probed at all — it states its truth instead
242
- // of promising the hall (sign-in stays locked without an address)
243
- assert.match(fn, /no address yet/, 'the domainless path cannot fall into an unverified hole');
244
- assert.doesNotMatch(fn, /Your project is up/, 'the status-alone headline is dead');
245
- });
246
-
247
- /* ── The done panel's GitHub step (task 1002855) ─────────────────────────────
248
- 'We created <repo> on GitHub for you' named a repo and handed the owner nothing
249
- to click, while the manual-create branch right below it rendered a full button:
250
- the one path where the PLATFORM did the work was the one that stranded them
251
- (owner-reported during the task 1002697 walk). Adopt named a repo unlinked too. ── */
252
-
253
- test('the auto-created repo is linked in the done panel, not merely named', async () => {
254
- const { body } = await request('/projects', 'game.test.local');
255
- const fn = (body.match(/function renderStepGithubRepo\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
256
- assert.ok(fn, 'renderStepGithubRepo() must be extractable');
257
- const auto = (fn.match(/if \(state\.repoAutoCreated\) \{([\s\S]*?)\n \}/) || [, ''])[1];
258
- assert.ok(auto, 'the auto-created branch must be extractable');
259
- assert.match(auto, /We created/, 'this is the copy the owner reported');
260
- // BOTH copies — scaffold landed and not yet landed — carry the link
261
- assert.equal((auto.match(/repoLink/g) || []).length, 2,
262
- 'the landed and pre-landed copies must both link the repo');
263
- assert.doesNotMatch(auto, /repoLabel/,
264
- 'no auto-created copy may still name the repo without linking it');
265
- });
266
-
267
- test('the adopt branch links the repo it names', async () => {
268
- const { body } = await request('/projects', 'game.test.local');
269
- const fn = (body.match(/function renderStepGithubRepo\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
270
- const adopt = (fn.match(/if \(state\.mode === 'adopt'\) \{([\s\S]*?)\n \}/) || [, ''])[1];
271
- assert.ok(adopt, 'the adopt branch must be extractable');
272
- assert.equal((adopt.match(/repoLink/g) || []).length, 2,
273
- 'both adopt copies must link the repo the owner picked');
274
- assert.doesNotMatch(adopt, /repoLabel/, 'adopt may not name a repo unlinked either');
224
+ // a teardown is a calm fact, never painted as a failure
225
+ assert.match(fn, /act === 'teardown' \|\| s === 'tearing_down'\) server = \{ state: 'todo', title: 'Server and database switching off'/);
226
+ assert.doesNotMatch(body, /or start another below/, 'never told to start another');
275
227
  });
276
228
 
277
- test('the auto-created link is a plain inline link, never the .ghbtn call to action', async () => {
229
+ test('"Address answers" ticks on a verified answer, never on status alone (task 1002895)', async () => {
278
230
  const { body } = await request('/projects', 'game.test.local');
279
- const fn = (body.match(/function renderStepGithubRepo\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
280
- const decl = (fn.match(/var repoLink = [^\n]*/) || [''])[0];
281
- assert.match(decl, /<a href="/, 'the link is built as an anchor');
282
- assert.match(decl, /esc\(repoUrl\)/, 'the href goes through the page escaper');
283
- assert.match(decl, /rel="noopener"/, 'a target=_blank link carries rel=noopener');
284
- assert.doesNotMatch(decl, /ghbtn/, 'there is nothing to DO here — no button treatment');
285
- // the manual-create branch keeps its real .ghbtn action, and an anchor nested
286
- // inside that anchor would be malformed — so only the two done branches link
287
- assert.match(fn, /class="ghbtn"/, 'the manual-create button survives untouched');
288
- // and the plain link needs a style, or it falls back to the browser's blue
289
- assert.match(body, /\.humanSteps p a\{/, 'the inline link is styled with the page palette');
290
- });
291
-
292
- test('a draft with no stored repo URL renders no link — never a guessed one', async () => {
231
+ const fn = homeFactsOf(body);
232
+ assert.match(fn, /else if \(inst\.last_verified_up === true\) \{ answers\.state = 'done';/, 'only a verified answer ticks it');
233
+ // the three honest rungs: verified, failed probe, and never-probed — null is
234
+ // deliberately distinct from false and must not read as a failure
235
+ assert.match(fn, /else if \(inst\.last_verified_up === false\) \{ answers\.state = 'now'; answers\.sub = 'Not answering yet\. We keep checking\.';/, 'a failed probe is spoken, never painted green');
236
+ assert.match(fn, /answers\.sub = 'Checking that it answers\.';/, 'the never-probed instance gets its own calm rung');
237
+ // gap row 8: domainless is never probed at all — the fact stays not reached
238
+ assert.match(fn, /if \(dom && s === 'active' && act !== 'teardown'\) \{/, 'no address: the fact is never under way');
239
+ assert.doesNotMatch(body, /Your project is up/, 'the status-alone headline is dead');
240
+ });
241
+
242
+ test('the repo is named as text from the server read, never a guessed link (tasks 1002855, 1004683)', async () => {
243
+ // Part 03 names the repo its app installs on in the Install fact: the owner read's
244
+ // github_setup.repo (github-setup.js targetRepo, strict), re-checked by seedSetup, or the
245
+ // page's own owner/repo. Never a link the page built: nothing on it is the founder's to do.
293
246
  const { body } = await request('/projects', 'game.test.local');
294
- const src = (body.match(/(function repoLinkFor\(\)\s*\{[\s\S]*?\n \})/) || [, ''])[1];
295
- assert.ok(src, 'repoLinkFor() must be extractable');
296
- const urlFor = (state) => new Function('state', `${src}\nreturn repoLinkFor();`)(state);
297
-
298
- // the real auto-created path: GitHub's own html_url, kept on the draft
299
- assert.equal(
300
- urlFor({ mode: 'greenfield', owner: 'someone', repo: 'thing', repoAutoCreated: true, repoUrl: 'https://github.com/someone/thing' }),
301
- 'https://github.com/someone/thing');
302
- // a draft written before task 1002855 has no stored URL — it degrades to none
303
- assert.equal(
304
- urlFor({ mode: 'greenfield', owner: 'someone', repo: 'thing', repoAutoCreated: true }), '');
305
- // adopt has no server-supplied URL: the repo the owner picked IS the address
306
- assert.equal(urlFor({ mode: 'adopt', owner: 'someone', repo: 'thing' }), 'https://github.com/someone/thing');
307
- // nothing named yet, and anything that isn't a github.com URL, link to nothing
308
- assert.equal(urlFor({ mode: 'adopt', owner: '', repo: '' }), '');
309
- assert.equal(urlFor({ mode: 'greenfield', owner: 'a', repo: 'b', repoUrl: 'javascript:alert(1)' }), '');
310
- assert.equal(urlFor({ mode: 'greenfield', owner: 'a', repo: 'b', repoUrl: 'https://evil.example/x' }), '');
247
+ const fn = (body.match(/function repoName\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
248
+ assert.ok(fn, 'repoName() must be extractable');
249
+ assert.match(fn, /if \(live\.repo\) return live\.repo;/);
250
+ assert.match(body, /var repo = typeof more\.repo === 'string' && \/\^\[A-Za-z0-9\]\[A-Za-z0-9-\]\{0,38\}\\\/\[A-Za-z0-9\._-\]\{1,100\}\$\/\.test\(more\.repo\)/, 'the server\'s repo is re-checked before it is shown');
251
+ assert.doesNotMatch(body, /function repoLinkFor\(|class="ghbtn"/, 'no guessed repo link, no GitHub pill');
311
252
  });
312
253
 
313
254
  test('the create-repo response keeps the html_url it already returned', async () => {
@@ -448,10 +389,12 @@ test('the address row surfaces the manual-DNS note the runner recorded (F16)', a
448
389
  assert.match(body, /inst\.dns_note/, 'the card reads the dns_note the runner writes');
449
390
  });
450
391
 
451
- test('the watch give-up hands the owner their projects view — "the hall has it" is dead', async () => {
452
- // the wizard (and its 33-minute watch cap) lives on /projects now
392
+ test('the watch never gives up on a timer, and "the hall has it" is dead', async () => {
393
+ // the wizard lives on /projects; since task 1004683 its watch has no 33-minute cap — it
394
+ // stops when the facts say nothing more will change (setupSettled), never on a tick count
453
395
  const { body } = await request('/projects', 'game.test.local');
454
- assert.match(body, /your projects page/, 'the give-up path must hand the owner the projects view');
396
+ assert.doesNotMatch(body, /this tab will stop watching now|ticks > 400/, 'no timer stands in for a fact');
397
+ assert.match(body, /var terminal = setupSettled\(\);/, 'the facts decide when the watch stops');
455
398
  assert.doesNotMatch(body, /the hall has it/, 'the false poll-stop copy must be gone');
456
399
  });
457
400