@bongos/core 1.21.113 → 1.21.115

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 (265) hide show
  1. package/.bongos-core.json +608 -188
  2. package/.claude/skills/ask-for-help/SKILL.md +1 -1
  3. package/.claude/skills/bongos-feedback/SKILL.md +1 -1
  4. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  5. package/.claude/skills/builder-ship/SKILL.md +1 -0
  6. package/GOVERNMENT.md +128 -11
  7. package/clients/bongos-client/README.md +1 -1
  8. package/clients/bongos-client/bongos-client.global.js +28 -0
  9. package/clients/bongos-client/index.cjs +28 -0
  10. package/clients/bongos-client/index.d.ts +45 -2
  11. package/clients/bongos-client/index.mjs +28 -0
  12. package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
  13. package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
  14. package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
  15. package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
  16. package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
  17. package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
  18. package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
  19. package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
  20. package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
  21. package/docs/adr/README.md +10 -1
  22. package/docs/api/openapi.json +989 -87
  23. package/docs/api-reference.md +18 -4
  24. package/docs/copy-inventory.md +709 -626
  25. package/docs/copy-registry.json +1673 -858
  26. package/docs/file-map.md +2 -1
  27. package/docs/module-api-changelog.md +4 -0
  28. package/docs/packs/engineer.md +2 -1
  29. package/docs/page-inventory.json +91 -6
  30. package/docs/page-readings.json +4875 -4748
  31. package/modules/builder-settings/law-locks.js +202 -0
  32. package/modules/builder-settings/routes/settings.js +66 -6
  33. package/modules/builder-settings/settings.js +7 -0
  34. package/modules/discord/ship-broadcast.js +22 -13
  35. package/modules/government/asks-db.js +54 -0
  36. package/modules/government/asks.js +269 -0
  37. package/modules/government/board-idea-reads.js +68 -0
  38. package/modules/government/board.js +21 -57
  39. package/modules/government/build-rights.js +148 -0
  40. package/modules/government/buildouts.js +160 -0
  41. package/modules/government/counts.js +81 -0
  42. package/modules/government/db.js +242 -0
  43. package/modules/government/decision-board.js +234 -8
  44. package/modules/government/decision-rules.js +2 -0
  45. package/modules/government/docket.js +114 -6
  46. package/modules/government/law-board.js +131 -0
  47. package/modules/government/laws.js +241 -0
  48. package/modules/government/leads.js +170 -0
  49. package/modules/government/migrations/government_027_laws.sql +143 -0
  50. package/modules/government/migrations/government_028_asks.sql +57 -0
  51. package/modules/government/migrations/government_029_leads.sql +135 -0
  52. package/modules/government/migrations/government_030_buildouts.sql +111 -0
  53. package/modules/government/migrations/government_031_treasury.sql +107 -0
  54. package/modules/government/module.json +2 -1
  55. package/modules/government/pollers/treasury-borrow.js +62 -0
  56. package/modules/government/positions.js +107 -0
  57. package/modules/government/presets.js +149 -0
  58. package/modules/government/routes/government.js +425 -2
  59. package/modules/government/routing.js +154 -0
  60. package/modules/government/treasury-db.js +166 -0
  61. package/modules/government/treasury.js +241 -0
  62. package/modules/hall-ui/public/atlas.html +2 -2
  63. package/modules/hall-ui/public/blockers.html +2 -2
  64. package/modules/hall-ui/public/board-room.html +2 -2
  65. package/modules/hall-ui/public/board-room.js +2 -0
  66. package/modules/hall-ui/public/city-draw.js +604 -11
  67. package/modules/hall-ui/public/city.css +157 -1
  68. package/modules/hall-ui/public/city.html +37 -7
  69. package/modules/hall-ui/public/city.js +441 -21
  70. package/modules/hall-ui/public/city.states.json +110 -5
  71. package/modules/hall-ui/public/collab.html +2 -2
  72. package/modules/hall-ui/public/commands.html +2 -2
  73. package/modules/hall-ui/public/copy-desk.html +2 -2
  74. package/modules/hall-ui/public/craft-mode.js +112 -29
  75. package/modules/hall-ui/public/deploy.html +2 -2
  76. package/modules/hall-ui/public/diagram-viewer.css +12 -0
  77. package/modules/hall-ui/public/diagram-viewer.js +4 -0
  78. package/modules/hall-ui/public/diagrams.html +2 -2
  79. package/modules/hall-ui/public/drachmae.html +2 -2
  80. package/modules/hall-ui/public/fleet.html +2 -2
  81. package/modules/hall-ui/public/gate.html +2 -2
  82. package/modules/hall-ui/public/goal-map.html +2 -2
  83. package/modules/hall-ui/public/goals.html +2 -2
  84. package/modules/hall-ui/public/gov-feeds.js +141 -0
  85. package/modules/hall-ui/public/gov-living.js +1002 -0
  86. package/modules/hall-ui/public/gov-positions.js +97 -0
  87. package/modules/hall-ui/public/government.html +2 -2
  88. package/modules/hall-ui/public/hall-render.js +3 -0
  89. package/modules/hall-ui/public/idea.html +2 -2
  90. package/modules/hall-ui/public/ideas.html +2 -2
  91. package/modules/hall-ui/public/index.html +3 -3
  92. package/modules/hall-ui/public/modules.html +2 -2
  93. package/modules/hall-ui/public/my-agents.css +334 -0
  94. package/modules/hall-ui/public/my-agents.html +72 -0
  95. package/modules/hall-ui/public/my-agents.js +603 -0
  96. package/modules/hall-ui/public/my-agents.states.json +37 -0
  97. package/modules/hall-ui/public/primer.html +2 -2
  98. package/modules/hall-ui/public/profile-connections.js +40 -28
  99. package/modules/hall-ui/public/profile.html +2 -2
  100. package/modules/hall-ui/public/project-settings.html +2 -2
  101. package/modules/hall-ui/public/ranks.html +2 -2
  102. package/modules/hall-ui/public/roadmap.html +2 -2
  103. package/modules/hall-ui/public/room.css +8 -0
  104. package/modules/hall-ui/public/roster.html +2 -2
  105. package/modules/hall-ui/public/sessions.html +2 -2
  106. package/modules/hall-ui/public/settings.html +2 -2
  107. package/modules/hall-ui/public/shell.js +116 -16
  108. package/modules/hall-ui/public/street-card.js +18 -1
  109. package/modules/hall-ui/public/street-deck.css +299 -0
  110. package/modules/hall-ui/public/street-deck.js +1145 -0
  111. package/modules/hall-ui/public/street-depth.js +712 -0
  112. package/modules/hall-ui/public/street-desk.js +5 -2
  113. package/modules/hall-ui/public/street-draw.js +109 -16
  114. package/modules/hall-ui/public/street-inside.js +137 -22
  115. package/modules/hall-ui/public/street-office.js +705 -0
  116. package/modules/hall-ui/public/street.css +161 -11
  117. package/modules/hall-ui/public/street.html +41 -11
  118. package/modules/hall-ui/public/street.js +78 -66
  119. package/modules/hall-ui/public/street.states.json +129 -21
  120. package/modules/hall-ui/public/studio.html +2 -2
  121. package/modules/hall-ui/public/style.css +5 -1
  122. package/modules/hall-ui/public/task.html +2 -2
  123. package/modules/hall-ui/public/thinking.html +2 -2
  124. package/modules/hall-ui/public/tweak-editor.html +2 -2
  125. package/modules/hall-ui/public/watch.html +2 -2
  126. package/modules/hall-ui/public/watch.js +14 -1
  127. package/modules/hall-ui/public/work.html +2 -2
  128. package/modules/hall-ui/records/living-city.md +57 -0
  129. package/modules/hall-ui/records/my-agents.md +21 -0
  130. package/modules/hall-ui/records/oversight-family.md +1 -0
  131. package/modules/hall-ui/records/profile-connections.md +10 -0
  132. package/modules/hall-ui/records/reading-rooms.md +2 -0
  133. package/modules/ideas/routes/blockers.js +92 -3
  134. package/modules/lifecycle/module.json +2 -1
  135. package/modules/lifecycle/moving.js +17 -1
  136. package/modules/lifecycle/routes/moving.js +6 -0
  137. package/modules/lifecycle/routes/project-settings.js +23 -0
  138. package/modules/onboarding/approval-broadcast.js +8 -0
  139. package/modules/onboarding/routes/access-requests.js +16 -6
  140. package/modules/platform-identity/connections.js +13 -2
  141. package/modules/platform-identity/routes/connections.js +7 -1
  142. package/modules/public-landing/public/account-private.states.json +2 -2
  143. package/modules/public-landing/public/account.html +65 -5
  144. package/modules/public-landing/public/account.states.json +3 -3
  145. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
  146. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
  147. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
  148. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
  149. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
  150. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
  151. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
  152. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
  153. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
  154. package/modules/public-landing/public/assets/world.css +4 -4
  155. package/modules/public-landing/public/connections-empty.states.json +14 -0
  156. package/modules/public-landing/public/connections-inactive.states.json +14 -0
  157. package/modules/public-landing/public/connections.html +450 -0
  158. package/modules/public-landing/public/connections.probes.json +414 -0
  159. package/modules/public-landing/public/connections.states.json +17 -0
  160. package/modules/public-landing/public/contact.html +2 -2
  161. package/modules/public-landing/public/guild.html +2 -2
  162. package/modules/public-landing/public/index.html +2 -2
  163. package/modules/public-landing/public/invitations.html +2 -2
  164. package/modules/public-landing/public/privacy.html +2 -2
  165. package/modules/public-landing/public/projects-hall-live.states.json +0 -5
  166. package/modules/public-landing/public/projects.html +169 -6
  167. package/modules/public-landing/public/projects.probes.json +10 -6
  168. package/modules/public-landing/public/projects.states.json +13 -13
  169. package/modules/public-landing/public/terms.html +2 -2
  170. package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
  171. package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
  172. package/modules/ui-design/kit/fixtures/connections.json +22 -0
  173. package/modules/ui-design/kit/serve.js +95 -0
  174. package/package-lock.json +2 -2
  175. package/package.json +1 -1
  176. package/release-notes.json +128 -0
  177. package/scripts/discord/README.md +8 -5
  178. package/scripts/discord/post.js +12 -1
  179. package/scripts/gds/artifact-audit.js +20 -3
  180. package/scripts/gds/ask.js +164 -0
  181. package/scripts/gds/autobongos-verify.js +21 -2
  182. package/scripts/gds/government-walk.js +638 -4
  183. package/scripts/gds/pulse.js +9 -6
  184. package/scripts/gds/ship-escalation.js +22 -3
  185. package/scripts/gds/ship-finish.js +5 -4
  186. package/scripts/gds/ship-merge-gate.js +18 -6
  187. package/scripts/hall-preview/README.md +41 -1
  188. package/scripts/hall-preview/pref-writes.js +140 -0
  189. package/scripts/hall-preview/refresh-fixtures.js +101 -7
  190. package/scripts/hall-preview/scrub-identity.js +366 -7
  191. package/scripts/hall-preview/server.js +27 -1
  192. package/scripts/render-diagrams.sh +4 -2
  193. package/src/bongos/auth-idp.js +6 -1
  194. package/src/bongos/project-settings.js +41 -0
  195. package/src/bongos/route-rank-check.js +27 -0
  196. package/src/bongos/routes/me.js +68 -7
  197. package/src/bongos/serve-internal.js +5 -1
  198. package/src/module-api.js +7 -1
  199. package/src/platform-server.js +10 -0
  200. package/tests/application_lifecycle.mjs +47 -5
  201. package/tests/conductor_main_e2e.mjs +21 -0
  202. package/tests/connections_api.mjs +18 -0
  203. package/tests/craft_mode_nav.mjs +5 -3
  204. package/tests/craft_mode_tallies.mjs +52 -16
  205. package/tests/discord_approvals.mjs +11 -0
  206. package/tests/discord_channels.mjs +20 -0
  207. package/tests/fixtures/gov-seated.mjs +64 -0
  208. package/tests/fixtures/government-ask-cards.json +1284 -0
  209. package/tests/government_act_alone_buildings.mjs +1 -0
  210. package/tests/government_agent_asks.mjs +322 -0
  211. package/tests/government_asks.mjs +408 -0
  212. package/tests/government_build_rights.mjs +253 -0
  213. package/tests/government_buildouts.mjs +382 -0
  214. package/tests/government_counts.mjs +143 -0
  215. package/tests/government_decision_rules.mjs +5 -1
  216. package/tests/government_laws.mjs +488 -0
  217. package/tests/government_laws_session.mjs +321 -0
  218. package/tests/government_leads.mjs +375 -0
  219. package/tests/government_positions.mjs +127 -0
  220. package/tests/government_presets.mjs +215 -0
  221. package/tests/government_routes.mjs +8 -1
  222. package/tests/government_routing.mjs +118 -0
  223. package/tests/government_seed.mjs +5 -0
  224. package/tests/government_treasury.mjs +394 -0
  225. package/tests/governor_docket.mjs +26 -2
  226. package/tests/hall_buildouts.mjs +208 -0
  227. package/tests/hall_city.mjs +14 -6
  228. package/tests/hall_city_living.mjs +617 -0
  229. package/tests/hall_gov_counts.mjs +336 -0
  230. package/tests/hall_gov_living.mjs +636 -0
  231. package/tests/hall_gov_real_site.mjs +655 -0
  232. package/tests/hall_matter_card.mjs +6 -1
  233. package/tests/hall_my_agents.mjs +481 -0
  234. package/tests/hall_nav.mjs +107 -4
  235. package/tests/hall_page_gate_map.mjs +4 -0
  236. package/tests/hall_preview_governor_fixtures.mjs +30 -3
  237. package/tests/hall_preview_scrub.mjs +59 -1
  238. package/tests/hall_preview_security.mjs +169 -0
  239. package/tests/hall_reading_world.mjs +44 -0
  240. package/tests/hall_roster_world.mjs +6 -2
  241. package/tests/hall_street.mjs +18 -2
  242. package/tests/hall_street_amend.mjs +1 -1
  243. package/tests/hall_street_deck.mjs +609 -0
  244. package/tests/hall_street_depth.mjs +559 -0
  245. package/tests/hall_street_desk.mjs +1 -1
  246. package/tests/hall_street_inside.mjs +29 -5
  247. package/tests/hall_tweak_editor.mjs +4 -1
  248. package/tests/hub_account_page.mjs +94 -3
  249. package/tests/hub_connections_page.mjs +507 -0
  250. package/tests/hub_invite_badge.mjs +1 -1
  251. package/tests/idp_device_exchange.mjs +18 -0
  252. package/tests/landing_legal_pages.mjs +8 -5
  253. package/tests/landing_page.mjs +23 -4
  254. package/tests/nav_permission_atoms.mjs +4 -1
  255. package/tests/profile_connections_ui.mjs +88 -25
  256. package/tests/projects_hub.mjs +15 -0
  257. package/tests/projects_hub_pre_uat.mjs +4 -2
  258. package/tests/render_check.mjs +2 -2
  259. package/tests/ship_broadcast_routing.mjs +57 -16
  260. package/tests/ship_broadcast_server.mjs +40 -7
  261. package/tests/ship_merge_gate.mjs +19 -0
  262. package/tests/ui_design_kit.mjs +2 -2
  263. package/tests/watch_applications_queue.mjs +23 -0
  264. package/tests/wizard_physics.mjs +1 -1
  265. package/tests/wizard_physics_more.mjs +133 -0
