@bongos/core 1.20.59 → 1.20.61
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 +151 -91
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +10 -0
- package/clients/bongos-client/index.cjs +10 -0
- package/clients/bongos-client/index.d.ts +14 -1
- package/clients/bongos-client/index.mjs +10 -0
- package/docs/adr/0358-a-hall-wears-a-look-and-its-night-takes-a-tint.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +324 -3
- package/docs/api-reference.md +8 -3
- package/docs/copy-inventory.md +343 -333
- package/docs/copy-registry.json +527 -433
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +758 -707
- package/modules/autonomy/db.js +13 -6
- package/modules/autonomy/migrations/autonomy_004_runner_identity.sql +30 -0
- package/modules/autonomy/routes/autonomy.js +16 -0
- package/modules/autonomy/runner-health.js +66 -5
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/gate.js +24 -8
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +5 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +8 -0
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/provisioning/look.js +169 -0
- package/modules/provisioning/migrations/provisioning_034_project_logo.sql +35 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/provisioning.js +4 -3
- package/modules/provisioning/routes/look.js +204 -0
- package/modules/provisioning/routes/provisioning.js +5 -59
- package/modules/provisioning/settings-push.js +76 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/projects.html +499 -28
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +6 -3
- package/modules/ui-design/kit/serve.js +50 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/autobongos-run.js +43 -2
- package/scripts/gds/fitness.js +4 -0
- package/src/bongos/routes.js +4 -1
- package/src/bongos/serve-internal.js +6 -0
- package/src/branding.js +26 -1
- package/src/hall-look.js +99 -0
- package/src/module-api.js +1 -1
- package/src/night-tint.js +26 -0
- package/tests/autonomy_runner_identity.mjs +215 -0
- package/tests/currency_label.mjs +7 -4
- package/tests/hall_tokens.mjs +10 -1
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/look_night_tint.mjs +169 -0
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/projects_hub.mjs +5 -4
- package/tests/projects_hub_app_status.mjs +1 -1
- package/tests/projects_hub_app_step.mjs +8 -7
- package/tests/projects_hub_look.mjs +255 -0
- package/tests/projects_hub_module_picker.mjs +2 -2
- package/tests/provision_settings_apply.mjs +12 -4
- package/tests/provisioning_look.mjs +376 -0
- package/tests/provisioning_settings.mjs +10 -7
- package/tests/provisioning_settings_apply.mjs +4 -4
- package/tests/provisioning_settings_env.mjs +4 -0
- package/tests/wizard_draft_resume.mjs +2 -2
- package/tests/wizard_front_door.mjs +2 -2
- package/tests/wizard_intent_resume.mjs +16 -15
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
"steps": [["wait",900],["click","[data-disc-ask]"],["wait",200],["fill","#mDiscName","old-lantern"],["wait",100],["click","[data-disc-yes]"],["wait",900]],
|
|
160
160
|
"expect": {"text":[["#mDisc .addr","This project is being disconnected."],["#mName","old-lantern"]],"hidden":["#mDiscName","[data-disc-ask]"]} },
|
|
161
161
|
{ "name": "the wizard driver (task 1003346): name, type, about, home (fresh; the stub grants repo access, so the new repository is named from the project), address (the handle's verdict), the review as a ledger, then the filing — the done panel with the standup rail and the human steps", "auth": true, "width": 1440, "url": "/projects?view=new",
|
|
162
|
-
"steps": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400],["click","#doCreate"],["wait",1500]],
|
|
162
|
+
"steps": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400],["click","#panel-look button[type=submit]"],["wait",400],["click","#doCreate"],["wait",1500]],
|
|
163
163
|
"expect": {"visible":["#panel-done","#liveProgress .prog","#liveProgress .prog .stages li.now","#humanSteps li"],"hidden":["#panel-review","#panel-name"],"text":[["#done-h","The drums are rolling"]],"attr":[["#panel-done","class","panel on"]]} },
|
|
164
164
|
{ "name": "the front door (task 1004417): Proceed is refused until the terms are ticked, the fork's demo door says the demo is not open yet, and Begin planet creation goes on to the name step with the rail back", "auth": true, "width": 1440, "url": "/projects?view=new",
|
|
165
165
|
"steps": [["wait",600],["click","#panel-terms button[type=submit]"],["wait",300],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#wizDemo"],["wait",200],["click","#panel-fork button[type=submit]"],["wait",400]],
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
"steps": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400]],
|
|
169
169
|
"expect": {"visible":["#panel-fork",".doorPill","#wizDemo","#panel-fork button[type=submit]"],"hidden":["#view-new .steps"]} },
|
|
170
170
|
{ "name": "the wizard driver at 390: the rail folds to two rows of three and the walk still reaches the review", "auth": true, "width": 390, "url": "/projects?view=new",
|
|
171
|
-
"steps": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400]],
|
|
171
|
+
"steps": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400],["click","#panel-look button[type=submit]"],["wait",400]],
|
|
172
172
|
"expect": {"visible":["#panel-review","#reviewList li","#doCreate"],"hidden":["#panel-address"],"text":[["#rev-h","Ready to roll"]]} }
|
|
173
173
|
]
|
|
174
174
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"in-manage-address": { "auth": true, "url": "/projects?view=manage&id=101", "expect": { "visible": ["#mAddress", "#mAddress h2", "#mDom .plain", "#mDomName", "#mDom [data-m-attach]", "#mDomWhat summary"], "count": [["#mDom .attach input[type=text]", 1], ["#mDom .attach button", 1]] } },
|
|
23
23
|
"in-manage-planet": { "auth": true, "url": "/projects?view=manage&id=101", "expect": { "visible": ["#mPlanetPick", "#mPick .pick .pickPreview .worldOrb .disc", "#mPick [data-pick-save]", "#mPick [data-pick-reset]", "#mPickWhat summary"], "count": [["#mPick .pickCells[role=\"radiogroup\"]", 3], ["#mPick .pickCell", 10], ["#mPick .pickCell.on", 3], ["#mPick .pickSw", 5], ["#mPick .pickSw.on", 1]], "attr": [["#mPick .pickPreview .worldOrb", "data-template", "pair-obsidian-tint"], ["#mOrb .worldOrb", "data-template", "pair-obsidian-tint"]], "checked": "#mPick input[name=\"mPickAccent\"][value=\"sea\"]", "text": [["#mPlanet .v small", "your choice."]] } },
|
|
24
24
|
"in-manage-card": { "auth": true, "url": "/projects?view=manage&id=101", "expect": { "visible": ["#mCard", "#mCardPick .pick .pickPreview .cardBand", "#mCardPick [data-card-save]", "#mCardWhat summary"], "count": [["#mCardPick .pickCells[role=\"radiogroup\"]", 1], ["#mCardPick .pickCell", 5], ["#mCardPick .pickCell.on", 1]], "checked": "#mCardPick input[name=\"mCardBackdrop\"][value=\"\"]", "attr": [["#mCardPick .cardBand", "data-own", ""]] } },
|
|
25
|
+
"in-manage-look": {"auth":true,"url":"/projects?view=manage&id=101","actions":[["wait",900],["focus","#mLkLook-grove"],["wait",200]],"expect":{"visible":["#mLook","#mLookPick .lookOpts","#mLkLook-grove","#mLkTint","#mLookSave",".lookPrev .mh.dark","#mLookWhat summary"]}},
|
|
25
26
|
"in-manage-kind": { "auth": true, "url": "/projects?view=manage&id=101", "expect": {"visible":["#mKind","#mKindPick .modBand","#mKindWhat summary"],"count":[["#mKindPick .modOpt",5],["#mKindPick .modOpt[aria-pressed=\"true\"]",0]],"text":[["#mKindPick .modOpt b","Game"],["#mKindPick .finePrint","This project has never said what kind it is. Saying so puts the word beside its name wherever it is listed; it changes nothing else."]]} },
|
|
26
27
|
"in-manage-invite": { "auth": true, "url": "/projects?view=manage&id=101", "expect": { "visible": ["#mInvite", "#mInvite h2", "#mInviteWho", "#mInvite [data-invite-send]"] } },
|
|
27
28
|
"in-manage-invite-handoff": { "auth": true, "url": "/projects?view=manage&id=101", "actions": [["wait",900],["fill","#mInviteWho","octo-builder"],["click","#mInvite [data-invite-send]"],["wait",400]], "expect": {"visible":["#mInviteList a","#mInviteMsg"],"hidden":[]} },
|
|
@@ -49,9 +50,11 @@
|
|
|
49
50
|
"in-new-address": { "auth": true, "url": "/projects?view=new", "actions": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400]], "expect": {"visible":["#panel-address"],"hidden":["#panel-home","#qWrap"]} },
|
|
50
51
|
"in-new-app": { "auth": true, "url": "/projects?view=new", "actions": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400]], "expect": {"visible":["#panel-app","#appOpts","#appOptRender"],"hidden":["#panel-address","#panel-review","#qWrap"]} },
|
|
51
52
|
"in-new-app-render": { "auth": true, "url": "/projects?view=new", "actions": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#appOptRender input"],["wait",300]], "expect": {"visible":["#panel-app","#renderSetup","#f-renderKey","#renderCheck"],"hidden":["#renderWs","#renderSvc","#renderNew"]} },
|
|
52
|
-
"in-new-
|
|
53
|
-
"in-new-
|
|
54
|
-
"in-new-
|
|
53
|
+
"in-new-look": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400]],"expect":{"visible":["#panel-look","#wizLook","#wizLook-chrome-world","#wizTint","#wizLogo",".lookPrev .mh.light",".lookPrev .mh.dark"],"hidden":["#panel-app","#panel-review"]}},
|
|
54
|
+
"in-new-look-custom": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400],["click","#wizLook-custom"],["wait",200]],"expect":{"visible":["#panel-look","#wizAccent","#wizAccentNote"],"hidden":["#panel-review"]}},
|
|
55
|
+
"in-new-review": { "auth": true, "url": "/projects?view=new", "actions": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400],["click","#panel-look button[type=submit]"],["wait",400]], "expect": {"visible":["#panel-review","#reviewList li"],"hidden":["#panel-address","#qWrap"]} },
|
|
56
|
+
"in-new-done": { "auth": true, "url": "/projects?view=new", "actions": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400],["click","#panel-look button[type=submit]"],["wait",400],["click","#doCreate"],["wait",1500]], "expect": {"visible":["#panel-done","#liveProgress .prog","#humanSteps li"],"hidden":["#panel-review","#qWrap"]} },
|
|
57
|
+
"in-new-resume-offer": { "auth": true, "url": "/projects?view=new", "actions": [["wait",600],["click","#termsAck"],["click","#panel-terms button[type=submit]"],["wait",400],["click","#panel-fork button[type=submit]"],["wait",400],["fill","#newName","Mercury"],["click","#panel-name button[type=submit]"],["wait",400],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-type button[type=submit]"],["wait",400],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#teamCards .tpill[data-team=\"small-team\"]"],["click","#panel-detail button[type=submit]"],["wait",900],["click","#panel-modules button[type=submit]"],["wait",400],["click","#panel-home button[type=submit]"],["wait",400],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#panel-address button[type=submit]"],["wait",400],["click","#panel-app button[type=submit]"],["wait",400],["click","#panel-look button[type=submit]"],["wait",400],["click","#doCreate"],["wait",1500],["click","#projTrig"],["wait",350],["click","#menuProjects [data-nav=\"new\"]"],["wait",500]], "expect": {"visible":["#panel-terms","#wizResume","#wizResumeGo"],"hidden":["#panel-done","#panel-name"],"text":[["#wizResumeName","Mercury"]]} },
|
|
55
58
|
"out-map-projects": { "auth": false, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "hidden": ["#menuProjects [data-nav=\"mine\"]"] } },
|
|
56
59
|
"card": { "auth": true, "actions": [["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://example.com\"]"], ["wait", 900]], "expect": { "dialog": true, "hasClass": [["#projCard", "has-art"]], "visible": ["#cardArt", "#cardFaces", "#cardBuildersFig", "#cardShippedFig", "#cardOpen", "#cardHall"], "text": [["#cardBuilders", "11"], ["#cardShipped", "0"], ["#cardRel", "you build here"], ["#cardRuns", "example.com"], ["#cardFaces .facesMore", "+6"]], "count": [["#cardFaces img", 5]], "href": [["#cardHall", "https://builders.example.com/"]] } },
|
|
57
60
|
"card-noart": { "auth": true, "actions": [["dispatchClick", "#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]"], ["wait", 600]], "expect": { "dialog": true, "hasClass": [["#projCard", "has-art", false]], "hidden": ["#cardArt", "#cardFaces", "#cardHall"], "visible": ["#cardBuildersFig", "#cardShippedFig", "#cardOpen"], "text": [["#cardShipped", "30+"], ["#cardRel", "you build here"]] } },
|
|
@@ -201,6 +201,8 @@ const STUB_VOCAB = {
|
|
|
201
201
|
join_grant: ['view', 'apply', 'full'],
|
|
202
202
|
artist_gate: ['off', 'advisory', 'strict'],
|
|
203
203
|
spend_payer: ['none', 'project', 'builder'],
|
|
204
|
+
look: ['own', 'chrome-world', 'expedition', 'grove', 'blueprint', 'custom'], // the look + its night (task 1004421)
|
|
205
|
+
night_tint: ['on', 'off'],
|
|
204
206
|
planet_template: null, // the template set is long; shape is checked, not membership
|
|
205
207
|
planet_accent: ['sea', 'terracotta', 'gold-soft', 'sun-lit', 'accent'],
|
|
206
208
|
card_backdrop: ['nebula', 'dust', 'deepfield', 'chrome'],
|
|
@@ -224,6 +226,53 @@ function settingsPatch(res, id, body) {
|
|
|
224
226
|
: { queued: false, restart: false, message: 'Saved. The sky, your list and the card draw it this way from here; nothing on your project restarts for it.' };
|
|
225
227
|
return json(res, 200, { ok: true, saved: FLOW_RECEIPTS.settings.settings, planet: PLANETS.get(String(id)) || NO_PLANET, push });
|
|
226
228
|
}
|
|
229
|
+
// a project's look (task 1004421, ADR 0358): GET/PATCH …/look and PUT/DELETE …/logo,
|
|
230
|
+
// validated the way routes/look.js validates and REMEMBERED for the run, so the manage
|
|
231
|
+
// page repaints from what it saved. The accent is stored as sent — the real route nudges
|
|
232
|
+
// it, and the page shows its own copy of that nudge before it ever asks.
|
|
233
|
+
const LOOK_STATE = new Map();
|
|
234
|
+
const LOGO_TYPES = ['image/png', 'image/jpeg', 'image/webp'];
|
|
235
|
+
function lookOf(id) {
|
|
236
|
+
return LOOK_STATE.get(String(id)) || { look: 'own', night_tint: 'on', look_accent: null, logo_url: null };
|
|
237
|
+
}
|
|
238
|
+
const LOOK_RECEIPT = { reachable: true, queued: true, restart: true, message: 'Saved. Your project will restart to pick this up — until then it keeps its current setting.' };
|
|
239
|
+
function lookRoute(req, res, rest) {
|
|
240
|
+
const m = rest.match(/^\/provisioning\/instances\/(\d+)\/(look|logo)$/);
|
|
241
|
+
if (!m) return false;
|
|
242
|
+
if (!AUTH) { json(res, 401, UNAUTH); return true; }
|
|
243
|
+
const id = m[1], cur = lookOf(id);
|
|
244
|
+
if (m[2] === 'look' && req.method === 'GET') { json(res, 200, { ok: true, look: cur, options: { look: STUB_VOCAB.look, night_tint: STUB_VOCAB.night_tint }, logo_max_bytes: 262144 }); return true; }
|
|
245
|
+
if (m[2] === 'look' && req.method === 'PATCH') {
|
|
246
|
+
readBody(req, (body) => {
|
|
247
|
+
const bad = Object.keys(body).find((k) => !['look', 'night_tint', 'accent'].includes(k))
|
|
248
|
+
|| (body.look !== undefined && !STUB_VOCAB.look.includes(body.look) && 'look')
|
|
249
|
+
|| (body.night_tint !== undefined && !STUB_VOCAB.night_tint.includes(body.night_tint) && 'night_tint');
|
|
250
|
+
if (bad) return json(res, 400, { error: { code: 'validation_failed', message: `bad field: ${bad}` } });
|
|
251
|
+
if (body.accent !== undefined && !/^#?[0-9a-f]{6}$/i.test(String(body.accent))) return json(res, 400, { error: { code: 'bad_accent', message: 'An accent is a colour written #rrggbb, for example #b48cf2.' } });
|
|
252
|
+
const next = { ...cur, ...(body.look ? { look: body.look } : {}), ...(body.night_tint ? { night_tint: body.night_tint } : {}), ...(body.accent ? { look_accent: String(body.accent).toLowerCase() } : {}) };
|
|
253
|
+
if (next.look === 'custom' && !next.look_accent) return json(res, 400, { error: { code: 'accent_required', message: 'Your own look needs an accent colour — send accent as #rrggbb.' } });
|
|
254
|
+
LOOK_STATE.set(id, next);
|
|
255
|
+
return json(res, 200, { ok: true, look: next, push: LOOK_RECEIPT, adjusted: null });
|
|
256
|
+
});
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
if (m[2] === 'logo' && req.method === 'PUT') {
|
|
260
|
+
readBody(req, (body) => {
|
|
261
|
+
if (!LOGO_TYPES.includes(body.content_type) || typeof body.image_b64 !== 'string' || !body.image_b64) return json(res, 400, { error: { code: 'logo_bad_type', message: 'A logo must be a PNG, JPEG or WebP image.' } });
|
|
262
|
+
const next = { ...cur, logo_url: 'data:' + body.content_type + ';base64,' + body.image_b64 };
|
|
263
|
+
LOOK_STATE.set(id, next);
|
|
264
|
+
return json(res, 200, { ok: true, look: next, push: LOOK_RECEIPT });
|
|
265
|
+
});
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
if (m[2] === 'logo' && req.method === 'DELETE') {
|
|
269
|
+
const next = { ...cur, logo_url: null };
|
|
270
|
+
LOOK_STATE.set(id, next);
|
|
271
|
+
json(res, 200, { ok: true, look: next, push: LOOK_RECEIPT });
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
227
276
|
// the row carries its planet: a saved pick for the run, else the fixture's own (or null)
|
|
228
277
|
function withPlanet(inst) {
|
|
229
278
|
const saved = PLANETS.get(String(inst.id));
|
|
@@ -433,6 +482,7 @@ const server = http.createServer((req, res) => {
|
|
|
433
482
|
// the manage flows (task 1003345): every door on a row and on the manage page only ASKS
|
|
434
483
|
// (ADR 0111) — the stub answers each ask with the route's receipt so the probes can drive
|
|
435
484
|
// attach / restart / teardown / revive / join end to end; nothing changes in the fixture
|
|
485
|
+
if (lookRoute(req, res, rest)) return;
|
|
436
486
|
if (req.method !== 'GET') {
|
|
437
487
|
const ps = rest.match(/^\/provisioning\/instances\/(\d+)\/settings$/);
|
|
438
488
|
if (ps && req.method === 'PATCH') return AUTH ? readBody(req, (body) => settingsPatch(res, ps[1], body)) : json(res, 401, UNAUTH);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.61",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.20.
|
|
9
|
+
"version": "1.20.61",
|
|
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.20.
|
|
3
|
+
"version": "1.20.61",
|
|
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
|
@@ -8471,5 +8471,17 @@
|
|
|
8471
8471
|
"id": "1004433",
|
|
8472
8472
|
"text": "The profile now knows each person by their own work. An artist's page counts pages approved, how many pages of each part of the site they have tweaked, and what pages earned them; an ideator's counts thoughts filed, how many b"
|
|
8473
8473
|
}
|
|
8474
|
+
],
|
|
8475
|
+
"1.20.60": [
|
|
8476
|
+
{
|
|
8477
|
+
"id": "1004421",
|
|
8478
|
+
"text": "Every project can now have its own look, so builders who work in several projects always know which hall they are in. Creating a project has a new Look step: four ready-made looks or your own colour, a switch to tint dark mode"
|
|
8479
|
+
}
|
|
8480
|
+
],
|
|
8481
|
+
"1.20.61": [
|
|
8482
|
+
{
|
|
8483
|
+
"id": "1004407",
|
|
8484
|
+
"text": "The Gate page now shows, for each Autobongos runner, which PC it is on, which Claude account it is spending, which code version it runs (and whether that is behind), and says \"Restarting\" while it relaunches for new code inste"
|
|
8485
|
+
}
|
|
8474
8486
|
]
|
|
8475
8487
|
}
|
|
@@ -368,7 +368,7 @@ const MAX_CLAIM_ATTEMPTS = 5;
|
|
|
368
368
|
const LIMIT_UNKNOWN_RESET_MS = 30 * 60 * 1000;
|
|
369
369
|
|
|
370
370
|
function newRunnerState() {
|
|
371
|
-
return { setAside: new Map(), queueGate: null, limitUntil: null, diskHead: null };
|
|
371
|
+
return { setAside: new Map(), queueGate: null, limitUntil: null, diskHead: null, mainHead: null };
|
|
372
372
|
}
|
|
373
373
|
const RUNNER_STATE = newRunnerState();
|
|
374
374
|
|
|
@@ -703,6 +703,43 @@ function writeHeartbeat(now = Date.now(), runnerState = RUNNER_STATE) {
|
|
|
703
703
|
// is the signal.
|
|
704
704
|
const RUNNER_STARTED_AT = new Date().toISOString();
|
|
705
705
|
|
|
706
|
+
// claudeAccountLabel — WHICH Claude account this machine's workers spend
|
|
707
|
+
// (task 1004407), as a label and never a secret. Workers inherit
|
|
708
|
+
// ANTHROPIC_API_KEY and CLAUDE_CODE_OAUTH_TOKEN from this process
|
|
709
|
+
// (WORKER_ENV_ALLOW), and either one outranks the stored login, so those are
|
|
710
|
+
// named first, by the variable that is set and never by its value. Otherwise it
|
|
711
|
+
// is the login Claude Code itself records in ~/.claude.json (oauthAccount): the
|
|
712
|
+
// email, plus the organisation when it has one. Read on every beat, so signing
|
|
713
|
+
// a different account in shows up without a restart; null when nothing can be
|
|
714
|
+
// read, which the card leaves blank rather than guessing.
|
|
715
|
+
function claudeAccountLabel({ env = process.env, readFile = (p) => fs.readFileSync(p, 'utf8') } = {}) {
|
|
716
|
+
if (env.ANTHROPIC_API_KEY) return 'an API key (ANTHROPIC_API_KEY)';
|
|
717
|
+
if (env.CLAUDE_CODE_OAUTH_TOKEN) return 'a Claude token (CLAUDE_CODE_OAUTH_TOKEN)';
|
|
718
|
+
try {
|
|
719
|
+
const dir = env.CLAUDE_CONFIG_DIR || os.homedir();
|
|
720
|
+
const acct = JSON.parse(readFile(path.join(dir, '.claude.json'))).oauthAccount || {};
|
|
721
|
+
const email = typeof acct.emailAddress === 'string' ? acct.emailAddress.trim() : '';
|
|
722
|
+
if (!email) return null;
|
|
723
|
+
const org = typeof acct.organizationName === 'string' ? acct.organizationName.trim() : '';
|
|
724
|
+
return (org ? `${email} · ${org}` : email).slice(0, 200);
|
|
725
|
+
} catch (_) { return null; }
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// heartbeatIdentity — the fields that say which code and which account (task
|
|
729
|
+
// 1004407). Undefined fields are dropped, so a runner that cannot read its own
|
|
730
|
+
// commit sends nothing for it rather than an empty string the route would refuse.
|
|
731
|
+
function heartbeatIdentity(deps = {}) {
|
|
732
|
+
const st = deps.state || RUNNER_STATE;
|
|
733
|
+
const out = {
|
|
734
|
+
head: LOADED_HEAD || undefined,
|
|
735
|
+
disk_head: st.diskHead && st.diskHead !== LOADED_HEAD ? st.diskHead : undefined,
|
|
736
|
+
main_head: st.mainHead || undefined,
|
|
737
|
+
claude_account: (deps.claudeAccountLabel || claudeAccountLabel)() || undefined,
|
|
738
|
+
};
|
|
739
|
+
for (const k of Object.keys(out)) if (out[k] === undefined) delete out[k];
|
|
740
|
+
return out;
|
|
741
|
+
}
|
|
742
|
+
|
|
706
743
|
async function postHeartbeat(fields = {}, deps = {}) {
|
|
707
744
|
try {
|
|
708
745
|
const api = deps.api || await (require('./cli-lib').cliClient)();
|
|
@@ -711,6 +748,7 @@ async function postHeartbeat(fields = {}, deps = {}) {
|
|
|
711
748
|
host: os.hostname(),
|
|
712
749
|
pid: process.pid,
|
|
713
750
|
started_at: RUNNER_STARTED_AT,
|
|
751
|
+
...heartbeatIdentity(deps),
|
|
714
752
|
...fields,
|
|
715
753
|
},
|
|
716
754
|
});
|
|
@@ -870,6 +908,9 @@ async function codeDrifted(deps = {}) {
|
|
|
870
908
|
let tip = null;
|
|
871
909
|
try { tip = await runFetchHead(deps); } catch (_) { return null; }
|
|
872
910
|
if (!tip) return null;
|
|
911
|
+
// Kept for the heartbeat (task 1004407), so the hall can say "behind main"
|
|
912
|
+
// without the server ever reaching GitHub itself.
|
|
913
|
+
(deps.state || RUNNER_STATE).mainHead = tip;
|
|
873
914
|
return tip !== base ? { from: base.slice(0, 8), to: String(tip).slice(0, 8) } : false;
|
|
874
915
|
}
|
|
875
916
|
|
|
@@ -1147,6 +1188,6 @@ module.exports = {
|
|
|
1147
1188
|
readArgv, pickTask, iteration, record, logPath, spawnWorker, workerEnv, WORKER_ENV_ALLOW, resolveWorkerBin,
|
|
1148
1189
|
worktreeName, readFence, reconcileLeftoverClaims, pollSignals, waitOrJump, forever, CLAIM_PREFIX, codeDrifted, loadedHead, UPGRADE_EXIT_CODE, MIN_UPTIME_BEFORE_UPGRADE_MS,
|
|
1149
1190
|
heartbeatPath, writeHeartbeat, readHeartbeat, pidAlive, anotherRunnerIsAlive, HEARTBEAT_STALE_MS,
|
|
1150
|
-
isRunnerTree, RUNNER_TREE_RE, postHeartbeat, RUNNER_STARTED_AT, failureReason,
|
|
1191
|
+
isRunnerTree, RUNNER_TREE_RE, postHeartbeat, claudeAccountLabel, RUNNER_STARTED_AT, failureReason,
|
|
1151
1192
|
newRunnerState, loadedFiles, supervisorTouched, refreshCheckout, LIMIT_UNKNOWN_RESET_MS, QUEUE_GATED_EXIT, CLAIM_SET_ASIDE_MS, MAX_CLAIM_ATTEMPTS, owedTaskIds,
|
|
1152
1193
|
};
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -566,6 +566,10 @@ const KERNEL_FILES = [
|
|
|
566
566
|
// config / identity (host context a module reads, never hardcodes)
|
|
567
567
|
'src/instance-config.js',
|
|
568
568
|
'src/branding.js',
|
|
569
|
+
// task 1004421 (ADR 0358): branding.js lays the chosen look over the pack through this
|
|
570
|
+
// file — the same identity/config category, requiring only instance-config and node
|
|
571
|
+
// built-ins, so it joins the roster rather than reading as a kernel→domain leak.
|
|
572
|
+
'src/hall-look.js',
|
|
569
573
|
'src/build-info.js',
|
|
570
574
|
// task 1003476 (goal 1000072): the project's stale-claim timer — host configuration
|
|
571
575
|
// a module reads rather than hardcodes, exactly the category instance-config.js and
|
package/src/bongos/routes.js
CHANGED
|
@@ -111,7 +111,10 @@ function buildGdsRouter() {
|
|
|
111
111
|
// 'completed'. The list is NOT trimmable to fit: modules/grading/grader-score.js runs
|
|
112
112
|
// the server-side permission-path pre-pass against it (SR-9 / SEC #863), so a short
|
|
113
113
|
// list would quietly weaken a security check rather than merely lose detail.
|
|
114
|
-
/^\/tasks\/\d+\/grade$/.test(req.path)
|
|
114
|
+
/^\/tasks\/\d+\/grade$/.test(req.path) ||
|
|
115
|
+
// task 1004421: a project's logo — up to 256 KB decoded, ~342 KB as base64.
|
|
116
|
+
// modules/provisioning/routes/look.js mounts its own 400 KB parser.
|
|
117
|
+
/^\/provisioning\/instances\/\d+\/logo$/.test(req.path)
|
|
115
118
|
) return next();
|
|
116
119
|
return globalJson(req, res, next);
|
|
117
120
|
});
|
|
@@ -43,6 +43,7 @@ const { buildStampedHtml } = require('../asset-version');
|
|
|
43
43
|
const { buildInfo } = require('../build-info');
|
|
44
44
|
const { buildBongosDownloads } = require('../bongos-downloads');
|
|
45
45
|
const { clientBranding, branding, applyBrandConditionals } = require('../branding');
|
|
46
|
+
const { nightTintCss } = require('../night-tint');
|
|
46
47
|
const { prelaunchGate } = require('./prelaunch-gate');
|
|
47
48
|
const { platformVisibilityGate } = require('./platform-visibility-gate');
|
|
48
49
|
const { schemaPending } = require('./schema-lag');
|
|
@@ -121,6 +122,11 @@ function themeMarkup(theme) {
|
|
|
121
122
|
if (decls.length) css.push(`:root{${decls.join('')}}`);
|
|
122
123
|
const backdrop = cssSafe(theme.backdrop);
|
|
123
124
|
if (backdrop) css.push(`body{background:${backdrop};}`);
|
|
125
|
+
// The night tint (ADR 0358): a hall wearing a look takes a hint of its accent into
|
|
126
|
+
// its dark grounds. Written as the sheet's own two dark twins, after the sheet, so
|
|
127
|
+
// equal specificity wins on order. clientBranding only sets it with a look, so a
|
|
128
|
+
// hall that chose none keeps the sheet's pure black.
|
|
129
|
+
if (theme.nightTint === true) css.push(nightTintCss());
|
|
124
130
|
if (css.length) out += `<style>${css.join('')}</style>`;
|
|
125
131
|
// esc() is lossless in an attribute: the HTML parser decodes the entities back
|
|
126
132
|
// before anything reads the URL, so the neutral pack's inline `data:image/svg+xml,
|
package/src/branding.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const fs = require('node:fs');
|
|
20
20
|
const path = require('node:path');
|
|
21
21
|
const { resolveCoreRoot, resolveInstanceRoot, resolveEnv, FALLBACK_ENV_PREFIX } = require('./instance-config');
|
|
22
|
+
const { applyLook, readLibraryLook } = require('./hall-look');
|
|
22
23
|
|
|
23
24
|
// ADR 0108 §1: the neutral starter ships WITH the core package; the instance
|
|
24
25
|
// pack is host content. Both resolvers return the repo root today, so this is
|
|
@@ -83,6 +84,16 @@ const ENV_OVERRIDES = [
|
|
|
83
84
|
// door is `open` and this is `none`. Like its neighbours it is memoized for the
|
|
84
85
|
// process, which is why the platform restarts the instance to apply a change.
|
|
85
86
|
['SPEND_PAYER', ['project', 'spendPayer']],
|
|
87
|
+
// The look this hall wears (ADR 0358, task 1004421) — own | a style-library name |
|
|
88
|
+
// custom — and its night tint (on | off). Policy keys like their neighbours: the
|
|
89
|
+
// platform pushes them and a restart applies them. LOOK_ACCENT (a custom look's one
|
|
90
|
+
// accent, already made readable by the platform) and LOGO_URL (the platform's
|
|
91
|
+
// content-addressed logo) are their companions. src/hall-look.js turns them into
|
|
92
|
+
// the fifteen; an unknown or malformed value leaves the hall on its own pack.
|
|
93
|
+
['LOOK', ['project', 'look']],
|
|
94
|
+
['NIGHT_TINT', ['project', 'nightTint']],
|
|
95
|
+
['LOOK_ACCENT', ['project', 'lookAccent']],
|
|
96
|
+
['LOGO_URL', ['project', 'logoUrl']],
|
|
86
97
|
];
|
|
87
98
|
|
|
88
99
|
const isObj = (v) => v && typeof v === 'object' && !Array.isArray(v);
|
|
@@ -115,7 +126,7 @@ function deepFreeze(o) {
|
|
|
115
126
|
// resolver. Decided in ADR 0142 (audit H11 / task 1002224).
|
|
116
127
|
//
|
|
117
128
|
// Pure resolver — all inputs injected, so tests drive it with zero I/O.
|
|
118
|
-
function resolveBranding({ neutral = {}, instance = {}, env = {} } = {}) {
|
|
129
|
+
function resolveBranding({ neutral = {}, instance = {}, env = {}, readLook = readLibraryLook } = {}) {
|
|
119
130
|
// structuredClone so the merged result shares no nested references with the
|
|
120
131
|
// neutral/instance inputs — otherwise the deepFreeze below would also freeze
|
|
121
132
|
// the caller's input objects (deepMerge shallow-copies only the top level).
|
|
@@ -142,6 +153,9 @@ function resolveBranding({ neutral = {}, instance = {}, env = {} } = {}) {
|
|
|
142
153
|
if (missing.length) {
|
|
143
154
|
throw new Error(`branding: missing required field(s): ${missing.map((p) => p.join('.')).join(', ')}`);
|
|
144
155
|
}
|
|
156
|
+
// The chosen look, laid over the pack's theme (ADR 0358). A no-op for a hall that
|
|
157
|
+
// chose none, which is every hall the platform has not pushed a look to.
|
|
158
|
+
applyLook(resolved, readLook);
|
|
145
159
|
return deepFreeze(resolved);
|
|
146
160
|
}
|
|
147
161
|
|
|
@@ -240,6 +254,11 @@ function clientBranding(b = branding()) {
|
|
|
240
254
|
},
|
|
241
255
|
favicon: ui.favicon || '',
|
|
242
256
|
backdrop: ui.backdrop || '',
|
|
257
|
+
// The look (ADR 0358): which one this hall wears ('' = its own pack), whether
|
|
258
|
+
// its night is tinted (only ever true with a look), and its logo address.
|
|
259
|
+
look: ui.look || '',
|
|
260
|
+
nightTint: ui.nightTint === true,
|
|
261
|
+
logo: ui.logo || '',
|
|
243
262
|
},
|
|
244
263
|
copy: {
|
|
245
264
|
tagline: b.copy?.tagline || '',
|
|
@@ -282,6 +301,12 @@ function clientBranding(b = branding()) {
|
|
|
282
301
|
// with?", not the platform's record of what they saved. The fallback is the
|
|
283
302
|
// platform default, which for this key IS today's behaviour.
|
|
284
303
|
spendPayer: b.project?.spendPayer || 'none',
|
|
304
|
+
// The look and its night (ADR 0358, task 1004421). Published for the read-back
|
|
305
|
+
// reason its neighbours are: the settings push compares this manifest with what
|
|
306
|
+
// the platform saved. The fallbacks are the platform defaults — 'own' is today's
|
|
307
|
+
// hall, and 'on' only ever tints a hall that wears a look.
|
|
308
|
+
look: b.project?.look || 'own',
|
|
309
|
+
nightTint: b.project?.nightTint || 'on',
|
|
285
310
|
},
|
|
286
311
|
};
|
|
287
312
|
}
|
package/src/hall-look.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/hall-look.js — the look a hall wears, laid over its branding pack (ADR 0358,
|
|
2
|
+
// task 1004421).
|
|
3
|
+
//
|
|
4
|
+
// The platform chooses a project's look (provisioning settings `look`, `night_tint`
|
|
5
|
+
// plus the companion LOOK_ACCENT and LOGO_URL) and pushes only NAMES over web.env;
|
|
6
|
+
// src/branding.js maps them onto project.look / nightTint / lookAccent / logoUrl and
|
|
7
|
+
// hands the merged pack here. This file turns the name into the fifteen, reading the
|
|
8
|
+
// style library the core package ships (a module's styles/<name>/pack.json, ADR 0219),
|
|
9
|
+
// so a palette never travels as env text. The library is found by its SHAPE — any
|
|
10
|
+
// shipped module's styles/ directory — never by a module key, which core may not name
|
|
11
|
+
// (ADR 0083 §Decision #4).
|
|
12
|
+
//
|
|
13
|
+
// own (or absent) the instance's own pack — exactly today's hall.
|
|
14
|
+
// <library name> that look's thirteen colours and two faces.
|
|
15
|
+
// custom the chrome world with ONE accent, already made readable in both
|
|
16
|
+
// modes by the platform (lookAdjustAccent); a value that is not a
|
|
17
|
+
// plain #rrggbb is ignored and the hall stays on its own pack.
|
|
18
|
+
//
|
|
19
|
+
// Favicon and backdrop stay the instance's (ADR 0219 §1) except that a looked hall
|
|
20
|
+
// gets a favicon in its own colours, and a logo, when there is one, is the favicon.
|
|
21
|
+
// PURE given `readLook`; the default reader is the only I/O.
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
|
|
27
|
+
const HEX = /^#[0-9a-f]{6}$/i;
|
|
28
|
+
const LOOK_NAME = /^[a-z][a-z-]{0,39}$/;
|
|
29
|
+
// A logo address is the platform's content-addressed image URL. https only, and no
|
|
30
|
+
// character that could end the attribute or the CSS it is written into.
|
|
31
|
+
const LOGO_URL = /^https:\/\/[^\s"'<>()\\]+$/;
|
|
32
|
+
|
|
33
|
+
// The style library's packs, read once each. null for an unknown name or a pack that
|
|
34
|
+
// does not carry the thirteen as hex — a broken file never half-applies.
|
|
35
|
+
const _looks = new Map();
|
|
36
|
+
function readLibraryLook(name, { root } = {}) {
|
|
37
|
+
if (!LOOK_NAME.test(String(name || ''))) return null;
|
|
38
|
+
const key = `${root || ''}|${name}`;
|
|
39
|
+
if (_looks.has(key)) return _looks.get(key);
|
|
40
|
+
let ui = null;
|
|
41
|
+
try {
|
|
42
|
+
const base = root || require('./instance-config').resolveCoreRoot(); // eslint-disable-line global-require
|
|
43
|
+
const modulesDir = path.join(base, 'modules');
|
|
44
|
+
const file = fs.readdirSync(modulesDir).sort()
|
|
45
|
+
.map((m) => path.join(modulesDir, m, 'styles', name, 'pack.json'))
|
|
46
|
+
.find((f) => fs.existsSync(f));
|
|
47
|
+
const pack = file ? JSON.parse(fs.readFileSync(file, 'utf8')) : null;
|
|
48
|
+
const t = pack && pack.theme && pack.theme.ui;
|
|
49
|
+
if (t && t.colors && Object.values(t.colors).every((v) => HEX.test(v)) && t.fonts) ui = { colors: t.colors, fonts: t.fonts };
|
|
50
|
+
} catch { ui = null; }
|
|
51
|
+
_looks.set(key, ui);
|
|
52
|
+
return ui;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The favicon a looked hall shows without a logo: the chrome world's two-circle mark
|
|
56
|
+
// redrawn in the look's own accent, terracotta and card.
|
|
57
|
+
function lookFavicon(colors) {
|
|
58
|
+
const c = (k) => encodeURIComponent(colors[k]);
|
|
59
|
+
return "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'>" +
|
|
60
|
+
`<circle cx='12' cy='17' r='8' fill='${c('accent')}'/><circle cx='22' cy='17' r='6' fill='${c('terracotta')}'/>` +
|
|
61
|
+
`<circle cx='12' cy='17' r='3.5' fill='${c('bgCard')}'/><circle cx='22' cy='17' r='2.5' fill='${c('bgCard')}'/></svg>`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// What the hall wears: { name, ui } or null for its own pack. PURE given readLook.
|
|
65
|
+
function chosenLook(project = {}, readLook = readLibraryLook) {
|
|
66
|
+
const name = String(project.look || 'own');
|
|
67
|
+
if (name === 'own') return null;
|
|
68
|
+
if (name === 'custom') {
|
|
69
|
+
const accent = String(project.lookAccent || '');
|
|
70
|
+
const base = HEX.test(accent) ? readLook('chrome-world') : null;
|
|
71
|
+
return base ? { name, ui: { colors: { ...base.colors, accent: accent.toLowerCase() }, fonts: base.fonts } } : null;
|
|
72
|
+
}
|
|
73
|
+
const ui = readLook(name);
|
|
74
|
+
return ui ? { name, ui } : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Lay the look over a resolved (still mutable) pack, in place. A logo applies with or
|
|
78
|
+
// without a look; the night tint only with one (ADR 0358 D2), so a hall that never
|
|
79
|
+
// chose a look renders exactly as before whatever its tint setting says.
|
|
80
|
+
function applyLook(resolved, readLook = readLibraryLook) {
|
|
81
|
+
const project = resolved.project || {};
|
|
82
|
+
const look = chosenLook(project, readLook);
|
|
83
|
+
const logo = LOGO_URL.test(String(project.logoUrl || '')) ? project.logoUrl : '';
|
|
84
|
+
if (!look && !logo) return resolved;
|
|
85
|
+
resolved.theme = resolved.theme && typeof resolved.theme === 'object' ? resolved.theme : {};
|
|
86
|
+
const ui = { ...(resolved.theme.ui || {}) };
|
|
87
|
+
if (look) {
|
|
88
|
+
ui.colors = { ...look.ui.colors };
|
|
89
|
+
ui.fonts = { ...look.ui.fonts };
|
|
90
|
+
ui.look = look.name;
|
|
91
|
+
ui.nightTint = project.nightTint !== 'off';
|
|
92
|
+
ui.favicon = lookFavicon(ui.colors);
|
|
93
|
+
}
|
|
94
|
+
if (logo) { ui.logo = logo; ui.favicon = logo; }
|
|
95
|
+
resolved.theme.ui = ui;
|
|
96
|
+
return resolved;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { applyLook, readLibraryLook };
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.20.
|
|
78
|
+
const CORE_VERSION = '1.20.61'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/night-tint.js — the night tint a hall wears with a look (ADR 0358, task 1004421).
|
|
2
|
+
//
|
|
3
|
+
// The hall's dark tier (modules/hall-ui/public/style.css) is pure black. A hall that
|
|
4
|
+
// wears a look takes a hint of that look's own accent into its four dark grounds
|
|
5
|
+
// instead, so two halls stay told apart at night too. It is a FORMULA over the
|
|
6
|
+
// thirteen, never a palette a look authors: no token is added, and a custom accent
|
|
7
|
+
// gets its own night for free. This table is the one copy — themeMarkup() emits it
|
|
8
|
+
// and tests/hall_tokens.mjs (UI_DESIGN_TINT=1) measures it.
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const NIGHT_TINT = Object.freeze({
|
|
12
|
+
'--bg': 'color-mix(in srgb, var(--accent) 6%, #000000)',
|
|
13
|
+
'--bg-deep': 'color-mix(in srgb, var(--accent) 6%, #000000)',
|
|
14
|
+
'--bg-frame': 'color-mix(in srgb, var(--accent) 6%, #08080a)',
|
|
15
|
+
'--bg-card': 'color-mix(in srgb, var(--accent) 5%, #101012)',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// The CSS that applies it: the same two dark twins the sheet writes (the [data-theme]
|
|
19
|
+
// switch shell.js owns, and the media block that covers first paint), at the same
|
|
20
|
+
// specificity. Injected AFTER the sheet, so later wins. PURE.
|
|
21
|
+
function nightTintCss() {
|
|
22
|
+
const decls = Object.entries(NIGHT_TINT).map(([k, v]) => `${k}:${v};`).join('');
|
|
23
|
+
return `html[data-theme="dark"]{${decls}}@media (prefers-color-scheme: dark){html:not([data-theme]){${decls}}}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { NIGHT_TINT, nightTintCss };
|