@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.
- package/.bongos-core.json +57 -47
- package/docs/copy-inventory.md +368 -366
- package/docs/copy-registry.json +475 -457
- package/docs/design/project-startup-direction.md +14 -0
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +10 -3
- package/docs/page-readings.json +1023 -977
- package/modules/lifecycle/check-rollup.js +11 -1
- package/modules/lifecycle/github-push.js +3 -7
- package/modules/public-landing/public/projects-hall-live.states.json +3 -2
- package/modules/public-landing/public/projects-setup.states.json +91 -0
- package/modules/public-landing/public/projects.html +535 -804
- package/modules/public-landing/public/projects.probes.json +3 -3
- package/modules/public-landing/public/projects.states.json +1 -1
- package/modules/ui-design/kit/fixtures/provisioning-instances-hall-live.json +22 -2
- package/modules/ui-design/kit/fixtures/provisioning-instances-setup.json +195 -0
- package/modules/ui-design/kit/serve.js +22 -4
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/ship-land.js +2 -2
- package/src/module-api.js +1 -1
- package/tests/cosmos_token_contrast.mjs +10 -18
- package/tests/github_handback_any_device.mjs +4 -3
- package/tests/github_one_trip.mjs +45 -47
- package/tests/github_org_account.mjs +2 -2
- package/tests/github_pick_up.mjs +40 -29
- package/tests/github_push_merge_driver.mjs +46 -0
- package/tests/onboard.mjs +13 -7
- package/tests/project_invite_ui.mjs +6 -3
- package/tests/project_modules_ui.mjs +3 -3
- package/tests/projects_hub.mjs +8 -9
- package/tests/projects_hub_cloud_hop.mjs +77 -125
- package/tests/projects_hub_dns_ready.mjs +42 -51
- package/tests/projects_hub_pre_uat.mjs +68 -69
- package/tests/public_landing_projects.mjs +45 -102
- package/tests/setup_status_facts.mjs +400 -0
- package/tests/wizard_github_feedback.mjs +90 -87
- package/tests/wizard_one_page.mjs +11 -9
- package/tests/wizard_auto_open.mjs +0 -94
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
by scope (#view-new: no card ground, no progress rail since task 1004504,
|
|
79
79
|
the doors as pills); what lands here is the wizard’s own structure —
|
|
80
80
|
fields on one bottom hairline, the choices as cells of one band, the review
|
|
81
|
-
as a ledger,
|
|
82
|
-
|
|
81
|
+
as a ledger, part 03's checked facts as one list (.setFact). No literal color
|
|
82
|
+
anywhere: every surface is a cosmos token or a
|
|
83
83
|
color-mix over one (C1/C2/C4); no plate, no box shadow, no glow — halos are
|
|
84
84
|
outlines. Naming: the wizard's field wrapper is .wizField — cosmos owns
|
|
85
|
-
.field (the orb map)
|
|
85
|
+
.field (the orb map). Its inputs are
|
|
86
86
|
scoped to the view so the glass
|
|
87
87
|
search pills (#mapQ, #mineQ) keep their own look. */
|
|
88
88
|
.panel{display:none;}
|
|
@@ -486,21 +486,7 @@ body:has(#view-new:not([hidden])) footer.foot{position:relative;z-index:1;backgr
|
|
|
486
486
|
.reassure.on{display:block;}
|
|
487
487
|
.reassure p{margin:0 0 10px;}
|
|
488
488
|
|
|
489
|
-
|
|
490
|
-
acts in GitHub's UI; we fill in the blanks). GitHub's brand black used to
|
|
491
|
-
be pinned here; in the world every GitHub door is the INVERTED pill the
|
|
492
|
-
gate's sign-in wears — ink on the ground — one grammar, still not the
|
|
493
|
-
accent, so a re-brand never paints it. */
|
|
494
|
-
.ghbtn{
|
|
495
|
-
display:inline-flex;align-items:center;justify-content:center;gap:10px;text-decoration:none;cursor:pointer;
|
|
496
|
-
min-height:44px;background:var(--ink);color:var(--bg);border:1px solid var(--ink);border-radius:999px;
|
|
497
|
-
font-family:var(--font-body);font-weight:500;font-size:15px;padding:0 22px;
|
|
498
|
-
box-shadow:none;transition:background .15s ease;
|
|
499
|
-
}
|
|
500
|
-
.ghbtn:hover{background:color-mix(in srgb,var(--ink) 88%,var(--bg));transform:none;}
|
|
501
|
-
.ghbtn:active{transform:none;}
|
|
502
|
-
.ghbtn .gh{width:17px;height:17px;fill:currentColor;}
|
|
503
|
-
.humanSteps .ghbtn{min-height:40px;font-size:14.5px;padding:0 18px;margin-top:12px;}
|
|
489
|
+
|
|
504
490
|
|
|
505
491
|
/* the doors under a step: back on the left, the primary on the right; the
|
|
506
492
|
About step's skip beside its next (task 1002334) wraps rather than crushes */
|
|
@@ -510,81 +496,61 @@ body:has(#view-new:not([hidden])) footer.foot{position:relative;z-index:1;backgr
|
|
|
510
496
|
/* the review: a ledger of what is about to be filed — label · value rows on
|
|
511
497
|
soft hairlines, the last row closed */
|
|
512
498
|
|
|
513
|
-
/* ──
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
.
|
|
518
|
-
.
|
|
519
|
-
.
|
|
520
|
-
|
|
521
|
-
.
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
.
|
|
526
|
-
.
|
|
527
|
-
.
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
499
|
+
/* ── part 03 after Create (task 1004683; the design of record
|
|
500
|
+
docs/design/mocks/day-night-rooms/setup-status.html): one sentence saying what is
|
|
501
|
+
happening now, then two groups of facts the server checked, Its home and GitHub.
|
|
502
|
+
Each fact is a dot, the fact itself, and on the right when it was checked or what
|
|
503
|
+
it found. No bar, no stage dots, nothing said twice; a fact that is not done keeps
|
|
504
|
+
muted TOKENS, never subtree opacity (task 1002751). The cloud hop opens only when
|
|
505
|
+
asked, below the facts. ── */
|
|
506
|
+
#panel-done:not(.on) .doneBody,#panel-done:not(.on) .again{display:none;}
|
|
507
|
+
#panel-done.on .partSub{display:none;}
|
|
508
|
+
.setNow{margin:10px 0 0;font-size:16px;font-weight:400;line-height:1.5;color:var(--ink-soft);max-width:62ch;min-height:24px;}
|
|
509
|
+
.setNow b{font-weight:500;color:var(--ink);}
|
|
510
|
+
.setNow a{display:inline-block;min-height:24px;line-height:24px;color:var(--link-ink);text-decoration:underline;text-underline-offset:3px;}
|
|
511
|
+
.setFactGrp{margin-top:22px;max-width:680px;}
|
|
512
|
+
.setFactGrp>h3{margin:0 0 4px;font-family:var(--label-face);font-size:var(--label-size);font-weight:400;letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
|
|
513
|
+
.setFact{display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:2px 12px;align-items:center;padding:11px 0;border-top:1px solid var(--rule);}
|
|
514
|
+
.setFact:first-child{border-top:0;}
|
|
515
|
+
.setFact .d{box-sizing:border-box;width:24px;height:24px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:13px;line-height:1;border:1px solid var(--glass-edge);color:var(--ink-soft);}
|
|
516
|
+
.setFact .t{font-size:15px;font-weight:400;line-height:1.4;color:var(--ink-soft);min-width:0;overflow-wrap:anywhere;}
|
|
517
|
+
.setFact .t small{display:block;margin-top:2px;font-size:12.5px;line-height:1.45;color:var(--ink-faint);}
|
|
518
|
+
.setFact .t small a{display:inline-block;min-height:24px;padding:3px 0;line-height:18px;color:var(--link-ink);text-decoration:underline;text-underline-offset:3px;}
|
|
519
|
+
.setFact .r{font-size:12.5px;color:var(--ink-faint);text-align:right;white-space:nowrap;}
|
|
520
|
+
.setFact .optional{margin-left:8px;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
|
|
521
|
+
.setFact.done .d{background:color-mix(in srgb,var(--ok) 12%,transparent);color:var(--ok);border-color:var(--ok);}
|
|
522
|
+
.setFact.done .t,.setFact.now .t,.setFact.you .t{color:var(--ink);}
|
|
523
|
+
.setFact.now .d{border:2px solid var(--accent);}
|
|
524
|
+
.setFact.you .d{background:color-mix(in srgb,var(--accent) 16%,transparent);border-color:color-mix(in srgb,var(--accent) 60%,transparent);color:var(--ink);}
|
|
525
|
+
.setFact.bad .d{background:color-mix(in srgb,var(--err) 10%,transparent);color:var(--err);border-color:var(--err);}
|
|
526
|
+
.setFact.bad .t{color:var(--err);}
|
|
527
|
+
.setFact .acts{grid-column:2 / 4;display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:8px;}
|
|
528
|
+
.setFact .acts .btn{min-height:44px;}
|
|
529
|
+
.setFact .acts .linklike{min-height:24px;padding:3px 0;font-weight:400;}
|
|
530
|
+
.setFact .acts details{flex-basis:100%;font-size:13px;color:var(--ink-soft);}
|
|
531
|
+
.setFact .acts details p{margin:6px 0 0;overflow-wrap:anywhere;}
|
|
532
|
+
/* the account choice sits beside Connect GitHub, inside the Approve fact (task 1004651) */
|
|
533
|
+
.setFact .acctPick{flex:1 1 220px;max-width:none;}
|
|
534
|
+
#view-new .part .setFact .acctPick .wizField{margin:0;}
|
|
535
|
+
.setFact .acctPick .acctTyped{margin-top:10px;}
|
|
536
|
+
.setFact .acctPick .hint{min-height:0;}
|
|
537
|
+
@media (prefers-reduced-motion:no-preference){
|
|
538
|
+
.setFact.now .d{animation:factBreathe 1.4s ease-in-out infinite;}
|
|
539
|
+
@keyframes factBreathe{0%,100%{border-color:var(--accent)}50%{border-color:color-mix(in srgb,var(--accent) 35%,transparent)}}
|
|
531
540
|
}
|
|
532
|
-
|
|
533
|
-
.
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
.humanSteps p a{color:var(--ink);font-weight:400;text-decoration:underline;text-underline-offset:3px;display:inline-block;padding:3px 0;min-height:24px;line-height:18px;}
|
|
537
|
-
.humanSteps p a:hover{color:var(--ink);}
|
|
538
|
-
/* live checklist states (task 1002694): a step the platform has verified done, and a
|
|
539
|
-
step that can't be performed yet (its prerequisite — usually the domain — is missing) */
|
|
540
|
-
.humanSteps>li.sdone{border-color:var(--rule-soft);background:none;}
|
|
541
|
-
.humanSteps>li.sdone::before{content:"done";text-transform:uppercase;background:none;color:var(--ok);}
|
|
542
|
-
.humanSteps>li.sdone b{color:var(--ink-soft);}
|
|
543
|
-
.humanSteps>li.slocked{border-color:var(--rule-soft);}
|
|
544
|
-
.humanSteps>li.slocked b{color:var(--ink-soft);}
|
|
545
|
-
.humanSteps>li.slocked p{color:var(--ink-faint);}
|
|
546
|
-
.humanSteps>li.slocked::before{color:var(--ink-faint);}
|
|
547
|
-
.humanSteps details{margin-top:10px;}
|
|
548
|
-
.humanSteps summary{cursor:pointer;font-weight:400;font-size:13.5px;color:var(--ink-soft);}
|
|
549
|
-
.humanSteps summary:hover{color:var(--ink);}
|
|
550
|
-
.humanSteps details p{margin-top:8px;}
|
|
551
|
-
.kv{display:flex;align-items:center;gap:10px;margin-top:8px;background:none;border:0;border-top:1px solid var(--rule-soft);border-radius:0;padding:8px 0;font-size:12.5px;}
|
|
552
|
-
.kv .kk{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;font-weight:400;color:var(--ink-faint);flex:0 0 auto;}
|
|
553
|
-
.kv code{font-family:var(--font-mono);font-size:12.5px;color:var(--ink-soft);word-break:break-all;flex:1;}
|
|
554
|
-
.copy{
|
|
555
|
-
flex:0 0 auto;cursor:pointer;min-height:28px;border-radius:999px;background:none;border:1px solid var(--rule);
|
|
556
|
-
font-family:var(--font-body);font-size:12.5px;font-weight:400;color:var(--ink-soft);padding:0 12px;
|
|
557
|
-
transition:border-color .12s ease,color .12s ease;
|
|
541
|
+
@media (max-width:520px){
|
|
542
|
+
.setFact{grid-template-columns:24px minmax(0,1fr);}
|
|
543
|
+
.setFact .r{grid-column:2;text-align:left;white-space:normal;}
|
|
544
|
+
.setFact .acts{grid-column:2;}
|
|
558
545
|
}
|
|
559
|
-
.
|
|
560
|
-
.
|
|
546
|
+
.hopLink{display:inline-block;margin-top:16px;min-height:24px;padding:3px 0;background:none;border:0;cursor:pointer;font-family:var(--font-body);font-size:13px;color:var(--link-ink);text-decoration:underline;text-underline-offset:3px;}
|
|
547
|
+
.hopLink:hover{color:var(--ink);}
|
|
548
|
+
.hopLink[hidden]{display:none;}
|
|
561
549
|
.samePath{font-size:13px;font-weight:400;color:var(--ink-faint);margin-top:18px;}
|
|
562
550
|
.samePath a{display:inline-block;padding:3px 0;min-height:24px;line-height:18px;}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
.
|
|
566
|
-
.autoOpen{display:flex;align-items:flex-start;gap:10px;margin-top:14px;font-size:14px;line-height:1.45;color:var(--ink-soft);cursor:pointer;min-height:24px;}
|
|
567
|
-
.autoOpen input{width:18px;height:18px;margin:1px 0 0;flex:0 0 auto;accent-color:var(--accent);cursor:pointer;}
|
|
568
|
-
|
|
569
|
-
/* ── the done panel's two moments (task 1004578): WAIT — one status line, a
|
|
570
|
-
thin bar and the cloud hop — then ARRIVE — the steps one at a time. The
|
|
571
|
-
panel's data-moment says which; "plain" (a snag, a teardown, a stopped
|
|
572
|
-
watch) is the panel as it always was. While waiting the strip, the locked
|
|
573
|
-
checklist and the later-lines stand down; the sign-in step can be opened
|
|
574
|
-
in place (the one step a founder can do before the project answers). ── */
|
|
575
|
-
.hop{display:none;margin-top:22px;max-width:680px;}
|
|
576
|
-
#panel-done[data-moment="wait"] .hop{display:block;}
|
|
577
|
-
#panel-done[data-moment="wait"] #liveProgress,
|
|
578
|
-
#panel-done[data-moment="wait"] #wizInvite,
|
|
579
|
-
#panel-done[data-moment="wait"] #wizInviteLater,
|
|
580
|
-
#panel-done[data-moment="wait"] #doneModules,
|
|
581
|
-
#panel-done[data-moment="wait"] .samePath,
|
|
582
|
-
#panel-done[data-moment="wait"] .again{display:none;}
|
|
583
|
-
/* part 03 before Create: the heading, its state and the sentence; the rest waits */
|
|
584
|
-
#panel-done:not(.on) .doneBody,#panel-done:not(.on) .again{display:none;}
|
|
585
|
-
.hopStatus{font-size:14px;font-weight:400;line-height:1.45;color:var(--ink-soft);margin:0;min-height:20px;}
|
|
586
|
-
.hopBar{height:3px;background:var(--rule-soft);margin:10px 0 12px;overflow:hidden;}
|
|
587
|
-
.hopBar span{display:block;height:100%;width:0;background:var(--accent);transition:width .8s ease;}
|
|
551
|
+
/* the cloud hop (task 1004578), opened in place by "Play cloud hop while you wait" */
|
|
552
|
+
.hop{margin-top:14px;max-width:680px;}
|
|
553
|
+
.hop[hidden]{display:none;}
|
|
588
554
|
.hopStage{position:relative;border-radius:14px;overflow:hidden;background:var(--space-deep);}
|
|
589
555
|
.hopStage{transition:opacity .5s ease;}
|
|
590
556
|
.hop.hopFading .hopStage{opacity:0;}
|
|
@@ -597,66 +563,13 @@ body:has(#view-new:not([hidden])) footer.foot{position:relative;z-index:1;backgr
|
|
|
597
563
|
.hopScore{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-soft);}
|
|
598
564
|
/* reduced motion: the sky stands still and there is nothing to steer */
|
|
599
565
|
.hop.isStill .hopRow{display:none;}
|
|
600
|
-
/*
|
|
601
|
-
#panel-done[data-moment="arrive"] #liveProgress .stages{display:none;}
|
|
602
|
-
/* the checklist (task 1004648, the mockup's steps): one dot per step — an empty ring,
|
|
603
|
-
a tick in the ok ink once the server has verified it — and every step on show */
|
|
604
|
-
#panel-done .humanSteps{margin-top:18px;}
|
|
605
|
-
#panel-done .humanSteps>li{grid-template-columns:24px 1fr;gap:0 12px;padding:12px 0 14px;border-top:0;}
|
|
606
|
-
#panel-done .humanSteps>li:last-child{border-bottom:0;}
|
|
607
|
-
#panel-done .humanSteps>li::before{content:"";box-sizing:border-box;width:24px;height:24px;padding:0;border-radius:50%;border:1px solid var(--glass-edge);
|
|
608
|
-
display:inline-flex;align-items:center;justify-content:center;font-family:var(--font-body);font-size:13px;letter-spacing:0;}
|
|
609
|
-
#panel-done .humanSteps>li.sdone::before{content:"✓";text-transform:none;color:var(--ok);background:color-mix(in srgb,var(--ok) 12%,transparent);border:1px solid var(--ok);}
|
|
610
|
-
#panel-done .humanSteps>li.slocked::before{border:1px solid var(--rule-soft);}
|
|
611
|
-
/* a stopped step (task 1004652, the mockup's "!" dot): what stopped, and the button that fixes it */
|
|
612
|
-
#panel-done .humanSteps>li.sstopped::before{content:"!";color:var(--err);background:color-mix(in srgb,var(--err) 10%,transparent);border:1px solid var(--err);}
|
|
613
|
-
.humanSteps .linklike{display:block;margin-top:10px;min-height:24px;padding:3px 0;background:none;border:0;cursor:pointer;font-family:var(--font-body);font-size:13.5px;color:var(--link-ink);text-decoration:underline;text-underline-offset:3px;text-align:left;}
|
|
614
|
-
.humanSteps .linklike:hover{color:var(--ink);}
|
|
615
|
-
/* the optional step (command-line sign-in, task 1004649): a dashed ring until it ticks */
|
|
616
|
-
#panel-done .humanSteps>li.soptional:not(.sdone)::before{border-style:dashed;}
|
|
617
|
-
#panel-done .humanSteps b{font-size:15px;line-height:24px;}
|
|
566
|
+
/* every required fact done: "<name> is alive.", the invite, and the Enter Genesis door */
|
|
618
567
|
.alive{margin-top:18px;padding-top:18px;border-top:1px solid var(--rule);}
|
|
568
|
+
.alive[hidden]{display:none;}
|
|
619
569
|
#view-new .aliveH{margin:0;font-family:var(--font-display);font-size:28px;font-weight:200;letter-spacing:-.02em;line-height:1.2;text-transform:none;color:var(--ink);}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
/* ── the standup strip (task 2083), drawn as the lifecycle rail (DESIGN.md):
|
|
624
|
-
three hairlines, a 7px dot, one lit step — no plate, no connector, no
|
|
625
|
-
pulse. The vocabulary is the page's fixed one (F22): filed / standing it
|
|
626
|
-
up / live; isLive in the ok ink, isErr in the error ink, flowDown neutral.
|
|
627
|
-
The marks mirror cosmos's .strip on the derived tokens (task 1002751). ── */
|
|
628
|
-
.prog{background:none;border:0;border-radius:0;padding:0;margin-top:22px;max-width:520px;}
|
|
629
|
-
.prog .stages{display:grid;grid-template-columns:repeat(3,1fr);gap:0 10px;list-style:none;margin:0;}
|
|
630
|
-
.prog .stages li{display:block;position:relative;border-top:1px solid var(--rule);padding:12px 0 0;}
|
|
631
|
-
.prog .stages li:not(:first-child)::before{content:none;}
|
|
632
|
-
.prog .sdot{position:absolute;top:-4px;left:0;width:7px;height:7px;border:1px solid var(--rule);border-radius:50%;background:var(--bg);display:block;}
|
|
633
|
-
.prog .stages li.done{border-top-color:var(--ink-soft);}
|
|
634
|
-
.prog .stages li.done .sdot{border-color:var(--ink-soft);background:var(--ink-soft);}
|
|
635
|
-
.prog .stages li.done .sdot::after{content:none;}
|
|
636
|
-
.prog .stages li.now{border-top-color:var(--accent);}
|
|
637
|
-
.prog .stages li.now .sdot{border-color:var(--accent);background:var(--accent);outline:4px solid color-mix(in srgb,var(--accent) 22%,transparent);}
|
|
638
|
-
.prog .stages li.now .sdot::after{content:none;animation:none;}
|
|
639
|
-
.prog .slbl{display:block;font-family:var(--label-face);font-size:var(--label-size);font-weight:400;letter-spacing:var(--label-space);text-transform:uppercase;line-height:1.3;color:var(--ink-faint);}
|
|
640
|
-
.prog .stages li.now .slbl{color:var(--ink);}
|
|
641
|
-
.prog .stages li.done .slbl{color:var(--ink-soft);}
|
|
642
|
-
.prog .pnote{text-align:left;font-size:13px;font-weight:400;line-height:1.45;color:var(--ink-soft);margin:8px 0 0;min-height:0;}
|
|
643
|
-
.prog .pnote a{color:var(--ink);font-weight:400;text-decoration:underline;text-underline-offset:3px;display:inline-block;padding:3px 0;min-height:24px;line-height:18px;}
|
|
644
|
-
.prog.isLive{background:none;border-color:transparent;}
|
|
645
|
-
.prog.isLive .stages li.done{border-top-color:var(--ok);}
|
|
646
|
-
.prog.isLive .stages li.done .sdot{border-color:var(--ok);background:var(--ok);}
|
|
647
|
-
.prog.isLive .slbl{color:var(--ok);}
|
|
648
|
-
.prog.isLive .stages li.done .slbl{color:var(--ok);}
|
|
649
|
-
.prog.isLive .pnote{color:var(--ok);font-size:13.5px;}
|
|
650
|
-
.prog.isErr{background:none;border-color:transparent;}
|
|
651
|
-
.prog.isErr .stages li.now{border-top-color:var(--err);}
|
|
652
|
-
.prog.isErr .stages li.now .sdot{border-color:var(--err);background:var(--err);outline:4px solid color-mix(in srgb,var(--err) 22%,transparent);}
|
|
653
|
-
.prog.isErr .pnote{color:var(--err);}
|
|
654
|
-
/* teardown's calm neutral (F22), the same vocabulary cosmos's .strip.flowDown speaks */
|
|
655
|
-
.prog.flowDown .stages li.done{border-top-color:var(--ink-soft);}
|
|
656
|
-
.prog.flowDown .stages li.now{border-top-color:var(--ink-soft);}
|
|
657
|
-
.prog.flowDown .stages li.now .sdot{border-color:var(--ink-soft);background:var(--ink-soft);outline:none;}
|
|
658
|
-
.prog.flowDown .slbl{color:var(--ink-faint);}
|
|
659
|
-
.prog.flowDown .stages li.now .slbl,.prog.flowDown .stages li.done .slbl{color:var(--ink-soft);}
|
|
570
|
+
.aliveDoor{display:flex;margin-top:16px;}
|
|
571
|
+
.aliveDoor .btn{min-height:48px;padding:0 28px;}
|
|
572
|
+
|
|
660
573
|
.again{
|
|
661
574
|
display:inline-block;margin-top:14px;font-size:13.5px;font-weight:400;color:var(--ink-soft);
|
|
662
575
|
background:none;border:none;cursor:pointer;font-family:var(--font-body);text-decoration:underline;
|
|
@@ -1815,7 +1728,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1815
1728
|
<!-- visibility stated on EVERY repo path (task 1002721) -->
|
|
1816
1729
|
<div class="hint">We never change it: public stays public, private stays private.</div>
|
|
1817
1730
|
</div>
|
|
1818
|
-
<div id="ghFresh" class="wizField" hidden>
|
|
1731
|
+
<div id="ghFresh" class="wizField" data-onboard-step="github-repo" hidden>
|
|
1819
1732
|
<span>New repository name</span>
|
|
1820
1733
|
<input type="text" id="ghNewName" placeholder="moonbase" autocomplete="off" maxlength="100" aria-label="New repository name">
|
|
1821
1734
|
<!-- visibility is stated on EVERY repo path (task 1002721); updateFreshPreview()
|
|
@@ -2120,21 +2033,30 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2120
2033
|
<p class="hint createWhy" id="createWhy" aria-live="polite"> </p>
|
|
2121
2034
|
<div class="alert" id="createAlert" role="alert"></div>
|
|
2122
2035
|
|
|
2123
|
-
<!-- 03 · Connect GitHub
|
|
2124
|
-
|
|
2125
|
-
|
|
2036
|
+
<!-- 03 · Connect GitHub until Create; then "Getting <name> ready" (task 1004683, the
|
|
2037
|
+
design of record docs/design/mocks/day-night-rooms/setup-status.html): one sentence
|
|
2038
|
+
saying what is happening now, then one list of facts the server checked, in two
|
|
2039
|
+
groups. Every fact's state is read off a server field (paintFacts); the facts are
|
|
2040
|
+
drawn from nodes, never markup from strings. -->
|
|
2126
2041
|
<div class="part waiting" id="panel-done" aria-labelledby="done-part-h">
|
|
2127
|
-
|
|
2042
|
+
<!-- "Connect GitHub" and "opens once your project exists" are the before-Create words;
|
|
2043
|
+
paintFacts() names it "Getting <name> ready" with its count after Create -->
|
|
2044
|
+
<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>
|
|
2128
2045
|
<p class="partSub">One trip to GitHub. Approve the app, pick your repo, and you land back here. We tick each step as it happens, from any tab or device.</p>
|
|
2129
2046
|
<div class="doneBody" id="doneBody">
|
|
2047
|
+
<!-- the ONE live region of part 03: written only when the sentence changes -->
|
|
2048
|
+
<p class="setNow" id="setupNow" role="status" aria-live="polite"></p>
|
|
2130
2049
|
<p class="hint" id="doneRender" role="status" hidden></p>
|
|
2131
2050
|
<p class="hint" id="doneLook" role="status" hidden></p>
|
|
2132
2051
|
<div id="doneApp" hidden></div>
|
|
2133
|
-
|
|
2134
|
-
|
|
2052
|
+
<div class="setFactGrp"><h3 id="homeFactsH">Its home</h3><div class="setFacts" id="homeFacts" role="list" aria-labelledby="homeFactsH"></div></div>
|
|
2053
|
+
<div class="setFactGrp"><h3 id="ghFactsH">GitHub</h3><div class="setFacts" id="ghFacts" role="list" aria-labelledby="ghFactsH"></div></div>
|
|
2054
|
+
<!-- whose account GitHub makes the app on (task 1004651, the mockup's account
|
|
2055
|
+
select beside Connect GitHub): paintFacts moves it into the Approve fact
|
|
2056
|
+
while Connect is offered, and back here otherwise -->
|
|
2135
2057
|
<div class="acctPick" id="aaPick" hidden>
|
|
2136
2058
|
<div class="row2">
|
|
2137
|
-
<label class="wizField"><span>Make the app on</span>
|
|
2059
|
+
<label class="wizField"><span class="vh">Make the app on</span>
|
|
2138
2060
|
<select id="aaSel" aria-label="Which GitHub account"><option value="personal">On my account</option><option value="org">On an organisation</option></select></label>
|
|
2139
2061
|
<label class="wizField" id="aaOrgField" hidden><span>Organisation</span><select id="aaOrg"></select></label>
|
|
2140
2062
|
</div>
|
|
@@ -2144,17 +2066,11 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2144
2066
|
</div>
|
|
2145
2067
|
<p class="hint" id="aaNote" aria-live="polite"></p>
|
|
2146
2068
|
</div>
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
<
|
|
2151
|
-
|
|
2152
|
-
hop — one cloud per public project on the platform, in its planet's
|
|
2153
|
-
colour. Shown only while data-moment="wait"; under reduced motion
|
|
2154
|
-
the sky is drawn once and stands still. -->
|
|
2155
|
-
<div class="hop" id="hop">
|
|
2156
|
-
<p class="hopStatus" id="hopStatus" role="status" aria-live="polite"></p>
|
|
2157
|
-
<div class="hopBar" aria-hidden="true"><span id="hopBarFill"></span></div>
|
|
2069
|
+
<!-- the cloud hop (task 1004578): one cloud per public project on the platform,
|
|
2070
|
+
in its planet's colour. It opens in place only when asked, and the wait never
|
|
2071
|
+
depends on it; under reduced motion the sky is drawn once and stands still. -->
|
|
2072
|
+
<button type="button" class="hopLink" id="hopLink" aria-expanded="false" aria-controls="hop">Play cloud hop while you wait</button>
|
|
2073
|
+
<div class="hop" id="hop" hidden>
|
|
2158
2074
|
<div class="hopStage">
|
|
2159
2075
|
<canvas id="hopCanvas" tabindex="0" role="img" aria-label="A cloud-hopping game to pass the wait: each cloud is a public project on the platform. Arrow keys steer, space jumps."></canvas>
|
|
2160
2076
|
</div>
|
|
@@ -2163,18 +2079,20 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2163
2079
|
<button type="button" class="btn small ghost" id="hopJump">Jump</button>
|
|
2164
2080
|
<button type="button" class="btn small ghost" id="hopRight" aria-label="Move right">→</button>
|
|
2165
2081
|
<span class="hopHint">Arrows or A and D steer, space jumps.</span>
|
|
2166
|
-
<span class="hopScore" id="hopScore"
|
|
2082
|
+
<span class="hopScore" id="hopScore"></span>
|
|
2167
2083
|
</div>
|
|
2168
2084
|
</div>
|
|
2169
|
-
<
|
|
2170
|
-
|
|
2171
|
-
|
|
2085
|
+
<!-- every required fact done (paintFacts): "<name> is alive.", the invite box once
|
|
2086
|
+
the hall answers (PS15), and the door into Genesis, the hall's founding mode -->
|
|
2087
|
+
<div class="alive" id="doneAlive" hidden>
|
|
2088
|
+
<!-- shown only when every required fact is done; paintAlive() puts the name in -->
|
|
2089
|
+
<h2 class="aliveH" id="done-h">Your project is alive.</h2>
|
|
2172
2090
|
<!-- the invite step (task 1002346, BV1.R27; ADR 0249) — OPTIONAL, and it stands
|
|
2173
2091
|
HERE because an invite needs a project that already exists. Hidden until the
|
|
2174
2092
|
created record carries an id and a slug; the script is the only thing that
|
|
2175
2093
|
shows it. -->
|
|
2176
2094
|
<div class="inviteBox" id="wizInvite" hidden>
|
|
2177
|
-
<p class="finePrint">Invite your first builders. They become founding builders. Optional: the invite comes from your project’s hall
|
|
2095
|
+
<p class="finePrint">Invite your first builders. They become founding builders. Optional: the invite comes from your project’s hall.</p>
|
|
2178
2096
|
<div class="attach">
|
|
2179
2097
|
<label class="vh" for="wizInviteWho">GitHub handle</label>
|
|
2180
2098
|
<input type="text" id="wizInviteWho" placeholder="GitHub handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="200">
|
|
@@ -2187,13 +2105,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2187
2105
|
<ul class="inviteList" id="wizInviteList"></ul>
|
|
2188
2106
|
<div class="projMsg" id="wizInviteMsg" role="status" aria-live="polite"></div>
|
|
2189
2107
|
</div>
|
|
2108
|
+
<div class="aliveDoor"><a class="btn" id="enterGenesis" data-onboard-step="enter-hall" target="_blank" rel="noopener">Enter Genesis</a></div>
|
|
2190
2109
|
<!-- the way back, the doneModules idiom: an optional step is a default
|
|
2191
2110
|
rather than a dead end only if the panel names where it lives after -->
|
|
2192
2111
|
<p class="samePath" id="wizInviteLater"></p>
|
|
2193
2112
|
<!-- the extras row was a default (task 1002342, BV1.R23) — one line saying
|
|
2194
2113
|
where they live afterwards, so accepting them is never a dead end -->
|
|
2195
2114
|
<p class="samePath" id="doneModules"></p>
|
|
2196
|
-
<p class="samePath">These steps are the same however a project is created.</p>
|
|
2197
2115
|
</div>
|
|
2198
2116
|
</div>
|
|
2199
2117
|
</div>
|
|
@@ -4805,35 +4723,6 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4805
4723
|
$('addrCustom').open = true;
|
|
4806
4724
|
}
|
|
4807
4725
|
}
|
|
4808
|
-
/* Pinned GitHub OAuth-app values for a domain (auth.js WEB_REDIRECT_ORIGIN —
|
|
4809
|
-
callback is the apex /api/gds/auth/web/callback). Mirrors onboard.oauthAppInstructions. */
|
|
4810
|
-
function oauthAppValues(domain) {
|
|
4811
|
-
var origin = 'https://' + domain;
|
|
4812
|
-
return { homepageUrl: origin, callbackUrl: origin + '/api/gds/auth/web/callback' };
|
|
4813
|
-
}
|
|
4814
|
-
/* GitHub's own "New repository" page, pre-filled with the owner + name. No auth or
|
|
4815
|
-
permissions on our side — the visitor creates the empty repo in GitHub's UI, which
|
|
4816
|
-
is exactly what the scaffold then clones. `owner` selects the account/org; omitting
|
|
4817
|
-
a blank param just leaves that field empty on GitHub. */
|
|
4818
|
-
function githubNewRepoUrl(owner, repo) {
|
|
4819
|
-
var u = new URL('https://github.com/new');
|
|
4820
|
-
if (repo) u.searchParams.set('name', repo);
|
|
4821
|
-
if (owner) u.searchParams.set('owner', owner);
|
|
4822
|
-
return u.toString();
|
|
4823
|
-
}
|
|
4824
|
-
/* github.com address of the repo this draft points at, or '' when we can't name one
|
|
4825
|
-
honestly. Adopt: owner/repo IS the address the owner picked. Auto-create: the
|
|
4826
|
-
html_url the create call returned — a draft predating task 1002855 has none, and
|
|
4827
|
-
gets no link rather than a guess that could 404. The scheme is hard-coded and both
|
|
4828
|
-
segments are encoded, so nothing here can become an off-site or javascript: href. */
|
|
4829
|
-
function repoLinkFor() {
|
|
4830
|
-
if (!state.owner || !state.repo) return '';
|
|
4831
|
-
if (state.mode === 'adopt') {
|
|
4832
|
-
return 'https://github.com/' + encodeURIComponent(state.owner) + '/' + encodeURIComponent(state.repo);
|
|
4833
|
-
}
|
|
4834
|
-
var stored = String(state.repoUrl || '');
|
|
4835
|
-
return stored.indexOf('https://github.com/') === 0 ? stored : '';
|
|
4836
|
-
}
|
|
4837
4726
|
function saveDraft() {
|
|
4838
4727
|
try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
|
|
4839
4728
|
}
|
|
@@ -4871,12 +4760,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4871
4760
|
var h = created ? $('done-part-h') : $(panels[step]).querySelector('h1');
|
|
4872
4761
|
if (h) { h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false }); }
|
|
4873
4762
|
}
|
|
4874
|
-
/* the cloud hop
|
|
4875
|
-
|
|
4876
|
-
if (typeof
|
|
4763
|
+
/* the cloud hop runs only on part 03, and only once asked (tasks 1004578, 1004683) —
|
|
4764
|
+
a stop, not a paint: renderDone paints the part */
|
|
4765
|
+
if (!created && typeof hopOpen === 'function') hopOpen(false);
|
|
4877
4766
|
if (step === 1) paintPage();
|
|
4878
4767
|
else paintCreate();
|
|
4879
|
-
if (typeof
|
|
4768
|
+
if (typeof paintFacts === 'function') paintFacts();
|
|
4880
4769
|
}
|
|
4881
4770
|
/* The page hosts every control at once, so it paints them all on entry. The repo
|
|
4882
4771
|
probe is authenticated — never fire it before /me settles (a pre-auth probe 401s
|
|
@@ -5202,7 +5091,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5202
5091
|
files — the server applies the bundle. A picker that could block a
|
|
5203
5092
|
project from being created would be worse than no picker. */
|
|
5204
5093
|
var starterBundles = null; /* the payload once it lands; null until then */
|
|
5205
|
-
var bundlesPromise = null; /* memoised
|
|
5094
|
+
var bundlesPromise = null; /* memoised — entering the step twice asks once */
|
|
5206
5095
|
var bundlesFailed = false; /* asked and did not get it; distinct from not yet asked */
|
|
5207
5096
|
/* Lazy, not at boot: /projects serves four views from one file, and the map
|
|
5208
5097
|
has no business paying for the picker's read. */
|
|
@@ -6806,325 +6695,400 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6806
6695
|
});
|
|
6807
6696
|
});
|
|
6808
6697
|
|
|
6809
|
-
/* ──
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6698
|
+
/* ── part 03 after Create (task 1004683; the design of record
|
|
6699
|
+
docs/design/mocks/day-night-rooms/setup-status.html, owner 2026-10-09): ONE list of
|
|
6700
|
+
facts the server checked, in two groups. Its home reads the owner's instance read
|
|
6701
|
+
(GET /provisioning/instances/:id: status, open_intent, domain, dns_resolves,
|
|
6702
|
+
last_verified_up); GitHub reads the github-setup checklist and its pick-up word
|
|
6703
|
+
(tasks 1004649–1004652). homeFacts() and ghFacts() are PURE over what those reads
|
|
6704
|
+
last said: every fact's state is one of todo · now · you · done · bad, and each
|
|
6705
|
+
comes from a server field — never a timer, a stage count or a click this page
|
|
6706
|
+
assumed. setupSentence() says what is happening now, once; paintFacts() draws it
|
|
6707
|
+
all from nodes. Nothing is said twice. ── */
|
|
6820
6708
|
var instanceId = null; /* set when a create lands / a resumed watch starts */
|
|
6821
6709
|
/* where a project is ACTED on since task 1003355: its own page. A list row
|
|
6822
6710
|
carries only view and manage now, so every pointer that names a control —
|
|
6823
6711
|
attach an address, retry a snagged standup, bring a torn-down project
|
|
6824
6712
|
back — must land there, not on the list, which has no such button. */
|
|
6825
6713
|
function projectPageLink(label) {
|
|
6826
|
-
return '<a href="' + (
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
var
|
|
6832
|
-
|
|
6833
|
-
var FALLBACK_HUMAN_KEYS = ['github-repo', 'oauth-app', 'device-flow', 'app-install', 'first-sign-in', 'enter-hall'];
|
|
6834
|
-
|
|
6835
|
-
function fetchPlan() {
|
|
6836
|
-
var domain = resolvedDomain() || '';
|
|
6837
|
-
var key = state.mode + '|' + domain;
|
|
6838
|
-
if (!planPromises[key]) {
|
|
6839
|
-
/* shape=cloud-host: this wizard only creates control-plane cloud-host
|
|
6840
|
-
instances, and the plan is shape-filtered server-side (direction §7) */
|
|
6841
|
-
planPromises[key] = fetch(API + '/provisioning/onboard-plan?mode=' + encodeURIComponent(state.mode) + '&shape=cloud-host' + (domain ? '&domain=' + encodeURIComponent(domain) : ''))
|
|
6842
|
-
.then(function (res) { if (!res.ok) throw new Error('plan'); return res.json(); })
|
|
6843
|
-
.then(function (d) { return (d && d.steps) || []; })
|
|
6844
|
-
.catch(function () {
|
|
6845
|
-
delete planPromises[key]; /* transient — retried on the next render */
|
|
6846
|
-
return FALLBACK_HUMAN_KEYS.map(function (k) { return { key: k, kind: 'human', label: '' }; });
|
|
6847
|
-
});
|
|
6848
|
-
}
|
|
6849
|
-
return planPromises[key];
|
|
6850
|
-
}
|
|
6851
|
-
|
|
6852
|
-
/* github-repo — adopt / auto-created / manual-create, each aware of whether the
|
|
6853
|
-
scaffold has landed (instance active ⇒ the build already pushed). The wizard
|
|
6854
|
-
files onboard_mode to the server, so for adopt the RUNNER clones + layers
|
|
6855
|
-
(task 1002562) — the owner no longer runs `bongos init --adopt` here. */
|
|
6856
|
-
function renderStepGithubRepo() {
|
|
6857
|
-
var landed = live.instStatus === 'active';
|
|
6858
|
-
/* the one-page checklist (task 1004649) shows only the GitHub steps the founder
|
|
6859
|
-
takes; a repo the platform made or adopted needs nothing, so it is not a row */
|
|
6860
|
-
if (state.mode === 'adopt' || state.repoAutoCreated || landed) return '';
|
|
6861
|
-
var repoLabel = state.owner && state.repo ? '<strong>' + esc(state.owner + '/' + state.repo) + '</strong>' : 'your new repository';
|
|
6862
|
-
/* Naming a repo without linking it stranded the owner on exactly the path where the
|
|
6863
|
-
platform did the work (task 1002855). Adopt links the repo the owner picked;
|
|
6864
|
-
auto-create links the html_url GitHub itself returned. A draft written before this
|
|
6865
|
-
change carries no stored URL and degrades to the bare name — never a guessed link.
|
|
6866
|
-
Only these two branches link: the manual branch below still has a real .ghbtn
|
|
6867
|
-
action, and nesting an <a> inside it would be malformed. */
|
|
6868
|
-
var repoUrl = repoLinkFor();
|
|
6869
|
-
var repoLink = repoUrl ? '<a href="' + esc(repoUrl) + '" target="_blank" rel="noopener">' + repoLabel + '</a>' : repoLabel;
|
|
6870
|
-
if (state.mode === 'adopt') {
|
|
6871
|
-
return landed
|
|
6872
|
-
? '<li class="sdone" data-onboard-step="github-repo"><b>Your repo carries the platform</b><p>The build layered the platform onto ' + repoLink + ' — existing files, history, and backlog kept. Pull to see it.</p></li>'
|
|
6873
|
-
: '<li data-onboard-step="github-repo"><b>Your existing repo</b><p>The build clones ' + repoLink + ' and layers the platform onto its existing history — nothing is created or emptied, and your backlog is imported. Nothing for you to do here.</p></li>';
|
|
6874
|
-
}
|
|
6875
|
-
if (state.repoAutoCreated) {
|
|
6876
|
-
return landed
|
|
6877
|
-
? '<li class="sdone" data-onboard-step="github-repo"><b>Repo ready — scaffold landed</b><p>We created ' + repoLink + ' (public, by design) and the build pushed your project’s starting files to it.</p></li>'
|
|
6878
|
-
: '<li data-onboard-step="github-repo"><b>Your empty repo is ready</b><p>We created ' + repoLink + ' on GitHub for you — public (projects here are open by design), empty, and waiting. The scaffold lands there when the build runs; nothing for you to do here.</p></li>';
|
|
6879
|
-
}
|
|
6880
|
-
var ghNewUrl = githubNewRepoUrl(state.owner, state.repo);
|
|
6881
|
-
return '<li' + (landed ? ' class="sdone"' : '') + ' data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>Do this first — the build needs the repo to exist. Click below (GitHub opens with the owner and name already filled in), press <strong>Create repository</strong>, and leave it completely empty. It must land at exactly ' + repoLabel + ' — leave GitHub’s Owner box as prefilled, because the build watches that exact address; a repo created under a different owner is never found. GitHub will ask public or private — your choice; both work here, and we never change it. Seeing “repository not found” above? That just means the repo isn’t created yet — make it here.</p>' +
|
|
6882
|
-
'<a class="ghbtn" href="' + esc(ghNewUrl) + '" target="_blank" rel="noopener">' + GH_ICON + 'Create ' + repoLabel + ' on GitHub</a></li>';
|
|
6883
|
-
}
|
|
6884
|
-
|
|
6885
|
-
/* The manual OAuth-app path stays as a documented fallback (ADR 0133), but the
|
|
6886
|
-
control-plane placement is named as the operator's, never handed to the owner
|
|
6887
|
-
as a command to run (gap 4). */
|
|
6888
|
-
function manualOauthDetails() {
|
|
6889
|
-
var oa = oauthAppValues(resolvedDomain());
|
|
6890
|
-
return '<details><summary>Prefer to create an OAuth app by hand?</summary>' +
|
|
6891
|
-
'<p>GitHub → <em>your account’s</em> Settings → Developer settings → OAuth Apps → New OAuth App. (Developer settings live under your account, not the repo.)</p>' +
|
|
6892
|
-
'<div class="kv"><span class="kk">Homepage URL</span><code>' + esc(oa.homepageUrl) + '</code>' + copyBtn(oa.homepageUrl) + '</div>' +
|
|
6893
|
-
'<div class="kv"><span class="kk">Callback URL</span><code>' + esc(oa.callbackUrl) + '</code>' + copyBtn(oa.callbackUrl) + '</div>' +
|
|
6894
|
-
'<p>The platform team places the generated credentials; this step ticks itself once they land.</p></details>';
|
|
6895
|
-
}
|
|
6896
|
-
|
|
6897
|
-
/* "Open it when it's ready" (task 1004548). The founder now waits for sign-in on
|
|
6898
|
-
this checklist instead of on the callback page, so the auto-open lives here: a
|
|
6899
|
-
tick, on unless they untick it (remembered per browser), that opens the project
|
|
6900
|
-
the moment it answers AND sign-in is wired. Armed only by a wait this page has
|
|
6901
|
-
actually watched, so revisiting a long-finished checklist never jumps away. */
|
|
6902
|
-
var AUTO_OPEN_KEY = 'cb-open-when-ready';
|
|
6903
|
-
var autoOpenArmed = false;
|
|
6904
|
-
var autoOpened = false;
|
|
6905
|
-
function autoOpenPref() {
|
|
6906
|
-
try { return localStorage.getItem(AUTO_OPEN_KEY) !== '0'; } catch (e) { return true; }
|
|
6907
|
-
}
|
|
6908
|
-
function autoOpenTick() {
|
|
6909
|
-
return '<label class="autoOpen"><input type="checkbox" data-action="auto-open"' + (autoOpenPref() ? ' checked' : '') + '>' +
|
|
6910
|
-
'<span>Open my project automatically when it’s ready</span></label>';
|
|
6911
|
-
}
|
|
6912
|
-
function maybeAutoOpen() {
|
|
6913
|
-
var domain = resolvedDomain();
|
|
6914
|
-
if (autoOpened || !autoOpenArmed || !domain || !autoOpenPref()) return;
|
|
6915
|
-
if (live.manifest !== 'done' || live.instStatus !== 'active' || !live.addrReady) return;
|
|
6916
|
-
autoOpened = true;
|
|
6917
|
-
var url = 'https://' + domain;
|
|
6918
|
-
/* a new tab keeps this checklist (two steps follow sign-in); a browser that
|
|
6919
|
-
blocks an unprompted tab gets this one sent instead — the founder asked */
|
|
6920
|
-
var w = null;
|
|
6921
|
-
try { w = window.open(url, '_blank'); } catch (e) {}
|
|
6922
|
-
if (w) { try { w.opener = null; } catch (e) {} }
|
|
6923
|
-
else window.location.assign(url);
|
|
6924
|
-
}
|
|
6925
|
-
|
|
6926
|
-
/* The four GitHub steps of part 03, in the mockup's words and order (task 1004649,
|
|
6927
|
-
docs/design/mocks/day-night-rooms/): Approve the <name> app on GitHub · Install it
|
|
6928
|
-
on your repo · Builders can sign in · Command-line sign-in is on. One click starts
|
|
6929
|
-
ONE trip — approve, straight on to installing, back here — and every tick is what
|
|
6930
|
-
the server verified (live.setup, the status poll's `checklist`). Every value that
|
|
6931
|
-
reaches the markup goes through esc(). */
|
|
6714
|
+
return '<a href="' + manageHref() + '">' + label + '</a>';
|
|
6715
|
+
}
|
|
6716
|
+
function manageHref() {
|
|
6717
|
+
return instanceId ? '?view=manage&id=' + encodeURIComponent(instanceId) : '?view=mine';
|
|
6718
|
+
}
|
|
6719
|
+
var live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, appOrg: null, setup: null, installUrl: null, pickUp: null, verifying: false, askedPlatform: false, inst: null, readAt: 0, gone: '', repo: null };
|
|
6720
|
+
|
|
6932
6721
|
function ghStep(key) { return live.setup && live.setup[key]; }
|
|
6933
6722
|
/* "Pick up where it stopped" (task 1004652): a stopped step says what stopped in one plain
|
|
6934
6723
|
sentence and carries the action that fixes it, with the app GitHub already made, never a
|
|
6935
6724
|
second one. live.pickUp is the server's word for what that action does (github-setup.js
|
|
6936
|
-
pickUpFor); live.verifying is whether the runner is still checking.
|
|
6725
|
+
pickUpFor); live.verifying is whether the runner is still checking. */
|
|
6937
6726
|
var ghTabTrouble = null; /* 'behind' | 'closed': what this page saw of the GitHub tab it opened */
|
|
6938
6727
|
var NO_SECOND_APP = 'Nothing is lost, and no second app will be made.';
|
|
6939
|
-
function
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6728
|
+
function projectName() { return state.name || state.slug || 'your project'; }
|
|
6729
|
+
/* a host name as the server stores it; anything else is no address. PURE. */
|
|
6730
|
+
function hostOk(d) { return typeof d === 'string' && d.length < 254 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/i.test(d); }
|
|
6731
|
+
/* the project's address: the server's once it has read, the page's own until then */
|
|
6732
|
+
function setupDomain() {
|
|
6733
|
+
if (live.inst) return hostOk(live.inst.domain) ? live.inst.domain : '';
|
|
6734
|
+
var d = resolvedDomain();
|
|
6735
|
+
return hostOk(d) ? d : '';
|
|
6736
|
+
}
|
|
6737
|
+
/* owner/repo: the server's (github-setup.js targetRepo, strict), else the page's own */
|
|
6738
|
+
function repoName() {
|
|
6739
|
+
if (live.repo) return live.repo;
|
|
6740
|
+
return state.owner && state.repo ? state.owner + '/' + state.repo : null;
|
|
6944
6741
|
}
|
|
6945
|
-
function appTitle() { return 'Approve the ' + esc(state.name || state.slug || 'project') + ' app on GitHub'; }
|
|
6946
6742
|
/* the organisation that owns the app, when the founder made it on one (task 1004651) */
|
|
6947
6743
|
function appOrgLogin() {
|
|
6948
6744
|
return typeof live.appOrg === 'string' && /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/.test(live.appOrg) ? live.appOrg : null;
|
|
6949
6745
|
}
|
|
6950
6746
|
/* the app's own settings on GitHub: under its organisation, or under the founder's
|
|
6951
|
-
account. A raw URL
|
|
6747
|
+
account. A raw URL, set as an attribute, never parsed as markup. */
|
|
6952
6748
|
function appSettingsUrl() {
|
|
6953
6749
|
if (!live.appSlug) return null;
|
|
6954
6750
|
var org = appOrgLogin();
|
|
6955
6751
|
return 'https://github.com' + (org ? '/organizations/' + encodeURIComponent(org) : '') + '/settings/apps/' + encodeURIComponent(live.appSlug);
|
|
6956
6752
|
}
|
|
6957
6753
|
|
|
6958
|
-
/*
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
function
|
|
6962
|
-
var
|
|
6963
|
-
if (!
|
|
6964
|
-
|
|
6754
|
+
/* When a fact was seen, said plainly, from a server timestamp (or the moment this page's
|
|
6755
|
+
read answered): "just now" / "2:14 pm" for when it happened, "checked 6s ago" /
|
|
6756
|
+
"checked 3m ago" / "checked 2:14 pm" for when it was looked at. '' when there is none. PURE. */
|
|
6757
|
+
function whenSaid(at, now, checked) {
|
|
6758
|
+
var t = typeof at === 'number' ? at : Date.parse(at || '');
|
|
6759
|
+
if (!t || isNaN(t)) return '';
|
|
6760
|
+
var s = Math.max(0, Math.round((now - t) / 1000));
|
|
6761
|
+
if (checked && s < 60) return 'checked ' + s + 's ago';
|
|
6762
|
+
if (checked && s < 3600) return 'checked ' + Math.floor(s / 60) + 'm ago';
|
|
6763
|
+
if (!checked && s < 60) return 'just now';
|
|
6764
|
+
var d = new Date(t), h = d.getHours(), m = d.getMinutes();
|
|
6765
|
+
var said = s < 86400 ? ((h % 12) || 12) + ':' + (m < 10 ? '0' : '') + m + ' ' + (h < 12 ? 'am' : 'pm')
|
|
6766
|
+
: d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
6767
|
+
return (checked ? 'checked ' : '') + said;
|
|
6768
|
+
}
|
|
6769
|
+
|
|
6770
|
+
/* Its home: three facts from the owner's instance read. PURE over (inst, now) and the
|
|
6771
|
+
time that read answered. A domain is NAMED only once the platform's own lookup has
|
|
6772
|
+
seen it answer (addressReady, task 1004505): a name typed before it resolves is
|
|
6773
|
+
cached as not found for up to half an hour. */
|
|
6774
|
+
function homeFacts(inst, now) {
|
|
6775
|
+
var checked = live.readAt ? whenSaid(live.readAt, now, true) : '';
|
|
6776
|
+
if (!inst) {
|
|
6777
|
+
return [{ key: 'address', state: 'todo', title: 'Address kept for you' },
|
|
6778
|
+
{ key: 'server', state: 'todo', title: 'Server and database running' },
|
|
6779
|
+
{ key: 'answers', state: 'todo', title: 'Address answers' }];
|
|
6780
|
+
}
|
|
6781
|
+
var s = inst.status, oi = inst.open_intent;
|
|
6782
|
+
/* an intent speaks only where the status does not already (F22): a requested project
|
|
6783
|
+
always has its provision intent open */
|
|
6784
|
+
var act = oi && s !== 'provisioning' && s !== 'tearing_down' && s !== 'requested' ? oi.action : '';
|
|
6785
|
+
var dom = hostOk(inst.domain) ? inst.domain : '', ready = addressReady(inst);
|
|
6786
|
+
var address = dom
|
|
6787
|
+
? { key: 'address', state: 'done', title: 'Address kept for you', sub: ready ? dom : 'Its name shows here once it reaches everyone.', right: whenSaid(inst.created_at, now) }
|
|
6788
|
+
: { key: 'address', state: 'todo', title: 'Address kept for you' };
|
|
6789
|
+
var server;
|
|
6790
|
+
if (act === 'teardown' || s === 'tearing_down') server = { state: 'todo', title: 'Server and database switching off', sub: 'The repository stays yours, and the database is kept.' };
|
|
6791
|
+
else if (s === 'torn_down' && act !== 'provision') server = { state: 'todo', title: 'Server and database switched off' };
|
|
6792
|
+
else if (s === 'requested' || s === 'provisioning' || act === 'provision') server = { state: 'now', title: 'Server and database starting', sub: 'Usually a few minutes.', right: checked };
|
|
6793
|
+
/* a settings change restarts the project (task 1003140): said by its state, queued or running */
|
|
6794
|
+
else if (s === 'active' && act === 'settings-apply') {
|
|
6795
|
+
server = oi.state === 'running'
|
|
6796
|
+
? { state: 'now', title: 'Server and database restarting', sub: 'To apply a settings change.', right: checked }
|
|
6797
|
+
: { state: 'now', title: 'Server and database running', sub: 'A settings change is queued. It restarts to apply it.', right: checked };
|
|
6965
6798
|
}
|
|
6799
|
+
else if (s === 'active') server = { state: 'done', title: 'Server and database running', right: whenSaid(inst.provisioned_at, now) };
|
|
6800
|
+
else if (s === 'error') server = { state: 'bad', title: 'Server and database stopped', sub: 'It stopped partway and won’t carry on by itself.', right: checked, act: 'manage', actText: 'Retry it on its own page' };
|
|
6801
|
+
else server = { state: 'todo', title: 'Server and database running' };
|
|
6802
|
+
server.key = 'server';
|
|
6803
|
+
var answers = { key: 'answers', state: 'todo', title: 'Address answers' };
|
|
6804
|
+
if (dom && s === 'active' && act !== 'teardown') {
|
|
6805
|
+
if (!ready) { answers.state = 'now'; answers.sub = 'New addresses can take a few minutes to reach everyone.'; answers.right = checked; }
|
|
6806
|
+
else if (inst.last_verified_up === true) { answers.state = 'done'; answers.right = whenSaid(inst.last_verified_at, now, true); }
|
|
6807
|
+
else if (inst.last_verified_up === false) { answers.state = 'now'; answers.sub = 'Not answering yet. We keep checking.'; answers.right = whenSaid(inst.last_verified_at, now, true); }
|
|
6808
|
+
else { answers.state = 'now'; answers.sub = 'Checking that it answers.'; answers.right = checked; }
|
|
6809
|
+
}
|
|
6810
|
+
return [address, server, answers];
|
|
6811
|
+
}
|
|
6812
|
+
|
|
6813
|
+
/* GitHub: the four facts of the one trip (task 1004649), each from the checklist the
|
|
6814
|
+
server derived (live.setup), the attempt's effective status (live.manifest and how far
|
|
6815
|
+
a failure got, task 1004572) and what picking it up does (live.pickUp, task 1004652).
|
|
6816
|
+
PURE over those and `now`. A stopped fact carries the one action that fixes it; only
|
|
6817
|
+
the FIRST stopped fact carries "Pick up where it stopped". */
|
|
6818
|
+
function ghFacts(now) {
|
|
6819
|
+
var checked = live.readAt ? whenSaid(live.readAt, now, true) : '';
|
|
6820
|
+
var dom = setupDomain(), name = projectName(), repo = repoName();
|
|
6821
|
+
var approve = { key: 'approve', title: 'Approve the ' + name + ' app on GitHub' };
|
|
6966
6822
|
/* GitHub made the app and the platform placed its keys: the slug is recorded only
|
|
6967
6823
|
after the placement (the runner), so a known slug outside a failure is the tick —
|
|
6968
|
-
|
|
6969
|
-
/* …including when a LATER step stopped (sign-in unconfirmed): the app and its keys are
|
|
6970
|
-
real, so this step stays ticked and the stopped one says so (task 1004652) */
|
|
6824
|
+
including when a LATER step stopped (sign-in unconfirmed) */
|
|
6971
6825
|
var appMade = (ghStep('approve') === 'done' || live.appSlug) && live.manifest !== 'expired' &&
|
|
6972
6826
|
(live.manifest !== 'error' || live.manifestFailure === 'sign_in_unconfirmed');
|
|
6973
|
-
if (appMade) {
|
|
6974
|
-
|
|
6827
|
+
if (appMade) { approve.state = 'done'; approve.sub = name + ' app on ' + (appOrgLogin() || 'your account'); }
|
|
6828
|
+
else if (!dom) { approve.state = 'todo'; approve.sub = 'Opens once it has an address.'; }
|
|
6829
|
+
/* GitHub refuses app manifests whose hook URL it cannot reach (owner walk, 2026-08-12) */
|
|
6830
|
+
else if (!apexBase()) { approve.state = 'todo'; approve.sub = 'GitHub can’t reach a platform on a local address, so this works only on a public one.'; }
|
|
6831
|
+
else if (live.manifest === 'error' || live.manifest === 'expired') {
|
|
6832
|
+
approve.state = 'bad'; approve.right = checked; approve.act = 'connect';
|
|
6833
|
+
if (live.manifest === 'expired') approve.sub = 'That setup link expired before GitHub was approved, so nothing was made. Start a fresh one.';
|
|
6834
|
+
else if (live.manifestCode === 'domain_required') { approve.sub = 'It needs an address first. Attach one on its own page.'; approve.act = 'manage'; approve.actText = 'Open its own page'; }
|
|
6835
|
+
else if (live.pickUp === 'convert') { approve.sub = 'GitHub approved the app, and the platform stopped before it finished collecting the app’s keys.'; approve.act = 'pick-up'; }
|
|
6836
|
+
else if (live.pickUp === 'operator' || live.manifestFailure === 'finish_failed') { approve.sub = 'GitHub made the app, but its keys never reached your project. Making another would leave you with two, so the platform team finishes this one.'; approve.act = 'ask-platform'; }
|
|
6837
|
+
else { approve.sub = 'GitHub setup didn’t start. Nothing was changed on GitHub.'; approve.tech = live.manifestMsg || ''; }
|
|
6975
6838
|
}
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6839
|
+
else if (live.manifest === 'code_received' || live.manifest === 'converting') { approve.state = 'now'; approve.sub = 'Approved. The platform is collecting the app’s keys.'; }
|
|
6840
|
+
else if (live.manifest) {
|
|
6841
|
+
/* waiting on GitHub: "Reopen GitHub" posts the SAME attempt again, never a second app */
|
|
6842
|
+
approve.state = 'you'; approve.act = 'reopen';
|
|
6843
|
+
approve.sub = ghTabTrouble === 'closed' ? 'The GitHub tab closed before you approved, so nothing was made. Reopen it to carry on.'
|
|
6844
|
+
: ghTabTrouble === 'behind' ? 'Your GitHub tab opened behind this window.' : 'Waiting for you in the GitHub tab.';
|
|
6981
6845
|
}
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
6846
|
+
else { approve.state = 'you'; approve.act = 'connect'; approve.sub = 'One trip: approve, install on your repo, and you are back here.'; }
|
|
6847
|
+
|
|
6848
|
+
var install = { key: 'install', title: 'Install it on your repo', sub: repo || '' };
|
|
6849
|
+
var installUrl = live.installUrl || (live.appSlug ? 'https://github.com/apps/' + encodeURIComponent(live.appSlug) + '/installations/new' : null);
|
|
6850
|
+
if (ghStep('install') === 'done') install.state = 'done';
|
|
6851
|
+
else if (!live.appSlug) install.state = 'todo';
|
|
6852
|
+
/* the runner stopped checking and GitHub never confirmed the install: the link opens the
|
|
6853
|
+
SAME app's install page, and the click asks the runner to look again */
|
|
6854
|
+
else if (live.pickUp === 'check') { install.state = 'bad'; install.sub = 'GitHub stopped before the app was on ' + (repo || 'your repo') + '.'; install.right = checked; install.act = 'pick-up-link'; install.href = installUrl; }
|
|
6855
|
+
else { install.state = 'you'; install.right = 'not yet seen'; install.act = 'install-link'; install.href = installUrl; }
|
|
6856
|
+
|
|
6857
|
+
var signIn = { key: 'sign_in', title: 'Builders can sign in' };
|
|
6858
|
+
if (live.manifest === 'done' || ghStep('sign_in') === 'done') { signIn.state = 'done'; signIn.sub = dom && live.addrReady ? 'Tested on ' + dom : ''; }
|
|
6859
|
+
else if (live.manifest === 'error' && live.manifestFailure === 'sign_in_unconfirmed') {
|
|
6860
|
+
/* the keys are placed but the project never confirmed sign-in: the runner re-asks it
|
|
6861
|
+
with the keys already there; a project that still will not answer goes to the
|
|
6862
|
+
platform team */
|
|
6863
|
+
if (live.verifying) { signIn.state = 'now'; signIn.sub = 'Asking your project again, with the app GitHub already made.'; }
|
|
6864
|
+
else {
|
|
6865
|
+
signIn.state = 'bad'; signIn.sub = 'Your project didn’t confirm that sign-in works.'; signIn.right = checked;
|
|
6866
|
+
signIn.act = live.pickUp === 'check' && install.state !== 'bad' ? ['pick-up', 'ask-platform'] : ['ask-platform'];
|
|
7001
6867
|
}
|
|
7002
|
-
else { snag = 'GitHub setup didn’t start. Nothing was changed on GitHub — try again.'; raw = live.manifestMsg; }
|
|
7003
|
-
return '<li class="sstopped" data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>' + snag + '</p>' +
|
|
7004
|
-
(raw ? '<details><summary>Technical details</summary><p>' + esc(raw) + '</p></details>' : '') + act + '</li>';
|
|
7005
6868
|
}
|
|
7006
|
-
if (
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
}
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
? 'The GitHub tab closed before the app was approved, so nothing was made on GitHub. Reopen it to carry on with this same setup.'
|
|
7020
|
-
: ghTabTrouble === 'behind'
|
|
7021
|
-
? 'Your GitHub tab opened behind this window. Reopen it to bring it to the front; it is this same setup, so no second app will be made.'
|
|
7022
|
-
: 'Waiting on GitHub — approve the app in the tab that opened. No GitHub tab? It may have opened behind this window or in another one. Once approved, GitHub takes you straight on to installing it on your repo, then back here; every step ticks itself.';
|
|
7023
|
-
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>' + wait + '</p>' +
|
|
7024
|
-
progressMarkup(0, 0, 'Waiting for your approval on GitHub…', '', 'signin') + autoOpenTick() +
|
|
7025
|
-
(ghTabTrouble
|
|
7026
|
-
? '<button type="button" class="ghbtn" data-action="reopen-github">' + GH_ICON + 'Reopen GitHub ↗</button>'
|
|
7027
|
-
: '<button type="button" class="linklike" data-action="reopen-github">No GitHub tab? Reopen GitHub</button>') + '</li>';
|
|
7028
|
-
}
|
|
7029
|
-
return '<li data-onboard-step="oauth-app"><b>' + appTitle() + '</b><p>One trip to GitHub: approve the app, install it on your repo, and you land back here. No keys to copy, and the secret never leaves the server side.</p>' +
|
|
7030
|
-
'<button type="button" class="ghbtn" data-action="manifest-start">' + GH_ICON + 'Connect GitHub ↗</button>' + manualOauthDetails() + '</li>';
|
|
7031
|
-
}
|
|
7032
|
-
|
|
7033
|
-
/* device-flow — "Command-line sign-in is on", the OPTIONAL fourth step (task 1004649).
|
|
7034
|
-
GitHub creates a manifest-born App with Device Flow OFF and offers no manifest
|
|
7035
|
-
field and no API to switch it on (the spike in task 1004649), so only the founder's
|
|
7036
|
-
own tick in the app's settings can — and the runner ticks this step when GitHub
|
|
7037
|
-
starts a device sign-in for the app. It never holds the project back: web sign-in
|
|
7038
|
-
works without it, and the CLI falls back to a web token meanwhile. */
|
|
7039
|
-
function renderStepDeviceFlow() {
|
|
7040
|
-
var title = '<b>Command-line sign-in is on</b>';
|
|
7041
|
-
if (ghStep('device_flow') === 'done') {
|
|
7042
|
-
return '<li class="sdone soptional" data-onboard-step="device-flow">' + title + '<p>Builders can sign in from a terminal too — the <code style="font-family:var(--font-mono)">bongos setup</code> command builders run on their own machine.</p></li>';
|
|
7043
|
-
}
|
|
7044
|
-
if (live.manifest !== 'done') {
|
|
7045
|
-
return '<li class="slocked soptional" data-onboard-step="device-flow">' + title + '<p>Optional — turn it on later. It lets builders sign in from a terminal, with the <code style="font-family:var(--font-mono)">bongos setup</code> command they run on their own machine; web sign-in works without it.</p></li>';
|
|
6869
|
+
else if (ghStep('sign_in') === 'now') { signIn.state = 'now'; signIn.right = 'not yet seen'; }
|
|
6870
|
+
else signIn.state = 'todo';
|
|
6871
|
+
|
|
6872
|
+
/* optional: GitHub leaves device flow off on a new app and lets only the founder switch
|
|
6873
|
+
it on (the spike in task 1004649); it never holds the project back */
|
|
6874
|
+
var device = { key: 'device_flow', title: 'Command-line sign-in', opt: true };
|
|
6875
|
+
if (ghStep('device_flow') === 'done') { device.state = 'done'; device.sub = 'Builders can sign in from a terminal too.'; }
|
|
6876
|
+
else {
|
|
6877
|
+
var settings = live.manifest === 'done' ? appSettingsUrl() : null;
|
|
6878
|
+
device.state = 'todo';
|
|
6879
|
+
device.sub = settings ? ['Turn it on later in ', { t: 'the app settings', href: settings }, '.'] : 'Turn it on later in the app settings.';
|
|
6880
|
+
/* past the runner's checking window: ask it to look again, once the install has ticked */
|
|
6881
|
+
if (live.pickUp === 'check' && ghStep('install') === 'done') device.act = 'check-again';
|
|
7046
6882
|
}
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
}
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
function
|
|
7065
|
-
var
|
|
7066
|
-
|
|
7067
|
-
|
|
6883
|
+
|
|
6884
|
+
var rows = [approve, install, signIn, device];
|
|
6885
|
+
/* "Send it to the platform team", once per attempt: sent, or try again */
|
|
6886
|
+
rows.forEach(function (r) {
|
|
6887
|
+
var acts = [].concat(r.act || []);
|
|
6888
|
+
if (acts.indexOf('ask-platform') === -1) return;
|
|
6889
|
+
if (live.askedPlatform === true) { r.sub = 'Sent. The platform team has it and finishes it from here.'; r.act = acts.filter(function (a) { return a !== 'ask-platform'; }); }
|
|
6890
|
+
else if (live.askedPlatform === 'failed') r.sub = [].concat(r.sub || [], ' That didn’t send. Try again.');
|
|
6891
|
+
});
|
|
6892
|
+
return rows;
|
|
6893
|
+
}
|
|
6894
|
+
|
|
6895
|
+
function factDone(r) { return r.state === 'done'; }
|
|
6896
|
+
function factRequired(r) { return !r.opt; }
|
|
6897
|
+
/* every required fact done: the optional one never holds the project back. PURE. */
|
|
6898
|
+
function setupReady(home, gh) { return home.concat(gh).filter(factRequired).every(factDone); }
|
|
6899
|
+
/* the part's count: "N of M done", the optional fact not counted; "ready ✓" when done. PURE. */
|
|
6900
|
+
function setupCount(home, gh) {
|
|
6901
|
+
var req = home.concat(gh).filter(factRequired);
|
|
6902
|
+
var done = req.filter(factDone).length;
|
|
6903
|
+
return done === req.length ? 'ready ✓' : done + ' of ' + req.length + ' done';
|
|
6904
|
+
}
|
|
6905
|
+
/* ONE sentence: what is happening now, and whether it is the founder's turn. Parts are
|
|
6906
|
+
{ t, b (bold), href } — text only, drawn as nodes. PURE over the facts and the reads. */
|
|
6907
|
+
function setupSentence(home, gh) {
|
|
6908
|
+
var inst = live.inst, name = projectName();
|
|
6909
|
+
if (live.gone === 'session') return [{ t: 'Your session ended. ' }, { t: 'Sign in again', href: API + '/auth/web/start?return=/projects%3Fview%3Dnew&repos=1' }, { t: ' to keep watching.' }];
|
|
6910
|
+
if (live.gone === 'missing') return [{ t: 'This project can’t be seen any more. It may have been switched off.' }];
|
|
6911
|
+
if (!inst) return [{ t: 'Asking the platform how it is going…' }];
|
|
6912
|
+
if (setupReady(home, gh)) {
|
|
6913
|
+
return [{ t: name + ' is ready.', b: true }].concat(ghStep('device_flow') === 'done' ? [] : [{ t: ' Command-line sign-in is optional; it ticks itself whenever you turn it on.' }]);
|
|
7068
6914
|
}
|
|
7069
|
-
|
|
7070
|
-
|
|
6915
|
+
var oi = inst.open_intent;
|
|
6916
|
+
if (inst.status === 'tearing_down' || (oi && oi.action === 'teardown') || (inst.status === 'torn_down' && !(oi && oi.action === 'provision'))) {
|
|
6917
|
+
return [{ t: (inst.status === 'torn_down' ? 'This project is switched off.' : 'This project is switching off.') + ' Bring it back any time from ' }, { t: 'its own page', href: manageHref() }, { t: '.' }];
|
|
7071
6918
|
}
|
|
7072
|
-
var
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
if (
|
|
7076
|
-
|
|
7077
|
-
|
|
6919
|
+
var all = home.concat(gh);
|
|
6920
|
+
var bad = all.filter(function (r) { return r.state === 'bad'; })[0];
|
|
6921
|
+
if (bad) return [{ t: 'One step stopped.', b: true }, { t: ' ' + (gh.indexOf(bad) !== -1 ? NO_SECOND_APP : 'Nothing is lost.') }];
|
|
6922
|
+
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() }, { t: '.' }];
|
|
6923
|
+
var homeDone = home.every(factDone);
|
|
6924
|
+
var approve = gh[0], install = gh[1];
|
|
6925
|
+
if (approve.state === 'you' && live.manifest) return [{ t: 'Your turn on GitHub:', b: true }, { t: ' approve the app in the tab that opened. This page ticks on its own when you do, on any device.' }];
|
|
6926
|
+
if (approve.state === 'you') {
|
|
6927
|
+
return homeDone ? [{ t: 'Its home is ready. ' }, { t: 'Your turn: connect GitHub.', b: true }]
|
|
6928
|
+
: [{ t: 'Setting up its home. ' }, { t: 'You can connect GitHub now', b: true }, { t: '; nothing waits on you otherwise.' }];
|
|
7078
6929
|
}
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
6930
|
+
if (install.state === 'you') return [{ t: 'Your turn on GitHub:', b: true }, { t: ' confirm the install on ' + (repoName() || 'your repo') + '. The repo is already picked.' }];
|
|
6931
|
+
if (gh.some(function (r) { return r.state === 'now'; })) return [{ t: 'Nothing is needed from you. The platform is checking with GitHub, and this page ticks on its own.' }];
|
|
6932
|
+
return [{ t: homeDone ? 'Its home is ready.' : 'Setting up its home. Nothing is needed from you.' }];
|
|
6933
|
+
}
|
|
6934
|
+
|
|
6935
|
+
/* ── drawing it: nodes and textContent only (no innerHTML), change-only so a poll that
|
|
6936
|
+
moved nothing writes nothing, and a fact's buttons are rebuilt only when the fact
|
|
6937
|
+
itself changed (its "checked 6s ago" is rewritten in place). The sentence is the
|
|
6938
|
+
one live region (#setupNow), written only when its words change. ── */
|
|
6939
|
+
var FACT_MARK = { done: '✓', bad: '!', you: '→', now: '', todo: '' };
|
|
6940
|
+
var FACT_SAID = { done: 'done', bad: 'stopped', you: 'your turn', now: 'under way', todo: 'not yet' };
|
|
6941
|
+
/* the canonical onboard plan's human step each GitHub fact is (modules/provisioning/
|
|
6942
|
+
onboard-plan.js); the plan's other two live on the page too — github-repo is the new
|
|
6943
|
+
repo part 02 makes at Create (#ghFresh), enter-hall the Enter Genesis door. The drift
|
|
6944
|
+
guard in tests/onboard.mjs holds the set equal to the plan's. */
|
|
6945
|
+
var FACT_PLAN_STEP = { approve: 'oauth-app', install: 'app-install', sign_in: 'first-sign-in', device_flow: 'device-flow' };
|
|
6946
|
+
var factsDrawn = {}; /* fact key → what was last drawn, without its right-hand time */
|
|
6947
|
+
var sentenceSaid = '';
|
|
6948
|
+
function textNode(tag, cls, text) {
|
|
6949
|
+
var e = document.createElement(tag);
|
|
6950
|
+
if (cls) e.className = cls;
|
|
6951
|
+
if (text != null) e.textContent = text;
|
|
6952
|
+
return e;
|
|
6953
|
+
}
|
|
6954
|
+
/* the only places a part 03 link may go: GitHub's own pages (the app's install page and
|
|
6955
|
+
settings), this page's own views, and this platform's sign-in. Anything else — a value
|
|
6956
|
+
a read handed over that is not one of these — is drawn as plain text, never a link. PURE. */
|
|
6957
|
+
function factHref(h) {
|
|
6958
|
+
h = String(h || '');
|
|
6959
|
+
return /^https:\/\/github\.com\/[A-Za-z0-9/_.?=&%-]*$/.test(h) || /^\?view=[a-z]+(&id=[0-9A-Za-z%]+)?$/.test(h) || /^\/api\/[A-Za-z0-9/_.?=&%-]*$/.test(h) ? h : '';
|
|
6960
|
+
}
|
|
6961
|
+
function partsInto(el, parts) {
|
|
6962
|
+
parts.forEach(function (p) {
|
|
6963
|
+
if (typeof p === 'string') p = { t: p };
|
|
6964
|
+
if (p.href && !factHref(p.href)) p = { t: p.t };
|
|
6965
|
+
var n;
|
|
6966
|
+
if (p.href) {
|
|
6967
|
+
n = textNode('a', null, p.t);
|
|
6968
|
+
n.setAttribute('href', p.href);
|
|
6969
|
+
if (/^https:\/\//.test(p.href)) { n.setAttribute('target', '_blank'); n.setAttribute('rel', 'noopener'); }
|
|
6970
|
+
} else n = p.b ? textNode('b', null, p.t) : document.createTextNode(p.t);
|
|
6971
|
+
el.appendChild(n);
|
|
6972
|
+
});
|
|
6973
|
+
}
|
|
6974
|
+
function factAct(k, r) {
|
|
6975
|
+
var b;
|
|
6976
|
+
if (k === 'pick-up-link' || k === 'install-link') {
|
|
6977
|
+
b = textNode('a', k === 'pick-up-link' ? 'btn small ghost' : 'linklike', k === 'pick-up-link' ? 'Pick up where it stopped ↗' : 'Open the install page ↗');
|
|
6978
|
+
/* the install page of THIS app on GitHub (seedSetup and ghFacts both build it to that
|
|
6979
|
+
shape); anything else falls back to the founder's own GitHub installations list */
|
|
6980
|
+
b.setAttribute('href', /^https:\/\/github\.com\/apps\/[A-Za-z0-9-]+\/installations\/new(\?state=[A-Za-z0-9%_-]+)?$/.test(r.href || '') ? r.href : 'https://github.com/settings/installations');
|
|
6981
|
+
b.setAttribute('target', '_blank'); b.setAttribute('rel', 'noopener');
|
|
6982
|
+
if (k === 'pick-up-link') b.setAttribute('data-action', 'pick-up');
|
|
6983
|
+
return b;
|
|
7092
6984
|
}
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
6985
|
+
if (k === 'manage') { b = textNode('a', 'btn small ghost', r.actText); b.setAttribute('href', manageHref()); return b; }
|
|
6986
|
+
var words = { connect: 'Connect GitHub ↗', reopen: 'Reopen GitHub ↗', 'pick-up': 'Pick up where it stopped', 'check-again': 'Turned it on? Check again', 'ask-platform': 'Send it to the platform team' };
|
|
6987
|
+
var cls = { connect: 'btn', reopen: 'btn small ghost', 'pick-up': 'btn small ghost', 'check-again': 'linklike', 'ask-platform': 'linklike' };
|
|
6988
|
+
var action = { connect: 'manifest-start', reopen: 'reopen-github', 'pick-up': 'pick-up', 'check-again': 'pick-up', 'ask-platform': 'ask-platform' };
|
|
6989
|
+
b = textNode('button', cls[k], words[k]);
|
|
6990
|
+
b.setAttribute('type', 'button');
|
|
6991
|
+
b.setAttribute('data-action', action[k]);
|
|
6992
|
+
return b;
|
|
6993
|
+
}
|
|
6994
|
+
function factNode(r) {
|
|
6995
|
+
var f = textNode('div', 'setFact ' + r.state);
|
|
6996
|
+
f.setAttribute('role', 'listitem');
|
|
6997
|
+
f.setAttribute('data-fact', r.key);
|
|
6998
|
+
if (FACT_PLAN_STEP[r.key]) f.setAttribute('data-onboard-step', FACT_PLAN_STEP[r.key]);
|
|
6999
|
+
var d = textNode('span', 'd', FACT_MARK[r.state]);
|
|
7000
|
+
d.setAttribute('aria-hidden', 'true');
|
|
7001
|
+
f.appendChild(d);
|
|
7002
|
+
var t = textNode('span', 't', r.title);
|
|
7003
|
+
if (r.opt) t.appendChild(textNode('span', 'optional', 'optional'));
|
|
7004
|
+
t.appendChild(textNode('span', 'vh', ', ' + FACT_SAID[r.state]));
|
|
7005
|
+
if (r.sub && r.sub.length) { var sm = textNode('small'); partsInto(sm, [].concat(r.sub)); t.appendChild(sm); }
|
|
7006
|
+
f.appendChild(t);
|
|
7007
|
+
f.appendChild(textNode('span', 'r', r.right || ''));
|
|
7008
|
+
var acts = [].concat(r.act || []);
|
|
7009
|
+
if (acts.length || r.tech) {
|
|
7010
|
+
var box = textNode('div', 'acts');
|
|
7011
|
+
acts.forEach(function (k) { box.appendChild(factAct(k, r)); });
|
|
7012
|
+
if (r.tech) {
|
|
7013
|
+
var det = textNode('details');
|
|
7014
|
+
det.appendChild(textNode('summary', null, 'Technical details'));
|
|
7015
|
+
det.appendChild(textNode('p', null, r.tech));
|
|
7016
|
+
box.appendChild(det);
|
|
7017
|
+
}
|
|
7018
|
+
f.appendChild(box);
|
|
7102
7019
|
}
|
|
7103
|
-
|
|
7104
|
-
|
|
7020
|
+
return f;
|
|
7021
|
+
}
|
|
7022
|
+
function drawFacts(list, rows) {
|
|
7023
|
+
if (!list) return;
|
|
7024
|
+
rows.forEach(function (r, i) {
|
|
7025
|
+
var shape = JSON.stringify(Object.assign({}, r, { right: '' }));
|
|
7026
|
+
var have = list.children[i];
|
|
7027
|
+
if (have && have.getAttribute('data-fact') === r.key && factsDrawn[r.key] === shape) {
|
|
7028
|
+
var right = have.querySelector('.r');
|
|
7029
|
+
if (right && right.textContent !== (r.right || '')) right.textContent = r.right || '';
|
|
7030
|
+
return;
|
|
7031
|
+
}
|
|
7032
|
+
var fresh = factNode(r);
|
|
7033
|
+
if (have) { parkAppPick(have); list.replaceChild(fresh, have); } else list.appendChild(fresh);
|
|
7034
|
+
factsDrawn[r.key] = shape;
|
|
7035
|
+
});
|
|
7036
|
+
while (list.children.length > rows.length) list.removeChild(list.lastChild);
|
|
7037
|
+
}
|
|
7038
|
+
/* the account choice sits beside Connect GitHub (task 1004651): moved into the Approve
|
|
7039
|
+
fact while Connect is offered, and back to its own place otherwise */
|
|
7040
|
+
function parkAppPick(from) {
|
|
7041
|
+
var box = $('aaPick');
|
|
7042
|
+
if (box && from && from.contains(box)) $('doneBody').insertBefore(box, $('hopLink'));
|
|
7043
|
+
}
|
|
7044
|
+
function placeAppPick(offered) {
|
|
7045
|
+
var box = $('aaPick');
|
|
7046
|
+
var acts = offered ? document.querySelector('#ghFacts [data-fact="approve"] .acts') : null;
|
|
7047
|
+
if (acts && box.parentNode !== acts) acts.appendChild(box);
|
|
7048
|
+
else if (!acts) parkAppPick($('ghFacts'));
|
|
7049
|
+
syncAppPick(!!acts);
|
|
7050
|
+
}
|
|
7051
|
+
/* every required fact done: "<name> is alive.", the invite once the hall answers (PS15)
|
|
7052
|
+
and the door into Genesis, the hall's founding mode, on the address that answered */
|
|
7053
|
+
function paintAlive(ready) {
|
|
7054
|
+
var inst = live.inst;
|
|
7055
|
+
var on = !!(ready && inst && hostOk(inst.domain) && addressReady(inst) && inst.last_verified_up === true);
|
|
7056
|
+
$('done-h').textContent = projectName() + ' is alive.';
|
|
7057
|
+
var box = $('doneAlive');
|
|
7058
|
+
if (box.hidden === !on) return;
|
|
7059
|
+
box.hidden = !on;
|
|
7060
|
+
/* inst.domain has passed hostOk (a bare host name, nothing else), so this is always an
|
|
7061
|
+
https URL to the project's own address — stricter than safeHttpUrl, which takes any URL */
|
|
7062
|
+
if (on) $('enterGenesis').setAttribute('href', 'https://' + inst.domain);
|
|
7063
|
+
else $('enterGenesis').removeAttribute('href');
|
|
7064
|
+
paintInviteGate(on);
|
|
7065
|
+
if (on) hopArrive();
|
|
7066
|
+
}
|
|
7067
|
+
function paintFacts() {
|
|
7068
|
+
var title = $('donePartT'), count = $('ghState');
|
|
7069
|
+
if (!title || !count || !live) return; /* hoisted: show() can ask before live is set */
|
|
7070
|
+
if (state.step !== 2) { title.textContent = 'Connect GitHub'; count.textContent = 'opens once your project exists'; return; }
|
|
7071
|
+
var now = Date.now();
|
|
7072
|
+
var home = homeFacts(live.inst, now), gh = ghFacts(now);
|
|
7073
|
+
var ready = setupReady(home, gh);
|
|
7074
|
+
title.textContent = 'Getting ' + projectName() + ' ready';
|
|
7075
|
+
count.textContent = setupCount(home, gh);
|
|
7076
|
+
drawFacts($('homeFacts'), home);
|
|
7077
|
+
drawFacts($('ghFacts'), gh);
|
|
7078
|
+
placeAppPick([].concat(gh[0].act || []).indexOf('connect') !== -1);
|
|
7079
|
+
var said = setupSentence(home, gh), key = JSON.stringify(said);
|
|
7080
|
+
if (key !== sentenceSaid) {
|
|
7081
|
+
sentenceSaid = key;
|
|
7082
|
+
var line = $('setupNow');
|
|
7083
|
+
line.textContent = '';
|
|
7084
|
+
partsInto(line, said);
|
|
7105
7085
|
}
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
/* enter-hall — not a row of the GitHub checklist (the "is alive" block below carries
|
|
7113
|
-
the way in); kept only for a project with no address, whose hall is local */
|
|
7114
|
-
function renderStepEnterHall() {
|
|
7115
|
-
var domain = resolvedDomain();
|
|
7116
|
-
if (domain) return '';
|
|
7117
|
-
return '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>No address yet — run <code style="font-family:var(--font-mono)">bongos dev</code> in your repo checkout to start the local hall on your machine (on an address it lives at /builders).</p></li>';
|
|
7118
|
-
}
|
|
7119
|
-
|
|
7120
|
-
var STEP_RENDERERS = {
|
|
7121
|
-
'github-repo': renderStepGithubRepo,
|
|
7122
|
-
'oauth-app': renderStepOauthApp,
|
|
7123
|
-
'device-flow': renderStepDeviceFlow,
|
|
7124
|
-
'app-install': renderStepAppInstall,
|
|
7125
|
-
'first-sign-in': renderStepFirstSignIn,
|
|
7126
|
-
'enter-hall': renderStepEnterHall
|
|
7127
|
-
};
|
|
7086
|
+
$('hopLink').hidden = ready;
|
|
7087
|
+
paintAlive(ready);
|
|
7088
|
+
}
|
|
7089
|
+
/* a fact's buttons drawn afresh — after an action that may have changed nothing the
|
|
7090
|
+
server says (a refused start, a pick-up that did not go through) */
|
|
7091
|
+
function repaintFacts() { factsDrawn = {}; paintFacts(); }
|
|
7128
7092
|
|
|
7129
7093
|
/* The newborn-instance welcome + account-nag suppression (direction §5.6,
|
|
7130
7094
|
idea 1000701) is NOT implemented here by design: it lives on the CHILD
|
|
@@ -7132,42 +7096,19 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7132
7096
|
control-plane wizard cannot reach. Spun out per the direction's §9
|
|
7133
7097
|
delivery-split as task 1002739. */
|
|
7134
7098
|
|
|
7135
|
-
/* the mockup's order: the repo (only when the founder must make it), approve,
|
|
7136
|
-
install, sign-in, command line; a plan step this page has no row for goes last */
|
|
7137
|
-
var STEP_ORDER = ['github-repo', 'oauth-app', 'app-install', 'first-sign-in', 'device-flow', 'enter-hall'];
|
|
7138
|
-
function stepRank(s) { var i = STEP_ORDER.indexOf(s.key); return i < 0 ? STEP_ORDER.length : i; }
|
|
7139
|
-
function renderSteps() {
|
|
7140
|
-
fetchPlan().then(function (steps) {
|
|
7141
|
-
var html = steps.filter(function (s) { return s && s.kind === 'human'; }).slice().sort(function (a, b) { return stepRank(a) - stepRank(b); }).map(function (s) {
|
|
7142
|
-
var r = STEP_RENDERERS[s.key];
|
|
7143
|
-
return r ? r(s) : '<li data-onboard-step="' + esc(s.key) + '"><b>' + esc(s.label || s.key) + '</b></li>';
|
|
7144
|
-
}).join('');
|
|
7145
|
-
/* the account picker stands beside Connect GitHub, so it shows while that is offered */
|
|
7146
|
-
syncAppPick(html.indexOf('data-action="manifest-start"') !== -1);
|
|
7147
|
-
if (html === lastStepsHtml) return; /* change-only re-render — polls never clobber open UI */
|
|
7148
|
-
lastStepsHtml = html;
|
|
7149
|
-
$('humanSteps').innerHTML = html;
|
|
7150
|
-
if (typeof stepsPaint === 'function') stepsPaint();
|
|
7151
|
-
maybeAutoOpen();
|
|
7152
|
-
});
|
|
7153
|
-
}
|
|
7154
|
-
|
|
7155
7099
|
function renderDone() {
|
|
7156
|
-
var adopt = state.mode === 'adopt';
|
|
7157
7100
|
$('doneRender').hidden = true; // sendRenderSetup shows it again when Render is the choice
|
|
7158
7101
|
$('doneApp').hidden = true; $('doneApp').textContent = '';
|
|
7159
|
-
$('done-h').textContent = 'Setting up ' + (state.name || state.slug || 'your project');
|
|
7160
|
-
$('doneLede').innerHTML = 'We’re setting up <strong>' + esc(state.name || state.slug) + '</strong>' +
|
|
7161
|
-
(adopt ? ' on your existing repo' : '') +
|
|
7162
|
-
': its server, database and address. Watch it here, or any time on <a href="?view=mine">your projects page</a>.';
|
|
7163
7102
|
/* the picker's later half (task 1002342, BV1.R23). The modules step is
|
|
7164
7103
|
skippable, so this panel names where it lives afterwards rather than
|
|
7165
7104
|
leaving an owner to go looking for it. */
|
|
7166
|
-
|
|
7105
|
+
var mods = $('doneModules');
|
|
7106
|
+
mods.textContent = '';
|
|
7107
|
+
partsInto(mods, ['Add or remove modules later in ', { t: 'the project’s settings', href: manageHref() }, '.']);
|
|
7167
7108
|
/* the invite step waits for the hall (task 1004503): until it answers, one line */
|
|
7168
7109
|
paintInviteGate(false);
|
|
7169
|
-
|
|
7170
|
-
|
|
7110
|
+
hopOpen(false); /* the cloud hop waits behind its link until asked (task 1004683) */
|
|
7111
|
+
paintFacts();
|
|
7171
7112
|
}
|
|
7172
7113
|
|
|
7173
7114
|
/* The wizard's own copy links between views ("your projects page", the
|
|
@@ -7190,30 +7131,20 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7190
7131
|
setView(to, true);
|
|
7191
7132
|
});
|
|
7192
7133
|
|
|
7193
|
-
/* One delegated listener
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
if (
|
|
7202
|
-
|
|
7203
|
-
if (
|
|
7204
|
-
var t = ev.target.closest('.copy');
|
|
7205
|
-
if (!t) return;
|
|
7206
|
-
navigator.clipboard.writeText(t.getAttribute('data-copy')).then(function () {
|
|
7207
|
-
t.classList.add('did'); t.textContent = 'Copied';
|
|
7208
|
-
setTimeout(function () { t.classList.remove('did'); t.textContent = 'Copy'; }, 1600);
|
|
7209
|
-
});
|
|
7210
|
-
});
|
|
7211
|
-
|
|
7212
|
-
$('humanSteps').addEventListener('change', function (ev) {
|
|
7213
|
-
var box = ev.target.closest && ev.target.closest('[data-action="auto-open"]');
|
|
7214
|
-
if (!box) return;
|
|
7215
|
-
try { localStorage.setItem(AUTO_OPEN_KEY, box.checked ? '1' : '0'); } catch (e) {}
|
|
7134
|
+
/* One delegated listener for part 03's actions — paintFacts redraws a fact's buttons,
|
|
7135
|
+
so the handlers live on the body. The pick-up LINK to GitHub's install page keeps its
|
|
7136
|
+
default (it opens) and asks the runner to look again (task 1004652). */
|
|
7137
|
+
$('doneBody').addEventListener('click', function (ev) {
|
|
7138
|
+
var b = ev.target.closest && ev.target.closest('[data-action]');
|
|
7139
|
+
if (!b) return;
|
|
7140
|
+
var a = b.getAttribute('data-action');
|
|
7141
|
+
if (a === 'manifest-start') startManifest(b);
|
|
7142
|
+
else if (a === 'pick-up') pickUpSetup(b);
|
|
7143
|
+
else if (a === 'reopen-github') reopenGithub(b);
|
|
7144
|
+
else if (a === 'ask-platform') askPlatform(b);
|
|
7216
7145
|
});
|
|
7146
|
+
/* the right-hand "checked 6s ago" stays true between reads (text only, change-only) */
|
|
7147
|
+
setInterval(function () { if (currentView === 'new' && state.step === 2 && !document.hidden) paintFacts(); }, 15000);
|
|
7217
7148
|
|
|
7218
7149
|
/* ── C8: the wizard's polls run only while the create view is on stage, like the
|
|
7219
7150
|
sky. Off-view a due tick parks itself instead of fetching; enter() resumes it.
|
|
@@ -7325,8 +7256,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7325
7256
|
function applyPickUp(d) {
|
|
7326
7257
|
if (typeof d.status === 'string') { live.manifest = d.status; live.manifestFailure = d.failure || null; rememberManifest(undefined, d.status); }
|
|
7327
7258
|
seedSetup(d.checklist, d.install_url, d);
|
|
7328
|
-
|
|
7329
|
-
renderSteps();
|
|
7259
|
+
repaintFacts();
|
|
7330
7260
|
}
|
|
7331
7261
|
/* …and keep watching: the attempt's own poll when this tab knows it, else the instance read */
|
|
7332
7262
|
function watchSetupAgain() {
|
|
@@ -7353,7 +7283,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7353
7283
|
}).then(function (res) {
|
|
7354
7284
|
live.askedPlatform = res.ok ? true : 'failed';
|
|
7355
7285
|
}).catch(function () { live.askedPlatform = 'failed'; })
|
|
7356
|
-
.then(
|
|
7286
|
+
.then(repaintFacts);
|
|
7357
7287
|
}
|
|
7358
7288
|
|
|
7359
7289
|
function startManifest(btn) {
|
|
@@ -7390,7 +7320,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7390
7320
|
noteAppOrg(d.account === 'org' ? d.org_login : null);
|
|
7391
7321
|
manifestStartedAt = Date.now();
|
|
7392
7322
|
rememberManifest(d.state, 'pending');
|
|
7393
|
-
|
|
7323
|
+
paintFacts();
|
|
7394
7324
|
pollManifest(d.state);
|
|
7395
7325
|
}).catch(function (err) {
|
|
7396
7326
|
if (String(err && err.message) === 'signing in') return;
|
|
@@ -7398,13 +7328,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7398
7328
|
/* GitHub already approved an app for this project (task 1004652): no second one —
|
|
7399
7329
|
this page was behind, so it re-learns the truth and draws that step's own action */
|
|
7400
7330
|
if (err && (err.code === 'app_exists' || err.code === 'github_in_progress')) {
|
|
7401
|
-
|
|
7331
|
+
repaintFacts();
|
|
7402
7332
|
if (instanceId) startProgress(instanceId);
|
|
7403
7333
|
return;
|
|
7404
7334
|
}
|
|
7405
7335
|
live.manifest = 'error'; live.manifestFailure = null; live.manifestMsg = String((err && err.message) || 'could not start');
|
|
7406
7336
|
live.manifestCode = String((err && err.code) || '');
|
|
7407
|
-
|
|
7337
|
+
repaintFacts();
|
|
7408
7338
|
});
|
|
7409
7339
|
}
|
|
7410
7340
|
|
|
@@ -7422,8 +7352,10 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7422
7352
|
var moreMoved = false;
|
|
7423
7353
|
if (more && typeof more === 'object') {
|
|
7424
7354
|
var pick = typeof more.pick_up === 'string' ? more.pick_up : null, ver = !!more.verifying;
|
|
7425
|
-
|
|
7426
|
-
|
|
7355
|
+
/* the repo it installs on: github-setup.js targetRepo, strict, re-checked here */
|
|
7356
|
+
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) ? more.repo : live.repo;
|
|
7357
|
+
moreMoved = pick !== live.pickUp || ver !== live.verifying || repo !== live.repo;
|
|
7358
|
+
live.pickUp = pick; live.verifying = ver; live.repo = repo;
|
|
7427
7359
|
}
|
|
7428
7360
|
var next = JSON.stringify(checklist), was = live.setup ? JSON.stringify(live.setup) : '';
|
|
7429
7361
|
var url = typeof installUrl === 'string' && /^https:\/\/github\.com\/apps\//.test(installUrl) ? installUrl : null;
|
|
@@ -7466,7 +7398,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7466
7398
|
}
|
|
7467
7399
|
var focused = !document.hidden && (typeof document.hasFocus !== 'function' || document.hasFocus());
|
|
7468
7400
|
var next = ghTabState(ghTab, ghTabLeft, ghTabSince, Date.now(), focused);
|
|
7469
|
-
if (next !== ghTabTrouble) { ghTabTrouble = next;
|
|
7401
|
+
if (next !== ghTabTrouble) { ghTabTrouble = next; paintFacts(); }
|
|
7470
7402
|
}
|
|
7471
7403
|
function pollManifest(stateToken) {
|
|
7472
7404
|
stopManifestPoll();
|
|
@@ -7480,14 +7412,15 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7480
7412
|
.then(function (res) { if (!res.ok) throw new Error('soft'); return res.json(); })
|
|
7481
7413
|
.then(function (d) {
|
|
7482
7414
|
if (myGen !== manifestGen) return;
|
|
7483
|
-
|
|
7415
|
+
live.readAt = Date.now(); /* the state-keyed read is a server read too */
|
|
7416
|
+
if (d.app_slug && d.app_slug !== live.appSlug) { live.appSlug = d.app_slug; rememberManifest(undefined, undefined, d.app_slug); paintFacts(); }
|
|
7484
7417
|
/* the server-verified ticks (task 1004649), and whose account the app is on */
|
|
7485
7418
|
var setupMoved = seedSetup(d.checklist, d.install_url, d);
|
|
7486
7419
|
if (noteAppOrg(d.app_org)) setupMoved = true;
|
|
7487
|
-
if (setupMoved)
|
|
7420
|
+
if (setupMoved) paintFacts();
|
|
7488
7421
|
var next = d.ready ? 'done' : (d.status || 'pending');
|
|
7489
7422
|
var failure = d.failure || null;
|
|
7490
|
-
if (next !== live.manifest || failure !== live.manifestFailure) { live.manifest = next; live.manifestFailure = failure; rememberManifest(undefined, next);
|
|
7423
|
+
if (next !== live.manifest || failure !== live.manifestFailure) { live.manifest = next; live.manifestFailure = failure; rememberManifest(undefined, next); paintFacts(); }
|
|
7491
7424
|
else noteManifestWait();
|
|
7492
7425
|
if (!d.terminal) manifestPollTimer = setTimeout(tick, wizPollDelay(4000));
|
|
7493
7426
|
else manifestPollTimer = null; /* a stale fired id would read as "still polling" and resume a finished poll */
|
|
@@ -7497,179 +7430,23 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7497
7430
|
tick();
|
|
7498
7431
|
}
|
|
7499
7432
|
|
|
7500
|
-
/* ──
|
|
7501
|
-
Read-only — GET /provisioning/instances/:id (own-scoped) every 5s.
|
|
7433
|
+
/* ── the owner's instance read (task 2083): poll the project until it is up.
|
|
7434
|
+
Read-only — GET /provisioning/instances/:id (own-scoped) every 5s. That one read
|
|
7435
|
+
is part 03's Its home, and its github_setup is the GitHub checklist after a reload
|
|
7436
|
+
(task 1004683: no stage strip, no tick counter — the facts are the read). ── */
|
|
7502
7437
|
var pollTimer = null;
|
|
7503
7438
|
var watchPending = false; /* a parked progress watch, resumed on re-entry */
|
|
7504
7439
|
var watchGen = 0; /* same orphaning contract as manifestGen above */
|
|
7505
7440
|
function stopPolling() { watchGen += 1; if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; } }
|
|
7506
|
-
/*
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
signin: { names: ['Approve on GitHub', 'Placing credentials', 'Ready to sign in'], cls: '' }
|
|
7516
|
-
};
|
|
7517
|
-
/* No live-region attributes here: this strip is destroyed and rebuilt on
|
|
7518
|
-
every 5s tick, and a live region that is itself replaced never announces.
|
|
7519
|
-
#liveProgress is the stable region; this is only its contents. (The
|
|
7520
|
-
mine-card twin stripMarkup() still carries its own — left for a later pass.) */
|
|
7521
|
-
function progressMarkup(stageNow, stagesDone, note, cls, flow) {
|
|
7522
|
-
var f = PROG_FLOWS[flow || 'standup'];
|
|
7523
|
-
return '<div class="prog ' + (f.cls ? f.cls + ' ' : '') + (cls || '') + '">' +
|
|
7524
|
-
'<ul class="stages">' + f.names.map(function (n, i) {
|
|
7525
|
-
var c = stagesDone > i ? 'done' : (stageNow === i ? 'now' : '');
|
|
7526
|
-
/* the dot carries the state for the eye only — a screen reader needs
|
|
7527
|
-
the word, and the live stage marked, or it hears three bare names */
|
|
7528
|
-
var word = c === 'done' ? 'done' : (c === 'now' ? 'current' : 'upcoming');
|
|
7529
|
-
return '<li class="' + c + '"' + (c === 'now' ? ' aria-current="step"' : '') + '>' +
|
|
7530
|
-
'<span class="sdot"></span><span class="slbl">' + n + '</span>' +
|
|
7531
|
-
'<span class="vh">, ' + word + '</span></li>';
|
|
7532
|
-
}).join('') + '</ul>' +
|
|
7533
|
-
'<p class="pnote">' + note + '</p></div>';
|
|
7534
|
-
}
|
|
7535
|
-
/* Change-only write into the stable live region: an identical tick performs
|
|
7536
|
-
no DOM write at all, so the region announces real movement only — the same
|
|
7537
|
-
contract renderSteps() uses with lastStepsHtml. */
|
|
7538
|
-
function setProgress(html) {
|
|
7539
|
-
var el = $('liveProgress');
|
|
7540
|
-
if (!el || html === lastProgHtml) return;
|
|
7541
|
-
lastProgHtml = html;
|
|
7542
|
-
el.innerHTML = html;
|
|
7543
|
-
if (typeof hopSync === 'function') hopSync(el);
|
|
7544
|
-
}
|
|
7545
|
-
/* The done panel's moment (task 1004578): 'wait' while the project is being
|
|
7546
|
-
stood up (the cloud hop), 'arrive' once it answers, and 'plain' for a snag,
|
|
7547
|
-
a teardown, a settings change, or a project standing with no address (it
|
|
7548
|
-
will never answer, so there is nothing to arrive at) — each keeps the strip
|
|
7549
|
-
as it always was. Same branch order as renderProgress below. */
|
|
7550
|
-
function momentFor(inst) {
|
|
7551
|
-
var s = inst && inst.status, oi = inst && inst.open_intent;
|
|
7552
|
-
/* only the intents renderProgress itself speaks for count — any other
|
|
7553
|
-
(a running standup, say) falls through to the status, as it does there */
|
|
7554
|
-
var spoken = oi && s !== 'provisioning' && s !== 'tearing_down' && s !== 'requested' ? oi.action : '';
|
|
7555
|
-
if (spoken === 'teardown' || spoken === 'settings-apply') return 'plain';
|
|
7556
|
-
if (spoken === 'provision') return 'wait';
|
|
7557
|
-
if (s === 'error' || s === 'tearing_down' || s === 'torn_down') return 'plain';
|
|
7558
|
-
if (s === 'active') {
|
|
7559
|
-
if (!inst.domain) return 'plain';
|
|
7560
|
-
if (addressReady(inst) && inst.last_verified_up) return 'arrive';
|
|
7561
|
-
}
|
|
7562
|
-
return 'wait';
|
|
7563
|
-
}
|
|
7564
|
-
function setMoment(m) {
|
|
7565
|
-
var panel = $('panel-done');
|
|
7566
|
-
if (!panel) return;
|
|
7567
|
-
if (typeof hopMoment === 'function') hopMoment(panel, m);
|
|
7568
|
-
else panel.setAttribute('data-moment', m);
|
|
7569
|
-
}
|
|
7570
|
-
/* Renders the current instance state; returns true when the state is terminal
|
|
7571
|
-
(no further polling wanted). The strip is chosen by the OPEN INTENT first,
|
|
7572
|
-
then status (F22): a torn-down instance with a queued revive must never be
|
|
7573
|
-
told to "start another project". */
|
|
7574
|
-
function renderProgress(inst) {
|
|
7575
|
-
if (!$('liveProgress')) return true;
|
|
7576
|
-
if (inst) setMoment(momentFor(inst));
|
|
7577
|
-
var s = inst && inst.status;
|
|
7578
|
-
var revive = !!(inst && inst.torn_down_at);
|
|
7579
|
-
var oi = inst && inst.open_intent;
|
|
7580
|
-
/* requested/provisioning/tearing_down speak their STATUS (a requested
|
|
7581
|
-
instance always has its provision intent open — the intent branch
|
|
7582
|
-
would make the status branch dead and diverge from the mine card) */
|
|
7583
|
-
if (oi && s !== 'provisioning' && s !== 'tearing_down' && s !== 'requested') {
|
|
7584
|
-
if (oi.action === 'provision') {
|
|
7585
|
-
setProgress(progressMarkup(0, 0,
|
|
7586
|
-
(s === 'torn_down' ? 'A revive is queued' : 'A standup is queued') + ' — this panel updates when it starts.',
|
|
7587
|
-
'', s === 'torn_down' ? 'revive' : 'standup'));
|
|
7588
|
-
return false;
|
|
7589
|
-
}
|
|
7590
|
-
if (oi.action === 'teardown') {
|
|
7591
|
-
setProgress(progressMarkup(0, 0, 'A teardown is queued — this panel updates when it starts.', '', 'teardown'));
|
|
7592
|
-
return false;
|
|
7593
|
-
}
|
|
7594
|
-
/* task 1003140: a settings change restarts the project — the panel says so, by state */
|
|
7595
|
-
if (oi.action === 'settings-apply') {
|
|
7596
|
-
setProgress(oi.state === 'running'
|
|
7597
|
-
? progressMarkup(1, 1, 'Applying a settings change — the project restarts to pick it up.', '', 'settings')
|
|
7598
|
-
: progressMarkup(0, 0, 'A settings change is queued — this panel updates when it starts.', '', 'settings'));
|
|
7599
|
-
return false;
|
|
7600
|
-
}
|
|
7601
|
-
}
|
|
7602
|
-
if (s === 'requested') {
|
|
7603
|
-
setProgress(progressMarkup(0, 0, 'Filed — waiting for the platform to pick it up. This updates the moment it starts.', '', revive ? 'revive' : 'standup'));
|
|
7604
|
-
} else if (s === 'provisioning') {
|
|
7605
|
-
setProgress(progressMarkup(1, 1,
|
|
7606
|
-
revive ? 'Bringing it back on the newest platform version — nothing needed from you.'
|
|
7607
|
-
: 'Raising the server, database, and address — nothing needed from you.',
|
|
7608
|
-
'', revive ? 'revive' : 'standup'));
|
|
7609
|
-
} else if (s === 'active') {
|
|
7610
|
-
/* Live lights on a VERIFIED answer, not on script completion (direction
|
|
7611
|
-
§7): the strip keys on the same last_verified_* overlay the manage
|
|
7612
|
-
list's chips read. null is deliberately distinct from false — a fresh
|
|
7613
|
-
standup stays unprobed for up to a sweep interval and must not read
|
|
7614
|
-
as a failure. */
|
|
7615
|
-
var addr = inst.domain ? 'https://' + inst.domain : null;
|
|
7616
|
-
var link = addr ? '<a href="' + esc(addr) + '" target="_blank" rel="noopener">' + esc(inst.domain) + '</a>' : null;
|
|
7617
|
-
var flow = revive ? 'revive' : 'standup';
|
|
7618
|
-
if (!addr) {
|
|
7619
|
-
/* domainless instances are never probed — there is no address to
|
|
7620
|
-
answer at, so a verified answer can never arrive; say what IS true
|
|
7621
|
-
instead of promising the hall (sign-in stays locked without one) */
|
|
7622
|
-
setProgress(progressMarkup(-1, 2,
|
|
7623
|
-
'The build finished — your project is standing, but it has no address yet, so sign-in can’t be wired up. Attach a domain any time from ' +
|
|
7624
|
-
projectPageLink('its own page') + '; the steps below that need one unlock then.', '', flow));
|
|
7625
|
-
return true;
|
|
7626
|
-
}
|
|
7627
|
-
/* task 1004505: no link until the platform's own lookup has seen the name
|
|
7628
|
-
answer. A founder's first click on a name that does not resolve yet is
|
|
7629
|
-
cached as "not found" by their resolver for up to half an hour. */
|
|
7630
|
-
if (!addressReady(inst)) {
|
|
7631
|
-
setProgress(progressMarkup(2, 2,
|
|
7632
|
-
'The build finished — your address is being set up. This panel links it the moment it answers.', '', flow));
|
|
7633
|
-
return false;
|
|
7634
|
-
}
|
|
7635
|
-
if (inst.last_verified_up === false) {
|
|
7636
|
-
setProgress(progressMarkup(2, 2,
|
|
7637
|
-
'The build finished, but ' + link + ' isn’t answering right now — this panel keeps checking.', '', flow));
|
|
7638
|
-
return false;
|
|
7639
|
-
}
|
|
7640
|
-
if (inst.last_verified_up) {
|
|
7641
|
-
/* the alive moment (task 1004504): said only once the project has answered */
|
|
7642
|
-
$('done-h').textContent = (state.name || state.slug || 'Your project') + ' is alive.';
|
|
7643
|
-
paintInviteGate(true); /* the hall answers, so others can join now */
|
|
7644
|
-
/* task 1004543: the line under it stops saying "setting up" once it answers */
|
|
7645
|
-
$('doneLede').innerHTML = 'Its home is ready. Watch the rest here, or any time on <a href="?view=mine">your projects page</a>.';
|
|
7646
|
-
setProgress(progressMarkup(-1, 3,
|
|
7647
|
-
'Your project is answering at ' + link + '. Finish the steps below and the hall is yours.', 'isLive', flow));
|
|
7648
|
-
return true;
|
|
7649
|
-
}
|
|
7650
|
-
/* null: standing, not checked yet — keep watching until the first probe */
|
|
7651
|
-
setProgress(progressMarkup(2, 2,
|
|
7652
|
-
'The build finished — checking that ' + link + ' answers…', '', flow));
|
|
7653
|
-
return false;
|
|
7654
|
-
} else if (s === 'error') {
|
|
7655
|
-
/* the honest snag (direction §5 / F19): no raw stderr as the headline, no
|
|
7656
|
-
false "keeps retrying" — retries stop, and the retry door is the project's
|
|
7657
|
-
own page (task 1003355 moved it off the row) */
|
|
7658
|
-
setProgress(progressMarkup(1, 1,
|
|
7659
|
-
'The standup stopped partway — it won’t continue on its own. Retry it (and see the technical details) from ' +
|
|
7660
|
-
projectPageLink('its own page') + '.', 'isErr'));
|
|
7661
|
-
} else if (s === 'tearing_down') {
|
|
7662
|
-
/* a deliberate end is calm, never styled as a failure (direction §5) —
|
|
7663
|
-
and it speaks the teardown vocabulary, not the standup's (F22) */
|
|
7664
|
-
setProgress(progressMarkup(1, 1, 'Taking the server offline. The repository stays yours; the database is kept.', '', 'teardown'));
|
|
7665
|
-
} else if (s === 'torn_down') {
|
|
7666
|
-
setProgress(progressMarkup(-1, 3, 'This instance was torn down. Bring it back any time from ' +
|
|
7667
|
-
projectPageLink('its own page') + ', or start another below.', '', 'teardown'));
|
|
7668
|
-
return true;
|
|
7669
|
-
} else {
|
|
7670
|
-
setProgress(progressMarkup(0, 0, 'Filed. Watching for the standup to start…'));
|
|
7671
|
-
}
|
|
7672
|
-
return false;
|
|
7441
|
+
/* Nothing more will change without the founder: every required fact done (and the
|
|
7442
|
+
runner no longer checking), a project switched off, or one standing with no address. */
|
|
7443
|
+
function setupSettled() {
|
|
7444
|
+
var inst = live.inst, oi = inst && inst.open_intent;
|
|
7445
|
+
if (!inst) return false;
|
|
7446
|
+
if (inst.status === 'torn_down' && !(oi && oi.action === 'provision')) return true;
|
|
7447
|
+
if (inst.status === 'active' && !hostOk(inst.domain) && !oi) return true;
|
|
7448
|
+
var now = Date.now();
|
|
7449
|
+
return setupReady(homeFacts(inst, now), ghFacts(now)) && !live.verifying;
|
|
7673
7450
|
}
|
|
7674
7451
|
function startProgress(id) {
|
|
7675
7452
|
if (!id) return;
|
|
@@ -7677,41 +7454,21 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7677
7454
|
stopPolling();
|
|
7678
7455
|
watchPending = false;
|
|
7679
7456
|
var myGen = watchGen;
|
|
7680
|
-
var ticks = 0;
|
|
7681
|
-
var watchFlow = 'standup'; /* the vocabulary the strip is speaking (F22) */
|
|
7682
|
-
setProgress(progressMarkup(0, 0, 'Filed. Watching for the standup to start…'));
|
|
7683
7457
|
function tick() {
|
|
7684
7458
|
if (myGen !== watchGen) return;
|
|
7685
7459
|
if (!wizPollActive()) { watchPending = true; stopPolling(); return; }
|
|
7686
7460
|
progressKick = tick;
|
|
7687
|
-
ticks += 1;
|
|
7688
|
-
if (ticks > 400) {
|
|
7689
|
-
/* ~33 min: this tab stops watching, but the standup keeps going — hand
|
|
7690
|
-
the long watch to the mine view, the owner's durable surface (task
|
|
7691
|
-
1002693). The old "stop quietly" here was a dead-end lie: the hall
|
|
7692
|
-
card it assumed was removed in task 1002675. */
|
|
7693
|
-
/* a SNAGGED standup keeps its honest error — "still building" would be
|
|
7694
|
-
a lie exactly when the founder most needs the truth */
|
|
7695
|
-
if (live.instStatus !== 'error') {
|
|
7696
|
-
setProgress(progressMarkup(1, 1,
|
|
7697
|
-
'Still working — this tab will stop watching now. Follow it any time on <a href="?view=mine">your projects page</a>.', '', watchFlow));
|
|
7698
|
-
}
|
|
7699
|
-
setMoment('plain');
|
|
7700
|
-
stopPolling();
|
|
7701
|
-
return;
|
|
7702
|
-
}
|
|
7703
7461
|
fetch(API + '/provisioning/instances/' + encodeURIComponent(id), { credentials: 'same-origin' })
|
|
7704
7462
|
.then(function (res) {
|
|
7705
|
-
if (res.status === 401)
|
|
7706
|
-
if (res.status === 404)
|
|
7463
|
+
if (res.status === 401) throw new Error('gone:session');
|
|
7464
|
+
if (res.status === 404) throw new Error('gone:missing');
|
|
7707
7465
|
if (!res.ok) throw new Error('soft');
|
|
7708
7466
|
return res.json();
|
|
7709
7467
|
})
|
|
7710
7468
|
.then(function (data) {
|
|
7711
7469
|
if (myGen !== watchGen) return;
|
|
7712
7470
|
var inst = data.instance;
|
|
7713
|
-
if (inst) inst.open_intent = data.open_intent || null; /* F22: the
|
|
7714
|
-
if (inst) watchFlow = inst.torn_down_at ? 'revive' : 'standup';
|
|
7471
|
+
if (inst) inst.open_intent = data.open_intent || null; /* F22: the facts read intents */
|
|
7715
7472
|
/* server-derived manifest truth (task 1003235): a reload, a second tab
|
|
7716
7473
|
or another device re-learns the GitHub-App leg from this same read.
|
|
7717
7474
|
A live manifest poll owns live.manifest while it runs, so the seed
|
|
@@ -7726,22 +7483,19 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7726
7483
|
/* the wait is timed from when the link was MINTED, so a reload still
|
|
7727
7484
|
reaches the "start again" offer (task 1004572) */
|
|
7728
7485
|
if (!manifestStartedAt && ga.started_at) manifestStartedAt = Date.parse(ga.started_at) || 0;
|
|
7729
|
-
if (seedChanged)
|
|
7486
|
+
if (seedChanged) rememberManifest(undefined, live.manifest, live.appSlug || undefined);
|
|
7730
7487
|
else noteManifestWait();
|
|
7731
7488
|
}
|
|
7732
7489
|
/* the server-verified checklist rides the same read (task 1004649) */
|
|
7733
7490
|
if (data.github_setup && !manifestPollTimer) {
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
if (seedMoved) renderSteps();
|
|
7737
|
-
}
|
|
7738
|
-
/* the checklist reflects live state (task 1002694): a status transition —
|
|
7739
|
-
e.g. the scaffold landing — re-renders the steps (change-only, so the
|
|
7740
|
-
5s tick never clobbers an open dropdown or mid-flight button) */
|
|
7741
|
-
if (inst && (inst.status !== live.instStatus || addressReady(inst) !== live.addrReady)) {
|
|
7742
|
-
live.instStatus = inst.status; live.addrReady = addressReady(inst); renderSteps();
|
|
7491
|
+
seedSetup(data.github_setup.checklist, data.github_setup.install_url, data.github_setup);
|
|
7492
|
+
noteAppOrg(data.github_setup.app_org);
|
|
7743
7493
|
}
|
|
7744
|
-
|
|
7494
|
+
/* the read IS Its home: every fact is redrawn from it (change-only) */
|
|
7495
|
+
live.inst = inst || null; live.readAt = Date.now(); live.gone = '';
|
|
7496
|
+
if (inst) { live.instStatus = inst.status; live.addrReady = addressReady(inst); }
|
|
7497
|
+
paintFacts();
|
|
7498
|
+
var terminal = setupSettled();
|
|
7745
7499
|
/* a GitHub leg still in flight keeps this read alive even once the
|
|
7746
7500
|
project is up — after a reload it is the ONLY thing watching it */
|
|
7747
7501
|
if (!terminal || manifestInFlight() || setupOpen()) pollTimer = setTimeout(tick, wizPollDelay(5000));
|
|
@@ -7751,8 +7505,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7751
7505
|
if (myGen !== watchGen) return;
|
|
7752
7506
|
var m = String(err && err.message || '');
|
|
7753
7507
|
if (m.indexOf('gone:') === 0) {
|
|
7754
|
-
|
|
7755
|
-
|
|
7508
|
+
/* signed out, or the project is gone: said once, in the sentence */
|
|
7509
|
+
live.gone = m.slice(5);
|
|
7510
|
+
paintFacts();
|
|
7756
7511
|
stopPolling();
|
|
7757
7512
|
return;
|
|
7758
7513
|
}
|
|
@@ -7774,9 +7529,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7774
7529
|
instanceId = null;
|
|
7775
7530
|
watchPending = false; manifestPending = false; manifestState = null;
|
|
7776
7531
|
manifestStartedAt = 0; ghTabTrouble = null;
|
|
7777
|
-
live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, appOrg: null, setup: null, installUrl: null, pickUp: null, verifying: false, askedPlatform: false };
|
|
7778
|
-
|
|
7779
|
-
lastProgHtml = '';
|
|
7532
|
+
live = { instStatus: null, addrReady: false, manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, appOrg: null, setup: null, installUrl: null, pickUp: null, verifying: false, askedPlatform: false, inst: null, readAt: 0, gone: '', repo: null };
|
|
7533
|
+
factsDrawn = {}; sentenceSaid = '';
|
|
7780
7534
|
hopReset();
|
|
7781
7535
|
try { sessionStorage.removeItem(DRAFT_KEY); } catch (e) {}
|
|
7782
7536
|
/* Every key the initial state declares has to be re-declared here. `modules`
|
|
@@ -7985,7 +7739,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7985
7739
|
}
|
|
7986
7740
|
function hopStart() {
|
|
7987
7741
|
var hopEl = $('hop');
|
|
7988
|
-
if (!hop || !hopEl) return; /* hoisted
|
|
7742
|
+
if (!hop || !hopEl || hopEl.hidden) return; /* hoisted; and the game runs only once asked */
|
|
7989
7743
|
hopEl.classList.toggle('isStill', REDUCED);
|
|
7990
7744
|
hopLoadFeed();
|
|
7991
7745
|
if (!hopSize()) return;
|
|
@@ -8161,28 +7915,31 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
8161
7915
|
hop.raf = requestAnimationFrame(hopFrame);
|
|
8162
7916
|
}
|
|
8163
7917
|
|
|
8164
|
-
/*
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
function
|
|
8168
|
-
|
|
8169
|
-
if (
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
7918
|
+
/* "Play cloud hop while you wait" (task 1004683): the game opens in place only when
|
|
7919
|
+
asked, and the wait never depends on it — the facts above tick whether it is open or
|
|
7920
|
+
not. Hoisted: show() can ask before the hop is declared. */
|
|
7921
|
+
function hopOpen(on) {
|
|
7922
|
+
var box = $('hop'), link = $('hopLink');
|
|
7923
|
+
if (!box || !link) return;
|
|
7924
|
+
box.hidden = !on;
|
|
7925
|
+
box.classList.remove('hopFading');
|
|
7926
|
+
link.setAttribute('aria-expanded', on ? 'true' : 'false');
|
|
7927
|
+
link.textContent = on ? 'Put the game away' : 'Play cloud hop while you wait';
|
|
7928
|
+
if (!hop) return;
|
|
7929
|
+
hop.target = on ? 'wait' : '';
|
|
7930
|
+
if (on) hopStart(); else hopStop();
|
|
7931
|
+
}
|
|
7932
|
+
/* every required fact done: a game that has been running gets the curtain first and
|
|
7933
|
+
then puts itself away; a closed game, or reduced motion, just stays away */
|
|
7934
|
+
function hopArrive() {
|
|
7935
|
+
if (!hop || hop.tr) return; /* the curtain is already up */
|
|
7936
|
+
if (!$('hop').hidden && !REDUCED && hop.raf && performance.now() - hop.since > 2500) {
|
|
8180
7937
|
cancelAnimationFrame(hop.raf); hop.raf = 0;
|
|
8181
|
-
hopCurtain(
|
|
7938
|
+
hopCurtain(function () { hopOpen(false); });
|
|
8182
7939
|
hop.raf = requestAnimationFrame(hopFrame);
|
|
8183
7940
|
return;
|
|
8184
7941
|
}
|
|
8185
|
-
|
|
7942
|
+
hopOpen(false);
|
|
8186
7943
|
}
|
|
8187
7944
|
function hopReset() {
|
|
8188
7945
|
if (!hop) return;
|
|
@@ -8190,35 +7947,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
8190
7947
|
hop.target = ''; hop.since = 0; hop.tr = 0; hop.trDone = null; hop.p = null;
|
|
8191
7948
|
hop.best = 0; hop.maxed = false;
|
|
8192
7949
|
if (hop.w) hopBuild();
|
|
8193
|
-
|
|
8194
|
-
}
|
|
8195
|
-
|
|
8196
|
-
/* the status line and the bar read what the strip already says — one
|
|
8197
|
-
vocabulary, one source (renderProgress), never a second copy */
|
|
8198
|
-
function hopSync(el) {
|
|
8199
|
-
var st = $('hopStatus');
|
|
8200
|
-
if (!st || !el) return;
|
|
8201
|
-
var note = el.querySelector('.pnote');
|
|
8202
|
-
var text = note ? note.textContent : '';
|
|
8203
|
-
if (st.textContent !== text) st.textContent = text;
|
|
8204
|
-
var lis = el.querySelectorAll('.stages li').length;
|
|
8205
|
-
var done = el.querySelectorAll('.stages li.done').length + (el.querySelector('.stages li.now') ? 0.5 : 0);
|
|
8206
|
-
$('hopBarFill').style.width = Math.max(4, lis ? Math.min(100, done / lis * 100) : 0) + '%';
|
|
8207
|
-
}
|
|
8208
|
-
|
|
8209
|
-
/* Part 03's checklist (task 1004648): every step on show at once, each ticking
|
|
8210
|
-
itself from what the server has verified (renderSteps, change-only), and the
|
|
8211
|
-
part's state line saying where the founder is. It replaces the paged steps. */
|
|
8212
|
-
function stepsPaint() {
|
|
8213
|
-
var list = $('humanSteps'), st = $('ghState');
|
|
8214
|
-
if (!list || !st) return;
|
|
8215
|
-
if (state.step !== 2) { st.textContent = 'opens once your project exists'; return; }
|
|
8216
|
-
/* the optional step (command-line sign-in) never holds "connected" back */
|
|
8217
|
-
var items = Array.prototype.slice.call(list.children).filter(function (li) { return !li.classList.contains('soptional'); });
|
|
8218
|
-
var done = items.filter(function (li) { return li.classList.contains('sdone'); }).length;
|
|
8219
|
-
st.textContent = items.length && done === items.length ? 'connected ✓'
|
|
8220
|
-
: manifestInFlight() ? 'waiting on GitHub…'
|
|
8221
|
-
: done ? done + ' of ' + items.length + ' done' : 'ready';
|
|
7950
|
+
hopOpen(false);
|
|
8222
7951
|
}
|
|
8223
7952
|
|
|
8224
7953
|
(function hopInput() {
|
|
@@ -8249,6 +7978,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
8249
7978
|
}
|
|
8250
7979
|
pad('hopLeft', 'L'); pad('hopRight', 'R');
|
|
8251
7980
|
$('hopJump').addEventListener('click', hopJump);
|
|
7981
|
+
/* the game opens in place only when asked, and puts itself away the same way */
|
|
7982
|
+
$('hopLink').addEventListener('click', function () { hopOpen($('hop').hidden); });
|
|
8252
7983
|
document.addEventListener('visibilitychange', function () { if (!document.hidden) hopStart(); });
|
|
8253
7984
|
window.addEventListener('resize', function () { if (hop.target === 'wait') { hop.sky = null; hopStart(); } });
|
|
8254
7985
|
})();
|