@@ -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;}
@@ -835,7 +846,7 @@ summary{min-height:24px;padding:3px 0;}
835
846
  ?view=map and carry the sub-view; setSub keeps the current one lit -->
836
847
  <li class="bandCell"><a class="bandT" href="?view=map" data-nav="map" data-sub="sky" aria-current="page">Sky</a><span class="bandC">the sky of every project</span></li>
837
848
  <li class="bandCell"><a class="bandT" href="?view=map&amp;sub=table" data-nav="map" data-sub="table">Table</a><span class="bandC">the same projects as rows</span></li>
838
- <li class="bandCell"><a class="bandT" href="?view=mine" data-nav="mine">Your projects</a><span class="bandC">the ones you own or build on</span></li>
849
+ <li class="bandCell"><a class="bandT" href="?view=mine" data-nav="mine">Your projects</a><span class="bandC">the ones you own</span></li>
839
850
  <li class="bandCell"><a class="bandT" href="?view=new" data-nav="new">New project</a><span class="bandC">five questions and a home</span></li>
840
851
  </ul>
841
852
  <!-- About and Docs are site pages, not project views: they live in the
@@ -851,7 +862,7 @@ summary{min-height:24px;padding:3px 0;}
851
862
  account page, where the person's own settings live (ADR 0337 D4.3).
