@bongos/core 1.21.30 → 1.21.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.bongos-core.json +47 -37
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +4 -0
  4. package/clients/bongos-client/index.cjs +4 -0
  5. package/clients/bongos-client/index.d.ts +7 -0
  6. package/clients/bongos-client/index.mjs +4 -0
  7. package/docs/api/openapi.json +125 -3
  8. package/docs/api-reference.md +4 -2
  9. package/docs/copy-inventory.md +393 -408
  10. package/docs/copy-registry.json +498 -663
  11. package/docs/module-api-changelog.md +4 -0
  12. package/docs/page-readings.json +655 -665
  13. package/modules/government/catalog.js +1 -0
  14. package/modules/government/migrations/government_024_version_focus_set.sql +19 -0
  15. package/modules/hall-ui/public/roadmap.css +5 -0
  16. package/modules/hall-ui/public/roadmap.js +64 -5
  17. package/modules/lifecycle/routes/versions.js +20 -0
  18. package/modules/public-landing/public/projects-hall-live.states.json +1 -41
  19. package/modules/public-landing/public/projects.html +109 -72
  20. package/modules/public-landing/public/projects.probes.json +5 -5
  21. package/modules/public-landing/public/projects.states.json +7 -7
  22. package/package-lock.json +2 -2
  23. package/package.json +1 -1
  24. package/release-notes.json +12 -0
  25. package/src/bongos/project-settings.js +28 -0
  26. package/src/module-api.js +3 -1
  27. package/tests/focus_version.mjs +96 -0
  28. package/tests/projects_hub.mjs +7 -4
  29. package/tests/projects_hub_app_step.mjs +4 -3
  30. package/tests/projects_hub_module_picker.mjs +12 -10
  31. package/tests/projects_hub_pre_uat.mjs +1 -1
  32. package/tests/wizard_physics_more.mjs +58 -34
  33. package/tests/wizard_six_screens.mjs +10 -8
@@ -164,17 +164,17 @@
164
164
  { "name": "the quick checks (task 1004504): a country with states asks for one, a yes to under-18s shows the age tiers, an existing entity opens the full list, and Continue reaches the review", "auth": true, "width": 1440, "url": "/projects?view=new",
165
165
  "steps": [["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-what button[type=submit]"],["wait",400],["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\"]"],["click","#physNext"],["wait",700]],
166
166
  "expect": {"visible":["#panel-review","#reviewRows .setRow[data-row=\"code\"]"],"hidden":["#panel-checks"]} },
167
- { "name": "the review is locked (task 1004504): Continue reads Confirm N more until every row is confirmed; Change opens a row in place and Use this confirms it", "auth": true, "width": 1440, "url": "/projects?view=new",
168
- "steps": [["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["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=\"team\"] [data-row-chg]"],["wait",200],["click","[data-phys-solo=\"many\"]"],["wait",600],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-use]"],["wait",200]],
169
- "expect": {"visible":["#panel-review","#reviewRows .setRow[data-row=\"rewards\"]","#reviewRows .setRow.done[data-row=\"team\"]"],"attr":[["#reviewGo","disabled",""]]} },
167
+ { "name": "the review (tasks 1004504, 1004550): Edit opens a row in place, Done confirms it, and Continue is the one confirmation for the rest", "auth": true, "width": 1440, "url": "/projects?view=new",
168
+ "steps": [["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["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=\"team\"] [data-row-chg]"],["wait",200],["click","[data-phys-solo=\"many\"]"],["wait",600],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-chg]"],["wait",200]],
169
+ "expect": {"visible":["#panel-review","#reviewRows .setRow[data-row=\"rewards\"]","#reviewRows .setRow.done[data-row=\"team\"]"]} },
170
170
  { "name": "the wizard driver (tasks 1003346 and 1004504): the fork, what, the checks, every review row confirmed (the stub grants repo access, so the new repository is named from the project; the dotless stub host takes a domain the founder owns), the look, then the filing — the done panel with the standup strip and the human steps", "auth": true, "width": 1440, "url": "/projects?view=new",
171
- "steps": [["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["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]],
171
+ "steps": [["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["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=\"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","#reviewGo"],["wait",500],["click","#doCreate"],["wait",1500]],
172
172
  "expect": {"visible":["#panel-done","#liveProgress .prog","#liveProgress .prog .stages li.now","#humanSteps li"],"hidden":["#panel-look","#panel-review"],"text":[["#done-h","Setting up Mercury"]]} },
