@bongos/core 1.24.30 → 1.24.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.bongos-core.json +153 -103
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +8 -0
  4. package/clients/bongos-client/index.cjs +8 -0
  5. package/clients/bongos-client/index.d.ts +8 -0
  6. package/clients/bongos-client/index.mjs +8 -0
  7. package/docs/api/openapi.json +139 -1
  8. package/docs/api-reference.md +7 -3
  9. package/docs/copy-inventory.md +192 -187
  10. package/docs/copy-registry.json +530 -458
  11. package/docs/module-api-changelog.md +4 -2
  12. package/docs/page-inventory.json +15 -35
  13. package/docs/page-readings.json +228 -265
  14. package/modules/copy-desk/module.json +1 -1
  15. package/modules/copy-desk/routes/copy-desk-studio.js +103 -0
  16. package/modules/copy-desk/tests/copy_no_cms.mjs +2 -2
  17. package/modules/hall-ui/public/ai-studio.html +1 -1
  18. package/modules/hall-ui/public/atlas.html +1 -1
  19. package/modules/hall-ui/public/blockers.html +1 -1
  20. package/modules/hall-ui/public/board-room.html +1 -1
  21. package/modules/hall-ui/public/city.html +1 -1
  22. package/modules/hall-ui/public/collab.html +1 -1
  23. package/modules/hall-ui/public/commands.html +1 -1
  24. package/modules/hall-ui/public/copy-desk.html +1 -1
  25. package/modules/hall-ui/public/craft-mode.js +1 -1
  26. package/modules/hall-ui/public/deploy.html +1 -1
  27. package/modules/hall-ui/public/diagrams.html +1 -1
  28. package/modules/hall-ui/public/drachmae.html +1 -1
  29. package/modules/hall-ui/public/fleet.html +1 -1
  30. package/modules/hall-ui/public/gate.html +1 -1
  31. package/modules/hall-ui/public/goal-map.html +1 -1
  32. package/modules/hall-ui/public/goals.html +1 -1
  33. package/modules/hall-ui/public/government.html +1 -1
  34. package/modules/hall-ui/public/hall-render.js +62 -86
  35. package/modules/hall-ui/public/home-city-draw.js +360 -52
  36. package/modules/hall-ui/public/home-city.css +47 -11
  37. package/modules/hall-ui/public/home-city.js +170 -40
  38. package/modules/hall-ui/public/home-gallery.css +129 -0
  39. package/modules/hall-ui/public/home-gallery.js +349 -0
  40. package/modules/hall-ui/public/home-studio.css +72 -0
  41. package/modules/hall-ui/public/home-studio.js +203 -0
  42. package/modules/hall-ui/public/idea.html +1 -1
  43. package/modules/hall-ui/public/ideas.html +1 -1
  44. package/modules/hall-ui/public/index.html +4 -1
  45. package/modules/hall-ui/public/market-watch-item.html +1 -1
  46. package/modules/hall-ui/public/market-watch-rules.html +1 -1
  47. package/modules/hall-ui/public/market-watch-settings.js +6 -1
  48. package/modules/hall-ui/public/market-watch.html +141 -54
  49. package/modules/hall-ui/public/market-watch.js +54 -0
  50. package/modules/hall-ui/public/market-watch.states.json +2 -1
  51. package/modules/hall-ui/public/modules.html +1 -1
  52. package/modules/hall-ui/public/my-agents.html +1 -1
  53. package/modules/hall-ui/public/primer.html +1 -1
  54. package/modules/hall-ui/public/profile.html +1 -1
  55. package/modules/hall-ui/public/project-settings.html +1 -1
  56. package/modules/hall-ui/public/ranks.html +1 -1
  57. package/modules/hall-ui/public/roadmap.html +1 -1
  58. package/modules/hall-ui/public/roster.html +1 -1
  59. package/modules/hall-ui/public/sessions.html +1 -1
  60. package/modules/hall-ui/public/settings.html +1 -1
  61. package/modules/hall-ui/public/shell.js +2 -3
  62. package/modules/hall-ui/public/street.html +1 -1
  63. package/modules/hall-ui/public/studio.html +9 -1
  64. package/modules/hall-ui/public/style.css +74 -27
  65. package/modules/hall-ui/public/task.html +1 -1
  66. package/modules/hall-ui/public/thinking.html +1 -1
  67. package/modules/hall-ui/public/tweak-editor.html +1 -1
  68. package/modules/hall-ui/public/watch.html +1 -1
  69. package/modules/hall-ui/public/work.html +1 -1
  70. package/modules/hall-ui/records/home-city.md +133 -0
  71. package/modules/hall-ui/records/loader.md +1 -1
  72. package/modules/hall-ui/records/market-watch-settings.md +16 -3
  73. package/modules/hall-ui/records/market-watch.md +3 -1
  74. package/modules/lifecycle/home-city.js +58 -10
  75. package/modules/lifecycle/home-gallery.js +320 -0
  76. package/modules/lifecycle/migrations/lifecycle_020_work_appreciations.sql +36 -0
  77. package/modules/lifecycle/module.json +1 -0
  78. package/modules/lifecycle/routes/home-gallery.js +37 -0
  79. package/modules/market-watch/routes/settings.js +1 -1
  80. package/package-lock.json +2 -2
  81. package/package.json +1 -1
  82. package/release-notes.json +35 -1
  83. package/scripts/bongos/leak-scan-allowlist.js +8 -0
  84. package/scripts/hall-preview/README.md +1 -0
  85. package/scripts/hall-preview/server.js +10 -0
  86. package/src/bongos/serve-internal.js +17 -3
  87. package/src/module-api.js +1 -1
  88. package/tests/hall_builders_page_pass.mjs +18 -27
  89. package/tests/hall_loading.mjs +1 -1
  90. package/tests/hall_nav.mjs +11 -4
  91. package/tests/hall_page_gate_map.mjs +7 -4
  92. package/tests/home_city.mjs +200 -5
  93. package/tests/home_city_widget.mjs +187 -27
  94. package/tests/home_gallery.mjs +255 -0
  95. package/tests/home_gallery_widget.mjs +220 -0
  96. package/tests/home_studio.mjs +169 -0
  97. package/tests/home_studio_widget.mjs +157 -0
  98. package/tests/market_watch_pages.mjs +162 -0
  99. package/tests/nav_permission_atoms.mjs +2 -2
  100. package/modules/hall-ui/public/market-watch-settings.html +0 -115
  101. package/modules/hall-ui/public/market-watch-settings.states.json +0 -14