852
863
  Apex-only because the page is — /account is served only where
853
864
  platform-identity runs, so a child instance loses the item whole. -->
854
- <!--apex-only:start--><li class="bandCell"><a class="bandT" href="/invitations">Invitations</a></li><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
865
+ <!--apex-only:start--><li class="bandCell"><a class="bandT" href="/invitations">Invitations</a></li><li class="bandCell"><a class="bandT" href="/connections">Connections</a></li><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
855
866
  <!-- the honest hover target; the delegated click upgrades it to the POST -->
856
867
  <li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
857
868
  </ul>
@@ -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 () {
@@ -144,9 +144,13 @@
144
144
  "auth": true, "width": 1440, "url": "/projects?view=manage&id=101",
145
145
  "steps": [["wait",900],["check","#mPick input[name=\"mPickShape\"][value=\"pair\"]"],["wait",100],["check","#mPick input[name=\"mPickMaterial\"][value=\"gold\"]"],["wait",100],["check","#mPick input[name=\"mPickStyle\"][value=\"wash\"]"],["wait",100],["check","#mPick input[name=\"mPickAccent\"][value=\"terracotta\"]"],["wait",100],["click","[data-pick-save]"],["wait",700]],
146
146
  "expect": {"text":[["#mPickMsg","Saved. The sky, your list and the card draw it this way from here; nothing on your project restarts for it."],["#mPlanet .v","the bongo pair in gold, washedyour choice."],["#mName","demo"]],"hasClass":[["#mPickMsg","ok",true]],"attr":[["#mOrb .worldOrb","data-template","pair-gold-wash"],["#mPick .pickPreview .worldOrb","data-template","pair-gold-wash"]],"checked":"#mPick input[name=\"mPickAccent\"][value=\"terracotta\"]","visible":["#mPick [data-pick-reset]"]} },
