@bongos/core 1.23.14 → 1.23.16
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 +96 -61
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +22 -1
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +932 -886
- package/docs/copy-registry.json +1807 -1336
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +8 -3
- package/docs/page-readings.json +578 -558
- package/modules/copy-desk/page-decisions.js +32 -4
- package/modules/copy-desk/page-status.js +19 -10
- package/modules/copy-desk/pages.js +61 -0
- package/modules/copy-desk/routes/copy-desk.js +7 -3
- package/modules/copy-desk/skills/tweak/SKILL.md +1 -1
- package/modules/copy-desk/tests/copy_page_next.mjs +1 -1
- package/modules/copy-desk/tests/copy_page_status.mjs +9 -4
- package/modules/hall-ui/public/approval-queue-lib.js +10 -3
- package/modules/hall-ui/public/approval-queue.js +7 -5
- package/modules/hall-ui/public/studio-lib.js +3 -2
- package/modules/lifecycle/lifecycle.js +2 -0
- package/modules/lifecycle/page-tweak-approve.js +50 -2
- package/modules/platform-identity/builder-of-the-week.js +180 -0
- package/modules/platform-identity/migrations/platform_identity_032_weekly_features.sql +53 -0
- package/modules/platform-identity/routes/community-leaderboard.js +25 -1
- package/modules/public-landing/public/account.html +2 -1
- package/modules/public-landing/public/community-week-none.states.json +16 -0
- package/modules/public-landing/public/community.html +142 -13
- package/modules/public-landing/public/community.states.json +6 -4
- package/modules/public-landing/public/connections.html +2 -1
- package/modules/public-landing/public/contact.html +2 -1
- package/modules/public-landing/public/guild.html +2 -1
- package/modules/public-landing/public/index.html +2 -1
- package/modules/public-landing/public/invitations.html +2 -1
- package/modules/public-landing/public/privacy.html +2 -1
- package/modules/public-landing/public/projects.html +2 -1
- package/modules/public-landing/public/terms.html +2 -1
- package/modules/ui-design/kit/fixtures/community-week-none.json +6 -0
- package/modules/ui-design/kit/fixtures/community-week.json +11 -0
- package/modules/ui-design/kit/serve.js +8 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/run-unit-tests.js +4 -0
- package/src/module-api.js +1 -1
- package/src/platform-server.js +5 -5
- package/tests/builder_of_the_week.mjs +266 -0
- package/tests/builder_of_the_week_db.mjs +166 -0
- package/tests/copy_desk_page_approve.mjs +53 -0
- package/tests/copy_desk_page_editor.mjs +2 -2
- package/tests/copy_desk_page_next.mjs +1 -1
- package/tests/copy_desk_page_release.mjs +1 -1
- package/tests/copy_desk_page_submit.mjs +2 -2
- package/tests/copy_stuck_page_tweaks.mjs +12 -3
- package/tests/hall_approval_queue.mjs +6 -6
- package/tests/hub_account_page.mjs +2 -2
- package/tests/hub_community_page.mjs +136 -25
- package/tests/landing_legal_pages.mjs +2 -2
- package/tests/privacy_matrix.mjs +53 -8
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
<div class="bandIn">
|
|
92
92
|
<ul class="bandRow">
|
|
93
93
|
<li class="bandCell"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li>
|
|
94
|
+
<li class="bandCell"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li>
|
|
94
95
|
<li class="bandCell"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li>
|
|
95
96
|
<li class="bandCell"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li>
|
|
96
97
|
<li class="bandCell knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
@@ -104,7 +105,7 @@
|
|
|
104
105
|
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
105
106
|
<li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
106
107
|
<li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
|
|
107
|
-
<li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
108
|
+
<li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
108
109
|
</ul>
|
|
109
110
|
</div>
|
|
110
111
|
</div>
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
<div class="bandIn">
|
|
72
72
|
<ul class="bandRow">
|
|
73
73
|
<li class="bandCell"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li>
|
|
74
|
+
<li class="bandCell"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li>
|
|
74
75
|
<li class="bandCell"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li>
|
|
75
76
|
<li class="bandCell"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li>
|
|
76
77
|
<li class="bandCell knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
85
86
|
<li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
86
87
|
<li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
|
|
87
|
-
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
88
|
+
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
88
89
|
</ul>
|
|
89
90
|
</div>
|
|
90
91
|
</div>
|
|
@@ -146,6 +146,7 @@
|
|
|
146
146
|
<div class="bandIn">
|
|
147
147
|
<ul class="bandRow">
|
|
148
148
|
<li class="bandCell"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li>
|
|
149
|
+
<li class="bandCell"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li>
|
|
149
150
|
<li class="bandCell"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li>
|
|
150
151
|
<li class="bandCell"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li>
|
|
151
152
|
<li class="bandCell knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
@@ -159,7 +160,7 @@
|
|
|
159
160
|
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
160
161
|
<li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
161
162
|
<li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
|
|
162
|
-
<li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
163
|
+
<li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
163
164
|
</ul>
|
|
164
165
|
</div>
|
|
165
166
|
</div>
|
|
@@ -720,6 +720,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
720
720
|
<div class="bandIn">
|
|
721
721
|
<ul class="bandRow">
|
|
722
722
|
<li class="bandCell"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li>
|
|
723
|
+
<li class="bandCell"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li>
|
|
723
724
|
<li class="bandCell"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li>
|
|
724
725
|
<li class="bandCell"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li>
|
|
725
726
|
<li class="bandCell knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
@@ -733,7 +734,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
733
734
|
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">sky project directory</span></li>
|
|
734
735
|
<li class="bandCell" id="mineCell" hidden><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
735
736
|
<li class="bandCell"><a class="bandT" href="/projects?view=new">+ Create New Project</a><span class="bandC">fresh start/fresh idea</span></li>
|
|
736
|
-
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
737
|
+
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
737
738
|
</ul>
|
|
738
739
|
<!-- About and Docs are site pages, not project views: they live in the footer row with
|
|
739
740
|
Privacy / Terms / Contact (owner, 2026-08-28; task 1003332). -->
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
<div class="bandIn">
|
|
102
102
|
<ul class="bandRow">
|
|
103
103
|
<li class="bandCell"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li>
|
|
104
|
+
<li class="bandCell"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li>
|
|
104
105
|
<li class="bandCell"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li>
|
|
105
106
|
<li class="bandCell"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li>
|
|
106
107
|
<li class="bandCell knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
@@ -114,7 +115,7 @@
|
|
|
114
115
|
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
115
116
|
<li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
116
117
|
<li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
|
|
117
|
-
<li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
118
|
+
<li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
118
119
|
</ul>
|
|
119
120
|
</div>
|
|
120
121
|
</div>
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
<div class="bandIn">
|
|
71
71
|
<ul class="bandRow">
|
|
72
72
|
<li class="bandCell"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li>
|
|
73
|
+
<li class="bandCell"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li>
|
|
73
74
|
<li class="bandCell"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li>
|
|
74
75
|
<li class="bandCell"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li>
|
|
75
76
|
<li class="bandCell knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
@@ -83,7 +84,7 @@
|
|
|
83
84
|
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
84
85
|
<li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
85
86
|
<li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
|
|
86
|
-
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
87
|
+
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
87
88
|
</ul>
|
|
88
89
|
</div>
|
|
89
90
|
</div>
|
|
@@ -859,6 +859,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
859
859
|
<div class="bandIn">
|
|
860
860
|
<ul class="bandRow">
|
|
861
861
|
<li class="bandCell"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li>
|
|
862
|
+
<li class="bandCell"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li>
|
|
862
863
|
<li class="bandCell"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li>
|
|
863
864
|
<li class="bandCell"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li>
|
|
864
865
|
<li class="bandCell knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
@@ -874,7 +875,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
874
875
|
<li class="bandCell"><a class="bandT" href="?view=map&sub=table" data-nav="map" data-sub="table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
875
876
|
<li class="bandCell"><a class="bandT" href="?view=mine" data-nav="mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
876
877
|
<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>
|
|
877
|
-
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
878
|
+
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
878
879
|
</ul>
|
|
879
880
|
<!-- About and Docs are site pages, not project views: they live in the
|
|
880
881
|
footer row with Privacy / Terms / Contact (owner, 2026-08-28; task 1003332). -->
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
<div class="bandIn">
|
|
71
71
|
<ul class="bandRow">
|
|
72
72
|
<li class="bandCell"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li>
|
|
73
|
+
<li class="bandCell"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li>
|
|
73
74
|
<li class="bandCell"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li>
|
|
74
75
|
<li class="bandCell"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li>
|
|
75
76
|
<li class="bandCell knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li>
|
|
@@ -83,7 +84,7 @@
|
|
|
83
84
|
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
84
85
|
<li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
85
86
|
<li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
|
|
86
|
-
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
87
|
+
<!--apex-only:start--><li class="bandCell bandFold"><a class="bandT" href="/community">Leaderboard</a><span class="bandC">who earned the most, across every project</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/week">Builder of the week</a><span class="bandC">the top earner of each week, and how it is chosen</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/find">Find a builder</a><span class="bandC">anyone building here, by name or role</span></li><li class="bandCell bandFold"><a class="bandT" href="/community/recruiters">Open to recruiters</a><span class="bandC">who is open to recruiting, for project owners</span></li><li class="bandCell bandFold knowCell" hidden><a class="bandT" href="/connections#connSuggest">Builders you may know</a><span class="bandC">connections or projects in common</span></li><!--apex-only:end-->
|
|
87
88
|
</ul>
|
|
88
89
|
</div>
|
|
89
90
|
</div>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "GET /community/builder-of-the-week for the kit stub (task 1002981): a week nobody won (or whose winner hides) and no past weeks yet, the answer on a hub's first week.",
|
|
3
|
+
"week": { "start": "2026-10-19", "end": "2026-10-26" },
|
|
4
|
+
"winner": null,
|
|
5
|
+
"past": []
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "GET /community/builder-of-the-week for the kit stub (task 1002981): the last full week, its winner and the weeks before it, newest first, as builder-of-the-week.js answers. A week whose winner hides, or that nobody won, is simply absent (21 Sep). Names are invented; the long one tests the row's wrap, 28 Sep the month-crossing label.",
|
|
3
|
+
"week": { "start": "2026-10-19", "end": "2026-10-26" },
|
|
4
|
+
"winner": { "week_start": "2026-10-19", "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 1240, "tasks_shipped": 4 },
|
|
5
|
+
"past": [
|
|
6
|
+
{ "week_start": "2026-10-12", "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 2210, "tasks_shipped": 9 },
|
|
7
|
+
{ "week_start": "2026-10-05", "handle": "maximiliana-van-der-berghe-lindqvist", "github_login": "mvdbl", "display_name": "Maximiliana van der Berghe-Lindqvist", "avatar_url": "/__kit/avatar.svg", "credits": 610, "tasks_shipped": 1 },
|
|
8
|
+
{ "week_start": "2026-09-28", "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 1875, "tasks_shipped": 6 },
|
|
9
|
+
{ "week_start": "2026-09-14", "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 430, "tasks_shipped": 2 }
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -729,6 +729,13 @@ function communitySearch(res, u, origin) {
|
|
|
729
729
|
.map((r) => withAvatar(r, origin));
|
|
730
730
|
return json(res, 200, { query: q, discipline: disc || null, count: results.length, results, page: { limit: 20, offset: 0 } });
|
|
731
731
|
}
|
|
732
|
+
// Builder of the Week (task 1002981): the fixture is already the route's answer — the
|
|
733
|
+
// week, its winner (or null) and the weeks before, newest first (builder-of-the-week.js).
|
|
734
|
+
function communityWeek(res, origin) {
|
|
735
|
+
const f = heldFixture('community-week');
|
|
736
|
+
if (stubStatus(res, f)) return;
|
|
737
|
+
return json(res, 200, { week: f.week, winner: f.winner ? withAvatar(f.winner, origin) : null, past: (f.past || []).map((r) => withAvatar(r, origin)) });
|
|
738
|
+
}
|
|
732
739
|
function scoutingRead(res, origin) {
|
|
733
740
|
const f = heldFixture('scouting');
|
|
734
741
|
if (stubStatus(res, f)) return;
|
|
@@ -766,6 +773,7 @@ const server = http.createServer((req, res) => {
|
|
|
766
773
|
if (rest === '/me/guild-requests' && req.method === 'GET') return AUTH ? myGuildRequests(res) : json(res, 401, UNAUTH);
|
|
767
774
|
if (rest === '/community/leaderboard' && req.method === 'GET') return communityBoard(res, u, origin);
|
|
768
775
|
if (rest === '/community/search' && req.method === 'GET') return communitySearch(res, u, origin);
|
|
776
|
+
if (rest === '/community/builder-of-the-week' && req.method === 'GET') return communityWeek(res, origin);
|
|
769
777
|
if (rest === '/scouting' && req.method === 'GET') return AUTH ? scoutingRead(res, origin) : json(res, 401, UNAUTH);
|
|
770
778
|
if (rest === '/connections' && req.method === 'GET') return AUTH ? connRead(res, 'list', origin) : json(res, 401, UNAUTH);
|
|
771
779
|
if (rest === '/connections/requests' && req.method === 'GET') return AUTH ? connRead(res, 'requests', origin) : json(res, 401, UNAUTH);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.16",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.23.
|
|
9
|
+
"version": "1.23.16",
|
|
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.23.
|
|
3
|
+
"version": "1.23.16",
|
|
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
|
@@ -10473,5 +10473,17 @@
|
|
|
10473
10473
|
"id": "1004644",
|
|
10474
10474
|
"text": "The tweak editor's split view now shows the landing and status pages under the words, instead of opening a second window."
|
|
10475
10475
|
}
|
|
10476
|
+
],
|
|
10477
|
+
"1.23.15": [
|
|
10478
|
+
{
|
|
10479
|
+
"id": "1002981",
|
|
10480
|
+
"text": "The community pages now have a Builder of the week: each Monday the builder who earned the most in the week before, among those who shipped something, is crowned and kept on record. It has its own page, with the rule spelled o"
|
|
10481
|
+
}
|
|
10482
|
+
],
|
|
10483
|
+
"1.23.16": [
|
|
10484
|
+
{
|
|
10485
|
+
"id": "1004900",
|
|
10486
|
+
"text": "An artist's rewritten page words are now approved in the Studio before anything changes on the page. Only approved words go on to be applied, and words sent back become a draft the artist can change again."
|
|
10487
|
+
}
|
|
10476
10488
|
]
|
|
10477
10489
|
}
|
|
@@ -71,6 +71,10 @@ const INTEGRATION = new Set([
|
|
|
71
71
|
// counting as zero, and the privacy matrix filtering are all planner facts the
|
|
72
72
|
// DB-free route test can only assert the shape of.
|
|
73
73
|
'community_leaderboard_db',
|
|
74
|
+
// task 1002981: Builder of the Week against real SQL — which snapshots open and
|
|
75
|
+
// close the week, the shipped-work floor and tie order, and the ON CONFLICT pin
|
|
76
|
+
// under two racing writers. The DB-free sibling is builder_of_the_week.
|
|
77
|
+
'builder_of_the_week_db',
|
|
74
78
|
// bfg, bfg_write, memory_isolation, memory_pull and setup_smoke LEFT this set under task
|
|
75
79
|
// 1004865: none of them ever needed a database. Each failed on a stale test harness (a fake
|
|
76
80
|
// server without res.fail, a stub on the wrong module, an old error shape, a renamed session
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.23.
|
|
84
|
+
const CORE_VERSION = '1.23.16'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/src/platform-server.js
CHANGED
|
@@ -338,11 +338,11 @@ function buildPlatformApp() {
|
|
|
338
338
|
res.type('html').send(html);
|
|
339
339
|
});
|
|
340
340
|
// THE COMMUNITY PAGES (task 1003363, ADR 0217 D1, ADR 0337 D1/D3): who is building
|
|
341
|
-
// across every project — the leaderboard, the builder
|
|
342
|
-
// viewer) and the recruiter list GET /scouting's own
|
|
343
|
-
//
|
|
344
|
-
// every read it makes is a platform-identity route.
|
|
345
|
-
app.get(['/community', '/community/find', '/community/recruiters'], (req, res, next) => {
|
|
341
|
+
// across every project — the leaderboard, the builder of the week (task 1002981), the
|
|
342
|
+
// builder search (the same for every viewer) and the recruiter list GET /scouting's own
|
|
343
|
+
// door admits. One file serves all four; its script shows the one the path names.
|
|
344
|
+
// Hub-only for the /account reason: every read it makes is a platform-identity route.
|
|
345
|
+
app.get(['/community', '/community/week', '/community/find', '/community/recruiters'], (req, res, next) => {
|
|
346
346
|
if (!isApexHost(req) || !isModuleEnabled('platform-identity')) return next();
|
|
347
347
|
const html = landingPage('community.html');
|
|
348
348
|
if (!html) return next();
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// tests/builder_of_the_week.mjs — Builder of the Week (community spec D5; task 1002981).
|
|
2
|
+
//
|
|
3
|
+
// DB-free. A fake pool answers the module's four statements: the existence probe on
|
|
4
|
+
// platform_identity_weekly_features, the winner computation, the INSERT that records
|
|
5
|
+
// the week, and the read of the crown and the weeks before it. Which snapshot becomes
|
|
6
|
+
// the week's start and end, and that the predicate really filters, need a real
|
|
7
|
+
// Postgres: tests/builder_of_the_week_db.mjs.
|
|
8
|
+
//
|
|
9
|
+
// What this pins:
|
|
10
|
+
// 1. the week: the last FULL Monday-to-Monday week (UTC) before now, at every
|
|
11
|
+
// boundary of the week.
|
|
12
|
+
// 2. the statements: both account reads carry the leaderboard's NUMBERS predicate
|
|
13
|
+
// (rollupVisibleSql) and not the recruiting one; the winner needs a shipped work
|
|
14
|
+
// and ties go to works, then karma, then handle; the write never overwrites.
|
|
15
|
+
// 3. crown at render: a recorded week is never recomputed; an unrecorded one is
|
|
16
|
+
// computed once and written once, however many reads arrive together; a week
|
|
17
|
+
// nobody won is written with no winner; a failed crowning is retried next read.
|
|
18
|
+
// 4. the read: the crown only for this week's row, the past weeks newest first, at
|
|
19
|
+
// most ARCHIVE_WEEKS of them, and no github_id anywhere.
|
|
20
|
+
// 5. the route: no-store, the same body with or without credentials, the 500
|
|
21
|
+
// envelope, and its own rate-limit scope.
|
|
22
|
+
//
|
|
23
|
+
// Run: node tests/builder_of_the_week.mjs
|
|
24
|
+
|
|
25
|
+
import { strict as assert } from 'node:assert';
|
|
26
|
+
import { createRequire } from 'node:module';
|
|
27
|
+
import http from 'node:http';
|
|
28
|
+
|
|
29
|
+
const require = createRequire(import.meta.url);
|
|
30
|
+
const express = require('express');
|
|
31
|
+
|
|
32
|
+
let passed = 0;
|
|
33
|
+
let failed = 0;
|
|
34
|
+
async function t(name, fn) {
|
|
35
|
+
try { await fn(); passed++; console.log(` ok ${name}`); }
|
|
36
|
+
catch (e) { failed++; console.error(` FAIL ${name}\n ${e.message}`); }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { rollupVisibleSql, scoutingListableSql } = require('../modules/platform-identity/account-visibility.js');
|
|
40
|
+
|
|
41
|
+
// ---- fake engine ------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
const PROBE = /^SELECT 1 FROM platform_identity_weekly_features WHERE week_start = \$1::date$/;
|
|
44
|
+
const isWinner = (sql) => /platform_identity_activity_snapshots/.test(sql);
|
|
45
|
+
const isInsert = (sql) => /^INSERT INTO platform_identity_weekly_features/.test(sql);
|
|
46
|
+
const isRead = (sql) => /FROM platform_identity_weekly_features f\s+JOIN platform_identity_accounts pa/.test(sql);
|
|
47
|
+
|
|
48
|
+
const engine = {
|
|
49
|
+
calls: [],
|
|
50
|
+
recorded: new Set(), // weeks the probe finds
|
|
51
|
+
winner: null, // what the winner statement returns
|
|
52
|
+
shown: [], // what the read returns (already filtered, newest first)
|
|
53
|
+
throwOn: null, // a predicate over the SQL that makes a call fail once
|
|
54
|
+
slowWinner: false,
|
|
55
|
+
query: async (text, params = []) => {
|
|
56
|
+
const sql = String(text).replace(/\s+/g, ' ').trim();
|
|
57
|
+
engine.calls.push({ sql, params });
|
|
58
|
+
if (engine.throwOn && engine.throwOn(sql)) { engine.throwOn = null; throw new Error('boom'); }
|
|
59
|
+
if (PROBE.test(sql)) return { rows: engine.recorded.has(params[0]) ? [{ '?column?': 1 }] : [] };
|
|
60
|
+
if (isWinner(sql)) {
|
|
61
|
+
if (engine.slowWinner) await new Promise((r) => setTimeout(r, 10));
|
|
62
|
+
return { rows: engine.winner ? [engine.winner] : [] };
|
|
63
|
+
}
|
|
64
|
+
if (isInsert(sql)) { engine.recorded.add(params[0]); return { rows: [], rowCount: 1 }; }
|
|
65
|
+
if (isRead(sql)) return { rows: engine.shown.slice(0, params[1]) };
|
|
66
|
+
throw new Error(`fake pool: unexpected statement ${sql.slice(0, 80)}`);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
function reset() {
|
|
70
|
+
engine.calls = []; engine.recorded = new Set(); engine.winner = null; engine.shown = [];
|
|
71
|
+
engine.throwOn = null; engine.slowWinner = false;
|
|
72
|
+
botw.clearCrownMemo();
|
|
73
|
+
}
|
|
74
|
+
const callsOf = (pred) => engine.calls.filter((c) => pred(c.sql));
|
|
75
|
+
|
|
76
|
+
// Pin the module doorway (ADR 0083) BEFORE the modules load.
|
|
77
|
+
const moduleApiPath = require.resolve('../src/module-api.js');
|
|
78
|
+
require.cache[moduleApiPath] = {
|
|
79
|
+
id: moduleApiPath, filename: moduleApiPath, loaded: true,
|
|
80
|
+
exports: {
|
|
81
|
+
pool: engine,
|
|
82
|
+
corsPublicGet: () => (_req, res, next) => { res.set('Access-Control-Allow-Origin', '*'); next(); },
|
|
83
|
+
logger: () => ({ error: () => {}, info: () => {}, warn: () => {} }),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const botw = require('../modules/platform-identity/builder-of-the-week.js');
|
|
88
|
+
const { attachFail } = require('../src/bongos/middleware/error-envelope.js');
|
|
89
|
+
const buildRoutes = require('../modules/platform-identity/routes/community-leaderboard.js');
|
|
90
|
+
|
|
91
|
+
const NOW = new Date('2026-10-28T09:30:00.000Z'); // a Wednesday
|
|
92
|
+
const shownRow = (week_start, handle, credits, ships) => ({
|
|
93
|
+
week_start, handle, github_login: handle, display_name: handle.toUpperCase(), avatar_url: null,
|
|
94
|
+
credits_delta: String(credits), ships_delta: String(ships),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ---- 1. the week ------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
await t('the crowned week is the last full Monday-to-Monday week (UTC) before now, at every edge', () => {
|
|
100
|
+
const week = (iso) => { const w = botw.crownedWeek(new Date(iso)); return [w.start.toISOString(), w.end.toISOString()]; };
|
|
101
|
+
const last = ['2026-10-19T00:00:00.000Z', '2026-10-26T00:00:00.000Z'];
|
|
102
|
+
assert.deepEqual(week('2026-10-28T09:30:00.000Z'), last, 'a Wednesday: the week before this one');
|
|
103
|
+
assert.deepEqual(week('2026-10-26T00:00:00.000Z'), last, 'the very first instant of Monday: the week that just ended');
|
|
104
|
+
assert.deepEqual(week('2026-11-01T23:59:59.999Z'), last, 'the last instant of Sunday: still the same week');
|
|
105
|
+
assert.deepEqual(week('2026-10-25T23:59:59.999Z'), ['2026-10-12T00:00:00.000Z', '2026-10-19T00:00:00.000Z'], 'Sunday night: the week ending tonight is not over yet');
|
|
106
|
+
assert.deepEqual(week('2027-01-01T12:00:00.000Z'), ['2026-12-21T00:00:00.000Z', '2026-12-28T00:00:00.000Z'], 'across a year');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ---- 2. the statements --------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
await t('the winner: eligible builders only, a shipped work required, ties by works then karma then handle', async () => {
|
|
112
|
+
reset();
|
|
113
|
+
await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
114
|
+
const [w] = callsOf(isWinner);
|
|
115
|
+
assert.ok(w.sql.includes(rollupVisibleSql('pa').replace(/\s+/g, ' ').trim()), 'the leaderboard’s numbers predicate (hide, private and provisional are out)');
|
|
116
|
+
assert.ok(!w.sql.includes(scoutingListableSql('pa').replace(/\s+/g, ' ').trim()), 'the recruiting opt-out does not apply (spec D3)');
|
|
117
|
+
assert.match(w.sql, /HAVING sum\(tasks_shipped\) >= 1/, 'at least one work shipped in the week');
|
|
118
|
+
assert.match(w.sql, /ORDER BY sum\(credits\) DESC, sum\(tasks_shipped\) DESC, sum\(karma\) DESC, handle COLLATE "C", github_login COLLATE "C" LIMIT 1/);
|
|
119
|
+
assert.match(w.sql, /s\.reported_at <= \$2::timestamptz/, 'the week’s end: the last snapshot at-or-before it');
|
|
120
|
+
assert.match(w.sql, /s\.reported_at <= \$1::timestamptz/, 'the week’s start: the last snapshot at-or-before it');
|
|
121
|
+
assert.match(w.sql, /WITH act AS \( SELECT a\.client_id, a\.github_id, a\.first_seen_at, pa\.handle, pa\.github_login FROM/, 'never the live totals: a past week is read from history');
|
|
122
|
+
assert.match(w.sql, /\) b ON true WHERE b\.credits IS NOT NULL OR act\.first_seen_at >= \$1::timestamptz \)/, 'no history before the week: counted only if first seen in it (no lifetime in one week)');
|
|
123
|
+
assert.deepEqual(w.params.map((d) => d.toISOString()), ['2026-10-19T00:00:00.000Z', '2026-10-26T00:00:00.000Z']);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await t('the record: written once, never overwritten', async () => {
|
|
127
|
+
reset();
|
|
128
|
+
engine.winner = { github_id: '42', credits: '900', tasks_shipped: '3' };
|
|
129
|
+
await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
130
|
+
const [ins] = callsOf(isInsert);
|
|
131
|
+
assert.match(ins.sql, /ON CONFLICT \(week_start\) DO NOTHING$/, 'the first decision stands');
|
|
132
|
+
assert.doesNotMatch(ins.sql, /DO UPDATE/);
|
|
133
|
+
assert.deepEqual(ins.params, ['2026-10-19', '42', '900', '3']);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await t('the read: the same predicate, at read time, and no github_id in the projection', async () => {
|
|
137
|
+
reset();
|
|
138
|
+
engine.recorded.add('2026-10-19');
|
|
139
|
+
await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
140
|
+
const [r] = callsOf(isRead);
|
|
141
|
+
assert.ok(r.sql.includes(rollupVisibleSql('pa').replace(/\s+/g, ' ').trim()), 'a winner who hid since is not drawn (spec D8)');
|
|
142
|
+
const cols = r.sql.match(/^SELECT (.+?) FROM /)[1];
|
|
143
|
+
assert.doesNotMatch(cols, /github_id/, 'github_id never crosses');
|
|
144
|
+
assert.deepEqual(r.params, ['2026-10-19', botw.ARCHIVE_WEEKS + 1]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ---- 3. crown at render ---------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
await t('a recorded week is never recomputed', async () => {
|
|
150
|
+
reset();
|
|
151
|
+
engine.recorded.add('2026-10-19');
|
|
152
|
+
await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
153
|
+
assert.equal(callsOf(isWinner).length, 0);
|
|
154
|
+
assert.equal(callsOf(isInsert).length, 0);
|
|
155
|
+
await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
156
|
+
assert.equal(callsOf((s) => PROBE.test(s)).length, 1, 'and once known, not even asked about again in this process');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await t('an unrecorded week is computed and written once, however many reads arrive together', async () => {
|
|
160
|
+
reset();
|
|
161
|
+
engine.slowWinner = true;
|
|
162
|
+
engine.winner = { github_id: '7', credits: '50', tasks_shipped: '1' };
|
|
163
|
+
await Promise.all([1, 2, 3, 4].map(() => botw.readBuilderOfTheWeek({ now: NOW, pool: engine })));
|
|
164
|
+
assert.equal(callsOf(isWinner).length, 1);
|
|
165
|
+
assert.equal(callsOf(isInsert).length, 1);
|
|
166
|
+
assert.equal(callsOf(isRead).length, 4, 'every read still reads the crown');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await t('a week nobody won is written with no winner, so it stays empty', async () => {
|
|
170
|
+
reset();
|
|
171
|
+
const out = await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
172
|
+
const [ins] = callsOf(isInsert);
|
|
173
|
+
assert.deepEqual(ins.params, ['2026-10-19', null, 0, 0]);
|
|
174
|
+
assert.equal(out.winner, null);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await t('a failed crowning is not remembered: the next read tries again', async () => {
|
|
178
|
+
reset();
|
|
179
|
+
engine.throwOn = isWinner;
|
|
180
|
+
await assert.rejects(() => botw.readBuilderOfTheWeek({ now: NOW, pool: engine }), /boom/);
|
|
181
|
+
assert.equal(callsOf(isInsert).length, 0, 'nothing written on a failure');
|
|
182
|
+
await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
183
|
+
assert.equal(callsOf(isInsert).length, 1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await t('a new week is crowned on its own first read', async () => {
|
|
187
|
+
reset();
|
|
188
|
+
engine.recorded.add('2026-10-19');
|
|
189
|
+
await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
190
|
+
await botw.readBuilderOfTheWeek({ now: new Date('2026-11-02T00:00:01.000Z'), pool: engine });
|
|
191
|
+
assert.deepEqual(callsOf(isInsert).map((c) => c.params[0]), ['2026-10-26']);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ---- 4. the read ----------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
await t('the crown is this week’s row only; the past weeks are newest first, capped', async () => {
|
|
197
|
+
reset();
|
|
198
|
+
engine.recorded.add('2026-10-19');
|
|
199
|
+
engine.shown = [shownRow('2026-10-19', 'ana', 1240, 4), shownRow('2026-10-12', 'bo', 300, 1)];
|
|
200
|
+
const out = await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
201
|
+
assert.deepEqual(out.week, { start: '2026-10-19', end: '2026-10-26' });
|
|
202
|
+
assert.deepEqual(out.winner, { week_start: '2026-10-19', handle: 'ana', github_login: 'ana', display_name: 'ANA', avatar_url: null, credits: 1240, tasks_shipped: 4 });
|
|
203
|
+
assert.deepEqual(out.past.map((p) => [p.week_start, p.handle, p.credits]), [['2026-10-12', 'bo', 300]]);
|
|
204
|
+
// this week's winner hid since (or nobody won): no crown, and last week does not move up into it
|
|
205
|
+
engine.shown = [shownRow('2026-10-12', 'bo', 300, 1)];
|
|
206
|
+
const hidden = await botw.readBuilderOfTheWeek({ now: NOW, pool: engine });
|
|
207
|
+
assert.equal(hidden.winner, null);
|
|
208
|
+
assert.deepEqual(hidden.past.map((p) => p.week_start), ['2026-10-12']);
|
|
209
|
+
engine.shown = Array.from({ length: 20 }, (_, i) => shownRow(`2026-0${(i % 9) + 1}-01`, `b${i}`, 10, 1));
|
|
210
|
+
assert.equal((await botw.readBuilderOfTheWeek({ now: NOW, pool: engine })).past.length, botw.ARCHIVE_WEEKS);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ---- 5. the route -----------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
function serve() {
|
|
216
|
+
const app = express();
|
|
217
|
+
app.use(attachFail);
|
|
218
|
+
app.use('/api/bongos', buildRoutes());
|
|
219
|
+
const server = app.listen(0, '127.0.0.1');
|
|
220
|
+
return new Promise((resolve) => server.once('listening', () => resolve(server)));
|
|
221
|
+
}
|
|
222
|
+
function get(server, path_, headers = {}) {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
http.get({ host: '127.0.0.1', port: server.address().port, path: path_, headers }, (res) => {
|
|
225
|
+
let body = '';
|
|
226
|
+
res.on('data', (c) => (body += c));
|
|
227
|
+
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body, json: body ? JSON.parse(body) : null }));
|
|
228
|
+
}).on('error', reject);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const server = await serve();
|
|
233
|
+
const URL_ = '/api/bongos/community/builder-of-the-week';
|
|
234
|
+
|
|
235
|
+
await t('GET /community/builder-of-the-week: the week, the crown and the past weeks; no-store; the same for every viewer', async () => {
|
|
236
|
+
reset();
|
|
237
|
+
engine.shown = [shownRow(botw.crownedWeek().start.toISOString().slice(0, 10), 'ana', 1240, 4)];
|
|
238
|
+
const anon = await get(server, URL_);
|
|
239
|
+
assert.equal(anon.status, 200);
|
|
240
|
+
assert.equal(anon.headers['cache-control'], 'no-store');
|
|
241
|
+
assert.equal(anon.headers['access-control-allow-origin'], '*');
|
|
242
|
+
assert.deepEqual(Object.keys(anon.json).sort(), ['past', 'week', 'winner']);
|
|
243
|
+
assert.equal(anon.json.winner.handle, 'ana');
|
|
244
|
+
assert.doesNotMatch(anon.body, /github_id/);
|
|
245
|
+
const signedIn = await get(server, URL_, { Cookie: 'bongos_session=x', Authorization: 'Bearer y' });
|
|
246
|
+
assert.equal(signedIn.body, anon.body, 'the route reads no session');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await t('a failure is the 500 envelope, never a stack', async () => {
|
|
250
|
+
reset();
|
|
251
|
+
engine.throwOn = (sql) => PROBE.test(sql);
|
|
252
|
+
const r = await get(server, URL_);
|
|
253
|
+
assert.equal(r.status, 500);
|
|
254
|
+
assert.equal(r.json.error.code, 'builder_of_the_week_failed');
|
|
255
|
+
assert.doesNotMatch(r.body, /boom/);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
await t('its own rate-limit budget, not the leaderboard’s', () => {
|
|
259
|
+
const { _internals } = buildRoutes;
|
|
260
|
+
assert.equal(_internals.WEEK_READ_RATE_SCOPE, 'builder-of-the-week-read');
|
|
261
|
+
assert.notEqual(_internals.WEEK_READ_RATE_SCOPE, _internals.READ_RATE_SCOPE);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
server.close();
|
|
265
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
266
|
+
if (failed) process.exit(1);
|