@bongos/core 1.21.6 → 1.21.7

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.
@@ -0,0 +1,216 @@
1
+ {
2
+ "_": "The create wizard done panel once the hall answers (task 1004503): the same walk as projects.states.json in-new-done, with the instance list aliased so the project just filed (id 102) is live and verified up. The invite box opens only now; in-new-done in the main file pictures it shut while the project is set up. Rendered by name: node modules/ui-design/kit/render.js --states modules/public-landing/public/projects-hall-live.states.json",
3
+ "page": "/projects",
4
+ "surface": "public-landing",
5
+ "modes": [
6
+ "dark"
7
+ ],
8
+ "modeQuery": false,
9
+ "stub": {
10
+ "fixtures": {
11
+ "provisioning-instances": "provisioning-instances-hall-live"
12
+ }
13
+ },
14
+ "ignoreRequests": [
15
+ "/api/(gds|bongos)/me$",
16
+ "/my-projects",
17
+ "/provisioning/onboard-plan",
18
+ "/env-manifest$"
19
+ ],
20
+ "ignoreConsole": [
21
+ "/api/(gds|bongos)/me",
22
+ "\\b401\\b",
23
+ "\\b404\\b",
24
+ "Unauthorized"
25
+ ],
26
+ "states": {
27
+ "in-new-done-live": {
28
+ "auth": true,
29
+ "url": "/projects?view=new",
30
+ "actions": [
31
+ [
32
+ "wait",
33
+ 600
34
+ ],
35
+ [
36
+ "click",
37
+ "#termsAck"
38
+ ],
39
+ [
40
+ "wait",
41
+ 100
42
+ ],
43
+ [
44
+ "click",
45
+ "#wizStart"
46
+ ],
47
+ [
48
+ "wait",
49
+ 400
50
+ ],
51
+ [
52
+ "fill",
53
+ "#newName",
54
+ "Mercury"
55
+ ],
56
+ [
57
+ "click",
58
+ "#typeCards .tpill[data-type=\"game\"]"
59
+ ],
60
+ [
61
+ "fill",
62
+ "#f-desc",
63
+ "A co-op farming game where the seasons never repeat."
64
+ ],
65
+ [
66
+ "click",
67
+ "#panel-what button[type=submit]"
68
+ ],
69
+ [
70
+ "wait",
71
+ 400
72
+ ],
73
+ [
74
+ "select",
75
+ "#physCountry",
76
+ "US"
77
+ ],
78
+ [
79
+ "wait",
80
+ 100
81
+ ],
82
+ [
83
+ "click",
84
+ "[data-phys=\"builder_geography\"][data-val=\"global\"]"
85
+ ],
86
+ [
87
+ "click",
88
+ "[data-phys=\"minors_on_board\"][data-val=\"false\"]"
89
+ ],
90
+ [
91
+ "click",
92
+ "[data-phys=\"entity\"][data-val=\"intent-solo\"]"
93
+ ],
94
+ [
95
+ "click",
96
+ "#physNext"
97
+ ],
98
+ [
99
+ "wait",
100
+ 700
101
+ ],
102
+ [
103
+ "click",
104
+ "#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"
105
+ ],
106
+ [
107
+ "wait",
108
+ 200
109
+ ],
110
+ [
111
+ "click",
112
+ "#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"
113
+ ],
114
+ [
115
+ "wait",
116
+ 200
117
+ ],
118
+ [
119
+ "fill",
120
+ "#f-slug",
121
+ "mercury"
122
+ ],
123
+ [
124
+ "wait",
125
+ 900
126
+ ],
127
+ [
128
+ "fill",
129
+ "#f-domain",
130
+ "mercury.example.com"
131
+ ],
132
+ [
133
+ "wait",
134
+ 200
135
+ ],
136
+ [
137
+ "click",
138
+ "#reviewRows .setRow[data-row=\"address\"] [data-row-use]"
139
+ ],
140
+ [
141
+ "wait",
142
+ 200
143
+ ],
144
+ [
145
+ "click",
146
+ "#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"
147
+ ],
148
+ [
149
+ "wait",
150
+ 150
151
+ ],
152
+ [
153
+ "click",
154
+ "#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"
155
+ ],
156
+ [
157
+ "wait",
158
+ 150
159
+ ],
160
+ [
161
+ "click",
162
+ "#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"
163
+ ],
164
+ [
165
+ "wait",
166
+ 150
167
+ ],
168
+ [
169
+ "click",
170
+ "#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"
171
+ ],
172
+ [
173
+ "wait",
174
+ 200
175
+ ],
176
+ [
177
+ "click",
178
+ "#reviewGo"
179
+ ],
180
+ [
181
+ "wait",
182
+ 500
183
+ ],
184
+ [
185
+ "click",
186
+ "#doCreate"
187
+ ],
188
+ [
189
+ "wait",
190
+ 1500
191
+ ],
192
+ [
193
+ "wait",
194
+ 6000
195
+ ]
196
+ ],
197
+ "expect": {
198
+ "visible": [
199
+ "#panel-done",
200
+ "#wizInvite",
201
+ "#wizInviteWho"
202
+ ],
203
+ "text": [
204
+ [
205
+ "#done-h",
206
+ "Mercury is alive."
207
+ ]
208
+ ]
209
+ }
210
+ }
211
+ },
212
+ "ignoreControls": [
213
+ ".skip",
214
+ ".vh"
215
+ ]
216
+ }
@@ -4873,10 +4873,45 @@ summary{min-height:24px;padding:3px 0;}
4873
4873
  show(2, true);