173
173
  { "name": "the front door at 390: the fork's two doors stack inside one pill", "auth": true, "width": 390, "url": "/projects?view=new",
174
174
  "steps": [["wait",600],["click","#termsAck"],["wait",100]],
175
175
  "expect": {"visible":["#panel-fork",".doorPill","#wizDemo","#wizStart"]} },
176
176
  { "name": "the wizard driver at 390: the review rows stack and the walk still reaches Create", "auth": true, "width": 390, "url": "/projects?view=new",
177
- "steps": [["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["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]],
177
+ "steps": [["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["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=\"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","#reviewGo"],["wait",500]],
178
178
  "expect": {"visible":["#panel-look","#wizLook","#doCreate"],"hidden":["#panel-review"],"text":[["#look-h","Give it a look."]]} }
179
179
  ]
180
180
  }
@@ -41,24 +41,24 @@
41
41
  "in-new-terms-refused": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["click","#termsAck"],["wait",200]],"expect":{"visible":["#panel-fork"],"hidden":["#panel-what"],"attr":[["#wizStart","disabled",""],["#wizDemo","disabled",""]]}},
42
42
  "in-new-fork-demo": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizDemo"],["wait",300]],"expect":{"visible":["#panel-fork","#demoSetup","#demoFitVerdict","#demoInvites","#wizBack"],"hidden":["#forkIntro"],"attr":[["#wizDemo","aria-expanded","true"]]}},
43
43
  "in-new-demo-what": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizDemo"],["wait",300],["fill","#demoIdea","A voting app for movie night"],["click","#demoStart"],["wait",400]],"expect":{"visible":["#panel-what","#demoTag"],"hidden":["#panel-fork"],"text":[["#wizEyebrow","New demo"]]}},
44
- "in-new-demo-review-address": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizDemo"],["wait",300],["fill","#demoIdea","A voting app for movie night"],["click","#demoStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-what button[type=submit]"],["wait",700],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",300],["fill","#f-slug","mercury"],["wait",900]],"expect":{"visible":["#panel-review","#reviewRows .setRow[data-row=\"address\"] .opts","#f-slug","#addrConfirm","#f-noaddr"],"hidden":["#panel-checks"],"attr":[["#reviewGo","disabled",""]],"text":[["#wizEyebrow","New demo"]]}},
44
+ "in-new-demo-review-address": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizDemo"],["wait",300],["fill","#demoIdea","A voting app for movie night"],["click","#demoStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-what button[type=submit]"],["wait",700],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",300],["fill","#f-slug","mercury"],["wait",900]],"expect":{"visible":["#panel-review","#reviewRows .setRow[data-row=\"address\"] .opts","#f-slug","#addrConfirm","#f-noaddr"],"hidden":["#panel-checks"],"text":[["#wizEyebrow","New demo"]]}},
45
45
  "in-new-what": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400]],"expect":{"visible":["#panel-what","#newName","#typeCards","#f-desc","#wizBack"],"hidden":["#panel-fork","#demoTag"],"attr":[["#wizRoom","data-screen","what"]]}},
