@bongos/core 1.21.172 → 1.21.173
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +30 -25
- package/docs/adr/0343-a-module-score-is-a-security-gate-then-an-average-of-visible-parts.md +10 -0
- package/docs/api/openapi.json +1 -1
- package/docs/copy-inventory.md +72 -70
- package/docs/copy-registry.json +107 -89
- package/docs/module-api-changelog.md +2 -0
- package/docs/modules-contract.md +2 -1
- package/docs/page-readings.json +90 -89
- package/modules/public-landing/public/index.html +9 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +14 -0
- package/src/bongos/auth-github.js +6 -1
- package/src/bongos/module-listing.js +76 -11
- package/src/bongos/routes/auth.js +6 -2
- package/src/bongos/routes/modules.js +15 -1
- package/src/module-api.js +1 -1
- package/tests/auth_switch_account.mjs +38 -0
- package/tests/module_store_listing.mjs +105 -5
- package/tests/speciality_injection.mjs +20 -0
|
@@ -3181,5 +3181,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3181
3181
|
landed since 1.21.170 with no explicit bump. run 39996371081. (task 1002620)
|
|
3182
3182
|
1.21.172 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3183
3183
|
landed since 1.21.171 with no explicit bump. run 39998870016. (task 1002620)
|
|
3184
|
+
1.21.173 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3185
|
+
landed since 1.21.172 with no explicit bump. run 40000711332. (task 1002620)
|
|
3184
3186
|
---------------------------------------------------------------------------
|
|
3185
3187
|
```
|
package/docs/modules-contract.md
CHANGED
|
@@ -396,7 +396,8 @@ Run `bongos upgrade` to confirm coreVersion compatibility, then `node scripts/gd
|
|
|
396
396
|
- **Getting listed** (task 1003807, [ADR 0343](adr/0343-a-module-score-is-a-security-gate-then-an-average-of-visible-parts.md)). Publishing a version does not by itself put it in the store's catalog, `GET /store/modules`. The catalog shows each module's newest version, judged by its current score. It must pass the Security gate first; a version that failed it, or has not been checked yet, is not listed. A new version with no install or reliability data yet is listed with a **New** label and is not ranked; the floor does not apply to it until it has a real score. Every other version is listed only if its overall score is **at least 50 out of 100**. Below that it is not listed at all. The floor is fixed in Bongos itself (`QUALITY_FLOOR` in `src/bongos/module-listing.js`) and returned in the catalog response; no author or instance setting moves it, and changing it is an owner-approved, reviewed task.
|
|
397
397
|
- **Tester feedback** (task 1003806, ADR 0344 D5). While a version is on the tester channel, what tester projects report about it becomes its Tester feedback part: the share of testers whose install worked and who hit no runtime errors, blended with ten neutral testers so a small sample cannot score high — `round(100 × (went well + 5) / (testers + 10))`. Reports from after the version left the tester channel, and the author's own, never count; under 3 testers it is not scored yet. The rule is `assessTesterFeedback` in `scripts/gds/module-assess-installs.js`.
|
|
398
398
|
- **What a module is strong and weak at** (task 1003799, ADR 0343 D4). Each catalog entry also carries `parts` (the current result of Tests, Clean installs, Reliability, Tester feedback and How-to) and a reading of them: `strengths` (a part scored **80 or more**), `weaknesses` (a part scored **below the floor**) and `unmeasured` (a part with no score yet, which is never counted as weak). Each strength and weakness has a one-sentence `detail` saying what the number rests on. Security is not among them: every listed version has passed it. The reading is `readParts` in `src/bongos/module-listing.js`; the hall's Modules tab shows it in its "In the store" group, so two modules with the same overall score can be told apart.
|
|
399
|
-
- **Sorting by a part** (task 1003800). `GET /store/modules?sort=<part>` orders the listed modules by one part a buyer cares about: `tests`, `install`, `reliability`, `tester_feedback` or `docs` (the response lists them as `sort_parts`, and says which one it applied as `sort`). Modules with a score for that part come first, highest first; a tie goes to the higher overall score, then the module key. A module with no score for that part goes last, so missing data never wins or loses a place on its own. New modules are not sorted: they are never ranked. Any other `sort` value is refused with a 400. Without `sort`,
|
|
399
|
+
- **Sorting by a part** (task 1003800). `GET /store/modules?sort=<part>` orders the listed modules by one part a buyer cares about: `tests`, `install`, `reliability`, `tester_feedback` or `docs` (the response lists them as `sort_parts`, and says which one it applied as `sort`). Modules with a score for that part come first, highest first; a tie goes to the higher overall score, then the module key. A module with no score for that part goes last, so missing data never wins or loses a place on its own. New modules are not sorted: they are never ranked. Any other `sort` value is refused with a 400. Without `sort`, the listed modules are in the ranking below; New stays in key order. The rule is `sortByPart` in `src/bongos/module-listing.js`.
|
|
400
|
+
- **The ranking, and a maximum price** (task 1003808, owner decision 2026-10-23). Without `sort`, listed modules are ranked **quality first**: highest overall score first; between equal scores, free modules first, then the lowest cash price, then the lowest credit price, then the module key. Price never buys a better place, it only orders modules the score could not tell apart (and it is never an input to the score, ADR 0343 D6). The response states the rule as `ranking`, and each entry carries its current `price` (`cents`, `credits`, `free`). A buyer may add `?max_cents=` and/or `?max_credits=` (whole numbers, 0 or more): modules that cost more are left out of both lists, free modules always stay, and a priced module stays when it is offered at or under a cap the buyer set. The applied caps come back as `max_price`. The rules are `rankListed` and `withinMaxPrice` in `src/bongos/module-listing.js`.
|
|
400
401
|
- **Testing new versions first** (task 1003802, [ADR 0344](adr/0344-a-tester-is-a-project-that-opts-in-once.md) D1). A project can choose to get each new version of its modules before everyone else. It is the project that opts in, never one builder for someone else's project, and never by default. `GET /modules/testing` returns `{ tester, general_only, updated_at, updated_by }` to anyone signed in. `POST /modules/testing { tester: true|false }` switches it, and `POST /modules/:key/general-only { general_only: true|false }` keeps one module on the versions everyone gets while the rest are tested. Both writes take the `module.enable` permission, the same one that turns modules on and off. Both are reversible: switching testing off puts the project back on general versions from its next update, and a module's "general only" mark is kept if testing is switched off and on again. The choice is stored in the project's own settings (`modules.tester`, `modules.general_only`); anything that cannot be read counts as not testing. `testsVersionsOf` in `src/bongos/module-testing.js` is the one answer to "does this project get the tester version of this module", which installs and updates use (task 1003805). The hall's Modules tab has the switch in its "Testing new versions" group, and the general-only control in each module's details.
|
|
401
402
|
|
|
402
403
|
### 7. Install from the store
|
package/docs/page-readings.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"adr": "0341 D3",
|
|
6
6
|
"reread": "node scripts/gds/page-reader.js --stale",
|
|
7
7
|
"how": ["Generated by node scripts/gds/page-reader.js (BV2.TW04, task 1004315; ADR 0341 D3). Do not hand-edit.","One entry per page of docs/page-inventory.json. Lines are in page order: the default state first, and a line only another state shows sits where that state shows it, with its state named.","placement: placed = a docs/copy-registry.json row renders it (file, line, string_id); shared = shell text every page of the surface carries (top bar, sidebar, footer), with its row when the registry has one; unplaced = the registry cannot place it (a rewrite becomes an engineer task, ADR 0341 D4).","text keeps {…} where the registry row has a hole, or where a value the page read from its API was filled in. A string that is only such data is counted under omitted.data, not listed.","reading_hash is over the non-shared lines (key, section, text); files_hash is over the page's inventory files. --check compares files_hash only and never opens a browser.","Rendered through the hall-preview harness (hall) and the ui-design kit stub (landing, status), at 1280x800 with the clock pinned, so a reading is the fixture world's page."],
|
|
8
|
-
"counts": {"pages":46,"lines":
|
|
8
|
+
"counts": {"pages":46,"lines":5404,"placed":1697,"shared":399,"unplaced":3308,"omitted_data":3498},
|
|
9
9
|
"pages": [
|
|
10
10
|
{
|
|
11
11
|
"id": "builders:atlas",
|
|
@@ -4772,98 +4772,99 @@
|
|
|
4772
4772
|
"surface": "landing",
|
|
4773
4773
|
"title": "{{productName}}: {{tagline}}",
|
|
4774
4774
|
"states_read": ["out","in","out-projects","in-profile","cli-out"],
|
|
4775
|
-
"files_hash": "
|
|
4776
|
-
"reading_hash": "
|
|
4777
|
-
"counts": {"lines":
|
|
4775
|
+
"files_hash": "bcc230bdbc47ebe3",
|
|
4776
|
+
"reading_hash": "2bc17fece5c02d85",
|
|
4777
|
+
"counts": {"lines":87,"placed":53,"shared":15,"unplaced":19},
|
|
4778
4778
|
"omitted": {"data":6},
|
|
4779
4779
|
"notes": [],
|
|
4780
4780
|
"lines": [
|
|
4781
|
-
{"key":"L0001","section":"skip link","text":"Skip to content","placement":"shared","file":"modules/public-landing/public/index.html","line":
|
|
4781
|
+
{"key":"L0001","section":"skip link","text":"Skip to content","placement":"shared","file":"modules/public-landing/public/index.html","line":669,"string_id":"a061df49e6cb"},
|
|
4782
4782
|
{"key":"L0002","section":"top bar","text":"Projects","placement":"shared"},
|
|
4783
|
-
{"key":"L0003","section":"top bar","text":"
|
|
4784
|
-
{"key":"L0004","section":"
|
|
4785
|
-
{"key":"L0005","section":"page top","text":"
|
|
4786
|
-
{"key":"L0006","section":"page top","text":"
|
|
4787
|
-
{"key":"L0007","section":"page top","text":"
|
|
4788
|
-
{"key":"L0008","section":"page top","text":"
|
|
4789
|
-
{"key":"L0009","section":"top
|
|
4790
|
-
{"key":"L0010","section":"
|
|
4791
|
-
{"key":"L0011","section":"page top","text":"
|
|
4792
|
-
{"key":"L0012","section":"page top","text":"
|
|
4793
|
-
{"key":"L0013","section":"page top","text":"
|
|
4794
|
-
{"key":"L0014","section":"page top","text":"
|
|
4795
|
-
{"key":"L0015","section":"page top","text":"
|
|
4796
|
-
{"key":"L0016","section":"
|
|
4797
|
-
{"key":"L0017","section":"Building Digitally,as simple as Bongos.","text":"
|
|
4798
|
-
{"key":"L0018","section":"Building Digitally,as simple as Bongos.","text":"
|
|
4799
|
-
{"key":"L0019","section":"Building Digitally,as simple as Bongos.","text":"
|
|
4800
|
-
{"key":"L0020","section":"Building Digitally,as simple as Bongos.","text":"
|
|
4801
|
-
{"key":"L0021","section":"
|
|
4802
|
-
{"key":"L0022","section":"Built on Bongos.","text":"
|
|
4803
|
-
{"key":"L0023","section":"Built on Bongos.","text":"
|
|
4804
|
-
{"key":"L0024","section":"Built on Bongos.","text":"
|
|
4805
|
-
{"key":"L0025","section":"Built on Bongos.","text":"
|
|
4806
|
-
{"key":"L0026","section":"Built on Bongos.","text":"
|
|
4807
|
-
{"key":"L0027","section":"Built on Bongos.","text":"
|
|
4808
|
-
{"key":"L0028","section":"Built on Bongos.","text":"
|
|
4809
|
-
{"key":"L0029","section":"
|
|
4810
|
-
{"key":"L0030","section":"A project as a codebase, regardless of function.","text":"A
|
|
4811
|
-
{"key":"L0031","section":"A project as a codebase, regardless of function.","text":"
|
|
4812
|
-
{"key":"L0032","section":"A project as a codebase, regardless of function.","text":"
|
|
4813
|
-
{"key":"L0033","section":"A project as a codebase, regardless of function.","text":"
|
|
4814
|
-
{"key":"L0034","section":"A project as a codebase, regardless of function.","text":"
|
|
4815
|
-
{"key":"L0035","section":"A project as a codebase, regardless of function.","text":"
|
|
4816
|
-
{"key":"L0036","section":"A project as a codebase, regardless of function.","text":"
|
|
4817
|
-
{"key":"L0037","section":"A project as a codebase, regardless of function.","text":"
|
|
4818
|
-
{"key":"L0038","section":"A project as a codebase, regardless of function.","text":"
|
|
4819
|
-
{"key":"L0039","section":"A project as a codebase, regardless of function.","text":"
|
|
4820
|
-
{"key":"L0040","section":"A project as a codebase, regardless of function.","text":"
|
|
4821
|
-
{"key":"L0041","section":"A project as a codebase, regardless of function.","text":"
|
|
4822
|
-
{"key":"L0042","section":"A project as a codebase, regardless of function.","text":"
|
|
4823
|
-
{"key":"L0043","section":"A project as a codebase, regardless of function.","text":"
|
|
4824
|
-
{"key":"L0044","section":"A project as a codebase, regardless of function.","text":"
|
|
4825
|
-
{"key":"L0045","section":"A project as a codebase, regardless of function.","text":"
|
|
4826
|
-
{"key":"L0046","section":"A project as a codebase, regardless of function.","text":"
|
|
4827
|
-
{"key":"L0047","section":"A project as a codebase, regardless of function.","text":"
|
|
4828
|
-
{"key":"L0048","section":"A project as a codebase, regardless of function.","text":"
|
|
4829
|
-
{"key":"L0049","section":"A project as a codebase, regardless of function.","text":"
|
|
4830
|
-
{"key":"L0050","section":"A project as a codebase, regardless of function.","text":"
|
|
4831
|
-
{"key":"L0051","section":"A project as a codebase, regardless of function.","text":"
|
|
4832
|
-
{"key":"L0052","section":"A project as a codebase, regardless of function.","text":"
|
|
4833
|
-
{"key":"L0053","section":"A project as a codebase, regardless of function.","text":"
|
|
4834
|
-
{"key":"L0054","section":"A project as a codebase, regardless of function.","text":"
|
|
4835
|
-
{"key":"L0055","section":"
|
|
4836
|
-
{"key":"L0056","section":"Types of projects","text":"
|
|
4837
|
-
{"key":"L0057","section":"Types of projects","text":"
|
|
4838
|
-
{"key":"L0058","section":"Types of projects","text":"
|
|
4839
|
-
{"key":"L0059","section":"Types of projects","text":"
|
|
4840
|
-
{"key":"L0060","section":"Types of projects","text":"
|
|
4841
|
-
{"key":"L0061","section":"Types of projects","text":"
|
|
4842
|
-
{"key":"L0062","section":"Types of projects","text":"
|
|
4843
|
-
{"key":"L0063","section":"Types of projects","text":"
|
|
4844
|
-
{"key":"L0064","section":"
|
|
4845
|
-
{"key":"L0065","section":"Ways to get paid/rewarded","text":"
|
|
4846
|
-
{"key":"L0066","section":"Ways to get paid/rewarded","text":"
|
|
4847
|
-
{"key":"L0067","section":"Ways to get paid/rewarded","text":"
|
|
4848
|
-
{"key":"L0068","section":"Ways to get paid/rewarded","text":"
|
|
4849
|
-
{"key":"L0069","section":"Ways to get paid/rewarded","text":"
|
|
4850
|
-
{"key":"L0070","section":"Ways to get paid/rewarded","text":"
|
|
4851
|
-
{"key":"L0071","section":"Ways to get paid/rewarded","text":"
|
|
4852
|
-
{"key":"L0072","section":"Ways to get paid/rewarded","text":"
|
|
4853
|
-
{"key":"L0073","section":"Ways to get paid/rewarded","text":"
|
|
4854
|
-
{"key":"L0074","section":"Ways to get paid/rewarded","text":"macOS,
|
|
4855
|
-
{"key":"L0075","section":"Ways to get paid/rewarded","text":"
|
|
4856
|
-
{"key":"L0076","section":"Ways to get paid/rewarded","text":"Linux,
|
|
4857
|
-
{"key":"L0077","section":"
|
|
4858
|
-
{"key":"L0078","section":"footer","text":"
|
|
4859
|
-
{"key":"L0079","section":"footer","text":"
|
|
4860
|
-
{"key":"L0080","section":"footer","text":"
|
|
4861
|
-
{"key":"L0081","section":"footer","text":"
|
|
4862
|
-
{"key":"L0082","section":"footer","text":"
|
|
4863
|
-
{"key":"L0083","section":"footer","text":"
|
|
4864
|
-
{"key":"L0084","section":"footer","text":"
|
|
4865
|
-
{"key":"L0085","section":"footer","text":"
|
|
4866
|
-
{"key":"L0086","section":"footer","text":"
|
|
4783
|
+
{"key":"L0003","section":"top bar","text":"New account","placement":"shared","file":"modules/public-landing/public/index.html","line":696,"string_id":"bb78cca99fe5"},
|
|
4784
|
+
{"key":"L0004","section":"top bar","text":"Review","placement":"shared","state":"in"},
|
|
4785
|
+
{"key":"L0005","section":"page top","text":"My projects","placement":"placed","file":"modules/public-landing/public/index.html","line":724,"string_id":"4a167ef6e515","state":"in-profile"},
|
|
4786
|
+
{"key":"L0006","section":"page top","text":"Invitations","placement":"unplaced","state":"in-profile"},
|
|
4787
|
+
{"key":"L0007","section":"page top","text":"Connections","placement":"unplaced","state":"in-profile"},
|
|
4788
|
+
{"key":"L0008","section":"page top","text":"Account","placement":"unplaced","state":"in-profile"},
|
|
4789
|
+
{"key":"L0009","section":"page top","text":"Sign out","placement":"placed","file":"modules/public-landing/public/index.html","line":731,"string_id":"f9af2d90ce5b","state":"in-profile"},
|
|
4790
|
+
{"key":"L0010","section":"top bar","text":"Sign in","placement":"shared","file":"modules/public-landing/public/index.html","line":698,"string_id":"ad80586f802a"},
|
|
4791
|
+
{"key":"L0011","section":"page top","text":"Sky","placement":"unplaced","state":"out-projects"},
|
|
4792
|
+
{"key":"L0012","section":"page top","text":"a universe of Projects","placement":"placed","file":"modules/public-landing/public/index.html","line":711,"string_id":"ed075e6e5f3a","state":"out-projects"},
|
|
4793
|
+
{"key":"L0013","section":"page top","text":"Table","placement":"unplaced","state":"out-projects"},
|
|
4794
|
+
{"key":"L0014","section":"page top","text":"sky project directory","placement":"placed","file":"modules/public-landing/public/index.html","line":712,"string_id":"dd8f15e45e84","state":"out-projects"},
|
|
4795
|
+
{"key":"L0015","section":"page top","text":"+ Create New Project","placement":"placed","file":"modules/public-landing/public/index.html","line":714,"string_id":"205f6e9eab89","state":"out-projects"},
|
|
4796
|
+
{"key":"L0016","section":"page top","text":"fresh start/fresh idea","placement":"placed","file":"modules/public-landing/public/index.html","line":714,"string_id":"46c35bc7dcdf","state":"out-projects"},
|
|
4797
|
+
{"key":"L0017","section":"Building Digitally,as simple as Bongos.","text":"Building Digitally,","placement":"placed","file":"modules/public-landing/public/index.html","line":745,"string_id":"16bc370da814"},
|
|
4798
|
+
{"key":"L0018","section":"Building Digitally,as simple as Bongos.","text":"as simple as Bongos.","placement":"placed","file":"modules/public-landing/public/index.html","line":745,"string_id":"acbabdef6f40"},
|
|
4799
|
+
{"key":"L0019","section":"Building Digitally,as simple as Bongos.","text":"Bringing AI to the builder, ideator, and artist in everyone.","placement":"placed","file":"modules/public-landing/public/index.html","line":747,"string_id":"750136e8637c"},
|
|
4800
|
+
{"key":"L0020","section":"Building Digitally,as simple as Bongos.","text":"Find a project to join","placement":"placed","file":"modules/public-landing/public/index.html","line":751,"string_id":"483a7eff25f2"},
|
|
4801
|
+
{"key":"L0021","section":"Building Digitally,as simple as Bongos.","text":"Start a project","placement":"placed","file":"modules/public-landing/public/index.html","line":752,"string_id":"2ae36be1c531"},
|
|
4802
|
+
{"key":"L0022","section":"Built on Bongos.","text":"Built on Bongos.","placement":"placed","file":"modules/public-landing/public/index.html","line":761,"string_id":"20b4fa9a0424"},
|
|
4803
|
+
{"key":"L0023","section":"Built on Bongos.","text":"At present, all Bongos projects are held by Verro Labs or its pilot clients.","placement":"placed","file":"modules/public-landing/public/index.html","line":762,"string_id":"b2da3498f53d"},
|
|
4804
|
+
{"key":"L0024","section":"Built on Bongos.","text":"Example World","placement":"placed","file":"modules/public-landing/public/index.html","line":766,"string_id":"1b5a6cfc494c"},
|
|
4805
|
+
{"key":"L0025","section":"Built on Bongos.","text":"a game, 11 builders","placement":"placed","file":"modules/public-landing/public/index.html","line":767,"string_id":"b7cee0f10353"},
|
|
4806
|
+
{"key":"L0026","section":"Built on Bongos.","text":"0 builders","placement":"unplaced"},
|
|
4807
|
+
{"key":"L0027","section":"Built on Bongos.","text":"Emersonian Circles","placement":"placed","file":"modules/public-landing/public/index.html","line":776,"string_id":"9a4a940d2a56"},
|
|
4808
|
+
{"key":"L0028","section":"Built on Bongos.","text":"1 builder","placement":"placed","file":"modules/public-landing/public/index.html","line":772,"string_id":"22825067f4c5"},
|
|
4809
|
+
{"key":"L0029","section":"Built on Bongos.","text":"View Projects","placement":"placed","file":"modules/public-landing/public/index.html","line":780,"string_id":"b97287183335"},
|
|
4810
|
+
{"key":"L0030","section":"A project as a codebase, regardless of function.","text":"A project as a codebase, regardless of function.","placement":"placed","file":"modules/public-landing/public/index.html","line":787,"string_id":"407982c3d4ff"},
|
|
4811
|
+
{"key":"L0031","section":"A project as a codebase, regardless of function.","text":"A laddered beat progresses all tasks.","placement":"placed","file":"modules/public-landing/public/index.html","line":788,"string_id":"545c82e99696"},
|
|
4812
|
+
{"key":"L0032","section":"A project as a codebase, regardless of function.","text":"claim","placement":"unplaced"},
|
|
4813
|
+
{"key":"L0033","section":"A project as a codebase, regardless of function.","text":"Take on a task","placement":"placed","file":"modules/public-landing/public/index.html","line":793,"string_id":"c160ff80c611"},
|
|
4814
|
+
{"key":"L0034","section":"A project as a codebase, regardless of function.","text":"build","placement":"unplaced"},
|
|
4815
|
+
{"key":"L0035","section":"A project as a codebase, regardless of function.","text":"run your preferred model","placement":"placed","file":"modules/public-landing/public/index.html","line":798,"string_id":"efbb62ff5488"},
|
|
4816
|
+
{"key":"L0036","section":"A project as a codebase, regardless of function.","text":"grade","placement":"unplaced"},
|
|
4817
|
+
{"key":"L0037","section":"A project as a codebase, regardless of function.","text":"evaluated by agentic agents","placement":"placed","file":"modules/public-landing/public/index.html","line":803,"string_id":"f8d9d6f2490f"},
|
|
4818
|
+
{"key":"L0038","section":"A project as a codebase, regardless of function.","text":"ship","placement":"unplaced"},
|
|
4819
|
+
{"key":"L0039","section":"A project as a codebase, regardless of function.","text":"agentically merged and made live","placement":"placed","file":"modules/public-landing/public/index.html","line":808,"string_id":"eedec34758da"},
|
|
4820
|
+
{"key":"L0040","section":"A project as a codebase, regardless of function.","text":"credit","placement":"unplaced"},
|
|
4821
|
+
{"key":"L0041","section":"A project as a codebase, regardless of function.","text":"equity or cash payouts","placement":"placed","file":"modules/public-landing/public/index.html","line":813,"string_id":"a7767cf28a36"},
|
|
4822
|
+
{"key":"L0042","section":"A project as a codebase, regardless of function.","text":"1,111 of 1,561 shipped on V1","placement":"placed","file":"modules/public-landing/public/index.html","line":821,"string_id":"14277fea260d"},
|
|
4823
|
+
{"key":"L0043","section":"A project as a codebase, regardless of function.","text":"The six most recent shipped tasks.","placement":"placed","file":"modules/public-landing/public/index.html","line":823,"string_id":"40ed6300ba27"},
|
|
4824
|
+
{"key":"L0044","section":"A project as a codebase, regardless of function.","text":"Landing composition round: three versions of the approved world","placement":"placed","file":"modules/public-landing/public/index.html","line":827,"string_id":"376b549bf7c8"},
|
|
4825
|
+
{"key":"L0045","section":"A project as a codebase, regardless of function.","text":"Percy Jackson","placement":"placed","file":"modules/public-landing/public/index.html","line":828,"string_id":"3eb67b892dea","occurrences":4},
|
|
4826
|
+
{"key":"L0046","section":"A project as a codebase, regardless of function.","text":"27 Aug","placement":"unplaced","occurrences":4},
|
|
4827
|
+
{"key":"L0047","section":"A project as a codebase, regardless of function.","text":"See what you did, by role, including every idea you filed and what became of it","placement":"placed","file":"modules/public-landing/public/index.html","line":833,"string_id":"67b61947244d"},
|
|
4828
|
+
{"key":"L0048","section":"A project as a codebase, regardless of function.","text":"ship.js still writes no session record when the session is rooted in another project dir","placement":"placed","file":"modules/public-landing/public/index.html","line":839,"string_id":"156d37716e14"},
|
|
4829
|
+
{"key":"L0049","section":"A project as a codebase, regardless of function.","text":"World round: PRODUCT.md and four style candidates across landing, hall and status","placement":"placed","file":"modules/public-landing/public/index.html","line":845,"string_id":"8a4892d03781"},
|
|
4830
|
+
{"key":"L0050","section":"A project as a codebase, regardless of function.","text":"Footer doors before launch: privacy, terms and contact pages linked from the landing page and the hub","placement":"placed","file":"modules/public-landing/public/index.html","line":851,"string_id":"0a0667aea2db"},
|
|
4831
|
+
{"key":"L0051","section":"A project as a codebase, regardless of function.","text":"26 Aug","placement":"unplaced","occurrences":2},
|
|
4832
|
+
{"key":"L0052","section":"A project as a codebase, regardless of function.","text":"Task titles are write-once, no route can edit one, so a wrong title is permanent","placement":"placed","file":"modules/public-landing/public/index.html","line":857,"string_id":"052e2da986c2"},
|
|
4833
|
+
{"key":"L0053","section":"A project as a codebase, regardless of function.","text":"VERRO LABS","placement":"placed","file":"modules/public-landing/public/index.html","line":871,"string_id":"6e1001190059"},
|
|
4834
|
+
{"key":"L0054","section":"A project as a codebase, regardless of function.","text":"Built by Verro Labs, Cloud Bongos is our way of making AI co-building usable by entire project groups, whether that's a 5,000-person company, a 100-person department, a college research project, or a solo dev.","placement":"placed","file":"modules/public-landing/public/index.html","line":872,"string_id":"215b5356f401"},
|
|
4835
|
+
{"key":"L0055","section":"A project as a codebase, regardless of function.","text":"For founders","placement":"placed","file":"modules/public-landing/public/index.html","line":882,"string_id":"dbced351ca0d"},
|
|
4836
|
+
{"key":"L0056","section":"Types of projects","text":"Types of projects","placement":"placed","file":"modules/public-landing/public/index.html","line":883,"string_id":"a165999f36a9"},
|
|
4837
|
+
{"key":"L0057","section":"Types of projects","text":"Open source.","placement":"placed","file":"modules/public-landing/public/index.html","line":885,"string_id":"275ca0d5ce65"},
|
|
4838
|
+
{"key":"L0058","section":"Types of projects","text":"Download our core code and protocols, and build instantly with your team.","placement":"placed","file":"modules/public-landing/public/index.html","line":885,"string_id":"d919980d2ca5"},
|
|
4839
|
+
{"key":"L0059","section":"Types of projects","text":"Bongos Managed.","placement":"placed","file":"modules/public-landing/public/index.html","line":887,"string_id":"4782de01c86b"},
|
|
4840
|
+
{"key":"L0060","section":"Types of projects","text":"Buy microtransaction packages of code modules on our marketplace.","placement":"placed","file":"modules/public-landing/public/index.html","line":889,"string_id":"101339c676b4"},
|
|
4841
|
+
{"key":"L0061","section":"Types of projects","text":"Have us handle your hosting.","placement":"placed","file":"modules/public-landing/public/index.html","line":890,"string_id":"41778f67b4c9"},
|
|
4842
|
+
{"key":"L0062","section":"Types of projects","text":"Get access to our advanced project presets and module packages.","placement":"placed","file":"modules/public-landing/public/index.html","line":891,"string_id":"8bb1f00b1558"},
|
|
4843
|
+
{"key":"L0063","section":"Types of projects","text":"Equity issuance, business filing, and builder compensation.","placement":"placed","file":"modules/public-landing/public/index.html","line":892,"string_id":"af5ca714d375"},
|
|
4844
|
+
{"key":"L0064","section":"Types of projects","text":"For builders","placement":"placed","file":"modules/public-landing/public/index.html","line":898,"string_id":"f510ba460300"},
|
|
4845
|
+
{"key":"L0065","section":"Ways to get paid/rewarded","text":"Ways to get paid/rewarded","placement":"placed","file":"modules/public-landing/public/index.html","line":899,"string_id":"3597d73ce7f9"},
|
|
4846
|
+
{"key":"L0066","section":"Ways to get paid/rewarded","text":"Build equity in multiple projects.","placement":"placed","file":"modules/public-landing/public/index.html","line":901,"string_id":"0f1ec6cf9dfa"},
|
|
4847
|
+
{"key":"L0067","section":"Ways to get paid/rewarded","text":"Work as a paid contractor for companies big, medium and small.","placement":"placed","file":"modules/public-landing/public/index.html","line":902,"string_id":"a581f96fb2c8"},
|
|
4848
|
+
{"key":"L0068","section":"Ways to get paid/rewarded","text":"Play as a: game or learning experience.","placement":"placed","file":"modules/public-landing/public/index.html","line":903,"string_id":"384c5b38f500"},
|
|
4849
|
+
{"key":"L0069","section":"Ways to get paid/rewarded","text":"Start a project","placement":"placed","file":"modules/public-landing/public/index.html","line":752,"string_id":"2ae36be1c531"},
|
|
4850
|
+
{"key":"L0070","section":"Ways to get paid/rewarded","text":"Find a project","placement":"placed","file":"modules/public-landing/public/index.html","line":915,"string_id":"0208e4bbba9c"},
|
|
4851
|
+
{"key":"L0071","section":"Ways to get paid/rewarded","text":"Download the CLI","placement":"placed","file":"modules/public-landing/public/index.html","line":918,"string_id":"41c49e22002d","state":"cli-out"},
|
|
4852
|
+
{"key":"L0072","section":"Ways to get paid/rewarded","text":"Windows · v0.4.2","placement":"unplaced","state":"cli-out"},
|
|
4853
|
+
{"key":"L0073","section":"Ways to get paid/rewarded","text":"Also for","placement":"unplaced","state":"cli-out"},
|
|
4854
|
+
{"key":"L0074","section":"Ways to get paid/rewarded","text":"macOS, Apple silicon","placement":"unplaced","state":"cli-out"},
|
|
4855
|
+
{"key":"L0075","section":"Ways to get paid/rewarded","text":"macOS, Intel","placement":"unplaced","state":"cli-out"},
|
|
4856
|
+
{"key":"L0076","section":"Ways to get paid/rewarded","text":"Linux, x64","placement":"unplaced","state":"cli-out"},
|
|
4857
|
+
{"key":"L0077","section":"Ways to get paid/rewarded","text":"Linux, ARM64","placement":"unplaced","state":"cli-out"},
|
|
4858
|
+
{"key":"L0078","section":"footer","text":"A non-profit, capture-resistant commons.","placement":"shared","file":"modules/public-landing/public/index.html","line":928,"string_id":"afe83bbbd0d4"},
|
|
4859
|
+
{"key":"L0079","section":"footer","text":"Built by agents, on Cloud Bongos.","placement":"shared","file":"modules/public-landing/public/index.html","line":928,"string_id":"bd315dd71ea7"},
|
|
4860
|
+
{"key":"L0080","section":"footer","text":"Open source","placement":"shared","file":"modules/public-landing/public/index.html","line":928,"string_id":"d6314ddcc1d0"},
|
|
4861
|
+
{"key":"L0081","section":"footer","text":"AGPL-3.0","placement":"shared"},
|
|
4862
|
+
{"key":"L0082","section":"footer","text":"About","placement":"shared"},
|
|
4863
|
+
{"key":"L0083","section":"footer","text":"Docs","placement":"shared"},
|
|
4864
|
+
{"key":"L0084","section":"footer","text":"Privacy","placement":"shared"},
|
|
4865
|
+
{"key":"L0085","section":"footer","text":"Terms","placement":"shared"},
|
|
4866
|
+
{"key":"L0086","section":"footer","text":"Contact","placement":"shared"},
|
|
4867
|
+
{"key":"L0087","section":"footer","text":"Builders' hall","placement":"shared","file":"modules/public-landing/public/index.html","line":934,"string_id":"90a128c97eae"}
|
|
4867
4868
|
]
|
|
4868
4869
|
},
|
|
4869
4870
|
{
|
|
@@ -173,6 +173,10 @@ main:focus{outline:none;box-shadow:none}
|
|
|
173
173
|
.signin{background:color-mix(in srgb,var(--ink) 92%,var(--bg));color:var(--bg);border:0;
|
|
174
174
|
text-transform:lowercase;font-weight:500;padding:0 26px}
|
|
175
175
|
.signin:hover{background:var(--ink);color:var(--bg)}
|
|
176
|
+
/* the second-account door: an outline beside the filled one, so sign-in stays the first door */
|
|
177
|
+
.newacct{background:transparent;border:1px solid color-mix(in srgb,var(--ink) 40%,transparent);
|
|
178
|
+
text-transform:lowercase;font-weight:500;margin-right:8px}
|
|
179
|
+
.newacct:hover{border-color:var(--ink)}
|
|
176
180
|
|
|
177
181
|
/* the menu bands (.menuBand — .band is this page's content sections): not floating cards. Full width on the card ground, closed by one hairline, a LAYER
|
|
178
182
|
over the page under the bar — the page does not reflow (owner, 2026-08-28; task 1003332). Cells in a
|
|
@@ -495,7 +499,8 @@ body{
|
|
|
495
499
|
.trig{font-size:13.5px}
|
|
496
500
|
.who-name{display:none}
|
|
497
501
|
.pill{min-height:42px;font-size:13.5px}
|
|
498
|
-
.signin{padding:0
|
|
502
|
+
.signin,.newacct{padding:0 14px}
|
|
503
|
+
.newacct{margin-right:6px}
|
|
499
504
|
.bandIn{padding:0 12px 16px}
|
|
500
505
|
.bandRow{grid-template-columns:1fr;gap:0}
|
|
501
506
|
.bandT{font-size:20px}
|
|
@@ -686,6 +691,9 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
686
691
|
<button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
|
|
687
692
|
<!-- the identity slot: the sign-in door signed out; the chip (the profile trigger) once
|
|
688
693
|
the session resolves — swapped client-side, never a wrong-state flash -->
|
|
694
|
+
<!-- a second account (task 1004789): switch=1 makes GitHub show its account picker
|
|
695
|
+
instead of silently re-using whoever is logged in there; stays when signed in -->
|
|
696
|
+
<a class="pill newacct" href="/api/bongos/auth/web/start?return=/&repos=1&switch=1" title="Sign in with a different GitHub account">New account</a>
|
|
689
697
|
<span class="whoSlot" id="who">
|
|
690
698
|
<a class="pill signin" href="/api/bongos/auth/web/start?return=/&repos=1">Sign in</a>
|
|
691
699
|
</span>
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.173",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.173",
|
|
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.
|
|
3
|
+
"version": "1.21.173",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -10106,5 +10106,19 @@
|
|
|
10106
10106
|
"id": "1004670",
|
|
10107
10107
|
"text": "The Studio now shows which pages have the most words to write: a short list of the five pages with the most text, each with how many lines it has and whether it has been tweaked, linked straight to the editor. The suggested ne"
|
|
10108
10108
|
}
|
|
10109
|
+
],
|
|
10110
|
+
"1.21.173": [
|
|
10111
|
+
{
|
|
10112
|
+
"id": "1004789",
|
|
10113
|
+
"text": "The home page now has a \"New account\" button next to Sign in. It opens GitHub's account picker, so you can sign in with a different GitHub account and get a second Cloud Bongos account. The password screen stays as it is."
|
|
10114
|
+
},
|
|
10115
|
+
{
|
|
10116
|
+
"id": "1004817",
|
|
10117
|
+
"text": "When one builder works two tasks at once in two separate copies of the project, each working session now gets only the expert guidance for its own task. Before, an artist task and an engineering task running side by side both"
|
|
10118
|
+
},
|
|
10119
|
+
{
|
|
10120
|
+
"id": "1003808",
|
|
10121
|
+
"text": "The module store now ranks the best-scoring modules first, uses price only to break ties (free first), and lets buyers hide anything above a price they set."
|
|
10122
|
+
}
|
|
10109
10123
|
]
|
|
10110
10124
|
}
|
|
@@ -234,7 +234,7 @@ async function pollDeviceFlow(deviceCode, userAgent) {
|
|
|
234
234
|
// Web Flow (browser)
|
|
235
235
|
// -------------------------------------------------------------------------
|
|
236
236
|
|
|
237
|
-
function buildAuthorizeUrl(state, redirectUri, { repos = false } = {}) {
|
|
237
|
+
function buildAuthorizeUrl(state, redirectUri, { repos = false, switchAccount = false } = {}) {
|
|
238
238
|
const creds = loadGithubCreds();
|
|
239
239
|
if (!creds) throw new Error('GitHub OAuth credentials not configured');
|
|
240
240
|
const u = new URL('https://github.com/login/oauth/authorize');
|
|
@@ -244,6 +244,11 @@ function buildAuthorizeUrl(state, redirectUri, { repos = false } = {}) {
|
|
|
244
244
|
// other caller gets identity-only. scopeForWebFlow keeps that decision in one place.
|
|
245
245
|
u.searchParams.set('scope', scopeForWebFlow({ repos }));
|
|
246
246
|
u.searchParams.set('state', state);
|
|
247
|
+
// `switchAccount:true` (the home page's "New account" button, task 1004789):
|
|
248
|
+
// GitHub otherwise silently re-uses whoever is logged in to github.com in this
|
|
249
|
+
// browser, so a second account could never be reached. select_account makes
|
|
250
|
+
// GitHub show its account picker, with "Use a different account".
|
|
251
|
+
if (switchAccount) u.searchParams.set('prompt', 'select_account');
|
|
247
252
|
return u.toString();
|
|
248
253
|
}
|
|
249
254
|
|
|
@@ -126,6 +126,54 @@ function sortByPart(entries, part) {
|
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// The ranking: quality first, price breaks ties (task 1003808, owner 2026-10-23)
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
//
|
|
133
|
+
// Above the floor, the default order of `listed` is the rule an author and a buyer
|
|
134
|
+
// can both check in one sentence:
|
|
135
|
+
//
|
|
136
|
+
// highest overall score first; between equal scores the cheaper module first
|
|
137
|
+
// (free first, then the lowest cash price, then the lowest credit price); then
|
|
138
|
+
// the key.
|
|
139
|
+
//
|
|
140
|
+
// Price never buys a better place: it only orders modules the score could not tell
|
|
141
|
+
// apart (price is never an input to the score, ADR 0343 D6). A buyer may also set a
|
|
142
|
+
// maximum price, which leaves out what costs more — a filter, not a ranking.
|
|
143
|
+
const RANKING = 'Highest overall score first; between equal scores, free modules and then the cheapest come first.';
|
|
144
|
+
|
|
145
|
+
// A module's current price row ({ price_cents, price_credits } or none) as the
|
|
146
|
+
// catalog shows it. No row is free (isFreePrice, module-store.js); 0 in either
|
|
147
|
+
// currency is free; null means not offered in that currency (core_257). Pure.
|
|
148
|
+
function priceOf(row) {
|
|
149
|
+
const cents = row && row.price_cents != null ? Number(row.price_cents) : null;
|
|
150
|
+
const credits = row && row.price_credits != null ? Number(row.price_credits) : null;
|
|
151
|
+
return { cents, credits, free: !row || cents === 0 || credits === 0 };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Within a buyer's maximum ({ cents, credits }, either may be null): a free module
|
|
155
|
+
// always is; a priced one is when it is offered at or under a cap the buyer set. Pure.
|
|
156
|
+
function withinMaxPrice(price, max) {
|
|
157
|
+
if (!max || (max.cents == null && max.credits == null)) return true;
|
|
158
|
+
if (price.free) return true;
|
|
159
|
+
return (max.cents != null && price.cents != null && price.cents <= max.cents)
|
|
160
|
+
|| (max.credits != null && price.credits != null && price.credits <= max.credits);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// The default order of `listed`, per RANKING. Returns a new array. Pure.
|
|
164
|
+
function rankListed(entries) {
|
|
165
|
+
const cheap = (n) => (n == null ? Infinity : n);
|
|
166
|
+
return [...entries].sort((a, b) => {
|
|
167
|
+
const pa = a.price || priceOf(null);
|
|
168
|
+
const pb = b.price || priceOf(null);
|
|
169
|
+
return ((b.overall ?? -1) - (a.overall ?? -1))
|
|
170
|
+
|| (Number(pb.free) - Number(pa.free))
|
|
171
|
+
|| (cheap(pa.cents) - cheap(pb.cents))
|
|
172
|
+
|| (cheap(pa.credits) - cheap(pb.credits))
|
|
173
|
+
|| a.key.localeCompare(b.key);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
129
177
|
// The newest version row per module key, in semver order (a version that does not
|
|
130
178
|
// parse is skipped, as latestVersions does in module-store.js).
|
|
131
179
|
function newestPerModule(rows) {
|
|
@@ -140,13 +188,14 @@ function newestPerModule(rows) {
|
|
|
140
188
|
return [...out.values()];
|
|
141
189
|
}
|
|
142
190
|
|
|
143
|
-
// The catalog: { floor, sort, sort_parts, listed: [...], new: [...] },
|
|
144
|
-
// { key, title, version, published_at, overall, is_new, parts,
|
|
145
|
-
// unmeasured } (the last four from readParts). Excluded
|
|
146
|
-
// out entirely: a module below the bar is not listed at all
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
191
|
+
// The catalog: { floor, ranking, sort, sort_parts, max_price, listed: [...], new: [...] },
|
|
192
|
+
// each entry { key, title, version, published_at, price, overall, is_new, parts,
|
|
193
|
+
// strengths, weaknesses, unmeasured } (the last four from readParts). Excluded
|
|
194
|
+
// versions are left out entirely: a module below the bar is not listed at all, and
|
|
195
|
+
// neither is one over the buyer's `maxPrice`. `listed` is in rankListed order unless
|
|
196
|
+
// `sort` names a part, which orders it by sortByPart; `new` is never ranked and
|
|
197
|
+
// stays in key order.
|
|
198
|
+
async function listCatalog({ db, floor = QUALITY_FLOOR, sort = null, maxPrice = null } = {}) {
|
|
150
199
|
const pool = db || require('./pool').pool;
|
|
151
200
|
const { rows: versions } = await pool.query(
|
|
152
201
|
`SELECT m.module_key, m.title, v.id AS version_id, v.version, v.published_at
|
|
@@ -162,6 +211,15 @@ async function listCatalog({ db, floor = QUALITY_FLOOR, sort = null } = {}) {
|
|
|
162
211
|
ORDER BY version_id, computed_at DESC, id DESC`, [newest.map((r) => r.version_id)]);
|
|
163
212
|
for (const s of rows) scores.set(String(s.version_id), s);
|
|
164
213
|
}
|
|
214
|
+
const prices = new Map();
|
|
215
|
+
if (newest.length) {
|
|
216
|
+
const { rows } = await pool.query(
|
|
217
|
+
`SELECT DISTINCT ON (module_key) module_key, price_cents, price_credits
|
|
218
|
+
FROM store_module_prices
|
|
219
|
+
WHERE module_key = ANY($1)
|
|
220
|
+
ORDER BY module_key, set_at DESC, id DESC`, [newest.map((r) => r.module_key)]);
|
|
221
|
+
for (const p of rows) prices.set(p.module_key, p);
|
|
222
|
+
}
|
|
165
223
|
// Each version's current result per part: the newest row per (version, part),
|
|
166
224
|
// the read idx_module_assessment_signals_current exists for (core_265).
|
|
167
225
|
const signals = new Map();
|
|
@@ -177,19 +235,26 @@ async function listCatalog({ db, floor = QUALITY_FLOOR, sort = null } = {}) {
|
|
|
177
235
|
signals.get(k).push(s);
|
|
178
236
|
}
|
|
179
237
|
}
|
|
180
|
-
const
|
|
238
|
+
const max = maxPrice && (maxPrice.cents != null || maxPrice.credits != null)
|
|
239
|
+
? { cents: maxPrice.cents ?? null, credits: maxPrice.credits ?? null } : null;
|
|
240
|
+
const out = { floor, ranking: RANKING, sort: sort || null, sort_parts: SORT_PARTS, max_price: max, listed: [], new: [] };
|
|
181
241
|
for (const r of newest.sort((a, b) => a.module_key.localeCompare(b.module_key))) {
|
|
182
242
|
const score = scores.get(String(r.version_id)) || null;
|
|
183
243
|
const { placement } = placeVersion(score, floor);
|
|
184
244
|
if (placement === 'excluded') continue;
|
|
245
|
+
const price = priceOf(prices.get(r.module_key));
|
|
246
|
+
if (!withinMaxPrice(price, max)) continue;
|
|
185
247
|
out[placement].push({
|
|
186
|
-
key: r.module_key, title: r.title, version: r.version, published_at: r.published_at,
|
|
248
|
+
key: r.module_key, title: r.title, version: r.version, published_at: r.published_at, price,
|
|
187
249
|
overall: placement === 'new' ? null : score.overall, is_new: placement === 'new',
|
|
188
250
|
...readParts(signals.get(String(r.version_id)) || [], { floor }),
|
|
189
251
|
});
|
|
190
252
|
}
|
|
191
|
-
|
|
253
|
+
out.listed = sort ? sortByPart(out.listed, sort) : rankListed(out.listed);
|
|
192
254
|
return out;
|
|
193
255
|
}
|
|
194
256
|
|
|
195
|
-
module.exports = {
|
|
257
|
+
module.exports = {
|
|
258
|
+
QUALITY_FLOOR, STRONG_AT, SORT_PARTS, RANKING, placeVersion, readParts, sortByPart,
|
|
259
|
+
priceOf, withinMaxPrice, rankListed, listCatalog,
|
|
260
|
+
};
|
|
@@ -697,6 +697,9 @@ module.exports = function buildAuthRouter() {
|
|
|
697
697
|
return sendAuthNotConfigured(res);
|
|
698
698
|
}
|
|
699
699
|
const wantsRepos = req.query.repos === '1' || req.query.repos === 'true';
|
|
700
|
+
// ?switch=1 — sign in as a DIFFERENT GitHub account (task 1004789): show
|
|
701
|
+
// GitHub's account picker instead of re-using the one logged in there.
|
|
702
|
+
const wantsSwitch = req.query.switch === '1' || req.query.switch === 'true';
|
|
700
703
|
// A sign-in begun on a satellite hub host (builders./status.) restarts on
|
|
701
704
|
// the canonical host (task 1004357). The handshake cookies are host-only now,
|
|
702
705
|
// and the callback always lands on the canonical host, so a state cookie set
|
|
@@ -708,13 +711,14 @@ module.exports = function buildAuthRouter() {
|
|
|
708
711
|
const back = raw.startsWith('/') ? `https://${host}${raw}` : raw;
|
|
709
712
|
const params = new URLSearchParams({ return: back });
|
|
710
713
|
if (wantsRepos) params.set('repos', '1');
|
|
714
|
+
if (wantsSwitch) params.set('switch', '1');
|
|
711
715
|
return res.redirect(`${WEB_REDIRECT_ORIGIN.replace(/\/+$/, '')}${apiBase(req)}/auth/web/start?${params}`);
|
|
712
716
|
}
|
|
713
717
|
// Already signed in on the canonical host and headed for a satellite hub
|
|
714
718
|
// host (a builder who signed in on the apex, then opened builders.): no
|
|
715
719
|
// GitHub round-trip, just hand that host its copy. A repo-scope sign-in
|
|
716
720
|
// still goes to GitHub — it exists to ask for the broader grant.
|
|
717
|
-
if (!wantsRepos) {
|
|
721
|
+
if (!wantsRepos && !wantsSwitch) {
|
|
718
722
|
const target = safeReturnTo(req.query.return);
|
|
719
723
|
if (target && !target.startsWith('/')) {
|
|
720
724
|
try {
|
|
@@ -774,7 +778,7 @@ module.exports = function buildAuthRouter() {
|
|
|
774
778
|
// way — the callback validates it identically, so the hub leg is CSRF-protected too.
|
|
775
779
|
res.redirect(auth.idpConfigured()
|
|
776
780
|
? auth.buildIdpAuthorizeUrl(state, redirectUri)
|
|
777
|
-
: auth.buildAuthorizeUrl(state, redirectUri, { repos: wantsRepos }));
|
|
781
|
+
: auth.buildAuthorizeUrl(state, redirectUri, { repos: wantsRepos, switchAccount: wantsSwitch }));
|
|
778
782
|
});
|
|
779
783
|
|
|
780
784
|
// GET /auth/web/handoff?code=&return= — the far side of the one-time session
|