4874
4874
  });
4875
4875
  /* the done panel's invite step names the demo's teammates (task 1004420): the hub
4876
- holds no invite of its own, so the founder sends each from the project's own box */
4876
+ holds no invite of its own, so the founder sends each from the project's own box.
4877
+ The names ride the created record too, so a reload before the hall is up keeps them
4878
+ (task 1004503). */
4879
+ function demoTeam() {
4880
+ if (state.demo && state.demo.invites && state.demo.invites.length) return state.demo.invites;
4881
+ var created = createdRecord();
4882
+ return created && Array.isArray(created.demo_invites) ? created.demo_invites : [];
4883
+ }
4884
+ /* The invite step (tasks 1002346 and 1004503). A second account cannot join until the
4885
+ project's hall is up, so the box opens only on the done panel's own verified Live
4886
+ (last_verified_up), never while the project is being set up. Before that, one plain
4887
+ line says when inviting opens, naming a demo's teammates so they are not lost. The
4888
+ CREATED RECORD is the proof the widget needs: no signed-in identity, no filed instance
4889
+ or no slug means nothing to invite to. Opening loads the suggestions once; a later
4890
+ tick does not ask again. */
4891
+ var invitesOpen = false;
4892
+ function paintInviteGate(live) {
4893
+ var created = createdRecord();
4894
+ var canInvite = !!(me && created && created.id && created.slug);
4895
+ if (live && canInvite) {
4896
+ if (invitesOpen) return;
4897
+ invitesOpen = true;
4898
+ setInviteSlug('wizInvite', created.slug);
4899
+ paintDemoTeam();
4900
+ loadInviteSuggestions('wizInvite', created.id);
4901
+ $('wizInviteLater').innerHTML = 'You can invite builders any time from ' + projectPageLink('the project’s own page') + '.';
4902
+ return;
4903
+ }
4904
+ invitesOpen = false;
4905
+ setInviteSlug('wizInvite', '');
4906
+ $('wizDemoTeam').hidden = true;
4907
+ var team = demoTeam();
4908
+ $('wizInviteLater').textContent = team.length
4909
+ ? 'You can invite ' + team.map(function (h) { return '@' + h; }).join(', ') + ' once its hall is up.'
4910
+ : 'You can invite builders once its hall is up.';
4911
+ }
4877
4912
  function paintDemoTeam() {
4878
4913
  var line = $('wizDemoTeam');
4879
- var who = state.demo && state.demo.invites && state.demo.invites.length ? state.demo.invites : [];
4914
+ var who = demoTeam();
4880
4915
  line.hidden = !who.length;
4881
4916
  line.textContent = who.length ? 'Your demo teammates: ' + who.map(function (h) { return '@' + h; }).join(', ') + '. Invite each one here.' : '';
4882
4917
  if (who.length && !$('wizInviteWho').value) $('wizInviteWho').value = who[0];
@@ -6399,7 +6434,9 @@ summary{min-height:24px;padding:3px 0;}
6399
6434
  sessionStorage.setItem(CREATED_KEY, JSON.stringify({
6400
6435
  id: data.instance && data.instance.id, slug: state.slug, domain: madeDomain,
6401
6436
  noaddr: !madeDomain,
6402
- mode: state.mode, name: state.name, at: Date.now()
6437
+ mode: state.mode, name: state.name, at: Date.now(),
6438
+ /* a demo's teammates, offered once the hall is up (task 1004503) */
6439
+ demo_invites: state.demo && state.demo.invites && state.demo.invites.length ? state.demo.invites.slice() : undefined
6403
6440
  }));
6404
6441
  sessionStorage.removeItem(DRAFT_KEY);
6405
6442
  } catch (e) {}