46
46
  "in-new-name-prefilled": {"auth":true,"url":"/projects?view=new&name=Mercury","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400]],"expect":{"visible":["#panel-what","#newName"]}},
47
47
  "in-new-checks": {"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]],"expect":{"visible":["#panel-checks","#physCountry","#physIntent"],"hidden":["#physAges","#physForms","#checksStop"],"attr":[["#wizRoom","data-screen","checks"]]}},
48
48
  "in-new-checks-filled": {"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],["select","#physRegion","US-CA"],["click","[data-phys=\"builder_geography\"][data-val=\"us-eu\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"true\"]"],["wait",200]],"expect":{"visible":["#physRegionField","#physAges"]}},
49
49
  "in-new-checks-entity": {"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],["click","#physHaveEntity"],["wait",200],["click","[data-phys=\"entity\"][data-val=\"for-profit\"]"],["wait",100]],"expect":{"visible":["#physForms","#physNoEntity"],"hidden":["#physIntent"]}},
50
- "in-new-review": {"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]],"expect":{"visible":["#panel-review","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]","#reviewGo"],"hidden":["#panel-checks","#reviewRows .setRow[data-row=\"rewards\"]"],"attr":[["#reviewGo","disabled",""],["#wizRoom","data-screen","review"]]}},
50
+ "in-new-review": {"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]],"expect":{"visible":["#panel-review","#reviewRows .setRow[data-row=\"code\"] [data-row-chg]","#reviewGo"],"hidden":["#panel-checks","#reviewRows .setRow[data-row=\"rewards\"]"],"text":[["#reviewGo","Looks right, continue"]],"attr":[["#wizRoom","data-screen","review"]]}},
51
51
  "in-new-review-code": {"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-chg]"],["wait",300],["click","#c-adopt"],["wait",400]],"expect":{"visible":["#reviewRows .setRow[data-row=\"code\"] .opts","#adoptNote","#ghAdopt"]}},
52
52
  "in-new-review-address": {"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=\"address\"] [data-row-chg]"],["wait",300],["fill","#f-slug","mercury"],["wait",900]],"expect":{"visible":["#f-slug","#addrConfirm","#f-noaddr"]}},
53
53
  "in-new-review-app": {"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=\"app\"] [data-row-chg]"],["wait",300],["click","#appOptRender input"],["wait",300]],"expect":{"visible":["#appOpts","#renderLater"]}},
54
54
  "in-new-review-extras": {"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=\"extras\"] [data-row-chg]"],["wait",900]],"expect":{"visible":["#modQ","#modCards .tpill","#modCoreNote"]}},
55
55
  "in-new-review-team": {"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=\"team\"] [data-row-chg]"],["wait",300],["click","[data-phys-solo=\"many\"]"],["wait",600]],"expect":{"visible":["#physTeam","#physGuessLine","#teamCards","#reviewRows .setRow[data-row=\"rewards\"]"]}},
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
- "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
- "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","#wizInviteLater"],"hidden":["#panel-look","#qWrap","#wizBack","#wizInvite"]}},
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
- "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"]}},
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=\"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]],"expect":{"visible":["#reviewGo","#reviewRows .setRow.done"],"text":[["#reviewGo","Looks right, continue"]]}},
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=\"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","#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=\"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","#reviewGo"],["wait",500],["click","#doCreate"],["wait",1500]],"expect":{"visible":["#panel-done","#liveProgress .prog","#humanSteps li","#wizInviteLater"],"hidden":["#panel-look","#qWrap","#wizBack","#wizInvite"]}},
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=\"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","#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
+ "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=\"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","#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"]}},
63
63
  "out-map-projects": { "auth": false, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "hidden": ["#menuProjects [data-nav=\"mine\"]"] } },
64
64
  "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.30",
3
+ "version": "1.21.32",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.30",
9
+ "version": "1.21.32",
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.30",
3
+ "version": "1.21.32",
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",
@@ -8936,5 +8936,17 @@
8936
8936
  "id": "1004493",
8937
8937
  "text": "The hall's sidebar now follows the mode you pick. Building shows the everyday tabs, Artistry shows Studio, Goals and Your thinking, Ideating shows the Sky, Your thinking and Goals, and Governing is the shortest list: Blockers,"
8938
8938
  }
