@bongos/core 1.21.114 → 1.21.116

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.
@@ -156,6 +156,8 @@
156
156
  remedy_failed: 'the automatic fix itself did not work',
157
157
  health: 'the project did not answer after restarting on the new core',
158
158
  migrate: 'updating the project’s database failed',
159
+ // task 1004734: the new core adds a rule some of the project's existing records break
160
+ migrate_check_violation: 'updating the project’s database failed: some records the project already has break a rule the new core adds',
159
161
  restart: 'the project would not restart',
160
162
  rollback_failed: 'the new core failed, and putting the old one back failed too',
161
163
  no_target: 'the move did not say which version to go to',
@@ -46,11 +46,6 @@
46
46
  "#f-desc",
47
47
  "A co-op farming game where the seasons never repeat."
48
48
  ],
49
- [
50
- "select",
51
- "#physCountry",
52
- "US"
53
- ],
54
49
  [
55
50
  "wait",
56
51
  100
@@ -127,6 +127,17 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
127
127
  .physRow2 select:focus{border-bottom-color:var(--ink);}
128
128
  .physRow2 select option{background:var(--bg);color:var(--ink);}
129
129
  .physRow2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 16px;}
130
+ /* "Where are you?" searchable (task 1004671): the page's own field, the list a card
131
+ under it, inside the field's width so a phone never scrolls sideways */
132
+ .cbox{position:relative;}
133
+ .cbox::after{content:"";position:absolute;right:18px;top:19px;width:7px;height:7px;border-right:1.5px solid var(--ink-soft);border-bottom:1.5px solid var(--ink-soft);transform:rotate(45deg);pointer-events:none;}
134
+ #view-new .part .cbox input[type=text]{padding-right:40px;text-overflow:ellipsis;}
135
+ .cboxList{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:6;margin:0;padding:6px;list-style:none;max-height:min(264px,46vh);overflow-y:auto;overscroll-behavior:contain;border-radius:14px;border:1px solid color-mix(in srgb,var(--ink) 40%,transparent);background:var(--bg-card);}
136
+ .cboxList li{display:flex;align-items:center;gap:12px;min-height:44px;padding:8px 12px;border-radius:10px;font-size:16px;line-height:1.3;color:var(--ink);cursor:pointer;overflow-wrap:anywhere;}
137
+ .cboxList li[data-cur]::after{content:"✓";margin-left:auto;color:var(--ink-soft);}
138
+ .cboxList li[aria-disabled="true"]{cursor:default;color:var(--ink-soft);}
139
+ @media (hover:hover){ .cboxList li[data-code]:hover{background:color-mix(in srgb,var(--ink) 8%,transparent);} }
140
+ .cboxList li[data-code][aria-selected="true"]{background:color-mix(in srgb,var(--accent) 24%,transparent);}
130
141
  .physNote{margin:6px 0 14px;padding:14px 0 0;border-top:1px solid var(--rule);max-width:62ch;}
131
142
  .physNoteH{font-size:15px;color:var(--ink);margin:0 0 8px;}
132
143
  .physTiers{display:grid;grid-template-columns:120px minmax(0,1fr);gap:8px 16px;margin:0 0 10px;font-size:14.5px;line-height:1.45;}
@@ -1610,7 +1621,16 @@ summary{min-height:24px;padding:3px 0;}
1610
1621
  <div class="checks" id="checksMain">
1611
1622
  <p class="checksHead">A few quick checks. They decide which legal and payout options we can offer.</p>
1612
1623
  <div class="physRow2">
1613
- <label class="wizField"><span>Where are you?</span><select id="physCountry"><option value="">Choose…</option></select></label>
1624
+ <!-- a searchable list (task 1004671): the select is the one value the page
1625
+ reads, and what a browser without script shows; the script hides it
1626
+ and puts the combobox in front of it -->
1627
+ <div class="wizField" id="physCountryField"><span id="physCountryLbl">Where are you?</span>
1628
+ <select id="physCountry" aria-labelledby="physCountryLbl"><option value="">Choose…</option></select>
1629
+ <div class="cbox" id="physCountryBox" hidden>
1630
+ <input type="text" id="physCountryIn" role="combobox" aria-labelledby="physCountryLbl" aria-autocomplete="list" aria-expanded="false" aria-controls="physCountryList" autocomplete="off" autocapitalize="off" spellcheck="false" placeholder="Type a country">
1631
+ <ul class="cboxList" id="physCountryList" role="listbox" aria-labelledby="physCountryLbl" hidden><li id="physCountryNone" role="option" aria-disabled="true" aria-selected="false" hidden>No country matches that</li></ul>
1632
+ </div>
1633
+ </div>
1614
1634
  <label class="wizField" id="physRegionField" hidden><span>State or province</span><select id="physRegion"><option value="">Choose…</option></select></label>
1615
1635
  </div>
1616
1636
  <div class="wizField"><span id="physAgeLbl">Will anyone under 18 build?</span>
@@ -5803,6 +5823,9 @@ summary{min-height:24px;padding:3px 0;}
5803
5823
  var a = phys().answers;
5804
5824
  if (!a.project_state) a.project_state = 'new';
5805
5825
  if (!a.framing) a.framing = 'managed';