147
- { "name": "manage flows: its planet — the pick handed back to the platform: the row's choice clears and the head falls back to the catalog's",
147
+ { "name": "manage flows: its planet — before handing back, a pick the catalog does not carry is saved on the same page: the bead in frost, plain; the head takes it, so the row now differs from the feed's pair in obsidian (task 1003645)",
148
148
  "auth": true, "width": 1440, "url": "/projects?view=manage&id=101",
149
- "steps": [["wait",900],["click","[data-pick-reset]"],["wait",700]],
149
+ "steps": [["wait",900],["check","#mPick input[name=\"mPickShape\"][value=\"bead\"]"],["wait",100],["check","#mPick input[name=\"mPickMaterial\"][value=\"frost\"]"],["wait",100],["check","#mPick input[name=\"mPickStyle\"][value=\"plain\"]"],["wait",100],["click","[data-pick-save]"],["wait",700]],
150
+ "expect": {"hasClass":[["#mPickMsg","ok",true]],"attr":[["#mOrb .worldOrb","data-template","bead-frost-plain"]],"visible":["#mPick [data-pick-reset]"]} },
151
+ { "name": "manage flows: its planet — the pick handed back to the platform: the row's choice clears and the head falls back to the catalog's",
152
+ "continue": true,
153
+ "steps": [["click","[data-pick-reset]"],["wait",700]],
150
154
  "expect": {"text":[["#mPickMsg","Saved. The sky, your list and the card draw it this way from here; nothing on your project restarts for it."],["#mName","demo"]],"hasClass":[["#mPickMsg","ok",true]],"attr":[["#mOrb .worldOrb","data-template","pair-obsidian-tint"]]} },
151
155
  { "name": "manage flows: its planet at 390 — the bands fold to pills and a pick still saves",
152
156
  "auth": true, "width": 390, "url": "/projects?view=manage&id=101",
@@ -162,16 +166,16 @@
162
166
  "steps": [["wait",600],["click","#wizDemo"],["wait",300],["click","#demoCancel"],["wait",300],["fill","#newName","Mercury"],["wait",100]],
163
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."]]} },
164
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",
165
- "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]],
166
170
  "expect": {"visible":["#physRegionField","#physAges","#physForms","#part-run"],"hidden":["#physIntent","#checksStop"]} },
167
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",
168
- "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]],
169
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"]]} },
170
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",
171
- "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]],
172
176
  "expect": {"visible":["#panel-done","#setupNow","#homeFacts","#ghFacts","#hopLink"],"hidden":["#wizParts","#hop","#doneAlive"],"text":[["#donePartT","Getting Mercury ready"]]} },
