@bongos/core 1.19.419 → 1.19.421
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 +108 -58
- package/.claude/skills/new-project/SKILL.md +22 -11
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +5 -1
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0209-one-budget-for-the-account-existence-oracle.md +79 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +103 -5
- package/docs/api-reference.md +4 -3
- package/docs/copy-inventory.md +282 -269
- package/docs/copy-registry.json +487 -368
- package/docs/design/mocks/hub/mine.html +377 -0
- package/docs/design/projects-hub-direction-v2.md +25 -1
- package/docs/file-map.md +2 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/hall-ui/public/settings.html +3 -2
- package/modules/hall-ui/public/settings.js +10 -6
- package/modules/onboarding/routes/access-requests.js +16 -3
- package/modules/platform-identity/account-visibility.js +94 -3
- package/modules/platform-identity/platform-identity.js +50 -55
- package/modules/platform-identity/routes/connections.js +3 -3
- package/modules/platform-identity/tests/platform-identity.mjs +20 -0
- package/modules/provisioning/migrations/provisioning_013_project_detail.sql +54 -0
- package/modules/provisioning/onboard-plan.js +13 -0
- package/modules/provisioning/provisioning.js +113 -5
- package/modules/provisioning/routes/provisioning.js +105 -0
- package/modules/provisioning/tests/provisioning.mjs +84 -0
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -0
- package/modules/ui-design/kit/fixtures/provisioning-instances-empty.json +4 -0
- package/modules/ui-design/kit/fixtures/provisioning-instances.json +99 -0
- package/modules/ui-design/kit/lib.js +6 -2
- package/modules/ui-design/kit/probe.js +1 -1
- package/modules/ui-design/kit/render.js +1 -1
- package/modules/ui-design/kit/serve.js +63 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +224 -18
- package/public-landing/projects-empty.states.json +49 -0
- package/public-landing/projects-floor.states.json +63 -0
- package/public-landing/projects.html +273 -93
- package/public-landing/projects.probes.json +32 -7
- package/public-landing/projects.states.json +10 -6
- package/scripts/gds/fitness.js +9 -0
- package/scripts/gds/onboard.js +41 -1
- package/scripts/gds/provision-net.js +7 -3
- package/src/bongos/middleware/rate-limit.js +68 -2
- package/src/bongos/routes/auth.js +19 -12
- package/src/module-api.js +13 -1
- package/tests/account_existence_rate_limit.mjs +375 -0
- package/tests/account_privacy_flags.mjs +17 -10
- package/tests/effective_visibility_predicate.mjs +891 -0
- package/tests/module_api.mjs +1 -0
- package/tests/onboard.mjs +56 -0
- package/tests/profile_route.mjs +22 -11
- package/tests/projects_hub.mjs +102 -5
- package/tests/projects_hub_pre_uat.mjs +45 -11
- package/tests/provision.mjs +20 -0
- package/tests/ui_design_kit.mjs +32 -2
|
@@ -50,15 +50,10 @@
|
|
|
50
50
|
#view-mine .hero,#view-manage .hero{max-width:none;}
|
|
51
51
|
|
|
52
52
|
/* ── the manage view (task 1003041): container rhythm only — every component
|
|
53
|
-
it draws (
|
|
54
|
-
|
|
53
|
+
it draws (the head, the ledger, the choice cells, the rail, the doors) is
|
|
54
|
+
cosmos vocabulary, scoped to #view-manage there (task 1003345) ── */
|
|
55
55
|
#view-manage .empty{text-align:left;}
|
|
56
56
|
#view-manage .empty p{margin-bottom:0;}
|
|
57
|
-
#view-manage .visOpt{display:flex;gap:10px;align-items:flex-start;padding:10px 12px;border:1px solid var(--rule);border-radius:10px;margin-bottom:8px;cursor:pointer;}
|
|
58
|
-
#view-manage .visOpt:has(input:checked){border-color:var(--accent);}
|
|
59
|
-
#view-manage .visOpt input{margin-top:4px;flex:none;}
|
|
60
|
-
#view-manage .visOpt b{display:block;font-size:15px;}
|
|
61
|
-
#view-manage .visOpt span{display:block;font-size:13px;color:var(--ink-soft);line-height:1.45;}
|
|
62
57
|
|
|
63
58
|
/* The bongo pair on the New-project view. Component-scoped custom properties,
|
|
64
59
|
never :root (C1). The drum head is SKIN — using --bg-card would turn it black
|
|
@@ -95,14 +90,17 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
|
|
|
95
90
|
.wizField{display:block;margin-bottom:18px;}
|
|
96
91
|
.wizField>span{display:block;font-size:13.5px;font-weight:700;color:var(--ink-soft);margin-bottom:7px;letter-spacing:0.2px;}
|
|
97
92
|
.wizField small{font-weight:500;color:var(--ink-faint);}
|
|
98
|
-
|
|
93
|
+
/* the About step's description is prose, so it is a textarea — it wears the
|
|
94
|
+
same control skin as every other field (task 1002334), never its own */
|
|
95
|
+
#view-new input[type=text], #view-new textarea{
|
|
99
96
|
width:100%;font-family:var(--font-body);font-size:17px;font-weight:600;color:var(--ink);
|
|
100
97
|
background:var(--bg-card);border:2px solid var(--ctl-line);border-radius:14px;padding:13px 16px;outline:none;
|
|
101
98
|
transition:border-color .15s ease, box-shadow .15s ease;
|
|
102
99
|
}
|
|
100
|
+
#view-new textarea{font-size:15.5px;line-height:1.5;resize:vertical;min-height:84px;display:block;}
|
|
103
101
|
/* no fade toward the card — a 70% mix washed placeholders to 2.89:1 (task 1002749) */
|
|
104
|
-
#view-new input[type=text]::placeholder{color:var(--ink-faint);font-weight:500;}
|
|
105
|
-
#view-new input[type=text]:focus{border-color:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 13%,transparent);}
|
|
102
|
+
#view-new input[type=text]::placeholder, #view-new textarea::placeholder{color:var(--ink-faint);font-weight:500;}
|
|
103
|
+
#view-new input[type=text]:focus, #view-new textarea:focus{border-color:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 13%,transparent);}
|
|
106
104
|
#view-new input[type=text].bad{border-color:var(--err);}
|
|
107
105
|
.hint{font-size:13px;font-weight:600;color:var(--ink-faint);margin-top:7px;min-height:18px;}
|
|
108
106
|
.hint.ok{color:var(--ok);}
|
|
@@ -146,6 +144,9 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
|
|
|
146
144
|
.humanSteps .ghbtn{font-size:14px;padding:9px 16px;margin-top:10px;}
|
|
147
145
|
|
|
148
146
|
.nav{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:26px;}
|
|
147
|
+
/* two controls sharing one end of a nav row — the About step's Skip beside its
|
|
148
|
+
Next (task 1002334). Wraps on a narrow card rather than crushing either. */
|
|
149
|
+
.navPair{display:inline-flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end;}
|
|
149
150
|
.btn.big{width:100%;justify-content:center;font-size:18px;padding:17px 30px;}
|
|
150
151
|
|
|
151
152
|
/* review list */
|
|
@@ -570,8 +571,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
570
571
|
<div class="wrap viewWrap">
|
|
571
572
|
<div class="hero" style="padding-left:0;padding-right:0">
|
|
572
573
|
<h1>Your projects</h1>
|
|
573
|
-
<p class="lede">Every project you own
|
|
574
|
-
Each
|
|
574
|
+
<p class="lede">Every project you own: what it is, whether it is answering, and the controls that matter.
|
|
575
|
+
Each row says what the platform <b>last verified</b>, and when.</p>
|
|
575
576
|
</div>
|
|
576
577
|
|
|
577
578
|
<div class="card">
|
|
@@ -611,11 +612,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
611
612
|
<p class="mineEmpty" id="mineEmpty" hidden>Nothing matches that. <button type="button" class="linklike" id="mineClear">Clear the filters</button></p>
|
|
612
613
|
|
|
613
614
|
<section class="empty" id="empty" hidden>
|
|
614
|
-
<
|
|
615
|
+
<h2>nothing of yours in this sky yet</h2>
|
|
616
|
+
<p>Name a project, pick a home for its code, and it gets an address of its own here. It shows up in this list the moment you file it.</p>
|
|
615
617
|
<a class="btn" href="?view=new" data-nav-to="new">Create your first project</a>
|
|
616
618
|
</section>
|
|
617
619
|
<ul class="projects" id="projectsList"></ul>
|
|
618
|
-
<p class="finePrint">Buttons here ask the platform to act; each
|
|
620
|
+
<p class="finePrint">Buttons here ask the platform to act; each row reports only what was verified, and when. A standup keeps going even with this tab closed.</p>
|
|
619
621
|
</section>
|
|
620
622
|
</div>
|
|
621
623
|
</div>
|
|
@@ -634,6 +636,19 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
634
636
|
<div class="wrap viewWrap">
|
|
635
637
|
<p class="backRow"><a href="?view=mine" data-nav-to="mine">← Your projects</a></p>
|
|
636
638
|
|
|
639
|
+
<!-- the head stands in EVERY state (task 1003345): the page's one h1 lives
|
|
640
|
+
here, outside #mMain, so the signed-out, not-found and loading states
|
|
641
|
+
keep a visible heading (the kit's one-header-main-h1 floor). The planet
|
|
642
|
+
and the chip fill only once a project has rendered; the clause hides
|
|
643
|
+
with #mMain (cosmos, :has). -->
|
|
644
|
+
<div class="hero mHead" style="padding-left:0;padding-right:0">
|
|
645
|
+
<span id="mOrb"></span>
|
|
646
|
+
<div>
|
|
647
|
+
<div class="titleRow"><h1 id="mTitle"><span id="mName">Manage</span></h1><span id="mChip"></span></div>
|
|
648
|
+
<p class="lede">Its address, its repository, what the platform last <b>verified</b> about it, and the controls you have today.</p>
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
|
|
637
652
|
<!-- signed out: the same identity-only door the mine view uses (ADR 0143) -->
|
|
638
653
|
<section id="mGate" class="card" hidden>
|
|
639
654
|
<p class="lede">This is a project’s management page — it answers only to the person who owns it. Sign in with GitHub to open yours.</p>
|
|
@@ -654,19 +669,18 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
654
669
|
<div class="loading" id="mLoading">Reading the project…</div>
|
|
655
670
|
|
|
656
671
|
<section id="mMain" hidden>
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
<p class="finePrint" id="mCreated" hidden></p>
|
|
672
|
+
<!-- the ledger of facts (task 1003345): each fact keeps its id and fills
|
|
673
|
+
as one label · value pair; a fact with no answer hides — never a dash -->
|
|
674
|
+
<div class="card" id="mFacts">
|
|
675
|
+
<div class="ledger">
|
|
676
|
+
<div id="mMeta" hidden></div>
|
|
677
|
+
<div id="mAddr" hidden></div>
|
|
678
|
+
<div id="mCreated" hidden></div>
|
|
679
|
+
<div id="mPlanet" hidden></div>
|
|
680
|
+
</div>
|
|
667
681
|
<!-- the card's own story, on the project's own page (task 1003232): the
|
|
668
|
-
per-flow
|
|
669
|
-
stripFor/errMarkup/canRetry/canRevive the list
|
|
682
|
+
per-flow rail, the honest snag, and the doors — the same
|
|
683
|
+
stripFor/errMarkup/canRetry/canRevive the list row reads -->
|
|
670
684
|
<div id="mStrip"></div>
|
|
671
685
|
<div class="projMsg err" id="mErr" hidden></div>
|
|
672
686
|
<div class="projMsg" id="mMsg" role="status" aria-live="polite"></div>
|
|
@@ -678,8 +692,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
678
692
|
private direction is refused (server + copy) until the private-repo
|
|
679
693
|
deploy fix lands, because the deploy pull cannot read a private repo
|
|
680
694
|
(walk finding N4). -->
|
|
681
|
-
<div class="card" id="mRepoVisibility">
|
|
682
|
-
<h2
|
|
695
|
+
<div class="card mSec" id="mRepoVisibility">
|
|
696
|
+
<h2>Repository visibility</h2>
|
|
683
697
|
<div id="mRepoVis"><p class="finePrint">Checking the repository on GitHub…</p></div>
|
|
684
698
|
<!-- the limitation itself, one click away (grade follow-up task 1003077):
|
|
685
699
|
the refusal above states it; this fold explains it and links the
|
|
@@ -698,8 +712,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
698
712
|
a change reaches a live project only by the restart the runner
|
|
699
713
|
performs (ADR 0190). The fold is a SIBLING of the volatile slot (the
|
|
700
714
|
#mRepoVisWhy shape) so no re-render can drop it. -->
|
|
701
|
-
<div class="card" id="mVisibility">
|
|
702
|
-
<h2
|
|
715
|
+
<div class="card mSec" id="mVisibility">
|
|
716
|
+
<h2>Who can see it</h2>
|
|
703
717
|
<div id="mVis"><p class="finePrint">Reading the project’s settings…</p></div>
|
|
704
718
|
<details class="finePrint" id="mVisWhat">
|
|
705
719
|
<summary>What “only signed-in members” changes</summary>
|
|
@@ -713,8 +727,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
713
727
|
RUNS is read back from its own public manifest (brand.project.
|
|
714
728
|
joinability) — never from the save's reply, which is a receipt. The
|
|
715
729
|
fold is a SIBLING of the volatile slot so no re-render can drop it. -->
|
|
716
|
-
<div class="card" id="mJoinability">
|
|
717
|
-
<h2
|
|
730
|
+
<div class="card mSec" id="mJoinability">
|
|
731
|
+
<h2>How people join</h2>
|
|
718
732
|
<div id="mJoin"><p class="finePrint">Reading the project’s settings…</p></div>
|
|
719
733
|
<details class="finePrint" id="mJoinWhat">
|
|
720
734
|
<summary>What each choice changes</summary>
|
|
@@ -730,8 +744,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
730
744
|
much as for a healthy one, which is why it asks for the project's own
|
|
731
745
|
name typed back: it is the only control on this page that ends
|
|
732
746
|
something, and the card's one-click confirm is too light for it. -->
|
|
733
|
-
<div class="card" id="mDelete">
|
|
734
|
-
<h2
|
|
747
|
+
<div class="card mSec" id="mDelete">
|
|
748
|
+
<h2>Take it offline</h2>
|
|
735
749
|
<div id="mDel"><p class="finePrint">Reading the project…</p></div>
|
|
736
750
|
<!-- the teardown-vs-delete distinction, stated once as a SIBLING of the
|
|
737
751
|
volatile slot (the #mRepoVisWhy shape) so no re-render can drop it.
|
|
@@ -748,7 +762,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
748
762
|
|
|
749
763
|
<!-- ══════════════════ VIEW · NEW PROJECT ══════════════════════════════════
|
|
750
764
|
The whole create wizard, absorbed from /create (task 1002763; direction
|
|
751
|
-
§4 — that route now 302s here).
|
|
765
|
+
§4 — that route now 302s here). Eight panels behind the six-beat rail; the
|
|
752
766
|
wizard block in the script below drives them. Same trust boundary as
|
|
753
767
|
ever: every button here only ENQUEUES a provisioning request (ADR 0111).
|
|
754
768
|
The name panel carries a static `on` so a no-script visitor still reads
|
|
@@ -759,9 +773,10 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
759
773
|
<ol class="steps" aria-label="Creation steps">
|
|
760
774
|
<li class="now" data-step="1" aria-current="step"><span class="snum">1</span><span class="slbl">Name</span></li>
|
|
761
775
|
<li data-step="2"><span class="snum">2</span><span class="slbl">Type</span></li>
|
|
762
|
-
<li data-step="3"><span class="snum">3</span><span class="slbl">
|
|
763
|
-
<li data-step="4"><span class="snum">4</span><span class="slbl">
|
|
764
|
-
<li data-step="5"><span class="snum">5</span><span class="slbl">
|
|
776
|
+
<li data-step="3"><span class="snum">3</span><span class="slbl">About</span></li>
|
|
777
|
+
<li data-step="4"><span class="snum">4</span><span class="slbl">Home</span></li>
|
|
778
|
+
<li data-step="5"><span class="snum">5</span><span class="slbl">Address</span></li>
|
|
779
|
+
<li data-step="6"><span class="snum">6</span><span class="slbl">Create</span></li>
|
|
765
780
|
</ol>
|
|
766
781
|
|
|
767
782
|
<div class="createSplit">
|
|
@@ -841,11 +856,45 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
841
856
|
</form>
|
|
842
857
|
</section>
|
|
843
858
|
|
|
844
|
-
<!-- step 3 ·
|
|
859
|
+
<!-- step 3 · about (task 1002334) — the OPTIONAL project-detail step.
|
|
860
|
+
The one panel in the wizard that can be walked past without an
|
|
861
|
+
answer, so the skip is a real button of its own, sitting beside
|
|
862
|
+
Next rather than hidden as "leave it blank and press Next" — an
|
|
863
|
+
optional step whose escape hatch has to be inferred is not
|
|
864
|
+
optional in practice. What skipping costs is stated ON the panel
|
|
865
|
+
(ADR 0182 D4: no description ⇒ the project is off the public map
|
|
866
|
+
until it has one), because a consequence discovered afterwards is
|
|
867
|
+
not a choice the owner made. -->
|
|
868
|
+
<section class="panel" id="panel-detail" aria-labelledby="detail-h">
|
|
869
|
+
<h1 id="detail-h">Tell us about it</h1>
|
|
870
|
+
<p class="lede">Optional — skip it and nothing about your project changes. Answering shapes what we set up for you, and a description is what puts the project on the public map.</p>
|
|
871
|
+
<form data-next="4">
|
|
872
|
+
<label class="wizField"><span>What are you building? <small>(a sentence or two)</small></span>
|
|
873
|
+
<textarea id="f-desc" rows="3" maxlength="600" placeholder="A co-op farming game where the seasons never repeat."></textarea>
|
|
874
|
+
</label>
|
|
875
|
+
<div class="wizField"><span id="teamLbl">Who’s building it?</span>
|
|
876
|
+
<div class="typeCards" id="teamCards" role="group" aria-labelledby="teamLbl">
|
|
877
|
+
<button type="button" class="tpill" data-team="solo" aria-pressed="false">Just me</button>
|
|
878
|
+
<button type="button" class="tpill" data-team="small-team" aria-pressed="false">A small team</button>
|
|
879
|
+
<button type="button" class="tpill" data-team="community" aria-pressed="false">An open community</button>
|
|
880
|
+
</div>
|
|
881
|
+
</div>
|
|
882
|
+
<div class="reassure" id="detailSkipNote"></div>
|
|
883
|
+
<div class="nav">
|
|
884
|
+
<button type="button" class="btn ghost" data-back="2">← Back</button>
|
|
885
|
+
<span class="navPair">
|
|
886
|
+
<button type="button" class="btn ghost" id="skipDetail">Skip for now</button>
|
|
887
|
+
<button type="submit" class="btn">Next →</button>
|
|
888
|
+
</span>
|
|
889
|
+
</div>
|
|
890
|
+
</form>
|
|
891
|
+
</section>
|
|
892
|
+
|
|
893
|
+
<!-- step 4 · home (repo + mode) -->
|
|
845
894
|
<section class="panel" id="panel-home" aria-labelledby="home-h">
|
|
846
895
|
<h1 id="home-h">Where the code lives</h1>
|
|
847
896
|
<p class="lede">Every project is a real GitHub repository that you own. Start from a clean slate, or bring a repo you already have.</p>
|
|
848
|
-
<form data-next="
|
|
897
|
+
<form data-next="5">
|
|
849
898
|
<div class="choices" role="group" aria-label="Starting point">
|
|
850
899
|
<button type="button" class="choice" id="c-green" aria-pressed="true">
|
|
851
900
|
<!-- token-built, not platform orange — public-landing/ ships wholesale to
|
|
@@ -904,13 +953,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
904
953
|
</div>
|
|
905
954
|
<div class="hint" id="h-home"> </div>
|
|
906
955
|
<div class="nav">
|
|
907
|
-
<button type="button" class="btn ghost" data-back="
|
|
956
|
+
<button type="button" class="btn ghost" data-back="3">← Back</button>
|
|
908
957
|
<button type="submit" class="btn">Next →</button>
|
|
909
958
|
</div>
|
|
910
959
|
</form>
|
|
911
960
|
</section>
|
|
912
961
|
|
|
913
|
-
<!-- step
|
|
962
|
+
<!-- step 5 · address — a CONFIRMATION, not a form (task 1002704: every project
|
|
914
963
|
gets its slug subdomain assigned up front). The domainless default was a
|
|
915
964
|
proven dead-end (QA walk, task 1002702).
|
|
916
965
|
Three choices, all legible without opening anything (task 1002898 N8):
|
|
@@ -922,7 +971,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
922
971
|
<section class="panel" id="panel-address" aria-labelledby="addr-h">
|
|
923
972
|
<h1 id="addr-h">Its address in the sky</h1>
|
|
924
973
|
<p class="lede">The handle names your project on the platform — and its address comes with it. Once it stands up, a few GitHub steps (the next screen walks you through them) switch sign-in on.</p>
|
|
925
|
-
<form data-next="
|
|
974
|
+
<form data-next="6">
|
|
926
975
|
<label class="wizField"><span>Project handle</span>
|
|
927
976
|
<input type="text" id="f-slug" placeholder="mercury" autocomplete="off" maxlength="40" spellcheck="false">
|
|
928
977
|
<div class="hint" id="h-slug"> </div>
|
|
@@ -944,13 +993,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
944
993
|
</label>
|
|
945
994
|
</div>
|
|
946
995
|
<div class="nav">
|
|
947
|
-
<button type="button" class="btn ghost" data-back="
|
|
996
|
+
<button type="button" class="btn ghost" data-back="4">← Back</button>
|
|
948
997
|
<button type="submit" class="btn">Next →</button>
|
|
949
998
|
</div>
|
|
950
999
|
</form>
|
|
951
1000
|
</section>
|
|
952
1001
|
|
|
953
|
-
<!-- step
|
|
1002
|
+
<!-- step 6 · review + create -->
|
|
954
1003
|
<section class="panel" id="panel-review" aria-labelledby="rev-h">
|
|
955
1004
|
<h1 id="rev-h">Ready to roll</h1>
|
|
956
1005
|
<p class="lede">One tap files the request and the platform starts standing your project up — server, database, address. Here's the beat you're about to drop:</p>
|
|
@@ -958,7 +1007,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
958
1007
|
<div class="alert" id="createAlert" role="alert"></div>
|
|
959
1008
|
<div class="nav" style="flex-direction:column;align-items:stretch;">
|
|
960
1009
|
<button type="button" class="btn big" id="doCreate">Create my project</button>
|
|
961
|
-
<button type="button" class="btn ghost" data-back="
|
|
1010
|
+
<button type="button" class="btn ghost" data-back="5" style="align-self:flex-start;margin-top:10px;">← Back</button>
|
|
962
1011
|
</div>
|
|
963
1012
|
<p class="finePrint">This files the request; the control plane does the real standup and nothing touches servers from this page. You'll get the few steps only you can do right after.</p>
|
|
964
1013
|
</section>
|
|
@@ -1243,7 +1292,6 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1243
1292
|
were orphans). Keeping the per-project variety is what makes a sky legible;
|
|
1244
1293
|
deriving it from contract tokens is what makes it survive a re-brand. */
|
|
1245
1294
|
var ORB_ACCENTS = ['var(--sea)', 'var(--terracotta)', 'var(--gold-soft)', 'var(--sun-lit)', 'var(--accent)'];
|
|
1246
|
-
var ORB_GLOWS = ORB_ACCENTS.map(function (t) { return 'color-mix(in srgb,' + t + ' 45%,transparent)'; });
|
|
1247
1295
|
/* THE PLANET TEMPLATES (task 1003319; the materials, task 1003337). A project's
|
|
1248
1296
|
orb is one of the world's three whole objects (blob / pair / bead) in one of
|
|
1249
1297
|
four materials (chrome — the first round, named by omission — frost, gold,
|
|
@@ -1968,7 +2016,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1968
2016
|
function loadCatalog() {
|
|
1969
2017
|
fetch(API + '/projects/featured', { credentials: 'same-origin' })
|
|
1970
2018
|
.then(function (res) { if (!res.ok) throw new Error('catalog'); return res.json(); })
|
|
1971
|
-
.then(function (data) { catalog = (data && data.projects) || []; assignAccents(); applyMapFilter(); })
|
|
2019
|
+
.then(function (data) { catalog = (data && data.projects) || []; assignAccents(); applyMapFilter(); repaintPlanets(); })
|
|
1972
2020
|
.catch(function () {
|
|
1973
2021
|
/* a child instance has no catalog module at all — say so plainly
|
|
1974
2022
|
rather than leave an empty sky with no explanation */
|
|
@@ -2488,13 +2536,16 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2488
2536
|
var root = $('view-new');
|
|
2489
2537
|
|
|
2490
2538
|
/* ── state ── */
|
|
2491
|
-
/* -
|
|
2492
|
-
so a draft written under
|
|
2493
|
-
reinterpreted — a stale step 4
|
|
2494
|
-
|
|
2495
|
-
|
|
2539
|
+
/* -v3 since task 1002334: the indices renumbered AGAIN (About joined as 3),
|
|
2540
|
+
so a draft written under either older numbering must be ignored, not
|
|
2541
|
+
reinterpreted — a stale step 4 meant Address under v1 and Home under v3,
|
|
2542
|
+
and replaying it would land on the wrong panel with the rail already
|
|
2543
|
+
marking the skipped ones done. Bump this key on EVERY renumbering. */
|
|
2544
|
+
var DRAFT_KEY = 'cb-create-draft-v3';
|
|
2496
2545
|
var CREATED_KEY = 'cb-created-instance';
|
|
2497
|
-
|
|
2546
|
+
/* `description` + `teamShape` are the optional About step (task 1002334) —
|
|
2547
|
+
both legitimately stay empty, and empty is what the create request omits. */
|
|
2548
|
+
var state = { step: 0, name: '', projType: null, description: '', teamShape: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
|
|
2498
2549
|
try { Object.assign(state, JSON.parse(sessionStorage.getItem(DRAFT_KEY) || '{}')); } catch (e) {}
|
|
2499
2550
|
var authKnown = false; /* flips true when the boot's /me settles, either way */
|
|
2500
2551
|
|
|
@@ -2508,7 +2559,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2508
2559
|
state.name = handedName.trim().slice(0, 80);
|
|
2509
2560
|
state.slug = deriveSlug(state.name); state.slugTouched = false;
|
|
2510
2561
|
state.repo = deriveRepo(state.name); state.repoTouched = false;
|
|
2511
|
-
state.step = state.step >= 1 && state.step <=
|
|
2562
|
+
state.step = state.step >= 1 && state.step <= 6 ? state.step : 1;
|
|
2512
2563
|
try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
|
|
2513
2564
|
}
|
|
2514
2565
|
} catch (e) {}
|
|
@@ -2700,16 +2751,17 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2700
2751
|
}
|
|
2701
2752
|
|
|
2702
2753
|
/* ── step navigation ──
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2754
|
+
Seven steps since task 1002334: gate 0 · name 1 · type 2 · about 3 ·
|
|
2755
|
+
home 4 · address 5 · review 6 · done 7 (the rail shows 1-6; done marks it
|
|
2756
|
+
all). About is the one step Next can pass with nothing filled in. */
|
|
2757
|
+
var panels = { 0: 'panel-gate', 1: 'panel-name', 2: 'panel-type', 3: 'panel-detail', 4: 'panel-home', 5: 'panel-address', 6: 'panel-review', 7: 'panel-done' };
|
|
2706
2758
|
/* the rail at the done panel (task 1003244): "done" is the Create step
|
|
2707
2759
|
still running — the marker stays on 5 until the project actually
|
|
2708
2760
|
answers, and only then does the whole rail read finished. aria-current
|
|
2709
2761
|
tells a screen reader the same thing. */
|
|
2710
2762
|
function paintRail(step) {
|
|
2711
|
-
var landed = step ===
|
|
2712
|
-
var cur = step ===
|
|
2763
|
+
var landed = step === 7 && !!(live && live.instStatus === 'active');
|
|
2764
|
+
var cur = step === 7 ? 6 : step;
|
|
2713
2765
|
root.querySelectorAll('.steps li').forEach(function (li) {
|
|
2714
2766
|
var n = Number(li.getAttribute('data-step'));
|
|
2715
2767
|
var now = !landed && n === cur;
|
|
@@ -2720,7 +2772,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2720
2772
|
the button role, the tab stop and the data-goto the delegated handler
|
|
2721
2773
|
below reads. The done panel offers none — the project is filed, and
|
|
2722
2774
|
there is nothing behind it to change. */
|
|
2723
|
-
if (step <
|
|
2775
|
+
if (step < 7 && n < cur) {
|
|
2724
2776
|
li.setAttribute('role', 'button');
|
|
2725
2777
|
li.setAttribute('tabindex', '0');
|
|
2726
2778
|
li.setAttribute('data-goto', String(n));
|
|
@@ -2739,7 +2791,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2739
2791
|
paintRail(step);
|
|
2740
2792
|
saveDraft();
|
|
2741
2793
|
/* the done step takes the whole card — the bongo pair steps aside */
|
|
2742
|
-
root.querySelector('.createSplit').classList.toggle('wizDone', step ===
|
|
2794
|
+
root.querySelector('.createSplit').classList.toggle('wizDone', step === 7);
|
|
2743
2795
|
/* focus moves ONLY on a deliberate step (the hub rule setView already
|
|
2744
2796
|
applies to this view: never on boot, never on a background settle) */
|
|
2745
2797
|
if (moveFocus) {
|
|
@@ -2749,15 +2801,15 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2749
2801
|
/* the repo probe is authenticated — never fire it before /me settles
|
|
2750
2802
|
(a pre-auth probe 401s for strangers and, worse, could seed the
|
|
2751
2803
|
draft's owner with myLogin()'s placeholder) */
|
|
2752
|
-
if (step ===
|
|
2804
|
+
if (step === 4) { syncHome(); if (authKnown) ensureRepoAccess(); }
|
|
2753
2805
|
/* a resumed draft can land PAST the Home step with the probe never run —
|
|
2754
2806
|
Create would then file a repo it promised to make but never did */
|
|
2755
|
-
else if (step >
|
|
2807
|
+
else if (step > 4 && step < 7 && authKnown && repoAccess === null) ensureRepoAccess();
|
|
2756
2808
|
/* re-check on every entry, not just on typing: a resumed draft arrives here with
|
|
2757
2809
|
a handle nobody has asked the server about, and a handle free ten minutes ago
|
|
2758
2810
|
may not be now (task 1002898 N10) */
|
|
2759
|
-
if (step ===
|
|
2760
|
-
if (step ===
|
|
2811
|
+
if (step === 5) { checkSlugAvailable(); renderAddrPanel(); }
|
|
2812
|
+
if (step === 6) renderReview();
|
|
2761
2813
|
}
|
|
2762
2814
|
|
|
2763
2815
|
/* One delegated pair on the rail, so paintRail can add and drop data-goto
|
|
@@ -2850,6 +2902,65 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2850
2902
|
});
|
|
2851
2903
|
paintType(state.projType || null);
|
|
2852
2904
|
|
|
2905
|
+
/* ── the OPTIONAL About step (task 1002334) ──
|
|
2906
|
+
Two answers, both skippable. The description is half the publish gate
|
|
2907
|
+
(ADR 0182 D4) and the team shape is what the setup recommendations read;
|
|
2908
|
+
neither gates the standup, and neither is ever guessed on the owner's
|
|
2909
|
+
behalf. paintDetail keeps the consequence visible while they are still on
|
|
2910
|
+
the panel — it flips between "here's what you'll get" and "here's what
|
|
2911
|
+
skipping costs", so pressing Skip is an informed choice rather than a
|
|
2912
|
+
discovery made three screens later. */
|
|
2913
|
+
var TEAM_META = {
|
|
2914
|
+
'solo': 'Just you. We keep the setup light and skip the joining machinery.',
|
|
2915
|
+
'small-team': 'A handful of people you know. We suggest a setup built around invites.',
|
|
2916
|
+
'community': 'Open to whoever turns up. We suggest a setup built for people applying to join.'
|
|
2917
|
+
};
|
|
2918
|
+
function teamLabel(t) {
|
|
2919
|
+
var b = t && root.querySelector('#teamCards .tpill[data-team="' + t + '"]');
|
|
2920
|
+
return b ? b.textContent : '';
|
|
2921
|
+
}
|
|
2922
|
+
function paintTeam(t) {
|
|
2923
|
+
root.querySelectorAll('#teamCards .tpill').forEach(function (b) {
|
|
2924
|
+
b.setAttribute('aria-pressed', String(b.getAttribute('data-team') === t));
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
function paintDetail() {
|
|
2928
|
+
var note = $('detailSkipNote');
|
|
2929
|
+
var told = String(state.description || '').trim();
|
|
2930
|
+
var parts = [];
|
|
2931
|
+
if (!told) {
|
|
2932
|
+
parts.push('No description yet — your project still stands up, it just stays off the public map until it has one. You can add it any time from its page.');
|
|
2933
|
+
} else {
|
|
2934
|
+
parts.push('That description is what puts your project on the public map.');
|
|
2935
|
+
}
|
|
2936
|
+
if (state.teamShape && TEAM_META[state.teamShape]) parts.push(TEAM_META[state.teamShape]);
|
|
2937
|
+
note.textContent = parts.join(' ');
|
|
2938
|
+
note.classList.add('on');
|
|
2939
|
+
}
|
|
2940
|
+
$('f-desc').value = state.description || '';
|
|
2941
|
+
$('f-desc').addEventListener('input', function () {
|
|
2942
|
+
state.description = this.value.slice(0, 600);
|
|
2943
|
+
paintDetail();
|
|
2944
|
+
saveDraft();
|
|
2945
|
+
});
|
|
2946
|
+
root.querySelectorAll('#teamCards .tpill').forEach(function (b) {
|
|
2947
|
+
b.addEventListener('click', function () {
|
|
2948
|
+
/* clicking the chosen pill again UNSAYS it — the only way back to "I did
|
|
2949
|
+
not answer", which an optional question has to keep offering */
|
|
2950
|
+
var pick = b.getAttribute('data-team');
|
|
2951
|
+
state.teamShape = state.teamShape === pick ? null : pick;
|
|
2952
|
+
paintTeam(state.teamShape);
|
|
2953
|
+
paintDetail();
|
|
2954
|
+
saveDraft();
|
|
2955
|
+
});
|
|
2956
|
+
});
|
|
2957
|
+
paintTeam(state.teamShape || null);
|
|
2958
|
+
paintDetail();
|
|
2959
|
+
/* Skip = go on with whatever is (or isn't) filled in. It deliberately does
|
|
2960
|
+
NOT erase an answer already given: a visitor who typed a description and
|
|
2961
|
+
then pressed Skip meant "I'm done here", not "throw that away". */
|
|
2962
|
+
$('skipDetail').addEventListener('click', function () { show(4, true); });
|
|
2963
|
+
|
|
2853
2964
|
/* ── GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099 / ADR 0143.
|
|
2854
2965
|
Progressive enhancement: probe GET /github/repos once we reach the Home step.
|
|
2855
2966
|
200 → we hold repo access → show the picker (adopt) / name field (fresh);
|
|
@@ -3046,7 +3157,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3046
3157
|
}
|
|
3047
3158
|
return true;
|
|
3048
3159
|
}
|
|
3049
|
-
|
|
3160
|
+
/* step 3 (About) has no clause: it is the optional one, and Next past an
|
|
3161
|
+
empty answer is the whole point (task 1002334). */
|
|
3162
|
+
if (step === 4) {
|
|
3050
3163
|
var homeErr = function (msg) { $('h-home').textContent = msg; $('h-home').className = 'hint err'; return false; };
|
|
3051
3164
|
if (repoAccess === 'need-connect') return homeErr('Allow GitHub repo access to pick or create your repository — or choose “enter it manually”.');
|
|
3052
3165
|
if (repoAccess === true && state.mode !== 'adopt') {
|
|
@@ -3061,7 +3174,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3061
3174
|
$('h-home').innerHTML = ' '; $('h-home').className = 'hint';
|
|
3062
3175
|
return true;
|
|
3063
3176
|
}
|
|
3064
|
-
if (step ===
|
|
3177
|
+
if (step === 5) {
|
|
3065
3178
|
if (!validSlug(state.slug)) {
|
|
3066
3179
|
$('h-slug').textContent = '2–40 lowercase letters, numbers, or dashes; no leading/trailing/double dash.';
|
|
3067
3180
|
$('h-slug').className = 'hint err'; $('f-slug').classList.add('bad');
|
|
@@ -3127,6 +3240,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3127
3240
|
var rows = [
|
|
3128
3241
|
['Project', esc(state.name)],
|
|
3129
3242
|
['Type', typeLabel(state.projType) || 'Not declared'],
|
|
3243
|
+
/* the About step is optional, so its row states the CONSEQUENCE of the
|
|
3244
|
+
skip rather than a bare em-dash — the review is the last screen before
|
|
3245
|
+
Create, and "off the map" should never be a surprise (task 1002334) */
|
|
3246
|
+
['About', String(state.description || '').trim()
|
|
3247
|
+
? esc(String(state.description).trim())
|
|
3248
|
+
: '<em style="font-weight:600;color:var(--warn)">not said — off the public map until you add it</em>'],
|
|
3249
|
+
['Who’s building it', teamLabel(state.teamShape) || 'Not said'],
|
|
3130
3250
|
['Starting from', state.mode === 'adopt' ? 'A repo you already have' : 'A fresh, empty repo'],
|
|
3131
3251
|
['Repository', '<code>' + esc(state.owner + '/' + state.repo) + '</code>'],
|
|
3132
3252
|
['Handle', '<code>' + esc(state.slug) + '</code>'],
|
|
@@ -3213,6 +3333,11 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3213
3333
|
body: JSON.stringify({
|
|
3214
3334
|
slug: state.slug,
|
|
3215
3335
|
type: state.projType || undefined,
|
|
3336
|
+
/* the optional About answers (task 1002334) — omitted when skipped,
|
|
3337
|
+
because absent means SKIPPED and an empty string would read as an
|
|
3338
|
+
answered-but-blank description to the publish gate */
|
|
3339
|
+
description: String(state.description || '').trim() || undefined,
|
|
3340
|
+
detail: state.teamShape ? { team_shape: state.teamShape } : undefined,
|
|
3216
3341
|
hosting_shape: 'standalone',
|
|
3217
3342
|
/* ONLY a bring-your-own domain travels (task 1002898 N2). The server owns
|
|
3218
3343
|
the assigned <slug>.<apex> default (task 1002704) and applies it to an
|
|
@@ -3257,7 +3382,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3257
3382
|
sessionStorage.removeItem(DRAFT_KEY);
|
|
3258
3383
|
} catch (e) {}
|
|
3259
3384
|
renderDone();
|
|
3260
|
-
show(
|
|
3385
|
+
show(7, true);
|
|
3261
3386
|
startProgress(data.instance && data.instance.id);
|
|
3262
3387
|
}).catch(function (err) {
|
|
3263
3388
|
if (String(err && err.message) === 'signing in') return;
|
|
@@ -3265,7 +3390,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3265
3390
|
if (err && err.gotoHome) {
|
|
3266
3391
|
/* A repo-creation problem (name taken / reconnect) — bounce back to the Home
|
|
3267
3392
|
step with the reason, so the user fixes it where the repo is chosen. */
|
|
3268
|
-
show(
|
|
3393
|
+
show(4, true);
|
|
3269
3394
|
$('h-home').textContent = err.homeMsg || 'Please revisit the repository step.';
|
|
3270
3395
|
$('h-home').className = 'hint err';
|
|
3271
3396
|
return;
|
|
@@ -3799,7 +3924,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3799
3924
|
5s tick never clobbers an open dropdown or mid-flight button) */
|
|
3800
3925
|
if (inst && inst.status !== live.instStatus) {
|
|
3801
3926
|
live.instStatus = inst.status; renderSteps();
|
|
3802
|
-
if (state.step ===
|
|
3927
|
+
if (state.step === 7) paintRail(7); /* the rail finishes only when the project answers */
|
|
3803
3928
|
}
|
|
3804
3929
|
var terminal = renderProgress(inst);
|
|
3805
3930
|
if (!terminal) pollTimer = setTimeout(tick, 5000);
|
|
@@ -3836,13 +3961,14 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3836
3961
|
lastStepsHtml = '';
|
|
3837
3962
|
lastProgHtml = '';
|
|
3838
3963
|
try { sessionStorage.removeItem(CREATED_KEY); sessionStorage.removeItem(DRAFT_KEY); } catch (e) {}
|
|
3839
|
-
state = { step: 1, name: '', projType: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
|
|
3964
|
+
state = { step: 1, name: '', projType: null, description: '', teamShape: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
|
|
3840
3965
|
$('newName').value = ''; $('f-owner').value = ''; $('f-repo').value = ''; $('f-slug').value = ''; $('f-domain').value = '';
|
|
3841
|
-
$('f-noaddr').checked = false;
|
|
3966
|
+
$('f-noaddr').checked = false; $('f-desc').value = '';
|
|
3842
3967
|
/* Re-seed the owner from the signed-in account for the next project. */
|
|
3843
3968
|
if (me && me.github_login) { state.owner = me.github_login; $('f-owner').value = state.owner; }
|
|
3844
3969
|
$('h-name').innerHTML = ' ';
|
|
3845
3970
|
paintType(null);
|
|
3971
|
+
paintTeam(null);
|
|
3846
3972
|
setMode('greenfield');
|
|
3847
3973
|
show(1, true);
|
|
3848
3974
|
});
|
|
@@ -3875,7 +4001,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3875
4001
|
manifestState = created.manifest_state; manifestPending = true;
|
|
3876
4002
|
}
|
|
3877
4003
|
renderDone();
|
|
3878
|
-
show(
|
|
4004
|
+
show(7, !!focus);
|
|
3879
4005
|
startProgress(created.id);
|
|
3880
4006
|
resumePolls();
|
|
3881
4007
|
return;
|
|
@@ -3883,7 +4009,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3883
4009
|
/* before identity settles, never render past the name step: deeper steps
|
|
3884
4010
|
carry authenticated side effects (the Home-step repo probe), and onAuth()
|
|
3885
4011
|
re-enters to the real draft step the moment /me answers */
|
|
3886
|
-
var step = authKnown && state.step >= 1 && state.step <=
|
|
4012
|
+
var step = authKnown && state.step >= 1 && state.step <= 6 ? state.step : 1;
|
|
3887
4013
|
if (step === 1) {
|
|
3888
4014
|
show(1, false);
|
|
3889
4015
|
if (focus) $('newName').focus();
|
|
@@ -4130,19 +4256,57 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4130
4256
|
return !!inst.open_intent; /* e.g. a queued teardown or domain-attach on an active instance */
|
|
4131
4257
|
}
|
|
4132
4258
|
|
|
4133
|
-
/* ── the
|
|
4134
|
-
|
|
4259
|
+
/* ── the planet on a row (task 1003345): the SAME object the sky shows. A
|
|
4260
|
+
catalog-matched instance takes its catalog row's template and accent
|
|
4261
|
+
(curated, or position-walked by assignAccents); an owned-only one walks
|
|
4262
|
+
by its slug, the rule the map's appended entries use — so one project is
|
|
4263
|
+
one object in both views and never re-colours between them. ── */
|
|
4264
|
+
function planetOf(inst) {
|
|
4265
|
+
var h = inst.domain ? hostOf(inst.domain) : '';
|
|
4266
|
+
for (var i = 0; h && i < catalog.length; i++) {
|
|
4267
|
+
if (hostOf(catalog[i].origin) === h) {
|
|
4268
|
+
return { tpl: templateOf(catalog[i]), acc: accentOf(catalog[i]), curated: ORB_TEMPLATES.indexOf(catalog[i].template) !== -1 };
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
return { tpl: slugTemplate(inst.slug), acc: ORB_ACCENTS[slugAccent(inst.slug)], curated: false };
|
|
4272
|
+
}
|
|
4273
|
+
/* shape[-material]-style → the data attributes cosmos.css draws from (the map's rule) */
|
|
4274
|
+
function planetAttrs(pl) {
|
|
4275
|
+
var parts = pl.tpl.split('-');
|
|
4276
|
+
return ' data-template="' + esc(pl.tpl) + '" data-shape="' + esc(parts[0]) +
|
|
4277
|
+
'" data-material="' + esc(parts.length === 3 ? parts[1] : 'chrome') +
|
|
4278
|
+
'" data-style="' + esc(parts[parts.length - 1]) + '"';
|
|
4279
|
+
}
|
|
4280
|
+
var PLANET_SHAPE_WORDS = { blob: 'the blob', pair: 'the bongo pair', bead: 'the bead' };
|
|
4281
|
+
var PLANET_STYLE_WORDS = { tint: 'tinted', wash: 'washed', plain: 'plain', chrome: 'plain' };
|
|
4282
|
+
/* the planet as a clause for the manage ledger: the accent as a dot, then shape, material, style */
|
|
4283
|
+
function planetWords(pl) {
|
|
4284
|
+
var parts = pl.tpl.split('-');
|
|
4285
|
+
var material = parts.length === 3 ? parts[1] : 'chrome';
|
|
4286
|
+
return '<span class="swDot" style="--sw:' + pl.acc + '" aria-hidden="true"></span>' +
|
|
4287
|
+
esc((PLANET_SHAPE_WORDS[parts[0]] || parts[0]) + ' in ' + material + ', ' +
|
|
4288
|
+
(PLANET_STYLE_WORDS[parts[parts.length - 1]] || parts[parts.length - 1]));
|
|
4289
|
+
}
|
|
4290
|
+
/* the catalog can land after a view rendered — the rows and the manage head take its planets then */
|
|
4291
|
+
function repaintPlanets() {
|
|
4292
|
+
Object.keys(byId).forEach(function (id) { renderCard(byId[id]); });
|
|
4293
|
+
if (manageInst) paintManagePlanet(manageInst);
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
/* ── the row's object: the planet, its state on the object (ghost = torn
|
|
4297
|
+
down, ashen = failed its check, dim = a stale probe); only a live
|
|
4298
|
+
project's planet is a door to its site ── */
|
|
4135
4299
|
function orbFor(inst) {
|
|
4136
|
-
var
|
|
4137
|
-
var cls = '
|
|
4300
|
+
var pl = planetOf(inst);
|
|
4301
|
+
var cls = 'worldOrb';
|
|
4138
4302
|
if (inst.status === 'torn_down' || inst.status === 'tearing_down') cls += ' ghost';
|
|
4139
4303
|
else if (inst.status === 'error' || (inst.status === 'active' && inst.last_verified_up === false)) cls += ' ashen';
|
|
4140
4304
|
else if (inst.status === 'active' && !freshUp(inst)) cls += ' dim';
|
|
4141
|
-
var
|
|
4305
|
+
var attrs = planetAttrs(pl) + ' style="--acc:' + pl.acc + ';--d:64px"';
|
|
4142
4306
|
if (inst.status === 'active' && inst.domain) {
|
|
4143
|
-
return '<a class="' + cls + '"
|
|
4307
|
+
return '<a class="' + cls + '"' + attrs + ' href="https://' + esc(inst.domain) + '" target="_blank" rel="noopener" title="Open ' + esc(inst.slug) + '" aria-label="Open ' + esc(inst.slug) + '"><span class="disc"></span></a>';
|
|
4144
4308
|
}
|
|
4145
|
-
return '<span class="' + cls + '"
|
|
4309
|
+
return '<span class="' + cls + '"' + attrs + ' aria-hidden="true"><span class="disc"></span></span>';
|
|
4146
4310
|
}
|
|
4147
4311
|
|
|
4148
4312
|
/* ── progress strips: each flow speaks its own stages (direction §4).
|
|
@@ -4628,6 +4792,17 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4628
4792
|
encodeURIComponent('/projects?view=manage&id=' + manageId);
|
|
4629
4793
|
}
|
|
4630
4794
|
|
|
4795
|
+
/* one label · value pair of the manage ledger */
|
|
4796
|
+
function ledgerRow(r) { return '<span class="k">' + r[0] + '</span><span class="v">' + r[1] + '</span>'; }
|
|
4797
|
+
/* the head's planet and the ledger's planet row (task 1003345) — one resolution
|
|
4798
|
+
(planetOf) for both, so the head, the row and the sky can never disagree; the
|
|
4799
|
+
planet row is the slot the picker (task 1003347) fills */
|
|
4800
|
+
function paintManagePlanet(inst) {
|
|
4801
|
+
var pl = planetOf(inst);
|
|
4802
|
+
$('mOrb').innerHTML = orbFor(inst);
|
|
4803
|
+
$('mPlanet').innerHTML = ledgerRow(['planet', planetWords(pl) + (pl.curated ? '' : '<small>chosen for it by the platform.</small>')]);
|
|
4804
|
+
$('mPlanet').hidden = false;
|
|
4805
|
+
}
|
|
4631
4806
|
function renderManage(inst) {
|
|
4632
4807
|
manageInst = inst;
|
|
4633
4808
|
$('mLoading').hidden = true;
|
|
@@ -4638,20 +4813,22 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4638
4813
|
/* the ONE chip site of this view — chipFor() or nothing, so the manage
|
|
4639
4814
|
page can never tell a different liveness story than the card (C6) */
|
|
4640
4815
|
$('mChip').innerHTML = chipFor(inst);
|
|
4641
|
-
|
|
4816
|
+
paintManagePlanet(inst);
|
|
4817
|
+
var rows = [];
|
|
4642
4818
|
if (inst.target_ref) {
|
|
4643
|
-
|
|
4644
|
-
'" target="_blank" rel="noopener"
|
|
4819
|
+
rows.push(['repository', '<a href="https://github.com/' + esc(inst.target_ref) +
|
|
4820
|
+
'" target="_blank" rel="noopener">' + esc(inst.target_ref) + '</a>']);
|
|
4645
4821
|
}
|
|
4646
|
-
|
|
4647
|
-
if (inst.onboard_mode === 'adopt')
|
|
4648
|
-
|
|
4649
|
-
$('mMeta').
|
|
4822
|
+
var server = inst.hosting_shape ? esc(SHAPE_LABEL[inst.hosting_shape] || inst.hosting_shape) : '';
|
|
4823
|
+
if (inst.onboard_mode === 'adopt') server += (server ? ', ' : '') + 'adopted repo';
|
|
4824
|
+
if (server) rows.push(['server', server]);
|
|
4825
|
+
$('mMeta').innerHTML = rows.map(ledgerRow).join('');
|
|
4826
|
+
$('mMeta').hidden = !rows.length;
|
|
4650
4827
|
var addr = addressRow(inst);
|
|
4651
|
-
$('mAddr').innerHTML = addr;
|
|
4828
|
+
$('mAddr').innerHTML = addr ? ledgerRow(['address', addr]) : '';
|
|
4652
4829
|
$('mAddr').hidden = !addr;
|
|
4653
4830
|
/* a fact with no answer hides — never a dash (the card rule) */
|
|
4654
|
-
$('mCreated').
|
|
4831
|
+
$('mCreated').innerHTML = inst.created_at ? ledgerRow(['created', esc(listedDate(inst.created_at))]) : '';
|
|
4655
4832
|
$('mCreated').hidden = !inst.created_at;
|
|
4656
4833
|
/* fetch-free like everything else here, so the once-a-minute tick may call
|
|
4657
4834
|
it: the delete section reads only status + open_intent, both already in
|
|
@@ -4661,8 +4838,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4661
4838
|
only, so a queued settings push shows up on the tick without a reload */
|
|
4662
4839
|
renderVisState(inst);
|
|
4663
4840
|
renderJoinState(inst);
|
|
4664
|
-
/* the page names its project —
|
|
4665
|
-
$('mTitle').textContent = inst.slug || 'Manage';
|
|
4841
|
+
/* the page names its project — the heading holds #mName; and in the tab */
|
|
4666
4842
|
document.title = (inst.slug ? inst.slug + ' — ' : '') + BASE_TITLE;
|
|
4667
4843
|
/* the card's own story, on the project's own page (task 1003232): the
|
|
4668
4844
|
per-flow strip, the honest snag, and the doors the list card has */
|
|
@@ -4761,6 +4937,10 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4761
4937
|
$('mGate').hidden = true;
|
|
4762
4938
|
$('mMissing').hidden = true;
|
|
4763
4939
|
$('mLoading').hidden = false;
|
|
4940
|
+
/* the head stands in every state — a fresh load must not wear the last project's name */
|
|
4941
|
+
$('mName').textContent = 'Manage';
|
|
4942
|
+
$('mChip').innerHTML = '';
|
|
4943
|
+
$('mOrb').innerHTML = '';
|
|
4764
4944
|
}
|
|
4765
4945
|
var asked = manageId; /* a stale answer must never paint a newer project */
|
|
4766
4946
|
fetch(API + '/provisioning/instances/' + encodeURIComponent(manageId), { credentials: 'same-origin' })
|