@@ -6650,16 +6687,8 @@ summary{min-height:24px;padding:3px 0;}
6650
6687
  skippable, so this panel names where it lives afterwards rather than
6651
6688
  leaving an owner to go looking for it. */
6652
6689
  $('doneModules').innerHTML = 'Add or remove modules later in ' + projectPageLink('the project’s settings') + '.';
6653
- /* the invite step (task 1002346, BV1.R27). The CREATED RECORD is the proof
6654
- the widget needs and the only one this panel has: no signed-in identity,
6655
- no filed instance or no slug ⇒ nothing to invite to, and the step stays
6656
- hidden. It is deliberately NOT keyed on instanceId, which startProgress
6657
- sets only AFTER this runs. */
6658
- var created = createdRecord();
6659
- setInviteSlug('wizInvite', (me && created && created.id && created.slug) ? created.slug : '');
6660
- paintDemoTeam();
6661
- loadInviteSuggestions('wizInvite', (me && created && created.slug) ? created.id : '');
6662
- $('wizInviteLater').innerHTML = 'You can invite builders any time from ' + projectPageLink('the project’s own page') + '.';
6690
+ /* the invite step waits for the hall (task 1004503): until it answers, one line */
6691
+ paintInviteGate(false);
6663
6692
  renderSteps();
6664
6693
  }
6665
6694
 
@@ -6912,6 +6941,7 @@ summary{min-height:24px;padding:3px 0;}
6912
6941
  if (inst.last_verified_up) {
6913
6942
  /* the alive moment (task 1004504): said only once the project has answered */
6914
6943
  $('done-h').textContent = (state.name || state.slug || 'Your project') + ' is alive.';
6944
+ paintInviteGate(true); /* the hall answers, so others can join now */
6915
6945
  setProgress(progressMarkup(-1, 3,
6916
6946
  'Your project is answering at ' + link + '. Finish the steps below and the hall is yours.', 'isLive', flow));
6917
6947
  return true;
@@ -56,7 +56,7 @@
56
56
  "in-new-review-run": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-chg]"],["wait",300],["click","[data-phys=\"framing\"][data-val=\"hybrid\"]"],["wait",600]],"expect":{"visible":["#physSuite"]}},
57
57
  "in-new-review-confirmed": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200]],"expect":{"visible":["#reviewGo","#reviewRows .setRow.done"],"text":[["#reviewGo","Continue"]]}},
58
58
  "in-new-look": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500]],"expect":{"visible":["#panel-look","#wizLook","#wizRecap","#doCreate"],"hidden":["#panel-review"],"attr":[["#wizRoom","data-screen","look"]]}},
59
- "in-new-done": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500],["click","#doCreate"],["wait",1500]],"expect":{"visible":["#panel-done","#liveProgress .prog","#humanSteps li"],"hidden":["#panel-look","#qWrap","#wizBack"]}},
59
+ "in-new-done": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500],["click","#doCreate"],["wait",1500]],"expect":{"visible":["#panel-done","#liveProgress .prog","#humanSteps li","#wizInviteLater"],"hidden":["#panel-look","#qWrap","#wizBack","#wizInvite"]}},
60
60
  "in-new-resume-offer": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500],["click","#doCreate"],["wait",1500],["click","#projTrig"],["wait",350],["click","#menuProjects [data-nav=\"new\"]"],["wait",500]],"expect":{"visible":["#panel-fork","#wizResume","#wizResumeGo"],"hidden":["#panel-done"],"text":[["#wizResumeName","Mercury"]]}},
61
61
  "in-new-look-custom": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500],["click","#wizLook-custom"],["wait",200]],"expect":{"visible":["#panel-look","#wizAccent","#wizAccentNote"],"hidden":["#panel-review"]}},