5826
+ /* where the founder is starts at the United States (task 1004671); a country
5827
+ taken back (null) stays taken back, and the last project's answers still win */
5828
+ if (a.home_country === undefined) a.home_country = 'US';
5806
5829
  if (!Number.isInteger(a.builders_at_start)) { a.builders_at_start = 1; a.builders_expected = 1; }
5807
5830
  if (physSolo()) state.teamShape = 'solo';
5808
5831
  else if (!state.teamShape || state.teamShape === 'solo') state.teamShape = 'small-team';
@@ -5827,7 +5850,146 @@ summary{min-height:24px;padding:3px 0;}
5827
5850
  });
5828
5851
  $('physRegionField').hidden = r.options.length === 1;
5829
5852
  r.value = a.home_region || '';
5853
+ cboxPaint();
5854
+ }
5855
+
5856
+ /* "Where are you?" as a searchable list (task 1004671). The select stays the one
5857
+ value everything reads — its change handler, the region, the screen, the draft,
5858
+ the fill from a last project — and the no-script fallback; the combobox only
5859
+ picks for it, from the select's own options (the PHYS_COUNTRIES copy, named by
5860
+ Intl). Matching ignores case and accents; every option is built from nodes. */
5861
+ var cboxItems = [], cboxShown = [], cboxActive = -1;
5862
+ var CBOX_ALSO = { US: 'usa', GB: 'uk' };
5863
+ function cboxFold(s) { return String(s || '').normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().trim(); }
5864
+ function cboxBuild() {
5865
+ if (cboxItems.length) return;
5866
+ var sel = $('physCountry'), list = $('physCountryList');
5867
+ for (var i = 0; i < sel.options.length; i += 1) {
5868
+ var o = sel.options[i];
5869
+ if (!o || !o.value) continue;
5870
+ var li = document.createElement('li');
5871
+ li.setAttribute('id', 'physCountryOpt-' + o.value);
5872
+ li.setAttribute('role', 'option');
5873
+ li.setAttribute('aria-selected', 'false');
5874
+ li.setAttribute('data-code', o.value);
5875
+ li.textContent = o.textContent;
5876
+ list.appendChild(li);
5877
+ cboxItems.push({ code: o.value, name: o.textContent, fold: cboxFold(o.textContent), also: CBOX_ALSO[o.value] || '', li: li });
5878
+ }
5830
5879
  }