@@ -7,7 +7,7 @@
7
7
  "category": { "parent": "quality" },
8
8
  "default": true,
9
9
  "maintenance": { "status": "core-maintained" },
10
- "contributes": { "routes": ["copy-desk", "copy-desk-artist", "copy-desk-release"], "migrations": true, "skills": ["tweak"], "pollers": ["stuck-page-digest", "auto-close-rounds"] },
10
+ "contributes": { "routes": ["copy-desk", "copy-desk-artist", "copy-desk-release", "copy-desk-studio"], "migrations": true, "skills": ["tweak"], "pollers": ["stuck-page-digest", "auto-close-rounds"] },
11
11
  "provides": ["copy-desk.artistStats", "copy-desk.stuckPageTweaksFor"],
12
12
  "consumes": ["lifecycle"]
13
13
  }
@@ -0,0 +1,103 @@
1
+ // modules/copy-desk/routes/copy-desk-studio.js — the artist's studio, its copy-desk
2
+ // half (task 1005061, Home v16 step 4; mock docs/design/mocks/hall/home/v16-studio.html).
3
+ //
4
+ // GET /copy-desk/studio — one bounded read for three of the studio's shelves:
5
+ //
6
+ // easel the page round the caller holds (the Tweak/CopyWrite Flow's `resume`), or,
7
+ // with nothing held, the page the flow would recommend next.
8
+ // waiting "Waiting for new inspiration": pages whose words changed since an artist
9
+ // last designed them (the recommendation's `changed` tier, drift from the
10
+ // page readings), most changed lines first, at most MAX_WAITING, with the
11
+ // whole count.
12
+ // words "Words to tune": the newest open copy-desk flags, at most MAX_WORDS.
13
+ //
14
+ // Everything is composed by the flow's own reader (page-status.js composeNext over
15
+ // the inventory, the readings, the ledger's page rounds and the page asks), so the
16
+ // studio and the Studio page can never disagree about a page. Read only: it writes no
17
+ // row and returns no replacement wording (copy_no_cms.mjs holds every route file here
18
+ // to that). The other two shelves, the works the caller pinned and signed, are the
19
+ // lifecycle module's (GET /home/studio/works).
20
+ //
21
+ // Apart from routes/copy-desk.js because that file sits at the 1,500-line ratchet.
22
+
23
+ 'use strict';
24
+
25
+ const express = require('express');
26
+ const api = require('../../../src/module-api');
27
+ const flags = require('../flags');
28
+ const pageData = require('../page-data');
29
+ const pageStatus = require('../page-status');
30
+
31
+ const log = api.logger('copy-desk');
32
+
33
+ const MAX_WAITING = 4;
34
+ const MAX_WORDS = 3;
35
+ const SURFACE_NAMES = Object.freeze({ landing: 'The public site', builders: 'The builders’ hall', status: 'The status page' });
36
+
37
+ function editorPath(pageId) { return `/tweak-editor?page=${encodeURIComponent(pageId)}`; }
38
+
39
+ // The studio's shape from the flow's answer and the newest open flags. Pure.
40
+ function composeStudio({ next, flagRows }) {
41
+ const n = next || {};
42
+ const all = [n.recommended, ...(n.alternatives || [])].filter(Boolean);
43
+ const changed = all.filter((c) => c.tier === 'changed' && c.changed_lines > 0)
44
+ .sort((a, b) => b.changed_lines - a.changed_lines);
45
+ const r = n.resume;
46
+ const easel = r
47
+ ? { held: true, page_id: r.page_id, title: r.title, place: SURFACE_NAMES[r.surface] || null, round: r.round || null, open: editorPath(r.page_id) }
48
+ : (n.recommended
49
+ ? { held: false, page_id: n.recommended.page_id, title: n.recommended.title, place: SURFACE_NAMES[n.recommended.surface] || null, why: n.recommended.why || null, open: editorPath(n.recommended.page_id) }
50
+ : null);
51
+ return {
52
+ easel,
53
+ waiting: {
54
+ total: changed.length,
55
+ pages: changed.slice(0, MAX_WAITING).map((c) => ({
56
+ page_id: c.page_id, title: c.title, place: SURFACE_NAMES[c.surface] || null, changed_lines: c.changed_lines, open: editorPath(c.page_id),
57
+ })),
58
+ },
59
+ words: (flagRows || []).slice(0, MAX_WORDS).map((f) => ({
60
+ id: String(f.id),
61
+ text: f.text_at_flag || null,
62
+ reason: f.reason || '',
63
+ place: SURFACE_NAMES[f.surface] || null,
64
+ })),
65
+ };
66
+ }
67
+
68
+ module.exports = function buildCopyDeskStudioRouter() {
69
+ const router = express.Router();
70
+
71
+ // rank: any-builder — GET /copy-desk/studio, the caller's easel, the pages waiting
72
+ // for an artist and the newest open flags. A work list, like /copy-desk/next and
73
+ // /copy-desk/queue, which any signed-in builder may already read.
74
+ router.get('/copy-desk/studio', api.requireBuilder, async (req, res) => {
75
+ try {
76
+ const inv = pageData.loadInventory();
77
+ const lifecycle = api.resolveOptional && api.resolveOptional('lifecycle');
78
+ const readsOk = inv.ok && !!lifecycle && typeof lifecycle.listPageTweakTasks === 'function';
79
+ const readings = pageData.loadReadings();
80
+ const [tasks, asks, flagRows] = await Promise.all([
81
+ readsOk ? lifecycle.listPageTweakTasks() : Promise.resolve([]),
82
+ readsOk ? flags.pageAskSummary() : Promise.resolve([]),
83
+ flags.listFlags({ status: 'open', scopes: flags.FLAG_SCOPES, limit: MAX_WORDS }),
84
+ ]);
85
+ // Every candidate, not the flow's four alternatives: the waiting shelf is a filter
86
+ // over all of them. Bounded by the inventory's pages, never the corpus.
87
+ const next = readsOk ? pageStatus.composeNext({
88
+ inventory: inv.data, readings: readings.ok ? readings.data : null, tasks, asks, builderId: req.builder.id,
89
+ alternatives: (inv.data.pages || []).length,
90
+ }) : null;
91
+ res.set('Cache-Control', 'private, no-store');
92
+ res.json(composeStudio({ next, flagRows }));
93
+ } catch (err) {
94
+ log.error({ err }, 'GET /copy-desk/studio failed');
95
+ if (!res.headersSent) res.fail('copy_studio_failed', 500);
96
+ }
97
+ });
98
+
99
+ return router;
100
+ };
101
+ module.exports.composeStudio = composeStudio;
102
+ module.exports.MAX_WAITING = MAX_WAITING;
103
+ module.exports.MAX_WORDS = MAX_WORDS;
@@ -74,8 +74,8 @@ const MODULE_FILES = ['registry.js', 'flags.js', 'proposals.js', 'pages.js', 'pa
74
74
 
75
75
  console.log('copy-desk: NO LIVE CMS');
76
76
 
77
- await test('the route files are exactly the three the module declares', () => {
78
- assert.deepEqual(ROUTE_FILES, ['routes/copy-desk-artist.js', 'routes/copy-desk-release.js', 'routes/copy-desk.js'],
77
+ await test('the route files are exactly the four the module declares', () => {
78
+ assert.deepEqual(ROUTE_FILES, ['routes/copy-desk-artist.js', 'routes/copy-desk-release.js', 'routes/copy-desk-studio.js', 'routes/copy-desk.js'],
79
79
  'a new route file is a new place a write could hide: add it here, and to the write-surface scan below');
80
80
  });
81
81
 
@@ -89,7 +89,7 @@
89
89
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
90
90
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
91
91
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
92
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
92
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
93
93
  <script src="/builders/palette.js" defer></script>
94
94
  <script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
95
95
  <script src="/builders/studio-lib.js?v=2026-10-09-day"></script>
@@ -92,7 +92,7 @@
92
92
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
93
93
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
94
94
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
95
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
95
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
96
96
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
97
97
  <script src="/builders/palette.js" defer></script>
98
98
  <script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
@@ -61,7 +61,7 @@
61
61
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
62
62
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
63
63
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
64
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
64
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
65
65
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
66
66
  <script src="/builders/palette.js" defer></script>
67
67
  <!-- the branding pack's currency label, loaded BEFORE blockers.js. -->
@@ -63,7 +63,7 @@
63
63
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
64
64
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
65
65
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
66
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
66
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
67
67
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
68
68
  <script src="/builders/palette.js" defer></script>
69
69
  <script src="/builders/hall-kit.js"></script>
@@ -108,7 +108,7 @@
108
108
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
109
109
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab" defer></script>
110
110
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch" defer></script>
111
- <script src="/builders/shell.js?v=2026-11-03-update-pill" defer></script>
111
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch" defer></script>
112
112
  <script src="/builders/palette.js" defer></script>
113
113
  <script src="/builders/gov-living.js?v=2026-10-09-positions1" defer></script>
114
114
  <script src="/builders/gov-feeds.js?v=2026-10-09-living3" defer></script>
@@ -162,7 +162,7 @@
162
162
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
163
163
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
164
164
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
165
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
165
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
166
166
  <script src="/builders/palette.js" defer></script>
167
167
  <script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
168
168
  <script src="/builders/collab.js?v=2026-09-24-archive"></script>
@@ -54,7 +54,7 @@
54
54
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
55
55
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
56
56
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
57
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
57
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></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/hall-kit.js"></script>
@@ -180,7 +180,7 @@
180
180
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
181
181
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
182
182
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
183
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
183
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
184
184
  <script src="/builders/palette.js" defer></script>
185
185
  <script src="/builders/hall-kit.js"></script>
186
186
  <script src="/builders/copy-desk.js?v=2026-09-01-redesign"></script>
@@ -107,7 +107,7 @@
107
107
  artistry: ['studio', 'goals', 'thinking'],
108
108
  ideating: ['goal-map', 'thinking', 'goals', 'market-watch'],
109
109
  governing: ['city', 'blockers', 'board-room',
110
- 'watch', 'gate', 'sessions', 'project-settings', 'deploy', 'fleet', 'market-watch-settings', 'government'],
110
+ 'watch', 'gate', 'sessions', 'project-settings', 'deploy', 'fleet', 'government'],
111
111
  };
112
112
  // A mode can lead with ONE tab and fold the rest of its own under a dropdown
113
113
  // (owner, 2026-10-08: the city is Governing's tab, everything else under it).
@@ -81,7 +81,7 @@
81
81
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
82
82
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
83
83
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
84
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
84
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
85
85
  <script src="/builders/palette.js" defer></script>
86
86
  <script src="/builders/hall-kit.js"></script>
87
87
  <!-- the shared ask card the move is drawn with (task 1004829) -->
@@ -97,7 +97,7 @@
97
97
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
98
98
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
99
99
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
100
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
100
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></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>
@@ -97,7 +97,7 @@
97
97
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
98
98
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
99
99
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
100
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
100
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></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/md-reader.js?v=2026-09-02-mdreader-2"></script>
@@ -60,7 +60,7 @@
60
60
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
61
61
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
62
62
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
63
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
63
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
64
64
  <script src="/builders/palette.js" defer></script>
65
65
  <script src="/builders/hall-kit.js"></script>
66
66
  <script src="/builders/fleet-costs.js"></script>
@@ -67,7 +67,7 @@
67
67
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
68
68
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
69
69
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
70
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
70
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
71
71
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
72
72
  <script src="/builders/palette.js" defer></script>
73
73
  <script src="/builders/hall-kit.js"></script>
@@ -140,7 +140,7 @@
140
140
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
141
141
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
142
142
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
143
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
143
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
144
144
  <script src="/builders/palette.js" defer></script>
145
145
 
146
146
  <script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
@@ -244,7 +244,7 @@
244
244
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
245
245
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
246
246
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
247
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
247
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
248
248
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
249
249
  <script src="/builders/palette.js" defer></script>
250
250
  <!-- task 1004727: the ONE karma vote control every task/idea list renders -->
@@ -157,7 +157,7 @@
157
157
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
158
158
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
159
159
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
160
- <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
160
+ <script src="/builders/shell.js?v=2026-11-04-one-market-watch"></script>
161
161
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
162
162
  <script src="/builders/palette.js" defer></script>
163
163
  <script src="/builders/hall-kit.js"></script>
@@ -273,69 +273,40 @@
273
273
  return v === 'open' || v === 'invite_only' ? v : 'apply';
274
274
  }
275
275
 
276
- // One landing per door: open → sign in and you're in; invite_only → nothing to
277
- // apply for, sign in once invited; apply → the request form.
276
+ // The signed-out page is one centred card (task: sign-in redesign): the
277
+ // sign-in button leads in every mode, and the door below it is the only part
278
+ // that depends on joinability — open → nothing to do, invite_only → nothing
279
+ // to apply for, apply → the request form, folded behind a disclosure so the
280
+ // card stays a sign-in screen rather than a form.
278
281
  function landingDoorHtml(mode) {
279
- const hub = `This is the builders' hub for ${brandWorld()} — where builders claim work, earn ${curLabel()}, and see what they have accomplished.`;
280
282
  const admitter = escapeHtml(rankLabel('archon'));
281
- const signIn = `<p class="landing__cta"><a class="landing__btn" href="${API}/auth/web/start">Sign in with GitHub</a></p>`;
282
283
  if (mode === 'open') {
283
284
  return `
284
- <p class="landing__lede">${hub} Anyone with a GitHub account can join: sign in and you're in, starting at the newcomer rank.</p>
285
- <div class="landing__request" id="landing-request">
286
- <h3 class="landing__request-h">New here — sign in to join</h3>
287
- <p class="landing__request-lede">No request and no invite needed. Sign in with GitHub, and a step-by-step guide takes you the rest of the way.</p>
288
- ${signIn}
289
- </div>`;
285
+ <p class="landing__door" id="landing-request">New here? Signing in creates your account.</p>`;
290
286
  }
291
287
  if (mode === 'invite_only') {
292
288
  return `
293
- <p class="landing__lede">${hub} This project is invite-only: the ${admitter} invites each builder, and there is nothing to apply for here.</p>
294
- <div class="landing__request" id="landing-request">
295
- <h3 class="landing__request-h">Invited? Sign in</h3>
296
- <p class="landing__request-lede">If the ${admitter} has invited you, sign in with GitHub and you're in. If not, ask them for an invite — this project doesn't take requests.</p>
297
- ${signIn}
298
- </div>`;
289
+ <p class="landing__door" id="landing-request">Invite-only — there is nothing to apply for here.</p>`;
299
290
  }
300
291
  return `
301
- <p class="landing__lede">
302
- ${hub} Access is
303
- invite-based: an Archon approves each new builder. To begin, request
304
- access below.
305
- </p>
306
-
307
- <div class="landing__request" id="landing-request">
308
- <h3 class="landing__request-h">New here — request access</h3>
309
- <p class="landing__request-lede">
310
- Leave your GitHub username and an Archon will let you in. No coding
311
- needed to start — once you're approved, sign in with GitHub and a
312
- step-by-step guide takes you the rest of the way.
313
- </p>
314
- <form class="landing__form" id="access-request-form" novalidate>
315
- <label class="landing__field">
316
- <span>GitHub username <span class="landing__req-mark" aria-hidden="true">*</span></span>
317
- <input type="text" id="ar-login" name="github_login" autocomplete="username"
318
- placeholder="e.g. octocat" maxlength="40" required>
319
- </label>
320
- <label class="landing__field">
321
- <span>Your name <span class="landing__opt">(optional)</span></span>
322
- <input type="text" id="ar-name" name="display_name" autocomplete="name"
323
- placeholder="So the Archon knows who you are" maxlength="100">
324
- </label>
325
- <label class="landing__field">
326
- <span>A word on why <span class="landing__opt">(optional)</span></span>
327
- <input type="text" id="ar-note" name="note"
328
- placeholder="How you got here, what you'd build" maxlength="500">
329
- </label>
330
- <p class="landing__form-hint">
331
- No GitHub account yet? Make a free one at
332
- <a href="https://github.com/signup" target="_blank" rel="noopener noreferrer">github.com</a>
333
- first — your username is all we need.
334
- </p>
335
- <button type="submit" class="landing__btn landing__btn--request" id="ar-submit">Request access</button>
336
- <p class="landing__request-status" id="ar-status" aria-live="polite"></p>
337
- </form>
338
- </div>`;
292
+ <details class="landing__disclosure">
293
+ <summary class="landing__summary">New here? Request access</summary>
294
+ <div class="landing__request" id="landing-request">
295
+ <form class="landing__form" id="access-request-form" novalidate>
296
+ <label class="landing__field">
297
+ <span>GitHub username</span>
298
+ <input type="text" id="ar-login" name="github_login" autocomplete="username"
299
+ placeholder="e.g. octocat" maxlength="40" required>
300
+ </label>
301
+ <label class="landing__field">
302
+ <span>Why you'd like in <span class="landing__opt">(optional)</span></span>
303
+ <input type="text" id="ar-note" name="note" maxlength="500">
304
+ </label>
305
+ <button type="submit" class="landing__btn landing__btn--quiet landing__btn--block" id="ar-submit">Request access</button>
306
+ <p class="landing__request-status" id="ar-status" aria-live="polite"></p>
307
+ </form>
308
+ </div>
309
+ </details>`;
339
310
  }
340
311
 
341
312
  // task 1002835: a karma vote on a peer's ship was a vote on a title alone.
@@ -360,28 +331,39 @@
360
331
  }
361
332
  return `<span class="acclaim__evidence">${parts.join(' · ')}</span>`;
362
333
  }
363
- // The RETURNING builder's door (task 1002851). `open` and `invite_only`
364
- // already render a real .landing__btn sign-in inside the primary door, so a
365
- // second button there would be a third scattered path rather than a clearer
366
- // one — those modes keep the prose note alone. Only the `apply` door, whose
367
- // primary control is the request FORM, left signing in as a footnote link.
368
- // It gets a real button: the request button's geometry so it reads as a
369
- // control, a quieter fill so the request path still leads for the strangers
370
- // who are most of this page's traffic.
371
- function returningDoorHtml(mode) {
372
- if (mode !== 'apply') return '';
334
+ // The sign-in itself — the card's one primary control, in every mode, so a
335
+ // returning builder never hunts for it below a form (task 1002851's intent).
336
+ function returningDoorHtml() {
373
337
  return `
374
- <div class="landing__returning" id="landing-returning">
375
- <h3 class="landing__request-h">Already a member?</h3>
376
- <p class="landing__request-lede">You've been approved before — sign in and pick up where you left off.</p>
377
- <p class="landing__cta"><a class="landing__btn landing__btn--quiet" href="${API}/auth/web/start">Sign in with GitHub</a></p>
378
- </div>`;
338
+ <a class="landing__btn landing__btn--block" id="landing-signin" href="${API}/auth/web/start">
339
+ <svg class="landing__gh" viewBox="0 0 16 16" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
340
+ Sign in with GitHub
341
+ </a>`;
342
+ }
343
+
344
+ // The project's mark, as the sidebar draws it: the pack's own logo when it has
345
+ // one (theme.logo, ADR 0358), otherwise the Cloud Bongos ring-and-moon.
346
+ function landingLogoHtml() {
347
+ const b = window.__BRANDING__ || {};
348
+ const logo = b.theme && b.theme.logo;
349
+ return logo
350
+ ? `<img class="landing__logo" src="${escapeHtml(logo)}" alt="" width="56" height="56" decoding="async">`
351
+ : '<svg class="landing__logo" viewBox="0 0 200 200" aria-hidden="true"><path class="landing__ring" d="M137 31 A78 78 0 1 0 170 66" fill="none" stroke-width="15" stroke-linecap="round"/><circle class="landing__planet" cx="100" cy="100" r="55"/><circle cx="156" cy="46" r="12" fill="#fb9569"/></svg>';
352
+ }
353
+
354
+ // Strip the hall's chrome (rail, top bar, foot) for the signed-out card —
355
+ // html[data-door="out"], styled in style.css. The test vm has no
356
+ // documentElement, hence the guard.
357
+ function markSignedOut() {
358
+ const root = document.documentElement;
359
+ if (root && root.dataset) root.dataset.door = 'out';
379
360
  }
380
361
 
381
362
  function renderLanding() {
382
- // task 1723: collapse the shell nav to the signed-out minimum (Home + a
383
- // Sign-in button). Cosmetic — the server gates every page regardless.
363
+ // task 1723: collapse the shell nav to the signed-out minimum. Cosmetic —
364
+ // the server gates every page regardless.
384
365
  window.OTBShell?.applyAccess({ authed: false });
366
+ markSignedOut();
385
367
 
386
368
  const welcome = $('#welcome-line');
387
369
  if (welcome) welcome.textContent = '';
@@ -390,20 +372,12 @@
390
372
  if (landing) {
391
373
  landing.hidden = false;
392
374
  const mode = joinabilityMode();
393
- // On the apply door the sign-in is a real button above, so the note keeps
394
- // only the privacy sentence — one sign-in path per landing, never two.
395
- const signInNote = mode === 'apply'
396
- ? ''
397
- : `Already a member? <a href="${API}/auth/web/start">Sign in with GitHub</a>. `;
398
375
  landing.innerHTML = `
399
- <div class="scroll landing">
400
- <h2 class="scroll__h">Welcome</h2>
401
- ${landingDoorHtml(mode)}
376
+ <div class="landing">
377
+ ${landingLogoHtml()}
378
+ <h2 class="landing__title">${brandWorld()}</h2>
402
379
  ${returningDoorHtml(mode)}
403
-
404
- <p class="landing__signin-note">
405
- ${signInNote}We store only your GitHub login, avatar, and the work you ship.
406
- </p>
380
+ ${landingDoorHtml(mode)}
407
381
  </div>
408
382
  `;
409
383
  wireAccessRequestForm();
@@ -419,6 +393,7 @@
419
393
  // deleted. Say so, and offer the way out: sign out, then sign in again.
420
394
  function renderSessionRefused(code) {
421
395
  window.OTBShell?.applyAccess({ authed: false });
396
+ markSignedOut();
422
397
  const landing = $('#landing-root');
423
398
  if (!landing) return;
424
399
  landing.hidden = false;
@@ -427,11 +402,12 @@
427
402
  ? `This account has been switched off, so it cannot open the hall. If that is a mistake, ask the ${admitter} to switch it back on.`
428
403
  : 'This sign-in is no longer accepted, so the hall cannot open with it.';
429
404
  landing.innerHTML = `
430
- <div class="scroll landing">
431
- <h2 class="scroll__h">You can't get in with this account</h2>
405
+ <div class="landing">
406
+ ${landingLogoHtml()}
407
+ <h2 class="landing__title">You can't get in with this account</h2>
432
408
  <p class="landing__request-lede">${why}</p>
433
409
  <p class="landing__request-lede">Sign out, then sign in with the GitHub account you use now. A new account needs the ${admitter}'s approval first.</p>
434
- <p class="landing__cta"><button type="button" class="landing__btn" id="refused-signout">Sign out</button></p>
410
+ <button type="button" class="landing__btn landing__btn--block" id="refused-signout">Sign out</button>
435
411
  </div>
436
412
  `;
437
413
  $('#refused-signout')?.addEventListener('click', () => {