@bongos/core 1.24.31 → 1.24.33
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 +19 -19
- package/docs/copy-inventory.md +11 -11
- package/docs/copy-registry.json +11 -11
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +211 -211
- package/modules/hall-ui/public/craft-mode.js +14 -5
- package/modules/hall-ui/public/home-city.js +14 -0
- package/modules/hall-ui/public/style.css +23 -0
- package/modules/hall-ui/records/home-city.md +10 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/module-api.js +1 -1
- package/tests/craft_mode_looks.mjs +16 -6
- package/tests/home_city_widget.mjs +20 -0
|
@@ -216,20 +216,29 @@
|
|
|
216
216
|
// under the brand (artHtml, the picker's own art, so the two never drift).
|
|
217
217
|
// Building and no mode have no entry here and wear nothing.
|
|
218
218
|
var LOOKS = { artistry: 'the studio · night', ideating: 'the sky', governing: 'the city' };
|
|
219
|
+
// The plate is the Home button in every mode (owner, 2026-11-04, task 1005065): the
|
|
220
|
+
// mode's scene with a home mark over it. Building wears no look, so its plate shows
|
|
221
|
+
// the city, the scene its Home is.
|
|
222
|
+
var PLATE_ART = { building: 'governing', artistry: 'artistry', ideating: 'ideating', governing: 'governing' };
|
|
223
|
+
var HOME_MARK = '<span class="craft-plate__home" aria-hidden="true"><svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round">' +
|
|
224
|
+
'<path d="M3 8.5 9 3l6 5.5V15a1 1 0 0 1-1 1h-3.5v-4h-3v4H4a1 1 0 0 1-1-1V8.5Z"/></svg></span>';
|
|
219
225
|
function remove(el) { if (el && el.parentNode) el.parentNode.removeChild(el); }
|
|
220
226
|
function applyLook(mode) {
|
|
221
227
|
var look = Object.prototype.hasOwnProperty.call(LOOKS, mode) ? LOOKS[mode] : null;
|
|
228
|
+
var art = Object.prototype.hasOwnProperty.call(PLATE_ART, mode) ? PLATE_ART[mode] : null;
|
|
222
229
|
var plate = document.getElementById('craft-plate');
|
|
223
|
-
if (plate && plate.getAttribute('data-plate') !== (
|
|
230
|
+
if (plate && plate.getAttribute('data-plate') !== (art ? mode : null)) { remove(plate); plate = null; }
|
|
224
231
|
var nav = document.getElementById('app-sidebar');
|
|
225
|
-
var brand =
|
|
232
|
+
var brand = art && nav && nav.querySelector ? nav.querySelector('.app-sidebar__brand') : null;
|
|
226
233
|
if (!plate && brand && brand.parentNode) {
|
|
227
|
-
plate = document.createElement('
|
|
234
|
+
plate = document.createElement('a');
|
|
228
235
|
plate.id = 'craft-plate';
|
|
229
236
|
plate.className = 'craft-plate';
|
|
237
|
+
plate.setAttribute('href', '/');
|
|
238
|
+
plate.setAttribute('aria-label', 'Home');
|
|
239
|
+
plate.setAttribute('title', 'Home');
|
|
230
240
|
plate.setAttribute('data-plate', mode);
|
|
231
|
-
plate.
|
|
232
|
-
plate.innerHTML = artHtml(mode);
|
|
241
|
+
plate.innerHTML = artHtml(art) + HOME_MARK;
|
|
233
242
|
brand.parentNode.insertBefore(plate, brand.nextSibling);
|
|
234
243
|
}
|
|
235
244
|
var mark = document.getElementById('craft-eyebrow');
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
// (home-city-draw.js skySvg) from the same read, and the ladder ranks the week's
|
|
23
23
|
// ideators — ideas filed, votes cast, ideas that became work. Every other craft keeps
|
|
24
24
|
// the builder's city exactly as it was.
|
|
25
|
+
//
|
|
26
|
+
// The governor's Home is the street (task 1005064; the v16 round's v16-governing.html):
|
|
27
|
+
// choosing Governing on Home, or opening Home with it chosen, goes to the street page
|
|
28
|
+
// (GovCity.STREET_HREF), which is never redrawn here.
|
|
25
29
|
(function () {
|
|
26
30
|
'use strict';
|
|
27
31
|
if (!window.OTB || typeof window.OTB.contributeHallWidget !== 'function') return;
|
|
@@ -295,7 +299,17 @@
|
|
|
295
299
|
host.hidden = !(art || shown);
|
|
296
300
|
}
|
|
297
301
|
|
|
302
|
+
// ---- the governor's Home is the street; tests swap the hop for a recorder ----
|
|
303
|
+
function streetHop() {
|
|
304
|
+
const href = window.GovCity && window.GovCity.STREET_HREF;
|
|
305
|
+
if (document.documentElement.dataset.craft !== 'governing' || !href) return false;
|
|
306
|
+
(window.OTBHomeStreetHop || ((h) => window.location.assign(h)))(href);
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
|
|
298
310
|
function mount(hostEl) {
|
|
311
|
+
document.addEventListener('otb:craft', streetHop);
|
|
312
|
+
if (streetHop()) return; // leaving for the street: build nothing, read nothing
|
|
299
313
|
host = hostEl;
|
|
300
314
|
host.innerHTML =
|
|
301
315
|
'<h2 id="home-city-h" class="sr-only">The project this week</h2>' +
|
|
@@ -1614,6 +1614,9 @@ html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing
|
|
|
1614
1614
|
never redrawn: each scene's pieces are placed from its edges or its centre. */
|
|
1615
1615
|
.craft-plate {
|
|
1616
1616
|
position: relative;
|
|
1617
|
+
display: block;
|
|
1618
|
+
color: inherit;
|
|
1619
|
+
text-decoration: none;
|
|
1617
1620
|
height: 40px;
|
|
1618
1621
|
margin: 10px 8px 0;
|
|
1619
1622
|
border-radius: var(--radius-sm);
|
|
@@ -1631,6 +1634,26 @@ html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing
|
|
|
1631
1634
|
transform-origin: 0 0;
|
|
1632
1635
|
}
|
|
1633
1636
|
.craft-plate .craft-art__city { top: 0; }
|
|
1637
|
+
/* At the plate's size the studio's houses vanished into the night (owner, 2026-11-04):
|
|
1638
|
+
lift them off the sky so the scene reads. The picker's larger door is unchanged. */
|
|
1639
|
+
.craft-plate .craft-art__house { background: #232b4d; }
|
|
1640
|
+
/* The plate is the Home button (task 1005065): a home mark sits over the scene. */
|
|
1641
|
+
.craft-plate__home {
|
|
1642
|
+
position: absolute;
|
|
1643
|
+
left: 50%;
|
|
1644
|
+
top: 50%;
|
|
1645
|
+
width: 24px;
|
|
1646
|
+
height: 24px;
|
|
1647
|
+
margin: -12px 0 0 -12px;
|
|
1648
|
+
display: grid;
|
|
1649
|
+
place-items: center;
|
|
1650
|
+
border-radius: 999px;
|
|
1651
|
+
color: #fff;
|
|
1652
|
+
background: var(--scrim);
|
|
1653
|
+
}
|
|
1654
|
+
.craft-plate__home svg { width: 14px; height: 14px; }
|
|
1655
|
+
.craft-plate:hover .craft-plate__home { background: rgba(0, 0, 0, .8); }
|
|
1656
|
+
.craft-plate:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
1634
1657
|
.app-sidebar:hover .craft-plate,
|
|
1635
1658
|
.app-sidebar:focus-within .craft-plate { height: 64px; margin: 10px 16px 0; }
|
|
1636
1659
|
.app-sidebar:hover .craft-plate .craft-art,
|
|
@@ -175,6 +175,16 @@ leaderboard.
|
|
|
175
175
|
- Pinned in `tests/home_studio.mjs` and `tests/home_studio_widget.mjs` (real browser, empty states included); harness
|
|
176
176
|
fixtures `copy-desk__studio.json` and `home__studio__works.json`.
|
|
177
177
|
|
|
178
|
+
## The governor's Home is the street (task 1005064)
|
|
179
|
+
|
|
180
|
+
The owner, 2026-11-04, after the four Homes went live: "when you tab between the pages the home screen
|
|
181
|
+
doesn't change." Building, Ideating and Artistry had their own Homes, but Governing still showed the
|
|
182
|
+
builder's city. The v16 round settled it ([`v16-governing.html`](../../../docs/design/mocks/hall/home/v16-governing.html)):
|
|
183
|
+
Governing's Home **is** the street page, never redrawn. Governing's nav already leads with `city`, the street.
|
|
184
|
+
So choosing Governing on Home, or opening Home with it chosen, goes to `GovCity.STREET_HREF`
|
|
185
|
+
(`home-city.js` `streetHop`). In Governing the street's "← Home" link therefore lands back on the street;
|
|
186
|
+
switching craft from the top bar leaves it. Pinned in `tests/home_city_widget.mjs`.
|
|
187
|
+
|
|
178
188
|
## Private areas
|
|
179
189
|
|
|
180
190
|
A private goal the viewer is neither a member of nor an Archon over still stands as a tower (its counts are structure), but the server sends no title for it or for the ideas tied to it; the drawing labels them "A private area" and "A private idea" (ADR 0112, as the goal map and the sky apply it). Pinned in `tests/home_city.mjs`.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.33",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.33",
|
|
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.24.
|
|
3
|
+
"version": "1.24.33",
|
|
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
|
@@ -11145,5 +11145,17 @@
|
|
|
11145
11145
|
"id": "1005054",
|
|
11146
11146
|
"text": "Home now shows the whole project as a living city: towers that grow as work ships, the people building them, and a slider to replay the last ten weeks."
|
|
11147
11147
|
}
|
|
11148
|
+
],
|
|
11149
|
+
"1.24.32": [
|
|
11150
|
+
{
|
|
11151
|
+
"id": "1005064",
|
|
11152
|
+
"text": "The governor's Home is now the street: choosing Owner on Home takes you straight to it, so each of the four crafts has its own Home."
|
|
11153
|
+
}
|
|
11154
|
+
],
|
|
11155
|
+
"1.24.33": [
|
|
11156
|
+
{
|
|
11157
|
+
"id": "1005065",
|
|
11158
|
+
"text": "The small picture at the top of the sidebar is now the Home button in every mode, with a home icon on it."
|
|
11159
|
+
}
|
|
11148
11160
|
]
|
|
11149
11161
|
}
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.33'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -86,25 +86,33 @@ test('each looked mode frames its own door scene under the brand, and names itse
|
|
|
86
86
|
const plate = p.plates()[0];
|
|
87
87
|
assert.equal(p.brand.nextSibling, plate, `${m}: the plate sits right under the brand`);
|
|
88
88
|
assert.equal(plate.getAttribute('data-plate'), m);
|
|
89
|
-
assert.equal(plate.
|
|
90
|
-
assert.equal(plate.
|
|
89
|
+
assert.equal(plate.tagName, 'A', `${m}: the plate is a link`);
|
|
90
|
+
assert.equal(plate.getAttribute('href'), '/', `${m}: the plate goes Home`);
|
|
91
|
+
assert.equal(plate.getAttribute('aria-label'), 'Home', `${m}: named Home for a screen reader`);
|
|
92
|
+
assert.ok(plate.innerHTML.startsWith(p.craft.artHtml(m)), `${m}: the plate IS the picker's door art, not a second copy`);
|
|
93
|
+
assert.match(plate.innerHTML, /class="craft-plate__home" aria-hidden="true"/, `${m}: a home mark sits over the scene`);
|
|
91
94
|
assert.equal(p.marks().length, 1, `${m}: exactly one eyebrow`);
|
|
92
95
|
assert.equal(p.title.nextSibling, p.marks()[0], `${m}: the eyebrow sits right after the page title`);
|
|
93
96
|
assert.equal(p.marks()[0].textContent, WORDS[m], `${m}: in the prototype's own words`);
|
|
94
97
|
}
|
|
95
98
|
});
|
|
96
99
|
|
|
97
|
-
test('BUILDING AND NO MODE WEAR NO LOOK: no
|
|
100
|
+
test('BUILDING AND NO MODE WEAR NO LOOK: no eyebrow; Building keeps only the Home plate, showing the city', () => {
|
|
98
101
|
const none = boot();
|
|
99
102
|
assert.equal(none.root.dataset.craft, undefined);
|
|
100
103
|
assert.equal(none.plates().length + none.marks().length, 0, 'no mode draws nothing');
|
|
101
104
|
for (const m of ['building']) {
|
|
102
105
|
const p = boot({ 'hall-craft': m });
|
|
103
|
-
assert.equal(p.
|
|
106
|
+
assert.equal(p.marks().length, 0, `${m} at boot names no look`);
|
|
107
|
+
assert.equal(p.plates().length, 1, `${m}: the Home plate is there too (task 1005065)`);
|
|
108
|
+
assert.equal(p.plates()[0].getAttribute('data-plate'), m);
|
|
109
|
+
assert.ok(p.plates()[0].innerHTML.startsWith(p.craft.artHtml('governing')), `${m}: its plate shows the city`);
|
|
104
110
|
p.craft.choose('governing');
|
|
105
111
|
assert.equal(p.plates().length, 1);
|
|
112
|
+
assert.equal(p.plates()[0].getAttribute('data-plate'), 'governing', 'switching mode swaps the plate');
|
|
106
113
|
p.craft.choose(m);
|
|
107
|
-
assert.equal(p.
|
|
114
|
+
assert.equal(p.marks().length, 0, `switching to ${m} removes the eyebrow`);
|
|
115
|
+
assert.equal(p.plates()[0].getAttribute('data-plate'), m, 'and swaps the plate back');
|
|
108
116
|
}
|
|
109
117
|
assert.deepEqual(Object.keys(none.craft.LOOKS).sort(), [...LOOKED].sort(), 'only the three crafts have a look');
|
|
110
118
|
});
|
|
@@ -112,7 +120,7 @@ test('BUILDING AND NO MODE WEAR NO LOOK: no plate, no eyebrow, and switching to
|
|
|
112
120
|
test('the art is drawn once: craft-mode.js has one artHtml and exports it', () => {
|
|
113
121
|
assert.equal(CRAFT.match(/function artHtml\(/g).length, 1);
|
|
114
122
|
assert.match(CRAFT, /artHtml: artHtml,/);
|
|
115
|
-
assert.match(CRAFT, /plate\.innerHTML = artHtml\(
|
|
123
|
+
assert.match(CRAFT, /plate\.innerHTML = artHtml\(art\) \+ HOME_MARK;/);
|
|
116
124
|
});
|
|
117
125
|
|
|
118
126
|
// Every selector in style.css that names a craft mode.
|
|
@@ -163,6 +171,8 @@ test('the shared treatment, the eyebrow and the plate are written as the directi
|
|
|
163
171
|
}
|
|
164
172
|
assert.match(CSS, /@media \(max-width: 640px\) \{ \.craft-eyebrow \{ display: none; \} \}/, 'the eyebrow is hidden on a phone');
|
|
165
173
|
assert.match(CSS, /\.craft-plate \{[^}]*height: 40px;/, '40px in the closed rail');
|
|
174
|
+
assert.match(CSS, /\.craft-plate__home \{[^}]*width: 24px;[^}]*height: 24px;/, 'the home mark clears the 24px floor');
|
|
175
|
+
assert.match(CSS, /\.craft-plate:focus-visible \{ outline: 2px solid var\(--focus\);/, 'the Home plate shows keyboard focus');
|
|
166
176
|
assert.match(CSS, /\.app-sidebar:focus-within \.craft-plate \{ height: 64px;/, '64px when the rail opens');
|
|
167
177
|
assert.match(CSS, /html\[data-theme="light"\] \.craft-plate \.craft-art__city \{ filter: invert\(1\) hue-rotate\(180deg\); \}/,
|
|
168
178
|
'the light city is the dark one inverted, not a second drawing');
|
|
@@ -41,7 +41,10 @@ const stubs = (status, craft) => `
|
|
|
41
41
|
return p.startsWith('/api/bongos/home/city') ? { ok: true, status: 200, data: window.__CITY } : { ok: false, status: 404, data: null };
|
|
42
42
|
} }) };
|
|
43
43
|
window.__chosen = [];
|
|
44
|
+
window.__hops = [];
|
|
45
|
+
window.OTBHomeStreetHop = (h) => window.__hops.push(h);
|
|
44
46
|
window.__craft = ${JSON.stringify(craft || null)};
|
|
47
|
+
if (window.__craft) document.documentElement.dataset.craft = window.__craft;
|
|
45
48
|
window.OTBCraftMode = { iconHtml: (k, cls) => '<svg class="' + cls + '" data-icon="' + k + '"></svg>', choose: (m) => window.__chosen.push(m), current: () => window.__craft };
|
|
46
49
|
window.OTB = { contributeHallWidget(w) { window.__widget = w; } };`;
|
|
47
50
|
|
|
@@ -162,6 +165,23 @@ test('the museum and the band of stars switch the craft; the core and a building
|
|
|
162
165
|
assert.match(r.tower, /^\/goals#\/goal\/\d+$/);
|
|
163
166
|
});
|
|
164
167
|
|
|
168
|
+
test('the governor\'s Home is the street: Governing on Home goes there, opened or chosen; other crafts stay', { skip }, async () => {
|
|
169
|
+
const opened = await boot({ craft: 'governing' });
|
|
170
|
+
const onOpen = await opened.page.evaluate(() => ({ hops: window.__hops, asked: window.__asked, built: !!document.querySelector('.hc-svg') }));
|
|
171
|
+
await opened.ctx.close();
|
|
172
|
+
assert.deepEqual(onOpen.hops, ['/builders/street'], 'Home opened with Governing chosen goes to the street');
|
|
173
|
+
assert.deepEqual(onOpen.asked, [], 'and asks the server for nothing on the way');
|
|
174
|
+
assert.equal(onOpen.built, false, 'and builds no city it is leaving');
|
|
175
|
+
|
|
176
|
+
const { ctx, page } = await boot();
|
|
177
|
+
await page.waitForFunction(() => document.querySelectorAll('.hc-rung').length > 0);
|
|
178
|
+
const flip = (m) => page.evaluate((mode) => { document.documentElement.dataset.craft = mode; window.__craft = mode; document.dispatchEvent(new CustomEvent('otb:craft', { detail: { mode } })); return window.__hops.slice(); }, m);
|
|
179
|
+
assert.deepEqual(await flip('artistry'), [], 'Artistry stays on Home');
|
|
180
|
+
assert.deepEqual(await flip('ideating'), [], 'Ideating stays on Home');
|
|
181
|
+
assert.deepEqual(await flip('governing'), ['/builders/street'], 'choosing Governing on Home goes to the street');
|
|
182
|
+
await ctx.close();
|
|
183
|
+
});
|
|
184
|
+
|
|
165
185
|
test('a refused read leaves the section hidden: never an empty plate', { skip }, async () => {
|
|
166
186
|
const { ctx, page } = await boot({ status: 401 });
|
|
167
187
|
await page.evaluate(() => new Promise((r) => setTimeout(r, 0)));
|