5880
+ /* the code itself first, then names that start with the words typed, then a word
5881
+ inside a name, then anywhere; ties keep the alphabet */
5882
+ function cboxMatches(q) {
5883
+ var f = cboxFold(q);
5884
+ if (!f) return cboxItems.slice();
5885
+ var ranked = [];
5886
+ cboxItems.forEach(function (it) {
5887
+ var at = it.fold.indexOf(f);
5888
+ var rank = it.code.toLowerCase() === f || it.also === f ? 0 : at === 0 ? 1 : at > 0 && it.fold.charAt(at - 1) === ' ' ? 2 : at > 0 ? 3 : -1;
5889
+ if (rank !== -1) ranked.push({ it: it, rank: rank });
5890
+ });
5891
+ ranked.sort(function (x, y) { return x.rank - y.rank; });
5892
+ return ranked.map(function (r) { return r.it; });
5893
+ }
5894
+ function cboxSetActive(i) {
5895
+ var input = $('physCountryIn'), list = $('physCountryList');
5896
+ cboxActive = i >= 0 && i < cboxShown.length ? i : -1;
5897
+ cboxShown.forEach(function (it, j) { it.li.setAttribute('aria-selected', String(j === cboxActive)); });
5898
+ if (cboxActive === -1) { input.removeAttribute('aria-activedescendant'); return; }
5899
+ var li = cboxShown[cboxActive].li;
5900
+ input.setAttribute('aria-activedescendant', 'physCountryOpt-' + cboxShown[cboxActive].code);
5901
+ if (li.offsetTop < list.scrollTop) list.scrollTop = li.offsetTop;
5902
+ else if (li.offsetTop + li.offsetHeight > list.scrollTop + list.clientHeight) list.scrollTop = li.offsetTop + li.offsetHeight - list.clientHeight;
5903
+ }
5904
+ /* q empty: the whole list, on the country already chosen */
5905
+ function cboxOpen(q) {
5906
+ cboxBuild();
5907
+ var list = $('physCountryList'), cur = phys().answers.home_country || '';
5908
+ var items = cboxMatches(q);
5909
+ cboxItems.forEach(function (it) { it.li.hidden = true; it.li.removeAttribute('data-cur'); it.li.setAttribute('aria-selected', 'false'); });
5910
+ items.forEach(function (it) {
5911
+ it.li.hidden = false;
5912
+ if (it.code === cur) it.li.setAttribute('data-cur', '');
5913
+ list.removeChild(it.li); list.appendChild(it.li);
5914
+ });
5915
+ $('physCountryNone').hidden = items.length > 0;
5916
+ cboxShown = items;
5917
+ list.hidden = false;
5918
+ $('physCountryIn').setAttribute('aria-expanded', 'true');
5919
+ var at = -1;
5920
+ if (q) at = items.length ? 0 : -1;
5921
+ else items.forEach(function (it, j) { if (it.code === cur) at = j; });
5922
+ if (!q) list.scrollTop = 0;
5923
+ cboxSetActive(at);
5924
+ }
5925
+ function cboxClose() {
5926
+ $('physCountryList').hidden = true;
5927
+ $('physCountryIn').setAttribute('aria-expanded', 'false');
5928
+ $('physCountryIn').removeAttribute('aria-activedescendant');
5929
+ cboxActive = -1;
5930
+ }
5931
+ function cboxIsOpen() { return !$('physCountryList').hidden; }
5932
+ /* the field says the chosen country's name, never what was half typed */
5933
+ function cboxPaint() {
5934
+ var code = phys().answers.home_country || '', name = '';
5935
+ if (code) {
5936
+ cboxBuild();
5937
+ cboxItems.forEach(function (it) { if (it.code === code) name = it.name; });
5938
+ }
5939
+ $('physCountryIn').value = name || (code ? physCountryName(code) : '');
5940
+ }
5941
+ function cboxPick(it) {
5942
+ cboxClose();
5943
+ $('physCountry').value = it.code;
5944
+ physCountryChanged();
5945
+ cboxPaint();
5946
+ }
5947
+ $('physCountry').hidden = true;
5948
+ $('physCountryBox').hidden = false;
5949
+ $('physCountryIn').addEventListener('input', function () { cboxOpen($('physCountryIn').value); });
5950
+ $('physCountryIn').addEventListener('focus', function () { if ($('physCountryIn').select) $('physCountryIn').select(); });
5951
+ $('physCountryIn').addEventListener('click', function () {
5952
+ if (cboxIsOpen()) return;
5953
+ if ($('physCountryIn').select) $('physCountryIn').select();
5954
+ cboxOpen('');
5955
+ });
5956
+ $('physCountryIn').addEventListener('keydown', function (ev) {
5957
+ var k = ev.key, open = cboxIsOpen();
5958
+ if (k === 'ArrowDown' || k === 'ArrowUp') {
5959
+ ev.preventDefault();
5960
+ if (!open) { cboxOpen(''); return; }
5961
+ if (!cboxShown.length) return;
5962
+ if (k === 'ArrowDown') cboxSetActive(cboxActive === -1 ? 0 : Math.min(cboxActive + 1, cboxShown.length - 1));
5963
+ else cboxSetActive(cboxActive === -1 ? cboxShown.length - 1 : Math.max(cboxActive - 1, 0));
5964
+ } else if (k === 'Enter') {
5965
+ if (!open) return;
5966
+ ev.preventDefault();
5967
+ if (cboxActive !== -1) cboxPick(cboxShown[cboxActive]);
5968
+ } else if (k === 'Escape') {
5969
+ if (!open) return;
5970
+ ev.preventDefault();
5971
+ if (ev.stopPropagation) ev.stopPropagation();
5972
+ cboxClose();
5973
+ cboxPaint();
5974
+ }
5975
+ });
5976
+ /* leaving the field keeps the chosen country, unless a whole name was typed */
5977
+ $('physCountryIn').addEventListener('blur', function () {
5978
+ if (!cboxIsOpen()) { cboxPaint(); return; }
5979
+ var f = cboxFold($('physCountryIn').value), exact = null;
5980
+ cboxItems.forEach(function (it) { if (f && (it.fold === f || it.code.toLowerCase() === f)) exact = it; });
5981
+ cboxClose();
5982
+ if (exact && exact.code !== (phys().answers.home_country || '')) cboxPick(exact);
5983
+ else cboxPaint();
5984
+ });
5985
+ /* a press on an option keeps the focus in the field; the click picks */
5986
+ $('physCountryList').addEventListener('mousedown', function (ev) { ev.preventDefault(); });
5987
+ $('physCountryList').addEventListener('click', function (ev) {
5988
+ var li = ev.target && ev.target.closest ? ev.target.closest('[data-code]') : null;
5989
+ if (!li) return;
5990
+ var code = li.getAttribute('data-code');
5991
+ cboxItems.forEach(function (it) { if (it.code === code) cboxPick(it); });
5992
+ });
5831
5993
 
5832
5994
  /* The soft-limit table's verdicts (spec D6): an option it hides is hidden and
5833
5995
  one it greys is disabled, each with its reason beside it. */
@@ -5976,7 +6138,7 @@ summary{min-height:24px;padding:3px 0;}
5976
6138
  $('physStartOver').addEventListener('click', function () {
5977
6139
  checksShow('main');
5978
6140
  paintChecks();
5979
- $('physCountry').focus();
6141
+ $('physCountryIn').focus();
5980
6142
  });