62
62
  "out-new": {"auth":false,"url":"/projects?view=new","expect":{"visible":["#panel-gate","#wizSignin"],"hidden":["#qWrap","#panel-fork"]}},
@@ -0,0 +1,106 @@
1
+ {
2
+ "_": "The owner's projects for the kit (task 1003345): four states the mine list and the manage page render — live, standing up, snagged, torn down. Timestamps written \"$now-3m\" / \"$now-1d\" are resolved by the stub at request time so \"checked 3m ago\" stays true. Mooring (task 1003345, the grade's finding): live but unreachable since its last probe, so the restart door renders and the restart probe has a row to drive.",
3
+ "instances": [
4
+ {
5
+ "id": 101,
6
+ "slug": "demo",
7
+ "status": "active",
8
+ "domain": "demo.cloudbongos.com",
9
+ "target_ref": "builder/demo",
10
+ "hosting_shape": "co-tenant",
11
+ "onboard_mode": "scaffold",
12
+ "created_at": "2026-08-01T10:00:00.000Z",
13
+ "provisioned_at": "2026-08-01T10:20:00.000Z",
14
+ "updated_at": "$now-3m",
15
+ "last_verified_at": "$now-3m",
16
+ "last_verified_up": true,
17
+ "error_note": null,
18
+ "dns_note": null,
19
+ "dns_resolves": true,
20
+ "torn_down_at": null,
21
+ "open_intent": null
22
+ },
23
+ {
24
+ "id": 102,
25
+ "slug": "mercury",
26
+ "status": "active",
27
+ "planet_template": "blob-frost-tint",
28
+ "planet_accent": "sun-lit",
29
+ "domain": "mercury.cloudbongos.com",
30
+ "target_ref": "builder/mercury",
31
+ "hosting_shape": "cloud-host",
32
+ "owner_monthly_usd": null,
33
+ "onboard_mode": "scaffold",
34
+ "created_at": "$now-6m",
35
+ "provisioned_at": "$now-1m",
36
+ "updated_at": "$now-2m",
37
+ "last_verified_at": "$now-1m",
38
+ "last_verified_up": true,
39
+ "error_note": null,
40
+ "dns_note": null,
41
+ "torn_down_at": null,
42
+ "open_intent": {
43
+ "action": "standup",
44
+ "state": "running",
45
+ "created_at": "$now-6m"
46
+ },
47
+ "dns_resolves": true
48
+ },
49
+ {
50
+ "id": 103,
51
+ "slug": "tidepool",
52
+ "status": "error",
53
+ "domain": "tidepool.cloudbongos.com",
54
+ "target_ref": "builder/tidepool",
55
+ "hosting_shape": "co-tenant",
56
+ "onboard_mode": "adopt",
57
+ "created_at": "2026-08-20T09:00:00.000Z",
58
+ "provisioned_at": null,
59
+ "updated_at": "$now-1d",
60
+ "last_verified_at": null,
61
+ "last_verified_up": null,
62
+ "error_note": "The deploy could not read the repository: it is private, and the deploy key only reads public ones.",
63
+ "dns_note": null,
64
+ "torn_down_at": null,
65
+ "open_intent": null
66
+ },
67
+ {
68
+ "id": 104,
69
+ "slug": "old-lantern",
70
+ "status": "torn_down",
71
+ "domain": "old-lantern.cloudbongos.com",
72
+ "target_ref": "builder/old-lantern",
73
+ "hosting_shape": "co-tenant",
74
+ "onboard_mode": "scaffold",
75
+ "created_at": "2026-07-02T09:00:00.000Z",
76
+ "provisioned_at": "2026-07-02T09:30:00.000Z",
77
+ "updated_at": "2026-08-15T12:00:00.000Z",
78
+ "last_verified_at": "2026-08-15T11:50:00.000Z",
79
+ "last_verified_up": true,
80
+ "error_note": null,
81
+ "dns_note": null,
82
+ "dns_resolves": true,
83
+ "torn_down_at": "2026-08-15T12:00:00.000Z",
84
+ "open_intent": null
85
+ },
86
+ {
87
+ "id": 105,
88
+ "slug": "mooring",
89
+ "status": "active",
90
+ "domain": "mooring.cloudbongos.com",
91
+ "target_ref": "builder/mooring",
92
+ "hosting_shape": "co-tenant",
93
+ "onboard_mode": "scaffold",
94
+ "created_at": "2026-08-10T09:00:00.000Z",
95
+ "provisioned_at": "2026-08-10T09:20:00.000Z",
96
+ "updated_at": "$now-20m",
97
+ "last_verified_at": "$now-20m",
98
+ "last_verified_up": false,
99
+ "error_note": null,
100
+ "dns_note": null,
101
+ "dns_resolves": true,
102
+ "torn_down_at": null,
103
+ "open_intent": null
104
+ }
105
+ ]
106
+ }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.6",
3
+ "version": "1.21.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.6",
9
+ "version": "1.21.7",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.6",
3
+ "version": "1.21.7",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -8788,5 +8788,11 @@
8788
8788
  "id": "1004440",