8939
+ ],
8940
+ "1.21.31": [
8941
+ {
8942
+ "id": "1004550",
8943
+ "text": "Setting up a new project is quicker: the final check screen now takes one click to accept, with less text and no per-row confirm buttons."
8944
+ }
8945
+ ],
8946
+ "1.21.32": [
8947
+ {
8948
+ "id": "1004551",
8949
+ "text": "An Archon can star a version on the Roadmap as the project's focus; it persists across reloads, and everyone else sees the star read-only (the server refuses non-Archons)."
8950
+ }
8939
8951
  ]
8940
8952
  }
@@ -48,6 +48,7 @@ const { logger } = require('./logger');
48
48
  // cannot collide with this one.
49
49
  const SETTING_KEYS = {
50
50
  ROT_DAYS: 'attention.rot_days',
51
+ FOCUS_VERSION: 'roadmap.focus_version',
51
52
  };
52
53
 
53
54
  // The last-resort rot timer, in days. 30 because that is the shortest window in
@@ -176,6 +177,31 @@ function describeRotTimer(resolved) {
176
177
  return `${base} — ignoring ${r.invalid.where}="${r.invalid.raw}" (${r.invalid.reason})`;
177
178
  }
178
179
 
180
+ // getFocusVersionId() — the version the project is focused on, or null.
181
+ // NO ROW (or an empty value) MEANS NO FOCUS: the focus is chosen by an Archon in the
182
+ // UI (task 1004551), never inferred in code, so absence is an answer and a default is
183
+ // never seeded. Whether the id still names a real version is the caller's concern —
184
+ // the write path refuses unknown ids, and a reader of a since-deleted version simply
185
+ // finds no card to star.
186
+ async function getFocusVersionId() {
187
+ const row = await getSetting(SETTING_KEYS.FOCUS_VERSION);
188
+ return row && row.value ? String(row.value) : null;
189
+ }
190
+
191
+ // setFocusVersionId(versionId, builderId) — set the focus, or clear it with null.
192
+ // Clearing deletes the row rather than storing '' so "never set" and "cleared" are the
193
+ // same state, as the null-is-meaningful rule on getSetting requires.
194
+ async function setFocusVersionId(versionId, builderId = null) {
195
+ if (versionId == null || versionId === '') {
196
+ await pool.query('DELETE FROM project_settings WHERE key = $1', [SETTING_KEYS.FOCUS_VERSION]);
197
+ // The row is gone, so updated_by cannot say who cleared it; the log line does.
198
+ logger.info(`project focus version cleared by builder ${builderId == null ? 'unknown' : builderId}`);
199
+ return null;
200
+ }
201
+ await setSetting(SETTING_KEYS.FOCUS_VERSION, versionId, builderId);
202
+ return String(versionId);
203
+ }
204
+
179
205
  // parseDays is exported for the tests only — the range check and the default are
180
206
  // asserted against the SAME constants the resolver reads, because a test that
181
207
  // re-typed 30 or 365 would be the second copy of the number this file exists to