5981
6143
  root.querySelectorAll('#view-new [data-phys]').forEach(function (b) {
5982
6144
  b.addEventListener('click', function () {
@@ -6058,12 +6220,13 @@ summary{min-height:24px;padding:3px 0;}
6058
6220
  root.querySelectorAll('#physExisting input[name="physMain"]').forEach(function (r) {
6059
6221
  r.addEventListener('change', function () { if (r.checked) { phys().answers.main_work = r.value; saveDraft(); paintSetup(); } });
6060
6222
  });
6061
- $('physCountry').addEventListener('change', function () {
6223
+ function physCountryChanged() {
6062
6224
  var a = phys().answers;
6063
6225
  a.home_country = $('physCountry').value || null;
6064
6226
  if (a.home_region && a.home_region.indexOf((a.home_country || '') + '-') !== 0) a.home_region = null;
6065
6227
  physHint(''); saveDraft(); paintChecks(); paintCreate(); physRecheck();
6066
- });
6228
+ }
6229
+ $('physCountry').addEventListener('change', physCountryChanged);
6067
6230
  $('physRegion').addEventListener('change', function () { phys().answers.home_region = $('physRegion').value || null; saveDraft(); });
6068
6231
  $('physHaveEntity').addEventListener('click', function () { phys().entityForms = true; saveDraft(); paintChecks(); });
6069
6232
  $('physNoEntity').addEventListener('click', function () {
@@ -166,16 +166,16 @@
166
166
  "steps": [["wait",600],["click","#wizDemo"],["wait",300],["click","#demoCancel"],["wait",300],["fill","#newName","Mercury"],["wait",100]],
167
167
  "expect": {"visible":["#part-project","#part-run","#doCreate"],"hidden":["#demoSetup","#demoTag"],"attr":[["#wizDemo","aria-expanded","false"],["#doCreate","disabled",""]],"text":[["#createWhy","Tick the terms to create your project."]]} },
168
168
  { "name": "the quick checks (task 1004648): a country with states asks for one, a yes to under-18s shows the age tiers, and an existing entity opens the full list — in place, in part 01", "auth": true, "width": 1440, "url": "/projects?view=new",
169
- "steps": [["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["select","#physCountry","US"],["wait",100],["select","#physRegion","US-CA"],["click","[data-phys=\"builder_geography\"][data-val=\"us-eu\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"true\"]"],["wait",200],["click","#physHaveEntity"],["wait",100],["click","[data-phys=\"entity\"][data-val=\"for-profit\"]"],["wait",300]],
169
+ "steps": [["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["wait",100],["select","#physRegion","US-CA"],["click","[data-phys=\"builder_geography\"][data-val=\"us-eu\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"true\"]"],["wait",200],["click","#physHaveEntity"],["wait",100],["click","[data-phys=\"entity\"][data-val=\"for-profit\"]"],["wait",300]],
170
170
  "expect": {"visible":["#physRegionField","#physAges","#physForms","#part-run"],"hidden":["#physIntent","#checksStop"]} },
171
171
  { "name": "part 02 (task 1004648): Change opens a row in place and reads Done, opening another closes it, and nothing needs confirming", "auth": true, "width": 1440, "url": "/projects?view=new",
172
- "steps": [["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-chg]"],["wait",200],["click","[data-phys-solo=\"many\"]"],["wait",600],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-chg]"],["wait",300]],
172
+ "steps": [["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-chg]"],["wait",200],["click","[data-phys-solo=\"many\"]"],["wait",600],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-chg]"],["wait",300]],
173
173
  "expect": {"visible":["#reviewRows .setRow[data-row=\"rewards\"]","#reviewRows .setRow.open[data-row=\"run\"] .opts"],"hidden":["#reviewRows .setRow[data-row=\"team\"] .opts"],"text":[["#reviewRows .setRow[data-row=\"run\"] [data-row-chg]","Done"],["#reviewRows .setRow[data-row=\"team\"] [data-row-chg]","Change"]]} },
174
174
  { "name": "the wizard driver (task 1004648): part 01, the checks, the address row (the dotless stub host takes a domain the founder owns), the tick, then Create — part 03 goes live as Getting <name> ready: one sentence, the checked facts, and the cloud hop behind its link", "auth": true, "width": 1440, "url": "/projects?view=new",
175
- "steps": [["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["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-chg]"],["wait",200],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",1500]],
175
+ "steps": [["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["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-chg]"],["wait",200],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",1500]],
176
176
  "expect": {"visible":["#panel-done","#setupNow","#homeFacts","#ghFacts","#hopLink"],"hidden":["#wizParts","#hop","#doneAlive"],"text":[["#donePartT","Getting Mercury ready"]]} },
177
177
  { "name": "the wizard driver at 320 (narrower than a 375 phone): the page stacks, nothing scrolls sideways, and the walk still reaches part 03", "auth": true, "width": 320, "url": "/projects?view=new",
178
- "steps": [["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["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-chg]"],["wait",200],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",1500]],
178
+ "steps": [["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["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-chg]"],["wait",200],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",1500]],
179
179
  "expect": {"noOverflow":true,"visible":["#panel-done","#homeFacts","#ghFacts"],"hidden":["#wizParts"]} },
180
180
  { "name": "invite a guild (task 1002303, R27): a /g/ address gives one pending line per member on its public page, each a link to the hall", "auth": true, "width": 1440, "url": "/projects?view=manage&id=101",
181
181
  "steps": [["wait",900],["fill","#mInviteGuild","/g/forge-crew"],["click","#mInvite [data-invite-guild]"],["wait",400]],
@@ -43,21 +43,21 @@
43
43
  "in-new-name-prefilled": {"auth":true,"url":"/projects?view=new&name=Mercury","actions":[["wait",600]],"expect":{"visible":["#panel-page","#newName"]}},
44
44
  "in-new-demo": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#wizDemo"],["wait",300]],"expect":{"visible":["#panel-page","#demoSetup","#demoFitVerdict","#demoInvites","#demoStart","#demoCancel"],"attr":[["#wizDemo","aria-expanded","true"]]}},
45
45
  "in-new-demo-used": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#wizDemo"],["wait",300],["fill","#demoIdea","A voting app for movie night"],["click","#demoStart"],["wait",400]],"expect":{"visible":["#demoTag","#newName","#part-run"],"hidden":["#demoSetup","#checksMain"],"text":[["#wizEyebrow","New demo"]]}},
46
- "in-new-checks-filled": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["select","#physCountry","US"],["wait",100],["select","#physRegion","US-CA"],["click","[data-phys=\"builder_geography\"][data-val=\"us-eu\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"true\"]"],["wait",300]],"expect":{"visible":["#physRegionField","#physAges"],"hidden":["#checksStop"]}},
46
+ "in-new-checks-filled": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["wait",100],["select","#physRegion","US-CA"],["click","[data-phys=\"builder_geography\"][data-val=\"us-eu\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"true\"]"],["wait",300]],"expect":{"visible":["#physRegionField","#physAges"],"hidden":["#checksStop"]}},
47
47
  "in-new-checks-entity": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#physHaveEntity"],["wait",200],["click","[data-phys=\"entity\"][data-val=\"for-profit\"]"],["wait",100]],"expect":{"visible":["#physForms","#physNoEntity"],"hidden":["#physIntent"]}},
48
- "in-new-row-code": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"code\"] .opts","#c-green","#c-adopt"]}},
49
- "in-new-row-address": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"address\"] .opts","#f-slug","#addrConfirm","#f-noaddr"]}},
50
- "in-new-row-team": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-chg]"],["wait",400],["click","[data-phys-solo=\"many\"]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"team\"] .opts","[data-phys-solo=\"many\"]"]}},
51
- "in-new-row-extras": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"extras\"] .opts","#modCards"]}},
52
- "in-new-row-rewards": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-chg]"],["wait",200],["click","[data-phys-solo=\"many\"]"],["wait",600],["click","#reviewRows .setRow[data-row=\"rewards\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"rewards\"] .opts","[data-phys=\"reward_intents\"][data-val=\"cash\"]"]}},
53
- "in-new-row-app": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"app\"] .opts","#appOpts"]}},
54
- "in-new-row-run": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-chg]"],["wait",400],["click","[data-phys=\"framing\"][data-val=\"hybrid\"]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"run\"] .opts","#physSuite"]}},
55
- "in-new-row-ship": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"ship\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"ship\"] .opts","#shipOpts"]}},
56
- "in-new-look": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"look\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"look\"] .opts","#wizLook"]}},
57
- "in-new-look-custom": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"look\"] [data-row-chg]"],["wait",400],["click","#wizLook-custom"],["wait",200]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"look\"] .opts","#wizLook","#wizAccent","#wizAccentNote"]}},
48
+ "in-new-row-code": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"code\"] .opts","#c-green","#c-adopt"]}},
49
+ "in-new-row-address": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"address\"] .opts","#f-slug","#addrConfirm","#f-noaddr"]}},
50
+ "in-new-row-team": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-chg]"],["wait",400],["click","[data-phys-solo=\"many\"]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"team\"] .opts","[data-phys-solo=\"many\"]"]}},
51
+ "in-new-row-extras": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"extras\"] .opts","#modCards"]}},
52
+ "in-new-row-rewards": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-chg]"],["wait",200],["click","[data-phys-solo=\"many\"]"],["wait",600],["click","#reviewRows .setRow[data-row=\"rewards\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"rewards\"] .opts","[data-phys=\"reward_intents\"][data-val=\"cash\"]"]}},
53
+ "in-new-row-app": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"app\"] .opts","#appOpts"]}},
54
+ "in-new-row-run": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-chg]"],["wait",400],["click","[data-phys=\"framing\"][data-val=\"hybrid\"]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"run\"] .opts","#physSuite"]}},
55
+ "in-new-row-ship": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"ship\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"ship\"] .opts","#shipOpts"]}},
56
+ "in-new-look": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"look\"] [data-row-chg]"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"look\"] .opts","#wizLook"]}},
57
+ "in-new-look-custom": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["click","#reviewRows .setRow[data-row=\"look\"] [data-row-chg]"],["wait",400],["click","#wizLook-custom"],["wait",200]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"look\"] .opts","#wizLook","#wizAccent","#wizAccentNote"]}},
58
58
  "in-new-create-blocked": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",400]],"expect":{"visible":["#reviewRows .setRow.open[data-row=\"address\"] .opts","#h-domain"],"hidden":["#doneBody"],"text":[["#createWhy","Fix the row that’s open, then create."]]}},