8789
8789
  "text": "A builder's profile on the Cloud Bongos hub now shows each craft's own work. An artist sees their approved pages, an ideator sees their filed, Full and board-ratified ideas, and the roles shown come from the work someone has a"
8790
8790
  }
8791
+ ],
8792
+ "1.21.7": [
8793
+ {
8794
+ "id": "1004503",
8795
+ "text": "When you start a project, the invite box now waits until your project is actually up, because nobody can join before then. Until it is, one line says when you can invite, and a demo's teammates are kept ready for that moment."
8796
+ }
8791
8797
  ]
8792
8798
  }
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.6'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.7'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -474,11 +474,69 @@ test('the two mounts keep their own lines — an invite on one is not drawn on t
474
474
  // in" and "is this their project" — are answered by the caller, which hands over
475
475
  // a slug or nothing. So the pins are on the calls.
476
476
 
477
- test('the wizard opens the step only for a signed-in owner of a project that already exists', () => {
478
- assert.match(SCRIPT, /setInviteSlug\('wizInvite', \(me && created && created\.id && created\.slug\) \? created\.slug : ''\);/,
479
- 'the created record IS the proof: no /me, no created id, no slug ⇒ no step');
480
- assert.ok(SCRIPT.indexOf("setInviteSlug('wizInvite'") > SCRIPT.indexOf('function renderDone()'),
481
- 'and it is the done panel that opens it — the panel that only exists after Create');
477
+ // The wizard's invite step waits for the hall (task 1004503): a second account cannot join a
478
+ // project whose hall is not up, so the box opens only on the done panel's verified Live. The
479
+ // gate is RUN here: demoTeam, paintInviteGate and paintDemoTeam sliced out of the script.
480
+ const GATE_START = ' function demoTeam() {';
481
+ function gateSlice() {
482
+ const a = SCRIPT.indexOf(GATE_START);
483
+ const b = SCRIPT.indexOf('\n }\n', SCRIPT.indexOf(' function paintDemoTeam() {'));
484
+ return a > -1 && b > a ? SCRIPT.slice(a, b + 6) : '';
485
+ }
486
+ function bootGate({ me = { github_login: 'kestrel' }, created = { id: 7, slug: 'moonbase' }, demo = null } = {}) {
487
+ const els = {};
488
+ const $ = (id) => (els[id] = els[id] || { id, hidden: false, textContent: '', innerHTML: '', value: '' });
489
+ const calls = { slug: [], sugg: [] };
490
+ const sandbox = {
491
+ $, me, state: { demo },
492
+ createdRecord: () => created,
493
+ setInviteSlug: (id, slug) => calls.slug.push([id, slug]),
494
+ loadInviteSuggestions: (id, iid) => calls.sugg.push([id, iid]),
495
+ projectPageLink: (label) => '<a href="?view=manage">' + label + '</a>',
496
+ };
497
+ vm.createContext(sandbox);
498
+ vm.runInContext(gateSlice(), sandbox);
499
+ return { run: (c) => vm.runInContext(c, sandbox), $, calls };
500
+ }
501
+
502
+ test('the wizard opens the invite step only once the hall is up, and only for a signed-in owner of a project that exists (tasks 1002346, 1004503)', () => {
503
+ assert.ok(gateSlice().includes('function paintInviteGate(live)'), 'the gate slice is found');
504
+ assert.match(SCRIPT, /function renderDone\(\) \{[\s\S]*?paintInviteGate\(false\);/, 'the done panel paints the gate shut');
505
+ assert.match(SCRIPT, /if \(inst\.last_verified_up\) \{[\s\S]{0,400}paintInviteGate\(true\);/, 'and opens it only on the verified Live');
506
+ assert.equal((SCRIPT.match(/setInviteSlug\('wizInvite'/g) || []).length, 2, 'the gate is the only caller: shut, or open on Live');
507
+
508
+ const before = bootGate();
509
+ before.run('paintInviteGate(false)');
510
+ assert.deepEqual(before.calls.slug, [['wizInvite', '']], 'before the hall is up: no invite control');
511
+ assert.deepEqual(before.calls.sugg, [], 'and no suggestions asked for');
512
+ assert.equal(before.$('wizInviteLater').textContent, 'You can invite builders once its hall is up.', 'one plain line says when');
513
+
514
+ const live = bootGate();
515
+ live.run('paintInviteGate(true)');
516
+ live.run('paintInviteGate(true)');
517
+ assert.deepEqual(live.calls.slug, [['wizInvite', 'moonbase']], 'on Live: the box opens on the created project, once');
518
+ assert.deepEqual(live.calls.sugg, [['wizInvite', 7]], 'and the suggestions are asked for once, not on every tick');
519
+ assert.match(live.$('wizInviteLater').innerHTML, /any time from <a href="\?view=manage">the project’s own page<\/a>/);
520
+
521
+ for (const [label, opts] of [['signed out', { me: null }], ['no created id', { created: { slug: 'moonbase' } }], ['no slug', { created: { id: 7 } }]]) {
522
+ const g = bootGate(opts);
523
+ g.run('paintInviteGate(true)');
524
+ assert.deepEqual(g.calls.slug, [['wizInvite', '']], label + ': the created record IS the proof — no step');
525
+ }
526
+ });
527
+
528
+ test('a demo\'s teammates are kept until the hall is up, then offered in the box (task 1004503)', () => {
529
+ const before = bootGate({ demo: { invites: ['ava', 'jo'] } });
530
+ before.run('paintInviteGate(false)');
531
+ assert.equal(before.$('wizInviteLater').textContent, 'You can invite @ava, @jo once its hall is up.', 'named while they wait');
532
+ assert.equal(before.$('wizDemoTeam').hidden, true, 'no invite prompt before the hall is up');
533
+ // a reload before the hall is up: the draft is gone, the created record still carries the names
534
+ const reloaded = bootGate({ created: { id: 7, slug: 'moonbase', demo_invites: ['ava'] } });
535
+ reloaded.run('paintInviteGate(true)');
536
+ assert.equal(reloaded.$('wizDemoTeam').hidden, false, 'on Live the teammates line shows');
537
+ assert.equal(reloaded.$('wizDemoTeam').textContent, 'Your demo teammates: @ava. Invite each one here.');
538
+ assert.equal(reloaded.$('wizInviteWho').value, 'ava', 'and the first is ready in the box');
539
+ assert.match(SCRIPT, /demo_invites: state\.demo && state\.demo\.invites && state\.demo\.invites\.length \? state\.demo\.invites\.slice\(\) : undefined/, 'the created record carries them');
482
540
  });
483
541
 
484
542
  test('the project page opens the step from its own manage read, which the API already own-scopes', () => {
@@ -643,8 +701,8 @@ test('a suggestion never renders a github_id, and its name goes through the esca
643
701
  test('the suggestion surface is mounted on BOTH the wizard and the project page', () => {
644
702
  assert.match(HUB, /<ul class="suggList" id="wizInviteSugg"><\/ul>/, 'the wizard mount');
645
703
  assert.match(HUB, /<ul class="suggList" id="mInviteSugg"><\/ul>/, 'and the project page mount');
646
- assert.match(SCRIPT, /loadInviteSuggestions\('wizInvite', \(me && created && created\.slug\) \? created\.id : ''\)/,
647
- 'the wizard asks keyed on the SAME created record the slug comes from (ADR 0249 §1)');
704
+ assert.match(SCRIPT, /setInviteSlug\('wizInvite', created\.slug\);\n\s*paintDemoTeam\(\);\n\s*loadInviteSuggestions\('wizInvite', created\.id\);/,
705
+ 'the wizard asks keyed on the SAME created record the slug comes from (ADR 0249 §1), and only once the gate opens (task 1004503)');
648
706
  assert.match(SCRIPT, /loadInviteSuggestions\('mInvite', inst\.slug \? inst\.id : ''\)/,
649
707
  'and the manage page asks with the id whose read already proved ownership');
650
708
  assert.match(COSMOS, /\.suggList\{/, 'styled in cosmos.css like its sibling list');
@@ -48,9 +48,12 @@ function boot() {
48
48
  const liveProgress = { innerHTML: '' };
49
49
  // the done panel's heading (task 1004504): "Setting up <name>" until the project answers
50
50
  const doneH = { textContent: 'Setting up Lantern' };
51
+ // the invite step's gate (task 1004503): renderProgress opens it only on a verified answer
52
+ const gates = [];
51
53
  const sandbox = {
52
54
  $: (id) => (id === 'liveProgress' ? liveProgress : id === 'done-h' ? doneH : null),
53
55
  state: { name: 'Lantern', slug: 'lantern' },
56
+ paintInviteGate: (live) => gates.push(live),
54
57
  esc: (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
55
58
  { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])),
56
59
  projectPageLink: (label) => '<a href="?view=manage&id=1">' + label + '</a>',
@@ -64,7 +67,7 @@ function boot() {
64
67
  };
65
68
  vm.createContext(sandbox);
66
69
  vm.runInContext(PROGRESS + '\n' + OVERLAY + '\n' + ADDRESS + '\n' + ORB, sandbox);
67
- return { sandbox, progress: () => liveProgress.innerHTML, heading: () => doneH.textContent };
70
+ return { sandbox, progress: () => liveProgress.innerHTML, heading: () => doneH.textContent, gates };
68
71
  }
69
72
 
70
73
  const NOW = () => new Date().toISOString();
@@ -93,13 +96,16 @@ test('once the lookup answers, the done panel links the address', () => {
93
96
  assert.equal(terminal, false, 'resolving is not yet answering — the panel still waits for the probe');
94
97
  assert.match(h.progress(), ADDRESS_LINK);
95
98
  assert.equal(h.heading(), 'Setting up Lantern', 'and the heading claims nothing yet (task 1004504)');
99
+ assert.deepEqual(h.gates, [], 'and the invite box stays shut: the hall has not answered (task 1004503)');
96
100
  const live = boot();
97
101
  assert.equal(live.sandbox.renderProgress({ ...base, dns_resolves: true, last_verified_up: true, last_verified_at: NOW() }), true);
98
102
  assert.match(live.progress(), ADDRESS_LINK);
99
103
  assert.equal(live.heading(), 'Lantern is alive.', 'the alive moment, only on a verified answer');
104
+ assert.deepEqual(live.gates, [true], 'and the invite box opens with it (task 1004503)');
100
105
  const down = boot();
101
106
  down.sandbox.renderProgress({ ...base, dns_resolves: true, last_verified_up: false, last_verified_at: NOW() });
102
107
  assert.equal(down.heading(), 'Setting up Lantern', 'a down probe is never "alive"');
108
+ assert.deepEqual(down.gates, [], 'nor opens the invite box');
103
109
  });
104
110
 
105
111
  test('the card and the manage page: no link and no name before the lookup answers, in every pre-live state', () => {
@@ -34,7 +34,7 @@ test('the platform floors are a pinned list — a floor cannot vanish quietly',
34
34
  });
35
35
 
36
36
  test('the shipped states files validate and sit beside the page they name', () => {
37
- for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/projects-empty.states.json', 'modules/public-landing/public/projects-dns-pending.states.json','modules/public-landing/public/index.states.json', 'modules/public-landing/public/account.states.json', 'modules/public-landing/public/account-private.states.json', 'modules/hall-ui/public/ranks.states.json']) {
37
+ for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/projects-empty.states.json', 'modules/public-landing/public/projects-dns-pending.states.json', 'modules/public-landing/public/projects-hall-live.states.json','modules/public-landing/public/index.states.json', 'modules/public-landing/public/account.states.json', 'modules/public-landing/public/account-private.states.json', 'modules/hall-ui/public/ranks.states.json']) {
38
38
  const json = readJson(rel);
39
39
  const v = lib.validateStatesFile(json);
40
40
  assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);