173
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",
174
- "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]],
175
179
  "expect": {"noOverflow":true,"visible":["#panel-done","#homeFacts","#ghFacts"],"hidden":["#wizParts"]} },
176
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",
177
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/"]] } },
@@ -72,7 +72,7 @@
72
72
  <ul class="bandRow">
73
73
  <li class="bandCell"><a class="bandT" href="/projects?view=map">Sky</a><span class="bandC">the sky of every project</span></li>
74
74
  <li class="bandCell"><a class="bandT" href="/projects?view=map&amp;sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
75
- <li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own or build on</span></li>
75
+ <li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
76
76
  <li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
77
77
  </ul>
78
78
  </div>
@@ -84,7 +84,7 @@
84
84
  <li class="bandCell"><a class="bandT" href="/projects?view=mine">My projects</a></li>
85
85
  <!-- apex-only because /account is: it is served only where platform-identity
86
86
  runs, so a child instance loses the item whole (as on projects.html) -->
87
- <!--apex-only:start--><li class="bandCell"><a class="bandT" href="/invitations">Invitations</a></li><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
87
+ <!--apex-only:start--><li class="bandCell"><a class="bandT" href="/invitations">Invitations</a></li><li class="bandCell"><a class="bandT" href="/connections">Connections</a></li><li class="bandCell"><a class="bandT" href="/account">Account</a></li><!--apex-only:end-->
88
88
  <!-- the honest hover target; assets/hub-bar.js upgrades the click to the POST -->