59
- "in-new-done": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["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-chg]"],["wait",200],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",1500]],"expect":{"visible":["#panel-done","#doneBody","#setupNow","#homeFacts","#ghFacts","#hopLink","#wizRecap"],"hidden":["#wizParts","#qWrap","#wizInvite","#hop","#doneAlive"],"attr":[["#doCreate","disabled",""],["#wizRoom","data-screen","done"]],"text":[["#donePartT","Getting Mercury ready"],["#doCreate","Created"]]}},
60
- "in-new-resume-offer": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["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-chg]"],["wait",200],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",1500],["click","#projTrig"],["wait",350],["click","#menuProjects [data-nav=\"new\"]"],["wait",500]],"expect":{"visible":["#panel-page","#wizResume","#wizResumeGo","#wizParts"],"hidden":["#doneBody"],"text":[["#wizResumeName","Mercury"]]}},
59
+ "in-new-done": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["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-chg]"],["wait",200],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",1500]],"expect":{"visible":["#panel-done","#doneBody","#setupNow","#homeFacts","#ghFacts","#hopLink","#wizRecap"],"hidden":["#wizParts","#qWrap","#wizInvite","#hop","#doneAlive"],"attr":[["#doCreate","disabled",""],["#wizRoom","data-screen","done"]],"text":[["#donePartT","Getting Mercury ready"],["#doCreate","Created"]]}},
60
+ "in-new-resume-offer": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["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\"]"],["wait",300],["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-chg]"],["wait",200],["click","#termsAck"],["wait",100],["click","#doCreate"],["wait",1500],["click","#projTrig"],["wait",350],["click","#menuProjects [data-nav=\"new\"]"],["wait",500]],"expect":{"visible":["#panel-page","#wizResume","#wizResumeGo","#wizParts"],"hidden":["#doneBody"],"text":[["#wizResumeName","Mercury"]]}},
61
61
  "out-new": {"auth":false,"url":"/projects?view=new","expect":{"visible":["#panel-gate","#wizSignin"],"hidden":["#qWrap","#panel-page"]}},