@@ -190,4 +216,6 @@ module.exports = {
190
216
  setSetting,
191
217
  resolveRotDays,
192
218
  describeRotTimer,
219
+ getFocusVersionId,
220
+ setFocusVersionId,
193
221
  };
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.30'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.32'; // 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');
@@ -444,6 +444,8 @@ module.exports = {
444
444
  get KEYS() { return require('./bongos/project-settings').SETTING_KEYS; },
445
445
  get get() { return require('./bongos/project-settings').getSetting; },
446
446
  get set() { return require('./bongos/project-settings').setSetting; },
447
+ get getFocusVersionId() { return require('./bongos/project-settings').getFocusVersionId; },
448
+ get setFocusVersionId() { return require('./bongos/project-settings').setFocusVersionId; },
447
449
  get describeRotTimer() { return require('./bongos/project-settings').describeRotTimer; },
448
450
  // The RANGE CHECK, shared rather than re-typed. The settings page validates a
449
451
  // submitted timer with the very parser the resolver reads it back through, so the
@@ -0,0 +1,96 @@
1
+ // tests/focus_version.mjs — the roadmap focus star (task 1004551): the settings accessor,
2
+ // the Archon-only write route and the any-builder read, run against a fake pool and fake
3
+ // requests. No DB needed.
4
+ //
5
+ // Run: node --test tests/focus_version.mjs
6
+
7
+ import assert from 'node:assert/strict';
8
+ import { test } from 'node:test';
9
+ import { readFileSync } from 'node:fs';
10
+ import { createRequire } from 'node:module';
11
+
12
+ process.env.NODE_ENV = 'test';
13
+ const require = createRequire(import.meta.url);
14
+
15
+ // A one-table fake pool, installed before project-settings loads.
16
+ const store = new Map();
17
+ const poolPath = require.resolve('../src/bongos/pool.js');
18
+ require(poolPath);
19
+ require.cache[poolPath].exports.pool = {
20
+ async query(sql, p) {
21
+ if (/^\s*SELECT/i.test(sql)) { const v = store.get(p[0]); return { rows: v == null ? [] : [{ key: p[0], value: v }] }; }
22
+ if (/^\s*INSERT/i.test(sql)) { store.set(p[0], p[1]); return { rows: [{ key: p[0], value: p[1] }] }; }
23
+ if (/^\s*DELETE/i.test(sql)) { store.delete(p[0]); return { rows: [] }; }
24
+ throw new Error(`unexpected sql: ${sql}`);
25
+ },
26
+ };
27
+ const ps = require('../src/bongos/project-settings.js');
28
+ const db = require('../modules/lifecycle/db.js');
29
+ const router = require('../modules/lifecycle/routes/versions.js')();
30
+
31
+ const src = (rel) => readFileSync(new URL('../' + rel, import.meta.url), 'utf8');
32
+ db.getVersion = async (id) => (id === 'BONGOS-V2' ? { id } : null);
33
+
34
+ // The route's own handler is the LAST layer on its stack (after the auth middleware).
35
+ function handler(method, path) {
36
+ const layer = router.stack.find((l) => l.route && l.route.path === path && l.route.methods[method]);
37
+ assert.ok(layer, `${method} ${path} is registered`);
38
+ return { chain: layer.route.stack, last: layer.route.stack[layer.route.stack.length - 1].handle };
39
+ }
40
+ async function call(method, path, req) {
41
+ const out = { status: 200, body: null };
42
+ const res = {
43
+ status(n) { out.status = n; return this; },
44
+ json(b) { out.body = b; return this; },
45
+ fail(code, o) { const a = typeof o === 'number' ? { status: o } : (o || {}); out.status = a.status; out.body = { error: { code, message: a.message || code } }; return this; },
46
+ };
47
+ await handler(method, path).last({ body: {}, builder: { id: 9, rank: 'archon' }, ...req }, res, (e) => { throw e; });
48
+ return out;
49
+ }
50
+
51
+ test('no row means no focus', async () => {
52
+ store.clear();
53
+ assert.equal(await ps.getFocusVersionId(), null);
54
+ });
55
+
56
+ test('PUT stores a known version and GET returns it', async () => {
57
+ store.clear();
58
+ const put = await call('put', '/versions/focus', { body: { version_id: 'BONGOS-V2' } });
59
+ assert.equal(put.body.focus_version_id, 'BONGOS-V2');
60
+ assert.equal(store.get(ps.SETTING_KEYS.FOCUS_VERSION), 'BONGOS-V2');
61
+ const got = await call('get', '/versions/focus', {});
62
+ assert.deepEqual(got.body, { focus_version_id: 'BONGOS-V2', can_set: true });
63
+ });
64
+
65
+ test('PUT with null clears the focus (the row is gone, not blank)', async () => {
66
+ store.set(ps.SETTING_KEYS.FOCUS_VERSION, 'BONGOS-V2');
67
+ const put = await call('put', '/versions/focus', { body: { version_id: null } });
68
+ assert.equal(put.body.focus_version_id, null);
69
+ assert.equal(store.has(ps.SETTING_KEYS.FOCUS_VERSION), false);
70
+ });
71
+
72
+ test('an unknown version is refused with a readable message and nothing is stored', async () => {
73
+ store.clear();
74
+ const put = await call('put', '/versions/focus', { body: { version_id: 'NOPE' } });
75
+ assert.equal(put.status, 404);
76
+ assert.match(put.body.error.message, /NOPE/);
77
+ assert.equal(store.size, 0);
78
+ });
79
+
80
+ test('a missing or blank version_id is refused, not treated as a clear', async () => {
81
+ for (const body of [{}, { version_id: ' ' }]) {
82
+ const put = await call('put', '/versions/focus', { body });
83
+ assert.equal(put.status, 400);
84
+ }
85
+ });
86
+
87
+ test('a non-Archon reads the focus but is told it cannot set it', async () => {
88
+ store.set(ps.SETTING_KEYS.FOCUS_VERSION, 'BONGOS-V2');
89
+ const got = await call('get', '/versions/focus', { builder: { id: 4, rank: 'metic' } });
90
+ assert.deepEqual(got.body, { focus_version_id: 'BONGOS-V2', can_set: false });
91
+ });
92
+
93
+ test('the write route carries the Archon-only permission gate; the read route does not', () => {
94
+ assert.match(src('modules/lifecycle/routes/versions.js'), /router\.put\('\/versions\/focus',\s*auth\.requireBuilder,\s*auth\.requirePermission\('version\.focus\.set'\)/);
95
+ assert.doesNotMatch(src('modules/lifecycle/routes/versions.js').match(/router\.get\('\/versions\/focus',[^\n]*/)[0], /requireRank/);
96
+ });
@@ -2316,8 +2316,11 @@ test('the extras picker ships as real markup on the review\'s Extras row and is
2316
2316
  assert.match(EXTRAS_ROW, /id="modCards"/, 'the band the picker paints its rows into');
2317
2317
  assert.match(EXTRAS_ROW, /class="typeCards"/, 'and it is the SAME band the type and team questions use — no new grammar');
2318
2318
  assert.match(EXTRAS_ROW, /id="modCoreNote"/, 'the always-on core is stated on the row');
2319
- // the suggested set is accepted by its own control — "Looks right" — never inferred
2320
- assert.match(HUB, /<div class="setRow" data-row="extras">[\s\S]*?<button type="button" class="rowOk" data-row-ok>Looks right<\/button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change<\/button>/,
2319
+ // the suggested set is accepted by a control — the page's "Looks right, continue", or
2320
+ // Done on the open row (task 1004550) — never inferred
2321
+ assert.match(HUB, /<div class="setRow" data-row="extras">[\s\S]*?<button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit<\/button>/,
2322
+ 'the row can be opened and changed');
2323
+ assert.match(HUB, /<button type="submit" class="btn" id="reviewGo">Looks right, continue<\/button>/,
2321
2324
  'accepting the suggested set is a control, not an inference');
2322
2325
  // confirming turns the shown set into an answer given; the row's validity never refuses it
2323
2326
  const confirm = (SCRIPT.match(/function confirmRow\(key\) \{([\s\S]*?)\n \}/) || [, ''])[1];
@@ -2397,6 +2400,6 @@ test('the kit stub answers the picker\'s read, so the panel renders in the desig
2397
2400
  assert.match(stub, /rest === '\/provisioning\/starter-bundles'/, 'the stub serves the picker\'s read');
2398
2401
  const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
2399
2402
  assert.ok(probes.some((p) => /the wizard driver \(tasks? 1003346\b/.test(p.name)
2400
- && p.steps.some((st) => st[1] === '#reviewRows .setRow[data-row="extras"] [data-row-ok]')),
2401
- 'and the end-to-end driver confirms the Extras row rather than stopping at it (task 1004504)');
2403
+ && p.steps.some((st) => st[1] === '#reviewGo')),
2404
+ 'and the end-to-end driver confirms the review rather than stopping at it (tasks 1004504, 1004550)');
2402
2405
  });
@@ -1,7 +1,7 @@
1
1
  // tests/projects_hub_app_step.mjs — the create wizard's "where your app runs" question,
2
2
  // EXECUTED (task 1004351, ADR 0345), and its Render setup (task 1004352, ADR 0348). Since
3
3
  // task 1004504 the old App step is the review's "App hosting" row: its controls moved into
4
- // the row whole, and "Use this" runs the check its Next used to.
4
+ // the row whole, and Done (the row's Edit, open) runs the check its Next used to.
5
5
  //
6
6
  // A static regex cannot tell a guard that works from one that is never consulted, so —
7
7
  // as tests/projects_hub_module_picker.mjs does for the picker — this slices the block out
@@ -162,7 +162,7 @@ test('the row is real markup: Render and Decide later, Decide later the default'
162
162
  assert.doesNotMatch(PANEL, /Coming soon/, 'and is no longer marked soon');
163
163
  assert.match(PANEL, /<input type="radio" name="appPlatform" value="later" checked>/, 'Decide later is the default');
164
164
  assert.ok(PANEL.indexOf('value="later"') < PANEL.indexOf('value="render"'), 'Decide later comes first');
165
- assert.match(PANEL, /<button type="button" class="btn small rowUse" data-row-use>Use this<\/button>/, 'Use this confirms the row');
165
+ assert.match(HUB.slice(HUB.indexOf('<div class="setRow" data-row="app">')), /^[\s\S]*?<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit<\/button>/, 'Edit opens the row, and reads Done to confirm it (task 1004550)');
166
166
  assert.doesNotMatch(HUB, /id="panel-app"/, 'the old App panel is gone');
167
167
  });
168
168
 
@@ -230,7 +230,8 @@ test('the hall is spelled one way on this page', () => {
230
230
  test('the key field says what the key is used for, and claims no protection it lacks', () => {
231
231
  const setup = (PANEL.match(/<div class="renderSetup"[\s\S]*?<div class="hint" id="h-render"/) || [''])[0];
232
232
  assert.match(setup, /<input type="password" id="f-renderKey" autocomplete="off"/, 'masked, and never autofilled');
233
- assert.match(setup, /used once, to create your app on your own Render account, and then thrown away/);
233
+ assert.match(setup, /Used once to create your app on your Render account, then thrown away — not kept, not even in this browser/);
234
+ assert.match(setup, /A key can act on your whole Render account/, 'what a key can reach is still said (task 1004550 shortened the rest)');
234
235
  assert.match(setup, /Render bills you directly/);
235
236
  assert.match(setup, /href="https:\/\/dashboard\.render\.com\/u\/settings#api-keys"/, 'links to where Render issues keys');
236
237
  assert.doesNotMatch(setup, /encrypt|secure|safe|protected/i, 'no protection claim the design does not give (ADR 0327 §2)');
@@ -1,8 +1,9 @@
1
1
  // tests/projects_hub_module_picker.mjs — the create wizard's module picker,
2
2
  // EXECUTED (BV1.R20, task 1002339; goal 1000046 criterion `proj-module-select`).
3
3
  // Since task 1004504 the picker is the review's Extras row: its controls moved into the
4
- // row whole, "Looks right" / "Use this" confirm the set on screen, and there is no Skip —
5
- // Create waits for every row, this one included.
4
+ // row whole, the page's one "Looks right, continue" (or Done on the open row) confirms
5
+ // the set on screen (task 1004550), and there is no Skip — Create waits for every row,
6
+ // this one included.
6
7
  //
7
8
  // tests/projects_hub.mjs pins this surface statically, on purpose (C7: "real
8
9
  // markup, not JS-built"). But the picker is the one part of the wizard whose
@@ -146,11 +147,11 @@ function makeEl(id) {
146
147
  };
147
148
  }
148
149
 
149
- // One review row, shaped like the markup: its hidden .opts, Looks right and Change.
150
+ // One review row, shaped like the markup: its hidden .opts and its one Edit / Done control.
150
151
  function makeRow(key) {
151
152
  const row = makeEl('row-' + key);
152
153
  row.setAttribute('data-row', key);
153
- const parts = { '.opts': makeEl('opts-' + key), '[data-row-ok]': makeEl('ok-' + key), '[data-row-chg]': makeEl('chg-' + key) };
154
+ const parts = { '.opts': makeEl('opts-' + key), '[data-row-chg]': makeEl('chg-' + key) };
154
155
  parts['.opts'].hidden = true;
155
156
  row.querySelector = (sel) => parts[sel] || null;
156
157
  return row;
@@ -220,7 +221,7 @@ function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
220
221
  // a click inside the review, through its one delegated listener, as the browser sends it
221
222
  const rowClick = (attr) => {
222
223
  const row = rowFor('#reviewRows .setRow[data-row="extras"]');
223
- const t = { hasAttribute: (a) => a === attr, closest: (sel) => (sel === '.setRow' ? row : null) };
224
+ const t = { hasAttribute: (a) => a === attr, closest: (sel) => (sel === '.setRow' ? row : null), focus() {} };
224
225
  $('reviewRows')._handlers.click({ target: { closest: () => t } });
225
226
  };
226
227
  return {
@@ -246,10 +247,11 @@ function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
246
247
  };
247
248
  band._handlers.click({ target });
248
249
  },
249
- // "Looks right" on the Extras row, "Change" to open it, "Use this" inside it
250
- looksRight: () => rowClick('data-row-ok'),
250
+ // the page's "Looks right, continue" (every row through its own check), Edit to
251
+ // open the Extras row, and Done — the same control, open — to confirm it
252
+ looksRight: () => sandbox.confirmAllRows(),
251
253
  change: () => rowClick('data-row-chg'),
252
- use: () => rowClick('data-row-use'),
254
+ use: () => rowClick('data-row-chg'),
253
255
  confirmed: () => !!(state.confirmed && state.confirmed.extras),
254
256
  unconfirmed: () => [...sandbox.unconfirmedRows()],
255
257
  forCreate: () => sandbox.modulesForCreate(),
@@ -355,7 +357,7 @@ test('an unconfirmed row answers NOTHING, and Create waits for it (no Skip since
355
357
  assert.ok(h.unconfirmed().includes('extras'), 'and the review holds Continue until the row is confirmed');
356
358
  });
357
359
 
358
- test('Change, a toggle, then Use this: the toggle stands as the answer', async () => {
360
+ test('Edit, a toggle, then Done: the toggle stands as the answer', async () => {
359
361
  // A visitor who changed something and then confirmed meant "this", not "throw that away".
360
362
  const h = boot({ state: { projType: 'game' } });
361
363
  await h.open();
@@ -368,7 +370,7 @@ test('Change, a toggle, then Use this: the toggle stands as the answer', async (
368
370
  assert.equal(h.confirmed(), true, 'and the row is confirmed');
369
371
  });
370
372
 
371
- test('Looks right CONFIRMS the set on screen — looking at it and accepting it is not the same as never being asked', async () => {
373
+ test('Looks right, continue CONFIRMS the set on screen — looking at it and accepting it is not the same as never being asked', async () => {
372
374
  const h = boot({ state: { projType: 'game' } });
373
375
  await h.open();
374
376
 
@@ -243,7 +243,7 @@ test('a resumed draft past the code row still probes repo access before Create',
243
243
  'every screen between the review and the done panel re-probes');
244
244
  });
245
245
 
246
- test('a half-typed custom domain says why Use this is dead; a taken handle silences the format cheer', () => {
246
+ test('a half-typed custom domain says why Done refuses it; a taken handle silences the format cheer', () => {
247
247
  assert.match(SCRIPT, /That doesn’t look like a domain yet \(e\.g\. moonbase\.example\.com\) — fix it, or clear the field to use the address above\./);
248
248
  assert.match(SCRIPT, /if \(avail === 'taken' \|\| avail === 'yours' \|\| avail === 'reserved'\) \{ var hs = \$\('h-slug'\); hs\.innerHTML = '&nbsp;'; hs\.className = 'hint'; \}/);
249
249
  });