@bongos/core 1.20.59 → 1.20.60
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 +136 -86
- 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 +13 -0
- 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 +308 -3
- package/docs/api-reference.md +7 -2
- package/docs/copy-inventory.md +338 -329
- package/docs/copy-registry.json +515 -430
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +757 -706
- 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/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 +6 -0
- 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/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
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
|
|
92
92
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
93
93
|
|
|
94
|
-
<script src="/builders/shell.js?v=2026-
|
|
94
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
95
95
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
96
96
|
<script src="/builders/palette.js" defer></script>
|
|
97
97
|
<script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
62
62
|
|
|
63
|
-
<script src="/builders/shell.js?v=2026-
|
|
63
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
64
64
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
65
65
|
<script src="/builders/palette.js" defer></script>
|
|
66
66
|
<script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
|
|
149
149
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
150
150
|
|
|
151
|
-
<script src="/builders/shell.js?v=2026-
|
|
151
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
152
152
|
<script src="/builders/palette.js" defer></script>
|
|
153
153
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
154
154
|
<script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
56
56
|
|
|
57
|
-
<script src="/builders/shell.js?v=2026-
|
|
57
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
58
58
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
59
59
|
<script src="/builders/palette.js" defer></script>
|
|
60
60
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
|
|
178
178
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
179
179
|
|
|
180
|
-
<script src="/builders/shell.js?v=2026-
|
|
180
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
181
181
|
<script src="/builders/palette.js" defer></script>
|
|
182
182
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
183
183
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
76
76
|
|
|
77
|
-
<script src="/builders/shell.js?v=2026-
|
|
77
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
78
78
|
<script src="/builders/palette.js" defer></script>
|
|
79
79
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
80
80
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
|
|
98
98
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
99
99
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
100
|
-
<script src="/builders/shell.js?v=2026-
|
|
100
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
101
101
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
102
102
|
<script src="/builders/palette.js" defer></script>
|
|
103
103
|
<script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
|
|
99
99
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
100
100
|
|
|
101
|
-
<script src="/builders/shell.js?v=2026-
|
|
101
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
102
102
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
103
103
|
<script src="/builders/palette.js" defer></script>
|
|
104
104
|
<script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
56
56
|
|
|
57
|
-
<script src="/builders/shell.js?v=2026-
|
|
57
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
58
58
|
<script src="/builders/palette.js" defer></script>
|
|
59
59
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
60
60
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
<div id="gate-modal-root"></div>
|
|
77
77
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
78
78
|
|
|
79
|
-
<script src="/builders/shell.js?v=2026-
|
|
79
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
80
80
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
81
81
|
<script src="/builders/palette.js" defer></script>
|
|
82
82
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
</div>
|
|
138
138
|
</div>
|
|
139
139
|
|
|
140
|
-
<script src="/builders/shell.js?v=2026-
|
|
140
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
141
141
|
<script src="/builders/palette.js" defer></script>
|
|
142
142
|
|
|
143
143
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
|
|
231
231
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
232
232
|
|
|
233
|
-
<script src="/builders/shell.js?v=2026-
|
|
233
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
234
234
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
235
235
|
<script src="/builders/palette.js" defer></script>
|
|
236
236
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
|
|
135
135
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
136
136
|
|
|
137
|
-
<script src="/builders/shell.js?v=2026-
|
|
137
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
138
138
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
139
139
|
<script src="/builders/palette.js" defer></script>
|
|
140
140
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
54
54
|
|
|
55
|
-
<script src="/builders/shell.js?v=2026-
|
|
55
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
56
56
|
<script src="/builders/palette.js" defer></script>
|
|
57
57
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
|
|
58
58
|
<!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
|
|
226
226
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
227
227
|
|
|
228
|
-
<script src="/builders/shell.js?v=2026-
|
|
228
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
229
229
|
<script src="/builders/palette.js" defer></script>
|
|
230
230
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
231
231
|
<!-- task 1003069 (R08) — the quick-filing form's routed result links a task
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
<script src="/builders/brand-apply.js?v=2026-06-27-r75" defer></script>
|
|
107
107
|
<!-- The app shell (sidebar + top bar + view router). Dependency-free; loaded
|
|
108
108
|
BEFORE builders.js so the chrome ids exist when events are wired. -->
|
|
109
|
-
<script src="/builders/shell.js?v=2026-
|
|
109
|
+
<script src="/builders/shell.js?v=2026-10-01-look" defer></script>
|
|
110
110
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
111
111
|
<script src="/builders/palette.js" defer></script>
|
|
112
112
|
<!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
|
|
102
102
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
103
103
|
|
|
104
|
-
<script src="/builders/shell.js?v=2026-
|
|
104
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
105
105
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
106
106
|
<script src="/builders/palette.js" defer></script>
|
|
107
107
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
|
|
116
116
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
117
117
|
|
|
118
|
-
<script src="/builders/shell.js?v=2026-
|
|
118
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
119
119
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
120
120
|
<script src="/builders/palette.js" defer></script>
|
|
121
121
|
<!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
|
|
188
188
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
189
189
|
chrome exists before the page script runs. -->
|
|
190
|
-
<script src="/builders/shell.js?v=2026-
|
|
190
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
191
191
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
192
192
|
<script src="/builders/palette.js" defer></script>
|
|
193
193
|
<!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
60
60
|
|
|
61
|
-
<script src="/builders/shell.js?v=2026-
|
|
61
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
62
62
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
63
63
|
<script src="/builders/palette.js" defer></script>
|
|
64
64
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
|
|
78
78
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
79
79
|
chrome exists before the page script runs. -->
|
|
80
|
-
<script src="/builders/shell.js?v=2026-
|
|
80
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
81
81
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
82
82
|
<script src="/builders/palette.js" defer></script>
|
|
83
83
|
<!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
43
43
|
|
|
44
|
-
<script src="/builders/shell.js?v=2026-
|
|
44
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
45
45
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
46
46
|
<script src="/builders/palette.js" defer></script>
|
|
47
47
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
|
|
100
100
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
101
101
|
chrome exists before the page script runs. -->
|
|
102
|
-
<script src="/builders/shell.js?v=2026-
|
|
102
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
103
103
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
104
104
|
<script src="/builders/palette.js" defer></script>
|
|
105
105
|
<!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
|
|
@@ -400,7 +400,7 @@
|
|
|
400
400
|
|
|
401
401
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
402
402
|
chrome exists before the page scripts run. -->
|
|
403
|
-
<script src="/builders/shell.js?v=2026-
|
|
403
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
404
404
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
405
405
|
<script src="/builders/palette.js" defer></script>
|
|
406
406
|
<!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
|
|
@@ -442,7 +442,11 @@
|
|
|
442
442
|
|
|
443
443
|
el.innerHTML =
|
|
444
444
|
'<div class="app-sidebar__brand"><a class="brand-mark" href="/" id="brand-mark">' +
|
|
445
|
-
'
|
|
445
|
+
// A project's logo (ADR 0358, task 1004421) takes the dot's place; the
|
|
446
|
+
// platform validated it as an https image address before it was pushed.
|
|
447
|
+
(brand.theme && brand.theme.logo
|
|
448
|
+
? '<img class="brand-mark__logo" src="' + esc(brand.theme.logo) + '" alt="" width="24" height="24" decoding="async">'
|
|
449
|
+
: '<span class="brand-mark__dot" aria-hidden="true"></span>') +
|
|
446
450
|
'<span class="brand-mark__name" id="brand-name"></span></a></div>' +
|
|
447
451
|
'<nav class="app-sidebar__nav" aria-label="Primary">' + groups + '</nav>' +
|
|
448
452
|
'<div class="app-sidebar__foot">' +
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
|
|
177
177
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
178
178
|
|
|
179
|
-
<script src="/builders/shell.js?v=2026-
|
|
179
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
180
180
|
<script src="/builders/palette.js" defer></script>
|
|
181
181
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
182
182
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -561,6 +561,14 @@ main:focus { outline: none; }
|
|
|
561
561
|
background: none;
|
|
562
562
|
flex-shrink: 0;
|
|
563
563
|
}
|
|
564
|
+
/* A project's logo in the dot's place (ADR 0358): the image is the founder's, so
|
|
565
|
+
it gets a soft square, never a recolour. */
|
|
566
|
+
.brand-mark__logo {
|
|
567
|
+
width: 24px; height: 24px;
|
|
568
|
+
border-radius: 6px;
|
|
569
|
+
object-fit: contain;
|
|
570
|
+
flex-shrink: 0;
|
|
571
|
+
}
|
|
564
572
|
.brand-mark__name {
|
|
565
573
|
display: none;
|
|
566
574
|
white-space: nowrap;
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell.js?v=2026-
|
|
141
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
142
142
|
<script src="/builders/palette.js" defer></script>
|
|
143
143
|
|
|
144
144
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
|
|
141
141
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
142
142
|
|
|
143
|
-
<script src="/builders/shell.js?v=2026-
|
|
143
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
144
144
|
<script src="/builders/palette.js" defer></script>
|
|
145
145
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
146
146
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell.js?v=2026-
|
|
165
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
166
166
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
167
167
|
<script src="/builders/palette.js" defer></script>
|
|
168
168
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell.js?v=2026-
|
|
174
|
+
<script src="/builders/shell.js?v=2026-10-01-look"></script>
|
|
175
175
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
176
176
|
<script src="/builders/palette.js" defer></script>
|
|
177
177
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// modules/provisioning/look.js — a project's look, night tint and logo, as the
|
|
2
|
+
// platform stores and pushes them (ADR 0358, task 1004421; spec D2 + D8).
|
|
3
|
+
//
|
|
4
|
+
// `look` and `night_tint` are POLICY settings: they join SETTINGS_VOCAB, ride web.env
|
|
5
|
+
// and a restart applies them, like every other key there (src/hall-look.js reads them
|
|
6
|
+
// on the instance). Their two companions are values, not choices from a list, so they
|
|
7
|
+
// are reserved settings-jsonb keys written only by routes/look.js:
|
|
8
|
+
//
|
|
9
|
+
// look_accent a custom look's ONE accent, stored AFTER lookAdjustAccent made it
|
|
10
|
+
// readable in both modes, so the instance never has to judge it.
|
|
11
|
+
// logo_url the content-addressed public address of the stored logo.
|
|
12
|
+
//
|
|
13
|
+
// Neither is in SETTINGS_VOCAB, so the generic settings PATCH cannot write them, and
|
|
14
|
+
// neither leaks through effectiveSettings.
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const crypto = require('node:crypto');
|
|
18
|
+
|
|
19
|
+
// The style library's looks (modules/ui-design/styles/, ADR 0219) — a test pins this
|
|
20
|
+
// list to the directory — plus `own` (the instance's own pack: today's hall, and the
|
|
21
|
+
// default) and `custom` (one accent over the chrome world).
|
|
22
|
+
const LIBRARY_LOOKS = Object.freeze(['chrome-world', 'expedition', 'grove', 'blueprint']);
|
|
23
|
+
const LOOK_SETTINGS_VOCAB = Object.freeze({
|
|
24
|
+
look: Object.freeze(['own', ...LIBRARY_LOOKS, 'custom']),
|
|
25
|
+
night_tint: Object.freeze(['on', 'off']),
|
|
26
|
+
});
|
|
27
|
+
// `own` is today's behaviour. The tint defaults ON (owner D2), and only ever tints a
|
|
28
|
+
// hall that wears a look, so the default changes nothing for a hall that chose none.
|
|
29
|
+
const LOOK_DEFAULTS = Object.freeze({ look: 'own', night_tint: 'on' });
|
|
30
|
+
|
|
31
|
+
const HEX = /^#[0-9a-f]{6}$/i;
|
|
32
|
+
const LOGO_URL = /^https:\/\/[^\s"'<>()\\]+$/;
|
|
33
|
+
const storedOf = (row) => (row && row.settings && typeof row.settings === 'object' && !Array.isArray(row.settings) ? row.settings : {});
|
|
34
|
+
const lookAccentOf = (row) => (HEX.test(String(storedOf(row).look_accent || '')) ? String(storedOf(row).look_accent).toLowerCase() : '');
|
|
35
|
+
const logoUrlOf = (row) => (LOGO_URL.test(String(storedOf(row).logo_url || '')) ? String(storedOf(row).logo_url) : '');
|
|
36
|
+
|
|
37
|
+
// The companion env (SETTINGS_COMPANION_ENV): always emitted, empty meaning none, so a
|
|
38
|
+
// value that came and went never leaves a stale line in a patched web.env.
|
|
39
|
+
const LOOK_COMPANION_ENV = Object.freeze({ LOOK_ACCENT: lookAccentOf, LOGO_URL: logoUrlOf });
|
|
40
|
+
|
|
41
|
+
// The project's look as the manage page and the wizard read it. PURE.
|
|
42
|
+
function lookChoice(row, effective) {
|
|
43
|
+
return {
|
|
44
|
+
look: effective.look,
|
|
45
|
+
night_tint: effective.night_tint,
|
|
46
|
+
look_accent: lookAccentOf(row) || null,
|
|
47
|
+
logo_url: logoUrlOf(row) || null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ONE algorithm, two copies: the create wizard and the manage page carry this exact
|
|
52
|
+
// function (modules/public-landing/public/projects.html), and
|
|
53
|
+
// tests/provisioning_look.mjs pins the two texts equal. The founder picks one colour;
|
|
54
|
+
// it must read in both modes, against everything the hall paints with it:
|
|
55
|
+
// * the dark ink the hall puts on an accent fill (#2a1305, --accent-on);
|
|
56
|
+
// * the light mode's derived accent ink (45% accent into #240e00) on the chrome
|
|
57
|
+
// world's paper and card;
|
|
58
|
+
// * the accent itself as text on the black grounds and on the tinted ones
|
|
59
|
+
// (src/night-tint.js, which mixes this same accent in);
|
|
60
|
+
// * the hall's info chip on the tinted card (--info on --info-weak, both mixed from
|
|
61
|
+
// the chrome world's sea and that card) — the thinnest pair a tint moves.
|
|
62
|
+
// 4.6 rather than 4.5 leaves rounding room; the info chip is held at exactly 4.5,
|
|
63
|
+
// computed as tests/hall_tokens.mjs computes it, because 4.6 there would move library
|
|
64
|
+
// accents that already pass. A colour that passes comes back as it
|
|
65
|
+
// was; one that fails keeps its hue and saturation and moves to the nearest
|
|
66
|
+
// lightness that passes. null only for a value that is not #rrggbb.
|
|
67
|
+
function lookAdjustAccent(hex) {
|
|
68
|
+
var m = /^#?([0-9a-f]{6})$/i.exec(String(hex || '').trim());
|
|
69
|
+
if (!m) return null;
|
|
70
|
+
var n = parseInt(m[1], 16);
|
|
71
|
+
var rgb = [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
72
|
+
function mix(a, p, b) { return [0, 1, 2].map(function (i) { return a[i] * p + b[i] * (1 - p); }); }
|
|
73
|
+
function lum(c) {
|
|
74
|
+
var v = c.map(function (x) { x /= 255; return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); });
|
|
75
|
+
return 0.2126 * v[0] + 0.7152 * v[1] + 0.0722 * v[2];
|
|
76
|
+
}
|
|
77
|
+
function ratio(a, b) { var x = lum(a), y = lum(b); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); }
|
|
78
|
+
function reads(c) {
|
|
79
|
+
var ink = mix(c, 0.45, [36, 14, 0]);
|
|
80
|
+
var darks = [[0, 0, 0], [16, 16, 18], mix(c, 0.06, [0, 0, 0]), mix(c, 0.05, [16, 16, 18])];
|
|
81
|
+
var info = mix([91, 111, 208], 0.8, [255, 255, 255]);
|
|
82
|
+
return ratio([42, 19, 5], c) >= 4.6 && ratio(ink, [237, 236, 238]) >= 4.6 && ratio(ink, [251, 250, 252]) >= 4.6 &&
|
|
83
|
+
darks.every(function (d) { return ratio(c, d) >= 4.6; }) && ratio(info, mix(info, 0.14, darks[3])) >= 4.5;
|
|
84
|
+
}
|
|
85
|
+
function toHex(c) { return '#' + c.map(function (x) { var s = Math.round(x).toString(16); return s.length < 2 ? '0' + s : s; }).join(''); }
|
|
86
|
+
var r = rgb[0] / 255, g = rgb[1] / 255, b = rgb[2] / 255;
|
|
87
|
+
var max = Math.max(r, g, b), min = Math.min(r, g, b), l = (max + min) / 2, h = 0, s = 0, d = max - min;
|
|
88
|
+
if (d) {
|
|
89
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
90
|
+
h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
91
|
+
h /= 6;
|
|
92
|
+
}
|
|
93
|
+
function hsl(L) {
|
|
94
|
+
function f(p, q, t) { t = (t + 1) % 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < 1 / 2 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; }
|
|
95
|
+
if (!s) return [L * 255, L * 255, L * 255];
|
|
96
|
+
var q = L < 0.5 ? L * (1 + s) : L + s - L * s, p = 2 * L - q;
|
|
97
|
+
return [f(p, q, h + 1 / 3) * 255, f(p, q, h) * 255, f(p, q, h - 1 / 3) * 255];
|
|
98
|
+
}
|
|
99
|
+
if (reads(rgb)) return { hex: toHex(rgb), adjusted: false };
|
|
100
|
+
for (var step = 1; step <= 200; step++) {
|
|
101
|
+
var tries = [l + step / 200, l - step / 200];
|
|
102
|
+
for (var t = 0; t < 2; t++) {
|
|
103
|
+
if (tries[t] < 0 || tries[t] > 1) continue;
|
|
104
|
+
var c = hsl(tries[t]).map(Math.round);
|
|
105
|
+
if (reads(c)) return { hex: toHex(c), adjusted: true };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── the logo ────────────────────────────────────────────────────────────────
|
|
112
|
+
// PNG, JPEG or WebP, at most 256 KB, checked by its bytes. Never SVG: an SVG is a
|
|
113
|
+
// document, and a document can carry script. The task-visuals screen
|
|
114
|
+
// (modules/lifecycle/task-visuals.js) is the precedent; this is its narrower cousin.
|
|
115
|
+
const LOGO_MAX_BYTES = 256 * 1024;
|
|
116
|
+
const LOGO_TYPES = Object.freeze({ 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp' });
|
|
117
|
+
const LOGO_MIME = Object.freeze({ png: 'image/png', jpg: 'image/jpeg', webp: 'image/webp' });
|
|
118
|
+
|
|
119
|
+
function sniffLogo(buf) {
|
|
120
|
+
if (!buf || buf.length < 12) return null;
|
|
121
|
+
if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return 'png';
|
|
122
|
+
if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return 'jpg';
|
|
123
|
+
if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
|
|
124
|
+
buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) return 'webp';
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// { ok:true, ext, mime, sha } or { ok:false, reason, message }. PURE; never throws.
|
|
129
|
+
function screenLogo(buf, contentType) {
|
|
130
|
+
const declared = LOGO_TYPES[String(contentType || '').split(';')[0].trim().toLowerCase()];
|
|
131
|
+
if (!declared) return { ok: false, reason: 'bad_type', message: 'A logo must be a PNG, JPEG or WebP image.' };
|
|
132
|
+
if (!buf || !buf.length) return { ok: false, reason: 'empty', message: 'The image is empty.' };
|
|
133
|
+
if (buf.length > LOGO_MAX_BYTES) {
|
|
134
|
+
return { ok: false, reason: 'too_big', message: `The image is ${Math.round(buf.length / 1024)} KB — a logo can be at most ${LOGO_MAX_BYTES / 1024} KB.` };
|
|
135
|
+
}
|
|
136
|
+
const ext = sniffLogo(buf);
|
|
137
|
+
if (ext !== declared) return { ok: false, reason: 'sniff_mismatch', message: 'The file is not really the kind of image it says it is.' };
|
|
138
|
+
return { ok: true, ext, mime: LOGO_MIME[ext], sha: crypto.createHash('sha256').update(buf).digest('hex') };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The stored logo's public file name: content-addressed, so the address changes when
|
|
142
|
+
// the image does (no stale cache), is unguessable, and names no project.
|
|
143
|
+
const logoFileName = (sha, ext) => `${sha}.${ext}`;
|
|
144
|
+
const LOGO_FILE = /^([0-9a-f]{64})\.(png|jpg|webp)$/;
|
|
145
|
+
|
|
146
|
+
// The logo's three statements. The route calls them through this module object, so a
|
|
147
|
+
// test can stand in for the database without one.
|
|
148
|
+
async function upsertLogo(db, instanceId, { sha, ext, mime }, buf) {
|
|
149
|
+
await db.query(
|
|
150
|
+
`INSERT INTO provisioning_logos (instance_id, sha256, ext, mime, bytes)
|
|
151
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
152
|
+
ON CONFLICT (instance_id) DO UPDATE
|
|
153
|
+
SET sha256 = EXCLUDED.sha256, ext = EXCLUDED.ext, mime = EXCLUDED.mime, bytes = EXCLUDED.bytes, updated_at = now()`,
|
|
154
|
+
[instanceId, sha, ext, mime, buf]);
|
|
155
|
+
}
|
|
156
|
+
async function deleteLogo(db, instanceId) {
|
|
157
|
+
await db.query('DELETE FROM provisioning_logos WHERE instance_id = $1', [instanceId]);
|
|
158
|
+
}
|
|
159
|
+
async function readLogo(db, sha, ext) {
|
|
160
|
+
const { rows } = await db.query('SELECT mime, bytes FROM provisioning_logos WHERE sha256 = $1 AND ext = $2 LIMIT 1', [sha, ext]);
|
|
161
|
+
return rows[0] || null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
upsertLogo, deleteLogo, readLogo,
|
|
166
|
+
LIBRARY_LOOKS, LOOK_SETTINGS_VOCAB, LOOK_DEFAULTS, LOOK_COMPANION_ENV,
|
|
167
|
+
lookChoice, lookAdjustAccent, lookAccentOf, logoUrlOf,
|
|
168
|
+
LOGO_MAX_BYTES, LOGO_MIME, screenLogo, sniffLogo, logoFileName, LOGO_FILE,
|
|
169
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- provisioning_034_project_logo.sql — a project's logo, kept by the platform
|
|
2
|
+
-- (task 1004421, BV2.PS06; ADR 0358 D6; goal 1000121, spec docs/specs/bongos-v2-project-startup.md).
|
|
3
|
+
--
|
|
4
|
+
-- WHY THIS EXISTS. A founder can give a project a logo at creation, and an existing
|
|
5
|
+
-- project's owner can add one on its manage page (spec D8). The hall shows it in its mark
|
|
6
|
+
-- and uses it as the browser tab's icon, and the hall is a different machine from the
|
|
7
|
+
-- platform — so the platform keeps the image and serves it at a public address the hall
|
|
8
|
+
-- is told (the LOGO_URL setting companion). An instance has no shared disk with the
|
|
9
|
+
-- platform, and the platform's own disk is replaced on every core upgrade, so the bytes
|
|
10
|
+
-- live here.
|
|
11
|
+
--
|
|
12
|
+
-- WHAT IT ADDS. One row per project that has a logo:
|
|
13
|
+
-- instance_id the project (provisioning_instances.id); deleting the project row drops it.
|
|
14
|
+
-- sha256 hex digest of the bytes. The public address is <sha256>.<ext>, so it is
|
|
15
|
+
-- content-addressed: it changes when the image does, names no project and
|
|
16
|
+
-- cannot be guessed.
|
|
17
|
+
-- ext / mime png | jpg | webp, from the bytes' own magic number (never SVG, which is a
|
|
18
|
+
-- document that can carry script). The route refuses anything else.
|
|
19
|
+
-- bytes the image itself, at most 256 KB (enforced by the route).
|
|
20
|
+
--
|
|
21
|
+
-- Additive and namespaced (ADR 0083): no down-migration, idempotent — safe to re-run.
|
|
22
|
+
|
|
23
|
+
BEGIN;
|
|
24
|
+
|
|
25
|
+
CREATE TABLE IF NOT EXISTS provisioning_logos (
|
|
26
|
+
instance_id bigint PRIMARY KEY REFERENCES provisioning_instances(id) ON DELETE CASCADE,
|
|
27
|
+
sha256 text NOT NULL,
|
|
28
|
+
ext text NOT NULL CHECK (ext IN ('png', 'jpg', 'webp')),
|
|
29
|
+
mime text NOT NULL,
|
|
30
|
+
bytes bytea NOT NULL,
|
|
31
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
32
|
+
);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS provisioning_logos_sha256 ON provisioning_logos (sha256);
|
|
34
|
+
|
|
35
|
+
COMMIT;
|
|
@@ -24,6 +24,7 @@ const { normalizeModuleSelection, effectiveModules } = require('./starter-bundle
|
|
|
24
24
|
// projection both have to read them.
|
|
25
25
|
const { PLANET_PHYSICS_QUESTIONS, canonicalAnswer } = require('./planet-physics');
|
|
26
26
|
const { softLimitsFor } = require('./soft-limits');
|
|
27
|
+
const { LOOK_SETTINGS_VOCAB, LOOK_DEFAULTS, LOOK_COMPANION_ENV } = require('./look');
|
|
27
28
|
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
29
30
|
// Constants (mirror the CHECK constraints in provisioning_001_tables.sql)
|
|
@@ -198,7 +199,7 @@ const SETTINGS_VOCAB = Object.freeze({
|
|
|
198
199
|
visibility: ['public', 'private', 'stealth'],
|
|
199
200
|
join_grant: ['view', 'apply', 'full'],
|
|
200
201
|
artist_gate: ['off', 'advisory', 'strict'],
|
|
201
|
-
spend_payer: ['none', 'project', 'builder'],
|
|
202
|
+
spend_payer: ['none', 'project', 'builder'], ...LOOK_SETTINGS_VOCAB, // + the look, ADR 0358 (look.js)
|
|
202
203
|
});
|
|
203
204
|
// Default = today's behavior on every axis, including the join door: ADR 0182's
|
|
204
205
|
// migration gives the hub column the same `public` default, "existing rows keep
|
|
@@ -206,7 +207,7 @@ const SETTINGS_VOCAB = Object.freeze({
|
|
|
206
207
|
// owner's ruling rather than the status quo — see the note above it.
|
|
207
208
|
const DEFAULT_SETTINGS = Object.freeze({
|
|
208
209
|
platform_visibility: 'public', joinability: 'apply', visibility: 'public', join_grant: 'full',
|
|
209
|
-
artist_gate: 'strict', spend_payer: 'none',
|
|
210
|
+
artist_gate: 'strict', spend_payer: 'none', ...LOOK_DEFAULTS,
|
|
210
211
|
});
|
|
211
212
|
|
|
212
213
|
// The project's PLANET (task 1003347, direction record §16): how the platform
|
|
@@ -517,7 +518,7 @@ function settingsEnvVars(row, prefix) {
|
|
|
517
518
|
// cutoff and every unresolved review counts, which is the right answer for a
|
|
518
519
|
// project that has been strict since it stood up.
|
|
519
520
|
const SETTINGS_COMPANION_ENV = Object.freeze({
|
|
520
|
-
ARTIST_GATE_SINCE: (row) => artistGateStrictSince(row) || '',
|
|
521
|
+
ARTIST_GATE_SINCE: (row) => artistGateStrictSince(row) || '', ...LOOK_COMPANION_ENV, // + accent, logo (look.js)
|
|
521
522
|
});
|
|
522
523
|
|
|
523
524
|
// The web.env block the runner writes at STANDUP (task 1003139): a comment that
|