62
62
  "out-map-projects": { "auth": false, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "hidden": ["#menuProjects [data-nav=\"mine\"]"] } },
63
63
  "card": { "auth": true, "actions": [["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://example.com\"]"], ["wait", 900]], "expect": { "dialog": true, "hasClass": [["#projCard", "has-art"]], "visible": ["#cardArt", "#cardFaces", "#cardBuildersFig", "#cardShippedFig", "#cardOpen", "#cardHall"], "text": [["#cardBuilders", "11"], ["#cardShipped", "0"], ["#cardRel", "you build here"], ["#cardRuns", "example.com"], ["#cardFaces .facesMore", "+6"]], "count": [["#cardFaces img", 5]], "href": [["#cardHall", "https://builders.example.com/"]] } },
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.114",
3
+ "version": "1.21.116",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.114",
9
+ "version": "1.21.116",
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.114",
3
+ "version": "1.21.116",
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",
@@ -9658,5 +9658,17 @@
9658
9658
  "id": "1004701",
9659
9659
  "text": "The living city is now the hall's base: the city, the street with depth, the decision deck, the government inside each building, and My agents. On a real project it shows only what the real data supports: no invented Leads, la"
9660
9660
  }
9661
+ ],
9662
+ "1.21.115": [
9663
+ {
9664
+ "id": "1004671",
9665
+ "text": "When you start a new project, \"Where are you?\" now starts on United States and is a search box instead of a long dropdown. Type a few letters and the list narrows to matching countries; capitals and accents don't matter, so \"c"
9666
+ }
9667
+ ],
9668
+ "1.21.116": [
9669
+ {
9670
+ "id": "1004734",
9671
+ "text": "When a project's update fails while updating its database, the deploy page and the auto-filed blocker now say which database change failed and why, so nobody has to log into the machine to find out."
9672
+ }
9661
9673
  ]
9662
9674
  }
@@ -27,7 +27,7 @@
27
27
 
28
28
  const fs = require('node:fs');
29
29
  const path = require('node:path');
30
- const { spawnSync } = require('node:child_process');
30
+ const { spawn, spawnSync } = require('node:child_process');
31
31
 
32
32
  // Relative to the instance root, POSIX on purpose: it is an argument to bash, and it
33
33
  // must stay byte-identical to the scaffold's script (tests/upgrade_migrate.mjs pins it
@@ -161,4 +161,64 @@ function preflightDbIdentity({ instanceDir, db, account = null }, run = spawnSyn
161
161
  };
162
162
  }
163
163
 
