@bongos/core 1.24.37 → 1.24.39
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 +66 -66
- package/docs/copy-inventory.md +21 -21
- package/docs/copy-registry.json +23 -23
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +814 -1015
- package/modules/hall-ui/public/ai-studio.html +2 -2
- package/modules/hall-ui/public/atlas.html +2 -2
- package/modules/hall-ui/public/blockers.html +2 -2
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/city.html +2 -2
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/craft-mode.js +5 -3
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/gate.html +2 -2
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +2 -2
- package/modules/hall-ui/public/market-watch-item.html +2 -2
- package/modules/hall-ui/public/market-watch-rules.html +2 -2
- package/modules/hall-ui/public/market-watch.html +2 -2
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/money.html +2 -2
- package/modules/hall-ui/public/my-agents.html +2 -2
- package/modules/hall-ui/public/primer.html +2 -2
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.html +2 -2
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/roster.html +2 -2
- package/modules/hall-ui/public/sessions.html +2 -2
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +106 -89
- package/modules/hall-ui/public/street-desk.js +1 -1
- package/modules/hall-ui/public/street-draw.js +16 -5
- package/modules/hall-ui/public/street-inside.js +16 -0
- package/modules/hall-ui/public/street.css +36 -0
- package/modules/hall-ui/public/street.html +2 -2
- package/modules/hall-ui/public/street.js +29 -16
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/watch.html +2 -2
- package/modules/hall-ui/public/work.html +2 -2
- package/modules/hall-ui/records/governor-street.md +12 -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_nav.mjs +12 -10
- package/tests/goal_map_page.mjs +5 -2
- package/tests/hall_matter_card.mjs +14 -7
- package/tests/hall_nav.mjs +20 -13
- package/tests/hall_street.mjs +38 -32
- package/tests/hall_street_depth.mjs +8 -6
- package/tests/hall_street_desk.mjs +8 -5
- package/tests/hall_street_inside.mjs +18 -0
- package/tests/nav_permission_atoms.mjs +1 -1
|
@@ -106,3 +106,15 @@ Owner decision 2026-10-04: the street should read at a glance without words, so
|
|
|
106
106
|
- **The evening view is unchanged** ("nothing waits on you." and its line still show when nothing needs you).
|
|
107
107
|
|
|
108
108
|
Tests: `tests/hall_street.mjs` asserts the model has no hint field, the module exports no `HINT`, and none of the street's four files carries the line; putting the markup or the constant back reds it.
|
|
109
|
+
|
|
110
|
+
## The hall is for work: the street as a square, the building as a place to work (owner, 2026-11-04)
|
|
111
|
+
|
|
112
|
+
The owner's direction: the builders' hall is where you go to do things, not to look at them. No menu on menu, no pop-ups (go to the place), no page header or footer that a web app does not need. On the street that meant:
|
|
113
|
+
|
|
114
|
+
- **The street is the governance square.** The working areas are the city's own buildings, so no district stands on the street: `streetModel()` draws one only with `districts: true` (the kit's tests still hold that row to the prototype), and without it the civic row is centred on the artboard. A wide window scales the street to the row (`FOCUS_W`, 1120), not the whole 1440 artboard, so the buildings are as large as the height allows and the haze either side is cropped; a phone still scrolls it sideways and opens at the first building.
|
|
115
|
+
- **The header strip and the six readings are gone** (`.street-top`, `.street-signs` are not displayed; the markup stays, so the counts, the deck link and the readings still compute). The shell's top bar names the page and the buildings carry their own state lines.
|
|
116
|
+
- **A pin takes you into its building** (`#building=<key>`), where each matter is a desk row you act on. It no longer opens the short card; the card is reached only by a pin with no building, and retiring it is a follow-up.
|
|
117
|
+
- **Inside a building the drawing is the floor picker.** On a wide window (`.inside--work`) the interior is laid out, not scaled as one 1440 x 708 picture: the section is cropped to the building (`CROP` in `fitInside`, 590 x 580 of the 800 x 708 section), at 0.5 to 0.72 so a floor stays a 24 px target, in its own column; the chosen floor's desk takes the rest of the window and scrolls on its own. The building's name sits on one line, then the desk, then the government layer, who holds it and how it decides, then the record. A phone keeps its column with the same order. A district keeps its scaled board.
|
|
118
|
+
- **A desk row is one full line.** The board's 20 / 26 / 24 caps were sized for a desk in a corner of the interior; `LIMITS` is now name 90 (14 words), fact 60, facts 48.
|
|
119
|
+
|
|
120
|
+
Tests: `tests/hall_street.mjs` (nine doors and no district on the page, a pin goes inside and fills no card, the row scaled and centred, a phone opening at the first building), `tests/hall_street_inside.mjs` (the cropped picker, never under half size, the board not scaled, a district untouched), `tests/hall_street_depth.mjs` (with no plot to fly from every agent rides the road), `tests/hall_street_desk.mjs` (the new limits).
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.39",
|
|
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.39",
|
|
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.39",
|
|
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
|
@@ -11185,5 +11185,17 @@
|
|
|
11185
11185
|
"id": "1005047",
|
|
11186
11186
|
"text": "Owners now have one Money page in the Government group: what the project spent and paid out, its treasury, and what each hosted project costs, all in one place. Project settings and the Primer link to it instead of repeating t"
|
|
11187
11187
|
}
|
|
11188
|
+
],
|
|
11189
|
+
"1.24.38": [
|
|
11190
|
+
{
|
|
11191
|
+
"id": "1005071",
|
|
11192
|
+
"text": "The hall sidebar is now seven labelled sections (Work, Make, Community, You, Govern, Run the project, Help), and every craft can reach Help."
|
|
11193
|
+
}
|
|
11194
|
+
],
|
|
11195
|
+
"1.24.39": [
|
|
11196
|
+
{
|
|
11197
|
+
"id": "1005077",
|
|
11198
|
+
"text": "The governor street is now a work-first square: the working areas left the street, the extra header and footer are gone, a pin takes you straight into its building, and inside a building the drawing is a compact floor picker s"
|
|
11199
|
+
}
|
|
11188
11200
|
]
|
|
11189
11201
|
}
|
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.39'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/craft_mode_nav.mjs
CHANGED
|
@@ -25,7 +25,7 @@ const CRAFT = read('craft-mode.js');
|
|
|
25
25
|
|
|
26
26
|
// Every NAV id, and the Government group's, read from shell.js itself.
|
|
27
27
|
const NAV_IDS = [...SHELL.matchAll(/\{ id: '([a-z-]+)', label:/g)].map((m) => m[1]);
|
|
28
|
-
const GOV_BLOCK = SHELL.slice(SHELL.indexOf("{ group: '
|
|
28
|
+
const GOV_BLOCK = SHELL.slice(SHELL.indexOf("{ group: 'Run the project'"));
|
|
29
29
|
const GOV_IDS = [...GOV_BLOCK.slice(0, GOV_BLOCK.indexOf('] }')).matchAll(/\{ id: '([a-z-]+)'/g)].map((m) => m[1]);
|
|
30
30
|
|
|
31
31
|
// A small element. Elements parsed from the sidebar's markup are built ONCE, so
|
|
@@ -132,8 +132,9 @@ test('THE ALWAYS-PRESENT LIST: one shared list, Builders first, then Collab and
|
|
|
132
132
|
const { craft } = boot();
|
|
133
133
|
// task 1004685 (the living-city prototype): My agents joins as a PROPOSAL for the
|
|
134
134
|
// owner — every craft has agents, and round-3 board 6 pairs it with the city.
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
// task 1005071 (owner, 2026-11-04): Help — the old Docs group — joins, so every craft has the Primer.
|
|
136
|
+
assert.deepEqual([...craft.ALWAYS_TABS], ['roster', 'collab', 'profile', 'my-agents', 'primer', 'commands', 'atlas', 'modules', 'apidocs'],
|
|
137
|
+
'Builders (NAV id roster) heads the list (owner, 2026-10-01); Collab and Profile join it (owner, 2026-10-03); My agents (prototype, task 1004685); Help (task 1005071)');
|
|
137
138
|
for (const m of CRAFTS) {
|
|
138
139
|
const tabs = craft.tabsFor(m);
|
|
139
140
|
assert.ok(Array.isArray(tabs), `${m} has a named tab list`);
|
|
@@ -180,7 +181,7 @@ test('each mode shows its own tabs, and switching updates the nav live', () => {
|
|
|
180
181
|
assert.equal(p.root.dataset.craft, m);
|
|
181
182
|
assert.deepEqual(new Set(p.showing()), new Set(p.craft.tabsFor(m)), `${m} shows exactly its list`);
|
|
182
183
|
for (const id of ['roster', 'collab', 'profile']) assert.ok(p.showing().includes(id), `${id} stays in ${m}`);
|
|
183
|
-
assert.equal(p.headShowing('
|
|
184
|
+
assert.equal(p.headShowing('run-the-project'), m === 'governing', `the Government head shows only where its tabs do (${m})`);
|
|
184
185
|
}
|
|
185
186
|
p.craft.choose('building');
|
|
186
187
|
assert.deepEqual(new Set(p.showing()), new Set(p.craft.tabsFor('building')), 'back to Building, its tabs return without a reload');
|
|
@@ -190,11 +191,12 @@ test('a mode never shows a tab the gate hid, and a later gate pass keeps the mod
|
|
|
190
191
|
const p = boot({ session: { 'hall-craft': 'governing' } });
|
|
191
192
|
p.signIn('thetes');
|
|
192
193
|
assert.ok(!p.showing().includes('watch'), 'a thetes in Governing does not get the metic-floored Watch tab');
|
|
193
|
-
assert.ok(!p.headShowing('
|
|
194
|
-
|
|
194
|
+
assert.ok(!p.headShowing('run-the-project'), 'nor its group head');
|
|
195
|
+
// task 1005071: sidebar order — Work, Community, You, Govern, then Help (every craft).
|
|
196
|
+
assert.deepEqual(p.showing(), ['blockers', 'collab', 'roster', 'profile', 'my-agents', 'city', 'board-room', 'primer', 'commands', 'atlas', 'modules', 'apidocs'], 'what a thetes board member in Governing sees');
|
|
195
197
|
p.signIn('archon');
|
|
196
198
|
assert.ok(p.showing().includes('watch'), 'promoted, the gate lets it through and the mode keeps it');
|
|
197
|
-
assert.ok(p.headShowing('
|
|
199
|
+
assert.ok(p.headShowing('run-the-project'), 'and its group head comes back with it, which only a mode pass after the gate can know');
|
|
198
200
|
assert.ok(!p.showing().includes('work'), 'and the gate pass did not bring back a tab the mode left out');
|
|
199
201
|
});
|
|
200
202
|
|
|
@@ -257,9 +259,9 @@ function bootTree({ mode = null, activeId = null } = {}) {
|
|
|
257
259
|
let into = group;
|
|
258
260
|
if (label) {
|
|
259
261
|
const head = el('BUTTON', 'nav-group__head');
|
|
260
|
-
head.dataset.group = label.toLowerCase();
|
|
262
|
+
head.dataset.group = label.toLowerCase().replace(/\s+/g, '-');
|
|
261
263
|
group.appendChild(head);
|
|
262
|
-
into = group.appendChild(el('DIV', 'nav-items', 'nav-items-' + label.toLowerCase()));
|
|
264
|
+
into = group.appendChild(el('DIV', 'nav-items', 'nav-items-' + label.toLowerCase().replace(/\s+/g, '-')));
|
|
263
265
|
}
|
|
264
266
|
for (const m of block.matchAll(/\{ id: '([a-z-]+)', label:/g)) {
|
|
265
267
|
into.appendChild(el('A', 'nav-item' + (m[1] === activeId ? ' nav-item--active' : ''), 'nav-' + m[1]));
|
|
@@ -306,7 +308,7 @@ test('GOVERNING LEADS WITH THE CITY: its other tabs fold into one dropdown right
|
|
|
306
308
|
assert.equal(head.getAttribute('aria-controls'), 'nav-items-craft-fold', 'the head names the region it opens');
|
|
307
309
|
assert.match(head.innerHTML, /More governing/);
|
|
308
310
|
assert.ok(head.hasAttribute('data-craft-off') === false, 'its head shows while a tab inside does');
|
|
309
|
-
assert.ok(p.sidebar.querySelectorAll('.nav-group__head').find((h) => h.dataset.group === '
|
|
311
|
+
assert.ok(p.sidebar.querySelectorAll('.nav-group__head').find((h) => h.dataset.group === 'run-the-project').hasAttribute('data-craft-off'),
|
|
310
312
|
'the emptied Government head hides, so no label sits over nothing');
|
|
311
313
|
p.craft.choose('building');
|
|
312
314
|
assert.equal(p.fold(), null, 'leaving Governing removes the dropdown');
|
package/tests/goal_map_page.mjs
CHANGED
|
@@ -415,8 +415,11 @@ await test('Sky sits directly under Goals in the nav, member-gated, at the URL G
|
|
|
415
415
|
const shell = read('shell.js');
|
|
416
416
|
const item = shell.indexOf("{ id: 'goal-map', label: 'Sky', icon: 'sky', href: '/goal-map', gate: 'member' }");
|
|
417
417
|
assert.ok(item > -1, 'the nav item, with the slot the map was denied (ADR 0227 §11)');
|
|
418
|
-
|
|
419
|
-
|
|
418
|
+
// task 1005071 moved Tasks up to second in Work (owner, 2026-11-04); Sky stays the
|
|
419
|
+
// item straight after Goals.
|
|
420
|
+
const goals = shell.indexOf("{ id: 'goals', label: 'Goals'");
|
|
421
|
+
const between = shell.slice(goals, item);
|
|
422
|
+
assert.ok(goals > -1 && goals < item && (between.match(/\{ id: '/g) || []).length === 1, 'directly under Goals');
|
|
420
423
|
assert.match(read('goals.html'), /href="\/builders\/goal-map"/, 'Goals still links to it');
|
|
421
424
|
});
|
|
422
425
|
|
|
@@ -290,9 +290,16 @@ const BASE = (d) => ({
|
|
|
290
290
|
'/api/bongos/government/constitution': { board: { membership: 'rank:metic+', pass_rule: 'unanimous', window_minutes: 1440, close_early_on_full_turnout: true } },
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
// A pin goes into its building now (owner, 2026-11-04: no pop-ups); the card
|
|
294
|
+
// still opens for a pin with no building, so these tests open it directly.
|
|
295
|
+
const openCard = (p) => {
|
|
296
|
+
const pin = p.byId.get('streetPins').children[0];
|
|
297
|
+
p.window.GovStreetPage.openCard(pin.attrs['data-matter'], pin, pin.attrs['data-matters'].split(' '));
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
test('the full card: the ballot is read, then the seats, counsel and acts are drawn as text', async () => {
|
|
294
301
|
const p = await bootPage({ feeds: BASE(docket([matter({ title: '<b>x</b>' })])) });
|
|
295
|
-
p
|
|
302
|
+
openCard(p);
|
|
296
303
|
await settle(); await settle();
|
|
297
304
|
assert.ok(p.calls.some((c) => c.url === '/api/bongos/government/board/items'), 'the room is read when a sitting opens');
|
|
298
305
|
const body = p.byId.get('streetCardBody');
|
|
@@ -308,7 +315,7 @@ test('vote for: one POST to the vote route, a toast, the docket re-read, and the
|
|
|
308
315
|
const feeds = BASE((() => { const d0 = docket([matter()]); return () => (reads++ === 0 ? d0 : after); })());
|
|
309
316
|
feeds['/api/bongos/government/docket'] = (() => { const d0 = docket([matter()]); return () => (reads++ === 0 ? d0 : after); })();
|
|
310
317
|
const p = await bootPage({ feeds, posts: { 'POST /api/bongos/government/board/items/41/votes': { ok: true, vote: {}, already_voted: false, karma_delta: 1 } } });
|
|
311
|
-
p
|
|
318
|
+
openCard(p);
|
|
312
319
|
await settle(); await settle();
|
|
313
320
|
byAct(p.byId.get('streetCardBody'), 'for').fire('click');
|
|
314
321
|
await settle(); await settle(); await settle();
|
|
@@ -324,7 +331,7 @@ test('vote for: one POST to the vote route, a toast, the docket re-read, and the
|
|
|
324
331
|
|
|
325
332
|
test('object: "no" opens the desk row\'s one-tap reasons, and a tap puts the reason and the part on the record', async () => {
|
|
326
333
|
const p = await bootPage({ feeds: BASE(docket([matter()])), posts: { 'POST /api/bongos/government/board/items/41/votes': { ok: true, vote: {}, item_closed: true, outcome: 'returned' } } });
|
|
327
|
-
p
|
|
334
|
+
openCard(p);
|
|
328
335
|
await settle(); await settle();
|
|
329
336
|
const body = () => p.byId.get('streetCardBody');
|
|
330
337
|
assert.equal(byAct(body(), 'object').textContent, D.ACT_WORDS.object, 'the desk row\'s word');
|
|
@@ -341,7 +348,7 @@ test('object: "no" opens the desk row\'s one-tap reasons, and a tap puts the rea
|
|
|
341
348
|
|
|
342
349
|
test('a refused act says why and records nothing; abstain sends nothing at all', async () => {
|
|
343
350
|
const p = await bootPage({ feeds: BASE(docket([matter()])), posts: { 'POST /api/bongos/government/board/items/41/votes': { __status: 403, error: { code: 'not_board_member' } } } });
|
|
344
|
-
p
|
|
351
|
+
openCard(p);
|
|
345
352
|
await settle(); await settle();
|
|
346
353
|
byAct(p.byId.get('streetCardBody'), 'for').fire('click');
|
|
347
354
|
await settle(); await settle();
|
|
@@ -361,7 +368,7 @@ test('release: the override is approved, the card closes, Release rises, and wit
|
|
|
361
368
|
feeds['/api/bongos/government/docket'] = () => (reads++ === 0 ? docket([ov]) : docket([]));
|
|
362
369
|
const p = await bootPage({ feeds, posts: { 'POST /api/bongos/override-requests/9/decide': { ok: true } } });
|
|
363
370
|
assert.equal(p.byId.get('streetEvening').hidden, true);
|
|
364
|
-
p
|
|
371
|
+
openCard(p);
|
|
365
372
|
byAct(p.byId.get('streetCardBody'), 'release').fire('click');
|
|
366
373
|
await settle(); await settle(); await settle();
|
|
367
374
|
assert.deepEqual(p.calls.filter((c) => c.method !== 'GET').map((c) => [c.method, c.url, c.body]), [['POST', '/api/bongos/override-requests/9/decide', { decision: 'approve' }]]);
|
|
@@ -476,7 +483,7 @@ test('more expands the same card in place: the full text, who and when, and the
|
|
|
476
483
|
const { d } = SOURCE_FIXTURES();
|
|
477
484
|
d.matters = d.matters.filter((m) => m.source === 'blockers');
|
|
478
485
|
const p = await bootPage({ feeds: { ...BASE(d) } });
|
|
479
|
-
p
|
|
486
|
+
openCard(p);
|
|
480
487
|
await settle(); await settle();
|
|
481
488
|
const body = () => p.byId.get('streetCardBody');
|
|
482
489
|
const toggle = () => byAct(body(), 'more');
|
package/tests/hall_nav.mjs
CHANGED
|
@@ -243,7 +243,10 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
243
243
|
// 'Archon' → 'Governance' in ADR 0157 (the group's pages opened to Metic, so a header
|
|
244
244
|
// naming the top rank was wrong), then → 'Government' by ADR 0174: a header
|
|
245
245
|
// reading "Archon" over items a Metic can open would misdescribe the ladder.
|
|
246
|
-
|
|
246
|
+
// task 1005071 (owner decision 2026-11-04, from the sitemap audit): every group is
|
|
247
|
+
// labelled, and the old unlabeled project group split into Work, Make and Govern.
|
|
248
|
+
// Docs became Help; Government became Run the project.
|
|
249
|
+
assert.deepEqual(nav.map((g) => g.label), ['Work', 'Make', 'Community', 'You', 'Govern', 'Run the project', 'Help']);
|
|
247
250
|
// task 1003113 (R02 of goal 1000074): 'copy-desk' joins the unlabeled group,
|
|
248
251
|
// after Tasks. It belongs with the project's own work and not in Docs — it is a
|
|
249
252
|
// QUEUE somebody acts on, not reference material — and not in You or Community,
|
|
@@ -285,12 +288,15 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
285
288
|
// task 1004939 (goal 1000137): 'market-watch' joins last — the project's scouts on its
|
|
286
289
|
// market, so its subject is the project. Module-gated: it is dropped wherever the
|
|
287
290
|
// default-off market-watch module is not enabled (this boot reports no module state).
|
|
288
|
-
|
|
289
|
-
|
|
291
|
+
// task 1005071: the project group split three ways. Work is the project's state;
|
|
292
|
+
// Make is where things get made; Govern is the board's rooms.
|
|
293
|
+
assert.deepEqual(nav[0].items.map((i) => i.id), ['home', 'work', 'goals', 'goal-map', 'roadmap', 'blockers', 'thinking'],
|
|
294
|
+
'Work = the project itself (task 1005071 order)');
|
|
295
|
+
assert.deepEqual(nav[1].items.map((i) => i.id), ['studio', 'market-watch'], 'Make = where things get made');
|
|
290
296
|
// task 1004685 (the living-city prototype, round-3 board 6): 'my-agents' joins You —
|
|
291
297
|
// how your own Claude works for you, the personal half of "the city steers
|
|
292
298
|
// everyone, My agents steers yours". Its subject is the viewer's own agents.
|
|
293
|
-
assert.deepEqual(nav[
|
|
299
|
+
assert.deepEqual(nav[3].items.map((i) => i.id), ['standing', 'profile', 'my-agents'], 'You = your own numbers and your own agents');
|
|
294
300
|
// task 1003444: 'people' LEFT the group — the owner deleted the hall's
|
|
295
301
|
// cross-project directory (ADR 0337 D5); its home is the hub, and /people
|
|
296
302
|
// redirects to /roster. Builders is the hall's directory now.
|
|
@@ -305,19 +311,20 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
305
311
|
// 'leaderboard') and is now a real page at /roster labelled Builders. That is
|
|
306
312
|
// what made this group read as one real page plus a link into the home page.
|
|
307
313
|
assert.deepEqual(nav[2].items.map((i) => i.id), ['collab', 'roster'], 'Community = everyone else, in this project');
|
|
314
|
+
assert.deepEqual(nav[4].items.map((i) => i.id), ['city', 'board-room'], 'Govern = the board\'s rooms, the city first');
|
|
308
315
|
assert.ok(!nav.flatMap((g) => g.items).some((i) => /^\/people\b/.test(i.href)), 'no nav item points at the deleted /people');
|
|
309
316
|
// task 1004473: 'commands' — every skill and CLI verb, read live — is reference
|
|
310
317
|
// material that reads the same for every builder, so it joins Docs.
|
|
311
318
|
// task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room. Ranks,
|
|
312
319
|
// Economy and Diagrams explained what the Primer explains; they are its chapters
|
|
313
320
|
// now, so Docs is the Primer and the four references that are not a document.
|
|
314
|
-
assert.deepEqual(nav[
|
|
321
|
+
assert.deepEqual(nav[6].items.map((i) => i.id),
|
|
315
322
|
['primer', 'commands', 'atlas', 'modules', 'apidocs'], 'Docs = reference material');
|
|
316
323
|
// 'government' (the Permissions tab, BV1.R113) is the group's one archon-gated item.
|
|
317
324
|
// 'project-settings' joins the Government group at the metic floor (task 1003280,
|
|
318
325
|
// goal 1000072) — it sits before 'government', which is the one archon-only item.
|
|
319
326
|
// 'board-room' LED this group from the ADR 0266 carve until task 1003744 moved
|
|
320
|
-
// it to the project group — see the note on nav[
|
|
327
|
+
// it to the project group — see the note on Govern (nav[4]). What is left is rank surface
|
|
321
328
|
// throughout, which is what lets the group keep its metic head honestly.
|
|
322
329
|
// 'deploy' (task 1003159, ADR 0293) is the SECOND archon item, beside 'government':
|
|
323
330
|
// its gate is the `core.pin.move` atom, archon-floor and system:true. It sits with
|
|
@@ -328,7 +335,7 @@ test('sidebar groups read by audience, in order', () => {
|
|
|
328
335
|
// 'market-watch-settings' (task 1004938) LEFT the group in task 1005045: the owner saw
|
|
329
336
|
// "Market Watch" twice and had to guess which was which (owner decision 2026-11-03,
|
|
330
337
|
// idea 1001310). The settings are an owner-only Settings tab on /market-watch now.
|
|
331
|
-
assert.deepEqual(nav[
|
|
338
|
+
assert.deepEqual(nav[5].items.map((i) => i.id), ['watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government']);
|
|
332
339
|
// task 1005045: and no label is left twice anywhere in the sidebar, so the next pair
|
|
333
340
|
// fails here instead of in front of the owner.
|
|
334
341
|
const labels = [...boot().sidebarHtml.matchAll(/<a class="nav-item" [^>]*>[\s\S]*?<span class="nav-item__label">([^<]*)<\/span>/g)].map((m) => m[1]);
|
|
@@ -355,7 +362,7 @@ test('Ranks, Economy and Diagrams are Primer chapters, not pages', () => {
|
|
|
355
362
|
for (const f of ['ranks.html', 'drachmae.html', 'diagrams.html']) {
|
|
356
363
|
assert.ok(!fs.existsSync(path.join(HALL, f)), `${f} left the hall`);
|
|
357
364
|
}
|
|
358
|
-
const primer = nav.find((g) => g.label === '
|
|
365
|
+
const primer = nav.find((g) => g.label === 'Help').items.find((i) => i.id === 'primer');
|
|
359
366
|
assert.equal(primer.href, '/primer', 'the Primer is the reading room the three became');
|
|
360
367
|
});
|
|
361
368
|
|
|
@@ -564,11 +571,11 @@ test('collapsible groups carry a stable storage key (collapse persistence)', ()
|
|
|
564
571
|
// hall-nav-collapsed storage key, so any builder who had collapsed the old "archon"
|
|
565
572
|
// group gets the new one expanded once — cosmetic, and preferable to keeping a stale
|
|
566
573
|
// slug that no longer matches the visible label.
|
|
567
|
-
|
|
574
|
+
// task 1005071: a label with spaces hyphenates, so the key stays a valid id.
|
|
575
|
+
for (const g of ['work', 'make', 'community', 'you', 'govern', 'run-the-project', 'help']) {
|
|
568
576
|
assert.match(html, new RegExp(`data-group="${g}"`), `${g} is collapsible (hall-nav-collapsed key)`);
|
|
569
577
|
}
|
|
570
|
-
assert.ok(!/
|
|
571
|
-
'the unlabeled project group stays always-expanded');
|
|
578
|
+
assert.ok(!/aria-controls="[^"]* /.test(html), 'no aria-controls id carries a space');
|
|
572
579
|
});
|
|
573
580
|
|
|
574
581
|
// ── a nav item can carry a COUNT badge (task 1003737) ───────────────────────
|
|
@@ -780,7 +787,7 @@ test('no glyph path data reads as an IP address to the release leak scan', () =>
|
|
|
780
787
|
// block (task 1004713). These drive the real shell and the real gov-positions.js.
|
|
781
788
|
|
|
782
789
|
const tick = () => new Promise((resolve) => setImmediate(resolve));
|
|
783
|
-
const GOVERNING = ['
|
|
790
|
+
const GOVERNING = ['city', 'board-room', 'watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government'];
|
|
784
791
|
const lead = (building, title) => ({ kind: 'lead', seat: 'lead:' + building, building, title });
|
|
785
792
|
const block = (held) => ({ held, title: held.length ? held[0].title : null, has_position: held.length > 0 });
|
|
786
793
|
|
|
@@ -799,7 +806,7 @@ test('POSITION: exactly the governing surfaces carry the flag — the City, the
|
|
|
799
806
|
const html = boot().sidebarHtml;
|
|
800
807
|
const flagged = [...html.matchAll(/id="nav-([^"]+)"[^>]*data-position="1"/g)].map((m) => m[1]);
|
|
801
808
|
assert.deepEqual(flagged, GOVERNING);
|
|
802
|
-
assert.match(html, /class="nav-group__head" data-gate="government" data-position="1" data-group="
|
|
809
|
+
assert.match(html, /class="nav-group__head" data-gate="government" data-position="1" data-group="run-the-project"/,
|
|
803
810
|
'the Government head carries it too, so no label is left over an emptied group');
|
|
804
811
|
const nav = parseNav(html);
|
|
805
812
|
assert.ok(!nav.find((g) => g.label === 'You').items.some((i) => flagged.includes(i.id)), 'your own pages never need a seat');
|
package/tests/hall_street.mjs
CHANGED
|
@@ -72,7 +72,9 @@ function docket(matters = [], over = {}) {
|
|
|
72
72
|
}
|
|
73
73
|
const goal = (id, status = 'open', title = `Goal ${id}`) => ({ id: String(id), title, status });
|
|
74
74
|
const rollup = (id, c) => ({ goal_id: String(id), done: 0, active: 0, ready: 0, blocked: 0, backlog: 0, cut: 0, ...c });
|
|
75
|
-
|
|
75
|
+
// The kit is held to the prototype's row, districts and all (`districts: true`); the
|
|
76
|
+
// page itself draws the civic row alone (owner, 2026-11-04), pinned further down.
|
|
77
|
+
const model = (over = {}) => S.streetModel({ docket: docket(), goals: [], rollups: [], mode: 'dark', now: NOW, districts: true, ...over });
|
|
76
78
|
|
|
77
79
|
// ── the drawing is the prototype's ───────────────────────────────────────────
|
|
78
80
|
|
|
@@ -299,7 +301,7 @@ test('the collision pass places in tier order: when only one of two can show, th
|
|
|
299
301
|
});
|
|
300
302
|
|
|
301
303
|
test('no docket: no pins, the readings say so, and nothing is invented', () => {
|
|
302
|
-
const m = S.streetModel({ docket: null, goals: [goal(1)], rollups: [], mode: 'dark', now: NOW });
|
|
304
|
+
const m = S.streetModel({ docket: null, goals: [goal(1)], rollups: [], mode: 'dark', now: NOW, districts: true });
|
|
303
305
|
assert.equal(m.pins.length, 0);
|
|
304
306
|
assert.equal(m.crates.length, 0);
|
|
305
307
|
assert.ok(m.signs.every((s) => s.figure === '—' && !s.hot && s.line === S.FALLBACK_LINE));
|
|
@@ -617,32 +619,30 @@ test('the page reads the four feeds and draws the docket\'s pins, the readings a
|
|
|
617
619
|
assert.equal(p.byId.get('hudMoving').textContent, '2');
|
|
618
620
|
assert.equal(p.byId.get('hudShare').textContent, '$24');
|
|
619
621
|
assert.equal(p.byId.get('hudDot').className, 'street-hud__dot street-hud__dot--hot');
|
|
620
|
-
assert.equal(p.byId.get('streetDoors').children.length,
|
|
622
|
+
assert.equal(p.byId.get('streetDoors').children.length, 9, 'the nine civic buildings, each a door; no district stands on the street (owner, 2026-11-04)');
|
|
623
|
+
assert.ok(p.byId.get('streetDoors').children.every((a) => !/district=/.test(a.attrs.href)), 'no door to a working area');
|
|
621
624
|
assert.equal(p.byId.get('streetHome').attrs.href, '/builders/city');
|
|
622
625
|
});
|
|
623
626
|
|
|
624
|
-
|
|
625
|
-
|
|
627
|
+
// Owner, 2026-11-04: no pop-ups, go to the place. A pin takes you into its
|
|
628
|
+
// building, where each matter is a desk row you act on; it opens no card.
|
|
629
|
+
test('a pin takes you into its building and opens no card', async () => {
|
|
630
|
+
const p = await bootPage({ feeds: FEEDS([matter({ id: 'board:1', building: 'charter', title: 'A vote', href: '/builders/board-room' })]) });
|
|
626
631
|
const pin = p.byId.get('streetPins').children[0];
|
|
632
|
+
assert.equal(pin.attrs['data-building'], 'charter');
|
|
627
633
|
pin.fire('click');
|
|
628
|
-
|
|
629
|
-
assert.equal(
|
|
630
|
-
assert.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
assert.equal(card.hidden, true);
|
|
637
|
-
assert.equal(e.prevented, true, 'the press is used, so nothing else answers it');
|
|
638
|
-
assert.equal(p.focused.el.attrs['data-matter'], 'board:1');
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
test('the card pager: a pin with several matters opens the most important with "1 of N", and previous/next reach every matter there', async () => {
|
|
634
|
+
assert.equal(p.window.location.hash, 'building=charter', 'the address names the building, so its inside opens');
|
|
635
|
+
assert.equal(p.byId.get('streetCardTitle').textContent, '', 'no card is filled');
|
|
636
|
+
assert.notEqual(p.byId.get('streetPins').children[0].attrs['aria-expanded'], 'true', 'and the pin does not say one is open');
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
// The card itself still works for the code paths that open it (a pin with no
|
|
640
|
+
// building); its pager is held here by opening it directly.
|
|
641
|
+
test('the card pager: a card over several matters opens the most important with "1 of N", and previous/next reach every matter there', async () => {
|
|
642
642
|
const ms = [matter({ id: 'blockers:1', state: 'moving', title: 'Third' }), matter({ id: 'blockers:2', state: 'needs', title: 'First' }), matter({ id: 'blockers:3', state: 'needs', title: 'Second' }), matter({ id: 'blockers:4', state: 'settled', title: 'Fourth' })];
|
|
643
643
|
const p = await bootPage({ feeds: FEEDS(ms) });
|
|
644
644
|
const pin = p.byId.get('streetPins').children.find((s) => s.attrs['data-building'] === 'planning');
|
|
645
|
-
pin.
|
|
645
|
+
p.window.GovStreetPage.openCard(pin.attrs['data-matter'], pin, pin.attrs['data-matters'].split(' '));
|
|
646
646
|
const body = p.byId.get('streetCardBody');
|
|
647
647
|
const pager = () => body.children[0];
|
|
648
648
|
const btn = (dir) => pager().children.find((c) => c.attrs['data-act'] === 'page-' + dir);
|
|
@@ -664,7 +664,8 @@ test('the card pager: a pin with several matters opens the most important with "
|
|
|
664
664
|
assert.equal(p.focused.el.attrs['data-building'], 'planning', 'Escape returns focus to the pin');
|
|
665
665
|
// one matter: no pager
|
|
666
666
|
const q = await bootPage({ feeds: FEEDS([matter({ id: 'board:1', building: 'charter' })]) });
|
|
667
|
-
q.byId.get('streetPins').children[0]
|
|
667
|
+
const one = q.byId.get('streetPins').children[0];
|
|
668
|
+
q.window.GovStreetPage.openCard(one.attrs['data-matter'], one, one.attrs['data-matters'].split(' '));
|
|
668
669
|
assert.ok(!q.byId.get('streetCardBody').children.some((c) => c.className === 'mcard-pager'), 'one matter, no pager');
|
|
669
670
|
});
|
|
670
671
|
|
|
@@ -737,17 +738,22 @@ test('arriving through a door marks that building, and a failed docket says so',
|
|
|
737
738
|
assert.equal(q.byId.get('hudShare').textContent, '—');
|
|
738
739
|
});
|
|
739
740
|
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
|
|
741
|
+
// Owner, 2026-11-04 (supersedes task 1004718's foot): the readings left the
|
|
742
|
+
// page, so a wide window scales the street to its civic row, not the whole
|
|
743
|
+
// artboard, crops the haze either side, and centres the street in its stage.
|
|
744
|
+
test('a wide window scales the street to its civic row and centres it; a phone scrolls it from the first building', async () => {
|
|
743
745
|
const p = await bootPage({ feeds: FEEDS([]), width: 1440 });
|
|
744
|
-
const win = p.byId.get('streetWindow'), sizer = p.byId.get('streetSizer');
|
|
745
|
-
win.clientHeight =
|
|
746
|
-
p.key('resize', {});
|
|
747
|
-
const h = parseFloat(sizer.style.height);
|
|
748
|
-
assert.ok(h < 900, 'the street is width-bound here');
|
|
749
|
-
assert.equal(win.style.flex, '0 0 ' + h + 'px', 'the window shrinks to the street, so the readings sit right under it');
|
|
750
|
-
win.clientHeight = 300;
|
|
746
|
+
const win = p.byId.get('streetWindow'), sizer = p.byId.get('streetSizer'), board = p.byId.get('streetBoard');
|
|
747
|
+
win.clientWidth = 1376; win.clientHeight = 844;
|
|
751
748
|
p.key('resize', {});
|
|
752
|
-
|
|
749
|
+
const s = Number(board.style['--street-scale']);
|
|
750
|
+
assert.ok(s > 1376 / 1440, 'larger than the whole artboard would allow');
|
|
751
|
+
assert.equal(win.style.flex, '', 'the window keeps the stage, so the street sits centred in it');
|
|
752
|
+
assert.equal(win.style.overflowX, 'hidden', 'no sideways scroll on a wide window');
|
|
753
|
+
assert.equal(parseFloat(sizer.style.width), 1376, 'the sizer is the window');
|
|
754
|
+
assert.ok(parseFloat(board.style.left) < 0, 'the artboard is centred, its haze cropped either side');
|
|
755
|
+
const q = await bootPage({ feeds: FEEDS([]), width: 390 });
|
|
756
|
+
const qwin = q.byId.get('streetWindow');
|
|
757
|
+
assert.equal(qwin.style.overflowX, '', 'a phone scrolls the street sideways');
|
|
758
|
+
assert.ok(qwin.scrollLeft > 0, 'starting at the first building, not the empty haze before it');
|
|
753
759
|
});
|
|
@@ -70,7 +70,7 @@ function feeds(over = {}) {
|
|
|
70
70
|
function living(f) { return L.livingModel({ ...f, now: NOW }); }
|
|
71
71
|
function street(over = {}, mode = 'dark') {
|
|
72
72
|
const f = feeds(over);
|
|
73
|
-
return S.streetModel({ docket: f.docket, goals: f.goals.goals, rollups: f.rollups.rollups, mode, now: NOW, depth: true, living: f.living === null ? null : living(f), roster: f.roster });
|
|
73
|
+
return S.streetModel({ docket: f.docket, goals: f.goals.goals, rollups: f.rollups.rollups, mode, now: NOW, depth: true, living: f.living === null ? null : living(f), roster: f.roster, districts: true });
|
|
74
74
|
}
|
|
75
75
|
const openGoals = () => fx('goals').goals.filter((g) => g.status === 'open');
|
|
76
76
|
|
|
@@ -338,7 +338,7 @@ test('an overlay handed in changes nothing; the Leads not read, no Lead, capsule
|
|
|
338
338
|
const c = street({ leads: null }).depth;
|
|
339
339
|
assert.deepEqual([c.plates.length, c.capsules.length, c.sites.length], [0, 0, 0], 'no Leads read: no plate, no capsule, no build-out going up');
|
|
340
340
|
// without the living model at all the deep street still draws its real skyline and windows
|
|
341
|
-
const none = S.streetModel({ docket: fx('government__docket'), goals: fx('goals').goals, rollups: fx('goals__rollups').rollups, mode: 'dark', now: NOW, depth: true, living: null, roster: null }).depth;
|
|
341
|
+
const none = S.streetModel({ docket: fx('government__docket'), goals: fx('goals').goals, rollups: fx('goals__rollups').rollups, mode: 'dark', now: NOW, depth: true, living: null, roster: null, districts: true }).depth;
|
|
342
342
|
assert.deepEqual(none.clusters, a.clusters);
|
|
343
343
|
assert.deepEqual(none.windows, street({ leads: null, docket: fx('government__docket') }).depth.windows, 'no living model and the hub as it is light the same, the docket\'s own');
|
|
344
344
|
assert.equal(none.agents, 0);
|
|
@@ -361,7 +361,7 @@ test('street-depth.js: its model half is pure, and the two drawing files require
|
|
|
361
361
|
const before = globalThis.GovStreetDepth;
|
|
362
362
|
delete globalThis.GovStreetDepth;
|
|
363
363
|
try {
|
|
364
|
-
const flat = S.streetModel({ docket: fx('government__docket'), goals: openGoals(), rollups: fx('goals__rollups').rollups, mode: 'dark', now: NOW, depth: true });
|
|
364
|
+
const flat = S.streetModel({ docket: fx('government__docket'), goals: openGoals(), rollups: fx('goals__rollups').rollups, mode: 'dark', now: NOW, depth: true, districts: true });
|
|
365
365
|
assert.equal(flat.depth, undefined);
|
|
366
366
|
} finally { globalThis.GovStreetDepth = before; }
|
|
367
367
|
});
|
|
@@ -500,9 +500,11 @@ test('the page draws the living street: the skyline, a pod per claim, the plates
|
|
|
500
500
|
assert.equal(cls('streetDepthFar', 'street-depth-sil__face').length, 1);
|
|
501
501
|
const claims = fx('tasks__claimable').active_claims.length;
|
|
502
502
|
assert.equal(cls('streetDepthSky', 'street-depth-pod').length + cls('streetDepthFront', 'street-depth-pod').length, claims, 'one pod per agent');
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
503
|
+
// The page stands no district (owner, 2026-11-04), so no claim flies a skylane
|
|
504
|
+
// from its goal's plot: every agent rides the road (the kit's skylanes are held
|
|
505
|
+
// with districts on, above).
|
|
506
|
+
assert.equal(cls('streetDepthSky', 'street-depth-pod').length, 0, 'no plot to fly from');
|
|
507
|
+
assert.equal(cls('streetDepthFront', 'street-depth-pod--road').length, claims, 'every agent on the road');
|
|
506
508
|
assert.equal(cls('streetDepthFront', 'street-depth-plate').length, 9);
|
|
507
509
|
assert.equal(all('streetDepthFront').concat(all('streetDepthSky')).filter((e) => 'data-proto' in e.attrs).length, 0, 'nothing is marked prototype');
|
|
508
510
|
assert.equal(cls('streetDepthFront', 'street-depth-jib').length, 1, 'the live site\'s crane');
|
|
@@ -105,11 +105,14 @@ test('fit() is the board\'s fit(), with a word cap on top', () => {
|
|
|
105
105
|
for (const n of [5, 12, 20]) for (const w of [1, 2, 3]) assert.ok(D.fit(LONG, n, w).length <= n, 'never longer than its cap');
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
// A row's name and fact were the board's 20 / 26 / 24 while the desk was a
|
|
109
|
+
// corner of the interior; the desk takes the window's width now (owner,
|
|
110
|
+
// 2026-11-04), so a row is one full line: name 90 (14 words), fact 60, facts 48.
|
|
111
|
+
test('the limits: a row is one full line (name 90, fact 60, facts 48, three), status 44, record 30 (four), three tiles, ten pips', () => {
|
|
109
112
|
const L = D.LIMITS;
|
|
110
|
-
assert.deepEqual([L.name, L.nameWords, L.fact, L.factLen, L.facts, L.actions, L.status, L.recordLen, L.record, L.tiles, L.tileWords, L.todayTarget], [
|
|
113
|
+
assert.deepEqual([L.name, L.nameWords, L.fact, L.factLen, L.facts, L.actions, L.status, L.recordLen, L.record, L.tiles, L.tileWords, L.todayTarget], [90, 14, 60, 48, 3, 2, 44, 30, 4, 3, 2, 10]);
|
|
111
114
|
const src = fs.readFileSync(path.join(ROOT, 'docs/design/mocks/governor-city/Desks.dc.html'), 'utf8');
|
|
112
|
-
for (const pin of ['
|
|
115
|
+
for (const pin of ['.slice(0, 3)', "this.fit('nothing needs you · ' + F.quiet, 44)", "this.fit(t + ' · now', 30)", '.slice(0, 4)', 'const target = 10']) assert.ok(src.includes(pin), `the board says ${pin}`);
|
|
113
116
|
});
|
|
114
117
|
|
|
115
118
|
test('THE LINE RULE: no row, tile or status any docket produces breaks it, on every floor of every building', () => {
|
|
@@ -140,8 +143,8 @@ test('breaks() names every way a desk can break the rule (so the rule test can f
|
|
|
140
143
|
const good = D.deskModel({ key: 'people', floor: 4, docket: dk, now: NOW });
|
|
141
144
|
assert.deepEqual(D.breaks(good), []);
|
|
142
145
|
const bad = j(good);
|
|
143
|
-
bad.rows[0].name = '
|
|
144
|
-
bad.rows[0].fact = 'x'.repeat(
|
|
146
|
+
bad.rows[0].name = Array(16).fill('word').join(' ');
|
|
147
|
+
bad.rows[0].fact = 'x'.repeat(61);
|
|
145
148
|
bad.rows[0].actions = [{ label: 'a' }, { label: 'b' }, { label: 'c' }];
|
|
146
149
|
bad.rows[0].facts = ['a', 'b', 'c', 'd'];
|
|
147
150
|
bad.tiles = [{ v: '0', l: 'need you' }, { v: '3', l: 'three whole words' }, { v: '4', l: 'ok' }, { v: '5', l: 'ok' }];
|
|
@@ -555,6 +555,24 @@ test('the markup carries the interior: one persistent lift, two flip blocks, the
|
|
|
555
555
|
assert.match(html, /<button type="button" class="inside-textbtn" id="insideAmend"/);
|
|
556
556
|
});
|
|
557
557
|
|
|
558
|
+
// Owner, 2026-11-04: a building is a place to work. On a wide window the
|
|
559
|
+
// drawing is a floor picker cropped to the building, at half size or more (a
|
|
560
|
+
// floor stays a 24 px target), and the desk has the rest of the window; the
|
|
561
|
+
// whole interior is no longer one scaled picture. A district keeps its board.
|
|
562
|
+
test('inside a building the drawing is a cropped floor picker, never under half size, and the board is not scaled', async () => {
|
|
563
|
+
const p = await bootPage({ hash: '#building=planning', feeds: FEEDS([]) });
|
|
564
|
+
const box = p.byId.get('insideArtBox'), art = p.byId.get('insideArt'), board = p.byId.get('insideBoard');
|
|
565
|
+
const k = Number(box.style['--inside-art-scale']);
|
|
566
|
+
assert.ok(k >= 0.5 && k <= 0.72, `picker scale ${k}`);
|
|
567
|
+
assert.equal(board.style['--inside-scale'], '1', 'the interior is laid out, not shrunk as one picture');
|
|
568
|
+
assert.equal(art.style['--inside-art-w'], Math.round(590 * k) + 'px', 'cropped to the building, not the 800 px section');
|
|
569
|
+
assert.equal(box.style['--inside-art-x'], Math.round(-50 * k) + 'px');
|
|
570
|
+
assert.ok(47 * k >= 24, 'a floor stays a 24 px target');
|
|
571
|
+
const d = await bootPage({ hash: '#district=1', feeds: FEEDS([]) });
|
|
572
|
+
assert.equal(d.byId.get('insideArtBox').style['--inside-art-scale'], '1', 'a district keeps its whole board');
|
|
573
|
+
assert.equal(d.byId.get('insideArtBox').style['--inside-art-x'], undefined, 'and is not cropped');
|
|
574
|
+
});
|
|
575
|
+
|
|
558
576
|
test('a building address draws the interior in place of the street, from the docket', async () => {
|
|
559
577
|
const ms = [matter({ id: 'access_requests:1', source: 'access_requests', building: 'people', state: 'needs', title: 'Admit Kai' })];
|
|
560
578
|
const p = await bootPage({ hash: '#building=people', feeds: FEEDS(ms) });
|
|
@@ -96,7 +96,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
96
96
|
// Market Watch settings nav item and its fuel glyph; task 1005041: the Update pill replaced the
|
|
97
97
|
// core-update banner; task 1005045: the Market Watch settings item left (one sidebar item);
|
|
98
98
|
// task 1005046: Ranks, Economy and Diagrams left Docs (one reading room, the Primer).
|
|
99
|
-
const SHELL_SHA = '
|
|
99
|
+
const SHELL_SHA = 'baf8a15f025eba68';
|
|
100
100
|
|
|
101
101
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
102
102
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|