89
89
  <li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
90
90
  </ul>
@@ -0,0 +1,12 @@
1
+ {
2
+ "connections": [],
3
+ "incoming": [],
4
+ "outgoing": [],
5
+ "suggestions": [
6
+ { "handle": "sana", "display_name": "Sana Iqbal", "avatar_url": "/__kit/avatar.svg", "reason": "You both build on Tidepool" },
7
+ { "handle": "owen-c", "display_name": "Owen Clarke", "avatar_url": null, "reason": "A project you both build on" }
8
+ ],
9
+ "directory": [
10
+ { "handle": "nadia", "display_name": "Nadia Haddad", "avatar_url": "/__kit/avatar.svg" }
11
+ ]
12
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "inactive": true,
3
+ "connections": [],
4
+ "incoming": [],
5
+ "outgoing": [],
6
+ "suggestions": []
7
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "connections": [
3
+ { "id": "41", "connected_at": "$now-12d", "handle": "mira", "display_name": "Mira Okafor", "avatar_url": "/__kit/avatar.svg" },
4
+ { "id": "42", "connected_at": "$now-40d", "handle": "tomas-r", "display_name": "Tomas Reyes", "avatar_url": null },
5
+ { "id": "43", "connected_at": "$now-73d", "handle": "juno", "display_name": null, "avatar_url": "/__kit/avatar.svg" }
6
+ ],
7
+ "incoming": [
8
+ { "id": "51", "note": "We were both on the Tidepool hall last month. Good working with you.", "created_at": "$now-2d", "seen_at": null, "handle": "priya", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg" },
9
+ { "id": "52", "note": null, "created_at": "$now-6d", "seen_at": "$now-5d", "handle": "dmitri-v", "display_name": "Dmitri Volkov", "avatar_url": null }
10
+ ],
11
+ "outgoing": [
12
+ { "id": "61", "note": null, "created_at": "$now-5d", "handle": "leo-b", "display_name": "Leo Brandt", "avatar_url": null }
13
+ ],
14
+ "suggestions": [
15
+ { "handle": "sana", "display_name": "Sana Iqbal", "avatar_url": "/__kit/avatar.svg", "reason": "2 connections in common" },
16
+ { "handle": "owen-c", "display_name": "Owen Clarke", "avatar_url": null, "reason": "You both build on Tidepool" },
17
+ { "handle": "rafa", "display_name": "Rafa", "avatar_url": "/__kit/avatar.svg", "reason": "A project you both build on" }
18
+ ],
19
+ "directory": [
20
+ { "handle": "nadia", "display_name": "Nadia Haddad", "avatar_url": "/__kit/avatar.svg" }
21
+ ]
22
+ }
@@ -567,6 +567,19 @@ function ownedRequests() {
567
567
  return Object.values(guildsHeld()).filter((g) => g.members && g.members.you.membership_kind === 'owner')
568
568
  .flatMap((g) => ((g.requests && g.requests.incoming) || []).map((r) => ({ ...r, guild: { slug: g.members.guild.slug, name: g.members.guild.name } })));
569
569
  }
570
+ // GET /me/guilds (the account page's guild notice, task 1004260; ADR 0337 D4.4): the guilds
571
+ // the builder is in, each with their own two switches, derived from the SAME held set the
572
+ // guild page reads and writes — so a switch flipped there reads back here, as on the server.
573
+ // The route's own row shape (guilds.listMyGuilds): slug, name, visibility, membership_kind,
574
+ // shown_publicly, counted_in_totals, joined_at; count is the array's length.
575
+ function myGuilds(res) {
576
+ const mine = Object.values(guildsHeld()).filter((g) => g.members && g.members.you).map((g) => ({
577
+ slug: g.members.guild.slug, name: g.members.guild.name, visibility: g.members.guild.visibility,
578
+ membership_kind: g.members.you.membership_kind, shown_publicly: g.members.you.shown_publicly,
579
+ counted_in_totals: g.members.you.counted_in_totals, joined_at: g.members.you.joined_at || null,
580
+ }));
581
+ return json(res, 200, { guilds: mine, count: mine.length });
582
+ }
570
583
  function myGuildRequests(res) {
571
584
  const q = heldFixture('me__guild-requests');
572
585
  const owner = ownedRequests();
@@ -598,6 +611,79 @@ function guildEngagementLive(slug, origin) {
598
611
  return (heldFixture('me__guild-engagements').engagements || []).some((e) => e.guild && e.guild.slug === slug.toLowerCase() && e.project && sameOrigin(e.project.origin, origin));
599
612
  }
600
613
 
614
+ // The connections page (task 1004278): GET /connections, /connections/requests and
615
+ // /connections/suggestions answered from ONE graph held for the run (connections.json, or
616
+ // the file a states file's stub.fixtures names), so an accept, decline, withdraw, remove or
617
+ // ask the page re-reads comes back changed. The routes' answers are mirrored
618
+ // (modules/platform-identity/routes/connections.js): a sent ask is the bare
619
+ // 201 { status: 'pending' } with no id, a decline a silent 204, a row the caller may not
620
+ // act on one 404, an open pair 409, your own handle 400, and an unknown handle the same 404
621
+ // a private or inactive one gets. "inactive": true in the graph answers every route 403,
622
+ // the provisional-profile refusal. The caps are copied (a kit module may not import one).
623
+ // FAIL=connections makes the two load-bearing reads 500; FAIL=connection-suggestions only
624
+ // the suggestions read, which the page treats as additive.
625
+ let GRAPH = null;
626
+ function graph() {
627
+ if (!GRAPH) GRAPH = stamp(heldFixture('connections'));
628
+ return GRAPH;
629
+ }
630
+ const STUB_CONN_HANDLE_MAX = 39;
631
+ const STUB_CONN_NOTE_MAX = 280;
632
+ let STUB_CONN_NEXT_ID = 900;
633
+ function withAvatar(row, origin) {
634
+ return typeof row.avatar_url === 'string' && row.avatar_url.startsWith('/') ? { ...row, avatar_url: origin + row.avatar_url } : row;
635
+ }
636
+ function connInactive(res) { return refuse(res, 403, 'profile_provisional', 'sign in to the platform once to activate your profile before connecting'); }
637
+ function connRead(res, which, origin) {
638
+ const g = graph();
639
+ if (g.inactive) return connInactive(res);
640
+ if (which !== 'suggestions' && FAIL.has('connections')) return json(res, 500, { error: { code: 'stub_failure', message: 'FAIL=connections' } });
641
+ if (which === 'suggestions' && FAIL.has('connection-suggestions')) return json(res, 500, { error: { code: 'stub_failure', message: 'FAIL=connection-suggestions' } });
642
+ const rows = (k) => (g[k] || []).map((r) => withAvatar(r, origin));
643
+ if (which === 'list') { const connections = rows('connections'); return json(res, 200, { connections, count: connections.length }); }
644
+ if (which === 'requests') {
645
+ const incoming = rows('incoming');
646
+ const outgoing = rows('outgoing').map(({ seen_at, ...r }) => r);
647
+ return json(res, 200, { incoming, outgoing, incoming_count: incoming.length, outgoing_count: outgoing.length });
648
+ }
649
+ const suggestions = rows('suggestions');
650
+ return json(res, 200, { suggestions, count: suggestions.length });
651
+ }
652
+ function connAsk(res, body) {
653
+ const g = graph();
654
+ if (g.inactive) return connInactive(res);
655
+ if (typeof body.handle !== 'string' || body.handle.length < 1 || body.handle.length > STUB_CONN_HANDLE_MAX) return refuse(res, 400, 'validation_failed', 'handle must be 1-39 characters');
656
+ if (body.note !== undefined && (typeof body.note !== 'string' || body.note.length > STUB_CONN_NOTE_MAX)) return refuse(res, 400, 'validation_failed', 'note must be at most 280 characters');
657
+ const h = body.handle.toLowerCase();
658
+ if (h === String(account().handle).toLowerCase()) return refuse(res, 400, 'cannot_connect_to_self', 'that is your own account');
659
+ const same = (r) => String(r.handle).toLowerCase() === h;
660
+ if (['connections', 'incoming', 'outgoing'].some((k) => (g[k] || []).some(same))) return refuse(res, 409, 'connection_already_open', 'there is already a request or a connection between you');
661
+ const who = (g.suggestions || []).find(same) || (g.directory || []).find(same);
662
+ if (!who) return refuse(res, 404, 'connection_target_not_found', 'connection_target_not_found');
663
+ g.outgoing = (g.outgoing || []).concat([{ id: String(STUB_CONN_NEXT_ID++), note: body.note || null, created_at: new Date().toISOString(), handle: who.handle, display_name: who.display_name || null, avatar_url: who.avatar_url || null }]);
664
+ g.suggestions = (g.suggestions || []).filter((r) => !same(r));
665
+ return json(res, 201, { status: 'pending' });
666
+ }
667
+ function connAnswer(res, id, verb) {
668
+ const g = graph();
669
+ if (g.inactive) return connInactive(res);
670
+ const r = /^\d{1,19}$/.test(id) && (g.incoming || []).find((x) => x.id === id);
671
+ if (!r) return refuse(res, 404, 'connection_request_not_found', 'connection_request_not_found');
672
+ g.incoming = g.incoming.filter((x) => x !== r);
673
+ if (verb === 'decline') { res.writeHead(204, { 'cache-control': 'no-store' }); return res.end(); }
674
+ g.connections = [{ id: r.id, connected_at: new Date().toISOString(), handle: r.handle, display_name: r.display_name, avatar_url: r.avatar_url }].concat(g.connections || []);
675
+ return json(res, 200, { status: 'accepted' });
676
+ }
677
+ function connRemove(res, id) {
678
+ const g = graph();
679
+ if (g.inactive) return connInactive(res);
680
+ for (const [k, outcome] of [['connections', 'removed'], ['outgoing', 'cancelled']]) {
681
+ const r = /^\d{1,19}$/.test(id) && (g[k] || []).find((x) => x.id === id);
682
+ if (r) { g[k] = g[k].filter((x) => x !== r); return json(res, 200, { outcome }); }
683
+ }
684
+ return refuse(res, 404, 'connection_not_found', 'connection_not_found');
685
+ }
686
+
601
687
  const server = http.createServer((req, res) => {
602
688
  const u = new URL(req.url, 'http://x');
603
689
  const p = u.pathname;
@@ -624,7 +710,16 @@ const server = http.createServer((req, res) => {
624
710
  if (guild && guild[2] === '/members' && req.method === 'GET') return AUTH ? guildRead(res, guild[1], 'members', origin) : json(res, 401, UNAUTH);
625
711
  if (guild && guild[2] === '/membership' && req.method === 'PATCH') return AUTH ? readBody(req, (body) => guildMembershipPatch(res, guild[1], body)) : json(res, 401, UNAUTH);
626
712
  if (guild && guild[2] === '/engagements' && req.method === 'POST') return AUTH ? readBody(req, (body) => guildEngagementPost(res, guild[1], body)) : json(res, 401, UNAUTH);
713
+ if (rest === '/me/guilds' && req.method === 'GET') return AUTH ? myGuilds(res) : json(res, 401, UNAUTH);
627
714
  if (rest === '/me/guild-requests' && req.method === 'GET') return AUTH ? myGuildRequests(res) : json(res, 401, UNAUTH);
715
+ if (rest === '/connections' && req.method === 'GET') return AUTH ? connRead(res, 'list', origin) : json(res, 401, UNAUTH);
716
+ if (rest === '/connections/requests' && req.method === 'GET') return AUTH ? connRead(res, 'requests', origin) : json(res, 401, UNAUTH);
717
+ if (rest === '/connections/suggestions' && req.method === 'GET') return AUTH ? connRead(res, 'suggestions', origin) : json(res, 401, UNAUTH);
718
+ if (rest === '/connections' && req.method === 'POST') return AUTH ? readBody(req, (body) => connAsk(res, body)) : json(res, 401, UNAUTH);
719
+ const connAct = rest.match(/^\/connections\/([^/]+)\/(accept|decline)$/);
720
+ if (connAct && req.method === 'POST') return AUTH ? connAnswer(res, connAct[1], connAct[2]) : json(res, 401, UNAUTH);
721
+ const connDel = rest.match(/^\/connections\/([^/]+)$/);
722
+ if (connDel && req.method === 'DELETE') return AUTH ? connRemove(res, connDel[1]) : json(res, 401, UNAUTH);
628
723
  const answer = rest.match(/^\/guild-requests\/([^/]+)\/(accept|decline)$/);
629
724
  if (answer && req.method === 'POST') return AUTH ? readBody(req, (body) => guildRequestAnswer(res, answer[1], answer[2], body)) : json(res, 401, UNAUTH);
630
725
  if (rest === '/provisioning/instances' && req.method === 'GET') return AUTH ? instances(res, null, false) : json(res, 401, UNAUTH);
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.113",
3
+ "version": "1.21.115",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.113",
9
+ "version": "1.21.115",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",