164
- module.exports = { migratePlan, preflightDbIdentity, asAccount, ACCOUNT_RE, CORE_MIGRATE_SCRIPT };
164
+ // ── what a FAILED migrate says, so nobody needs SSH to read it (task 1004734) ────────────
165
+ //
166
+ // Seen 2026-10-14: toast's move 1.21.20 → 1.21.115 rolled back and the hub kept exactly
167
+ // "migrate failed (exit 3)". Exit 3 is psql under ON_ERROR_STOP: ONE statement failed on
168
+ // that project's real data. Which file, and which error, reached only the box's journal,
169
+ // because runMigrate spawned with stdio 'inherit'. migrate.sh prints `apply <version>`
170
+ // before each file, and psql -f prefixes its ERROR with the file too, so the output
171
+ // already names both; it just never came back.
172
+ //
173
+ // teeSpawn: the child's stdout/stderr are piped and WRITTEN THROUGH as they arrive — the
174
+ // journal still reads live, exactly as under 'inherit' — while the last TEE_TAIL chars are
175
+ // kept for migrateFailureDetail. Async, because a sync spawn cannot write through.
176
+ const TEE_TAIL = 64 * 1024;
177
+ function teeSpawn(cmd, args, opts = {}, { spawnImpl = spawn, out = process.stdout, err = process.stderr } = {}) {
178
+ return new Promise((resolve) => {
179
+ let output = '';
180
+ const keep = (c) => { output = (output + c).slice(-TEE_TAIL); };
181
+ let child;
182
+ try { child = spawnImpl(cmd, args, { ...opts, stdio: ['inherit', 'pipe', 'pipe'] }); } catch (e) { resolve({ error: e }); return; }
183
+ for (const [pipe, sink] of [[child.stdout, out], [child.stderr, err]]) {
184
+ pipe.setEncoding('utf8');
185
+ pipe.on('data', (c) => { sink.write(c); keep(c); });
186
+ }
187
+ child.on('error', (e) => resolve({ error: e }));
188
+ child.on('close', (status) => resolve({ status, output }));
189
+ });
190
+ }
191
+
192
+ // The failing migration and psql's ERROR line, as " at <file>: ERROR: …" — or '' when the
193
+ // output names neither. Secret-scrubbed (the line is stored on the hub and shown to the
194
+ // owner) and capped. The LAST `apply` line is the file it stopped in: migrate.sh stops at
195
+ // the first failure. PURE.
196
+ const DETAIL_MAX = 300;
197
+ function migrateFailureDetail(text) {
198
+ const lines = String(text || '').split(/\r?\n/);
199
+ const last = (re) => { for (let i = lines.length - 1; i >= 0; i--) { const m = re.exec(lines[i]); if (m) return m; } return null; };
200
+ const err = last(/\b((?:ERROR|FATAL):\s+.*)$/);
201
+ const file = (last(/^apply\s+(\S+)\s*$/) || (err && /^psql:(?:.*[\\/])?([^\\/:]+?)(?:\.sql)?:\d+:/.exec(err.input)) || [])[1] || '';
202
+ let msg = err ? err[1].trim() : '';
203
+ if (msg) {
204
+ // No scrubber, no error text: a credential in a psql line must never reach the hub.
205
+ try { msg = require('../../modules/security/secret-scrub.js').scrubSecrets(msg); } catch { msg = ''; }
206
+ }
207
+ const cap = (s, n) => (s.length > n ? `${s.slice(0, n - 1)}…` : s);
208
+ return (file ? ` at ${cap(file, 80)}` : '') + (msg ? `: ${cap(msg, DETAIL_MAX)}` : '');
209
+ }
210
+
211
+ /** runMigrate's spawn result → { ok, code, detail } | { ok:false, reason }. PURE. */
212
+ function migrateResult(r) {
213
+ if (!r || r.error) return { ok: false, reason: r && r.error ? r.error.message : 'spawn failed' };
214
+ if (r.status === 0) return { ok: true, code: 0 };
215
+ // r.output from teeSpawn; stdout/stderr from a spawnSync-shaped run (tests, any injected run)
216
+ return { ok: false, code: r.status, detail: migrateFailureDetail([r.output, r.stdout, r.stderr].filter(Boolean).join('\n')) };
217
+ }
218
+
219
+ /** The sentence runUpgrade fails with; its "migrate failed" prefix is what upgrade-outcome.js keys on. PURE. */
220
+ function migrateError(mig) {
221
+ return `migrate failed (${mig.reason || `exit ${mig.code}`})${mig.detail || ''}`;
222
+ }
223
+
224
+ module.exports = { migratePlan, preflightDbIdentity, asAccount, ACCOUNT_RE, CORE_MIGRATE_SCRIPT, teeSpawn, migrateFailureDetail, migrateResult, migrateError };
@@ -63,6 +63,9 @@ const REASON_CLASS = Object.freeze({
63
63
  npm_install: 'transient',
64
64
  integrity_pin: 'unknown',
65
65
  migrate: 'unknown',
66
+ // a migration's CHECK constraint is broken by rows the project already has (task 1004734): a
67
+ // retry fails identically and no remedy may rewrite the owner's data, so a person decides.
68
+ migrate_check_violation: 'unknown',
66
69
  restart: 'unknown',
67
70
  health: 'unknown',
68
71
  // the runner's one automatic fix did not clear it, or itself failed (task 1004448)
@@ -86,6 +89,7 @@ const UPGRADE_REASONS = [
86
89
  [/^(--registry and --from|--from tarball not found|no core tarball at)/, 'core_source'],
87
90
  [/^npm install failed/, 'npm_install'],
88
91
  [/^integrity pin/, 'integrity_pin'],
92
+ [/^migrate failed .*\bERROR: .*check constraint/, 'migrate_check_violation'], // upgrade-migrate.js migrateError (task 1004734)
89
93
  [/^migrate failed/, 'migrate'],
90
94
  [/^restart failed/, 'restart'],
91
95
  [/^(health check failed after restart|served core version did not change)/, 'health'],
@@ -33,7 +33,7 @@ const { parseSemver, compareSemver, isStable, listAvailableVersions } = require(
33
33
  const { checkArtistGate } = require('./upgrade-artist-gate');
34
34
  // The database side of a move: which command migrates, as which account, and whether that
35
35
  // account can reach the database (tasks 1004274, 1002712, 1004512).
36
- const { preflightDbIdentity, asAccount, ACCOUNT_RE } = require('./upgrade-migrate');
36
+ const { preflightDbIdentity, asAccount, ACCOUNT_RE, teeSpawn, migrateResult, migrateError } = require('./upgrade-migrate');
37
37
 
38
38
  const CORE_PKG = '@bongos/core';
39
39
  const ARTIFACT = 'bongos-core';
@@ -519,13 +519,13 @@ function npmInstall(instanceDir, run = spawnSync) {
519
519
  return !r || r.error ? { ok: false, reason: r && r.error ? r.error.message : 'spawn failed' } : { ok: r.status === 0, code: r.status };
520
520
  }
521
521
 
522
- function runMigrate(instanceDir, run = spawnSync, account = null) {
522
+ async function runMigrate(instanceDir, run = spawnSync, account = null) {
523
523
  const { cmd, args, env } = require('./upgrade-migrate').migratePlan(instanceDir); // its own migrate script, else the core's migrate.sh (task 1004274)
524
524
  // `account`: the project's own unix+Postgres identity when the move itself is running as
525
525
  // the account that owns the CHECKOUT instead (--migrate-as; upgrade-migrate.js, task 1004512).
526
526
  const q = asAccount(account, cmd, args, env ? { INIT_CWD: instanceDir } : {});
527
- const r = run(q.cmd, q.args, { cwd: instanceDir, stdio: 'inherit', ...(env ? { env } : {}) });
528
- return !r || r.error ? { ok: false, reason: r && r.error ? r.error.message : 'spawn failed' } : { ok: r.status === 0, code: r.status };
527
+ // TEED, not inherited: still streams live, and a failure names its migration + psql's ERROR line (upgrade-migrate.js, task 1004734)
528
+ return migrateResult(await (run === spawnSync ? teeSpawn : run)(q.cmd, q.args, { cwd: instanceDir, ...(env ? { env } : {}) }));
529
529
  }
530
530
 
531
531
  // WHICH database will migrate target? (task 1002755)
@@ -1224,9 +1224,9 @@ async function runUpgrade(opts, deps = {}) {
1224
1224
 
1225
1225
  // 4. migrate (additive core_* migrations).
1226
1226
  if (!opts.skipMigrate) {
1227
- const mig = runMigrate(instanceDir, run, opts.migrateAs || null);
1227
+ const mig = await runMigrate(instanceDir, run, opts.migrateAs || null);
1228
1228
  if (!mig.ok) {
1229
- const error = `migrate failed (${mig.reason || 'exit ' + mig.code})`;
1229
+ const error = migrateError(mig); // names the failing migration + its ERROR line (task 1004734)
1230
1230
  if (rollbackOnFailure && snapshot) {
1231
1231
  const rb = await doRollback('migrate failed');
1232
1232
  return { ok: false, error, fromVersion, toVersion: targetVersion, rolledBack: true, rollback: rb };
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.114'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.116'; // 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');
@@ -1551,6 +1551,21 @@ await t('a real rollback says rolled back', async () => {
1551
1551
  assert.match(r.error, /failed and was rolled back/);
1552
1552
  });
1553
1553
 
1554
+ await t('a failed migrate reaches the stored failure with its migration and psql\'s ERROR line (task 1004734)', async () => {
1555
+ // upgrade.js's REAL migrate result → its real error sentence → its real outcome line, in the
1556
+ // frame its main() writes to stderr (pinned by "upgrade.js ends a failed run…" below).
1557
+ const run = () => ({ status: 3, stdout: 'apply core_998_ok\napply core_999_x\n',
1558
+ stderr: 'psql:migrations/core_999_x.sql:7: ERROR: check constraint "tasks_kind_check" of relation "tasks" is violated by some row\n' });
1559
+ const mig = await U.runMigrate(fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'mig-')), run);
1560
+ const res = { ok: false, error: require('../scripts/gds/upgrade-migrate.js').migrateError(mig), rolledBack: true, rollback: {} };
1561
+ const stderr = `\n✖ ${res.error}\n${require('../scripts/gds/upgrade-outcome.js').outcomeLine(res)}`;
1562
+ const r = await R.coreUpgradeInstance(inst(), upgradeThrows(stderr)(makeDeps()), intent());
1563
+ assert.deepEqual(r.failure, { class: 'unknown', reason: 'migrate_check_violation' });
1564
+ assert.match(r.error, /failed and was rolled back/);
1565
+ assert.match(r.error, /core_999_x/, 'the stored sentence names the migration');
1566
+ assert.match(r.error, /ERROR: +check constraint "tasks_kind_check" of relation "tasks" is violated by some row/, 'and psql\'s own error');
1567
+ });
1568
+
1554
1569
  await t('a FAILED rollback is its own outcome, and terminal — never retried unattended', async () => {
1555
1570
  const d = upgradeThrows('\n✖ health check failed after restart\nBONGOS_UPGRADE_OUTCOME {"stage":"rollback_failed","reason":"rollback_failed"}\n')(makeDeps());
1556
1571
  const r = await R.coreUpgradeInstance(inst(), d, intent());