@bongos/core 1.21.141 → 1.21.143
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 +57 -47
- package/docs/adr/0351-a-criterion-closes-on-a-uat.md +1 -1
- package/docs/copy-inventory.md +109 -108
- package/docs/copy-registry.json +204 -205
- package/docs/module-api-changelog.md +5 -1
- package/docs/page-inventory.json +1 -0
- package/docs/page-readings.json +405 -405
- package/modules/copy-desk/page-status.js +51 -2
- package/modules/copy-desk/tests/copy_page_status.mjs +37 -0
- package/modules/government/db.js +6 -0
- package/modules/government/decision-board.js +2 -1
- package/modules/hall-ui/public/city-draw.js +4 -1
- package/modules/hall-ui/public/city.css +1 -1
- package/modules/hall-ui/public/city.html +3 -3
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/my-agents.js +35 -10
- package/modules/hall-ui/public/my-agents.states.json +1 -1
- package/modules/hall-ui/public/settings-interaction.js +7 -2
- package/modules/hall-ui/public/settings-law-locks.js +53 -0
- package/modules/hall-ui/public/settings.html +3 -2
- package/modules/hall-ui/public/settings.js +9 -4
- package/modules/hall-ui/public/street-deck.css +4 -0
- package/modules/hall-ui/public/street-office.js +73 -1
- package/modules/hall-ui/public/street.html +4 -4
- package/modules/hall-ui/public/street.js +4 -1
- package/modules/hall-ui/public/studio-lib.js +21 -1
- package/modules/hall-ui/public/studio.css +11 -0
- package/modules/hall-ui/public/studio.js +8 -1
- package/modules/hall-ui/records/living-city.md +3 -1
- package/modules/ideas/governor-docket.js +4 -1
- package/modules/lifecycle/criterion-uat-db.js +18 -1
- package/modules/lifecycle/criterion-uat.js +12 -3
- package/modules/lifecycle/routes/project-settings.js +2 -2
- package/modules/public-landing/public/contact.html +10 -10
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +20 -0
- package/src/bongos/project-settings.js +5 -4
- package/src/module-api.js +1 -1
- package/tests/criterion_uat.mjs +42 -0
- package/tests/government_tidy_up.mjs +223 -0
- package/tests/hall_studio_home.mjs +20 -0
- package/tests/landing_legal_pages.mjs +1 -1
|
@@ -271,9 +271,57 @@ function orderedSurfaces(inventory) {
|
|
|
271
271
|
return ids.map((id, i) => ({ id, i })).sort((a, b) => rank(a.id) - rank(b.id) || a.i - b.i).map((x) => x.id);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// Pages in flight (task 1004662). The status label counts SHIPPED rounds only,
|
|
276
|
+
// so a page submitted weeks ago still read "Untweaked" and the Approval queue
|
|
277
|
+
// (applied rounds only) stayed empty: the artist's finished work looked lost.
|
|
278
|
+
// This is every page with an open round, where it is, since when (the round's
|
|
279
|
+
// last update), and whether it has sat there too long. Waiting for review is
|
|
280
|
+
// the artist's own move and a draft is theirs to finish, so neither is stuck;
|
|
281
|
+
// a state the reads cannot name always needs a look.
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
285
|
+
const IN_FLIGHT = Object.freeze({
|
|
286
|
+
draft: { word: 'Being written', stuck_after: null },
|
|
287
|
+
writing: { word: 'Being written', stuck_after: null },
|
|
288
|
+
submitted: { word: 'Submitted, waiting to be applied', stuck_after: 3 * DAY_MS },
|
|
289
|
+
applying: { word: 'Being applied', stuck_after: DAY_MS },
|
|
290
|
+
waiting_for_artist: { word: 'Waiting for your review', stuck_after: null },
|
|
291
|
+
landing: { word: 'Approved, going live', stuck_after: DAY_MS },
|
|
292
|
+
unknown: { word: 'Needs a look', stuck_after: 0 },
|
|
293
|
+
});
|
|
294
|
+
const IN_FLIGHT_ORDER = ['unknown', 'landing', 'applying', 'submitted', 'waiting_for_artist', 'writing', 'draft'];
|
|
295
|
+
|
|
296
|
+
function inFlightFor(list, byPage, nowMs) {
|
|
297
|
+
const out = [];
|
|
298
|
+
for (const p of list) {
|
|
299
|
+
const o = p.open_round;
|
|
300
|
+
if (!o) continue;
|
|
301
|
+
const rounds = byPage.get(p.id) || [];
|
|
302
|
+
const r = rounds.find((x) => String(x.id) === o.task_id) || {};
|
|
303
|
+
const kind = IN_FLIGHT[o.state] || IN_FLIGHT.unknown;
|
|
304
|
+
const since = iso(r.updated_at || r.created_at);
|
|
305
|
+
const age = since ? nowMs - Date.parse(since) : null;
|
|
306
|
+
out.push({
|
|
307
|
+
page_id: p.id,
|
|
308
|
+
title: p.title,
|
|
309
|
+
surface: p.surface,
|
|
310
|
+
task_id: o.task_id,
|
|
311
|
+
state: o.state,
|
|
312
|
+
state_word: kind.word,
|
|
313
|
+
since,
|
|
314
|
+
stuck: kind.stuck_after != null && (kind.stuck_after === 0 || (age != null && age > kind.stuck_after)),
|
|
315
|
+
holder: o.holder,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const rank = (s) => { const i = IN_FLIGHT_ORDER.indexOf(s); return i === -1 ? 0 : i; };
|
|
319
|
+
return out.sort((a, b) => (b.stuck - a.stuck) || rank(a.state) - rank(b.state) || String(a.since || '').localeCompare(String(b.since || '')));
|
|
320
|
+
}
|
|
321
|
+
|
|
274
322
|
// GET /copy-desk/pages — every page's status, count and drift, plus the
|
|
275
|
-
// per-surface rollup (Artist Review Status).
|
|
276
|
-
function composePages({ inventory, readings, tasks }) {
|
|
323
|
+
// per-surface rollup (Artist Review Status) and the pages in flight.
|
|
324
|
+
function composePages({ inventory, readings, tasks, now = Date.now() }) {
|
|
277
325
|
const byPage = indexRounds(tasks);
|
|
278
326
|
const readingsOk = !!readings;
|
|
279
327
|
const rIdx = readingIndex(readings);
|
|
@@ -289,6 +337,7 @@ function composePages({ inventory, readings, tasks }) {
|
|
|
289
337
|
pages: list,
|
|
290
338
|
surfaces,
|
|
291
339
|
totals: { tweaked, total: list.length, line: `${tweaked} of ${list.length} pages tweaked` },
|
|
340
|
+
in_flight: inFlightFor(list, byPage, now instanceof Date ? now.getTime() : Number(now)),
|
|
292
341
|
};
|
|
293
342
|
}
|
|
294
343
|
|
|
@@ -236,5 +236,42 @@ await test('no payout rows yet (TW13 unbuilt) reads 0 credits, while pages tweak
|
|
|
236
236
|
assert.deepEqual([carol.credits, carol.this_week, carol.pages_tweaked], [0, 0, 0], 'an abandoned round is not a page tweaked');
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
// Pages in flight (task 1004662): the live case. Submitted rounds never
|
|
240
|
+
// applied and approved rounds stuck before landing read "Untweaked" and never
|
|
241
|
+
// reach the Approval queue, so the list is the only place they show.
|
|
242
|
+
await test('in_flight: every open round, its plain state, how long, and stuck when it has sat too long', () => {
|
|
243
|
+
const NOW = new Date('2026-10-16T12:00:00.000Z');
|
|
244
|
+
const ago = (d) => new Date(NOW.getTime() - d * 86400000).toISOString();
|
|
245
|
+
const pg = (id) => ({ id, surface: id.split(':')[0], title: id, path: '/', files: [] });
|
|
246
|
+
const ids = ['landing:a', 'landing:b', 'landing:c', 'builders:d', 'builders:e', 'builders:f', 'builders:g', 'status:h'];
|
|
247
|
+
const inventory = { surfaces: [{ id: 'landing' }, { id: 'builders' }, { id: 'status' }], pages: ids.map(pg) };
|
|
248
|
+
const batch = (id) => pages.composeBatchBlock({ page_id: id, reading_hash: 'h', submitted_at: ago(9), lines: [] });
|
|
249
|
+
const round = (n, id, extra) => ({ id: String(n), source: 'page-tweak', source_ref: pages.roundSourceRef(id, 1), description: pages.roundTaskBody(pg(id), 1), ...extra });
|
|
250
|
+
const tasks = [
|
|
251
|
+
round(1, 'landing:a', { status: 'ready', description: `${pages.roundTaskBody(pg('landing:a'), 1)}\n\n${batch('landing:a')}`, updated_at: ago(9) }),
|
|
252
|
+
round(2, 'landing:b', { status: 'ready', description: `${pages.roundTaskBody(pg('landing:b'), 1)}\n\n${batch('landing:b')}`, updated_at: ago(1) }),
|
|
253
|
+
round(3, 'landing:c', { status: 'confirmed', updated_at: ago(17) }),
|
|
254
|
+
round(4, 'builders:d', { status: 'completed', grade_passed: true, updated_at: ago(20) }),
|
|
255
|
+
round(5, 'builders:e', { status: 'active', holder_id: '7', holder_login: 'me', holder_worktree: false, updated_at: ago(30) }),
|
|
256
|
+
round(6, 'builders:f', { status: 'ready', updated_at: ago(0.2) }),
|
|
257
|
+
round(7, 'builders:g', { status: 'shipped', shipped_at: ago(3), updated_at: ago(3) }),
|
|
258
|
+
round(8, 'status:h', { status: 'active', holder_id: '8', holder_worktree: true, updated_at: ago(2) }),
|
|
259
|
+
];
|
|
260
|
+
const out = status.composePages({ inventory, readings: null, tasks, now: NOW });
|
|
261
|
+
assert.deepEqual(out.in_flight.map((f) => [f.page_id, f.state, f.state_word, f.stuck]), [
|
|
262
|
+
['builders:f', 'unknown', 'Needs a look', true],
|
|
263
|
+
['landing:c', 'landing', 'Approved, going live', true],
|
|
264
|
+
['status:h', 'applying', 'Being applied', true],
|
|
265
|
+
['landing:a', 'submitted', 'Submitted, waiting to be applied', true],
|
|
266
|
+
['landing:b', 'submitted', 'Submitted, waiting to be applied', false],
|
|
267
|
+
['builders:d', 'waiting_for_artist', 'Waiting for your review', false],
|
|
268
|
+
['builders:e', 'writing', 'Being written', false],
|
|
269
|
+
], 'a shipped round is not in flight; waiting for review and writing are never stuck');
|
|
270
|
+
assert.equal(out.in_flight[1].since, ago(17));
|
|
271
|
+
assert.equal(out.in_flight[1].task_id, '3');
|
|
272
|
+
assert.equal(out.pages.find((p) => p.id === 'landing:a').status_label, 'Untweaked', 'the status still counts only what shipped');
|
|
273
|
+
assert.deepEqual(status.composePages({ inventory, readings: null, tasks: [], now: NOW }).in_flight, []);
|
|
274
|
+
});
|
|
275
|
+
|
|
239
276
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
240
277
|
process.exit(failed ? 1 : 0);
|
package/modules/government/db.js
CHANGED
|
@@ -866,6 +866,12 @@ async function applyLeadAct({ itemId, action }) {
|
|
|
866
866
|
return { done: true, lead, ended };
|
|
867
867
|
} catch (err) {
|
|
868
868
|
await client.query('ROLLBACK').catch(() => {});
|
|
869
|
+
// Two appointments to an unseated building passed at once (task 1004724):
|
|
870
|
+
// both saw no Lead, and the one-Lead-in-office index refused the second.
|
|
871
|
+
// That is a refusal to say plainly, not a database error.
|
|
872
|
+
if (err && err.code === '23505' && err.constraint === 'government_leads_in_office_uniq') {
|
|
873
|
+
return { done: false, reason: 'lead_seated_meanwhile', what_this_means: 'Another appointment to this building landed first, so this one changed nothing. Propose again to replace that Lead.' };
|
|
874
|
+
}
|
|
869
875
|
throw err;
|
|
870
876
|
} finally {
|
|
871
877
|
client.release();
|
|
@@ -394,7 +394,8 @@ async function openMatter({ area, title, detailMd = null, action = null, propose
|
|
|
394
394
|
async function leadMatterRefusal(action) {
|
|
395
395
|
const inOffice = (await db.listLeads()).find((l) => l.building === action.building) || null;
|
|
396
396
|
if (action.type === 'remove_lead') {
|
|
397
|
-
|
|
397
|
+
// task 1004724: say what to do next, not only what is missing
|
|
398
|
+
return inOffice ? null : { ok: false, reason: 'no_lead', area: 'people', building: action.building, what_this_means: `${decisionRules.areaByKey(action.building).name} has no Lead to remove; the owner holds it. To give it one, propose a Lead from its office.` };
|
|
398
399
|
}
|
|
399
400
|
if (!(await db.getActiveBuilder(action.builder_id))) return { ok: false, reason: 'unknown_builder', builder_id: String(action.builder_id) };
|
|
400
401
|
if (inOffice && String(inOffice.builder_id) === String(action.builder_id) && inOffice.charter === action.charter) {
|
|
@@ -237,7 +237,10 @@
|
|
|
237
237
|
// Charter, the tallest, takes the back corner and rises over the quarter;
|
|
238
238
|
// Planning takes the centre, where nothing in front is tall enough to hide its
|
|
239
239
|
// lit floor (task 1004718: behind the Charter, Planning's floor never showed).
|
|
240
|
-
|
|
240
|
+
// Task 1004724 carried the rule to every column: Rules stood behind Release and
|
|
241
|
+
// People behind Automation, so the taller one in front hid their lit floors; in
|
|
242
|
+
// each column the taller building now stands at the back.
|
|
243
|
+
const CIVIC_SLOTS = Object.freeze([['charter', 5, 5], ['release', 7, 5], ['planning', 9, 5], ['automation', 5, 7], ['treasury', 7, 7], ['rules', 9, 7], ['disputes', 5, 9], ['people', 7, 9], ['review', 9, 9]]);
|
|
241
244
|
const ROOF = Object.freeze({ charter: 158, rules: 36, treasury: 66, planning: 51, people: 31, release: 72, disputes: 60, review: 53, automation: 86 });
|
|
242
245
|
|
|
243
246
|
// The four prototype districts (route = the parcel road it feeds), then the
|
|
@@ -210,7 +210,7 @@ html[data-fullscreen="on"] .city-top { right: 184px; }
|
|
|
210
210
|
.city-plaque[hidden] { display: none; }
|
|
211
211
|
.city-plaque__tile { display: inline-flex; align-items: baseline; gap: 7px; }
|
|
212
212
|
.city-plaque__figure { font-size: 22px; font-weight: 300; letter-spacing: -0.03em; line-height: 1.1; font-variant-numeric: tabular-nums; color: var(--ink); }
|
|
213
|
-
.city-plaque__words { font-family: var(--
|
|
213
|
+
.city-plaque__words { font-family: var(--font-body); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint); }
|
|
214
214
|
.city-plaque__tile[data-key="you"] .city-plaque__figure { color: var(--accent-deep); }
|
|
215
215
|
|
|
216
216
|
/* the card a building opens in place, in the desk line: who, one fact, its count, two doors */
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
(tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet).
|
|
13
13
|
The prototype carried its own Google Fonts link for the same two faces. -->
|
|
14
14
|
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
15
|
-
<link rel="stylesheet" href="/builders/city.css?v=2026-10-
|
|
15
|
+
<link rel="stylesheet" href="/builders/city.css?v=2026-10-17-tidy">
|
|
16
16
|
<!-- the ask card in the glass language, shared with the street's deck (task 1004702) -->
|
|
17
|
-
<link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-
|
|
17
|
+
<link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-17-tidy">
|
|
18
18
|
</head>
|
|
19
19
|
<!-- THE GOVERNOR CITY, HOME (task 1004524, goal 1000125, R02). The home view
|
|
20
20
|
of docs/design/mocks/governor-city/Street.dc.html (homeView and the home
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
<script src="/builders/palette.js" defer></script>
|
|
110
110
|
<script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
|
|
111
111
|
<script src="/builders/gov-feeds.js?v=2026-10-09-living3"></script>
|
|
112
|
-
<script src="/builders/city-draw.js?v=2026-10-
|
|
112
|
+
<script src="/builders/city-draw.js?v=2026-10-17-tidy"></script>
|
|
113
113
|
<!-- a building's card answers its first ask in place through the deck's own
|
|
114
114
|
route (task 1004702): the desk rows (street-desk.js over the street's kit)
|
|
115
115
|
and the deck's card and sender (street-deck.js) -->
|
|
@@ -67,6 +67,6 @@
|
|
|
67
67
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
68
68
|
<script src="/builders/gov-living.js?v=2026-10-10-real1"></script>
|
|
69
69
|
<script src="/builders/gov-positions.js?v=2026-10-09-positions1"></script>
|
|
70
|
-
<script src="/builders/my-agents.js?v=2026-10-
|
|
70
|
+
<script src="/builders/my-agents.js?v=2026-10-17-tidy"></script>
|
|
71
71
|
</body>
|
|
72
72
|
</html>
|
|
@@ -205,11 +205,17 @@
|
|
|
205
205
|
// setting to your value. Offered only to a builder who may open a board item.
|
|
206
206
|
const canPropose = !!(obj(me.permissions) && me.permissions[PROPOSE_ATOM] === true);
|
|
207
207
|
const habits = [];
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
208
|
+
// filed this visit (`proposed`, by setting), or already open on the board
|
|
209
|
+
// (`pending`, setting=value → item, pendingLaws) — so a reload keeps it
|
|
210
|
+
const filed = obj(o.proposed) || {};
|
|
211
|
+
const pending = obj(o.pending) || {};
|
|
212
|
+
const habit = (key, value, law) => {
|
|
213
|
+
const item = filed[key] || pending[key + '=' + str(value)] || null;
|
|
214
|
+
return {
|
|
215
|
+
key, name: nameFor(key) + ' ' + wordFor(key, value), fact: item ? 'proposed' : 'only you, for now',
|
|
216
|
+
law, lock: { setting: key, value }, proposed: !!item, item: item && item !== true ? str(item) : null,
|
|
217
|
+
};
|
|
218
|
+
};
|
|
213
219
|
const held = (key) => !!(byKey[key] && (byKey[key].locked || byKey[key].cappedBy));
|
|
214
220
|
const inter = obj(me.interaction);
|
|
215
221
|
if (inter) {
|
|
@@ -271,6 +277,21 @@
|
|
|
271
277
|
|
|
272
278
|
// The laws vote a habit files (POST /government/board/laws): one law, locking the
|
|
273
279
|
// setting to the proposer's value.
|
|
280
|
+
// The habits already up for a vote (task 1004724): every OPEN laws sitting on
|
|
281
|
+
// the board (GET /government/board/items), as setting=value → its item id, so a
|
|
282
|
+
// habit proposed before a reload still reads "up for a vote".
|
|
283
|
+
function pendingLaws(room) {
|
|
284
|
+
const out = {};
|
|
285
|
+
arr(obj(room) && room.open).forEach((it) => {
|
|
286
|
+
const s = obj(it && it.subject);
|
|
287
|
+
if (!s || s.kind !== 'laws' || it.closed_at) return;
|
|
288
|
+
arr(obj(s.proposed) && s.proposed.enact).forEach((law) => arr(obj(law) && law.locks).forEach((l) => {
|
|
289
|
+
if (obj(l) && l.setting != null && Object.prototype.hasOwnProperty.call(l, 'value')) out[str(l.setting) + '=' + str(l.value)] = str(it.id);
|
|
290
|
+
}));
|
|
291
|
+
});
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
|
|
274
295
|
function proposalFor(h, login) {
|
|
275
296
|
return {
|
|
276
297
|
enact: [{ text: h.law, locks: [h.lock] }],
|
|
@@ -278,7 +299,7 @@
|
|
|
278
299
|
};
|
|
279
300
|
}
|
|
280
301
|
|
|
281
|
-
const pure = { buildView, writeFor, proposalFor, SETTING, LAW_LINES, GROUPS };
|
|
302
|
+
const pure = { buildView, writeFor, proposalFor, pendingLaws, SETTING, LAW_LINES, GROUPS };
|
|
282
303
|
root.OTBMyAgents = pure;
|
|
283
304
|
if (typeof module !== 'undefined' && module.exports) { module.exports = pure; return; }
|
|
284
305
|
if (typeof document === 'undefined' || !root.BongosClient) return;
|
|
@@ -296,7 +317,7 @@
|
|
|
296
317
|
const LOCK = '<svg class="ma-lock__icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="7" width="9" height="6.5" rx="1.5"/><path d="M5.5 7V5.2a2.5 2.5 0 0 1 5 0V7"/></svg>';
|
|
297
318
|
const TICK = '<svg class="ma-tick" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>';
|
|
298
319
|
|
|
299
|
-
const S = { me: null, constitution: null, presets: null, skills: null, specialities: null, preview: null, proposed: {}, switched: {}, adopted: [], skillsOpen: false, readAll: false, view: null };
|
|
320
|
+
const S = { me: null, constitution: null, presets: null, skills: null, specialities: null, preview: null, proposed: {}, pending: {}, switched: {}, adopted: [], skillsOpen: false, readAll: false, view: null };
|
|
300
321
|
|
|
301
322
|
function asNow() {
|
|
302
323
|
try { return root.sessionStorage.getItem('hall-as') === 'nobody' || /[?&]as=nobody\b/.test(root.location.search) ? 'nobody' : null; }
|
|
@@ -305,7 +326,7 @@
|
|
|
305
326
|
function view() {
|
|
306
327
|
S.view = buildView({
|
|
307
328
|
me: S.me, constitution: S.constitution, presets: S.presets, skills: S.skills, specialities: S.specialities,
|
|
308
|
-
preview: S.preview, proposed: S.proposed, switched: S.switched, adopted: S.adopted, as: asNow(), project: PROJECT, now: Date.now(),
|
|
329
|
+
preview: S.preview, proposed: S.proposed, pending: S.pending, switched: S.switched, adopted: S.adopted, as: asNow(), project: PROJECT, now: Date.now(),
|
|
309
330
|
}, { GovLiving: root.GovLiving, GovPositions: root.GovPositions });
|
|
310
331
|
return S.view;
|
|
311
332
|
}
|
|
@@ -560,7 +581,9 @@
|
|
|
560
581
|
const err = r && r.data && r.data.error;
|
|
561
582
|
const code = err && (err.code || err);
|
|
562
583
|
if (code === 'preset_in_force') { toast('Not filed: ' + p.name + ' is already in force.'); return; }
|
|
563
|
-
|
|
584
|
+
// the setting by its row name, never its key (task 1004724)
|
|
585
|
+
const held = err.details && err.details.setting ? nameFor(str(err.details.setting)).toLowerCase() : 'that setting';
|
|
586
|
+
if (code === 'setting_already_locked') { toast('Not filed: a law of the project\'s own already holds ' + held + '. Repeal it first.'); return; }
|
|
564
587
|
toast('Not filed' + (r && r.status ? ' (' + r.status + ')' : '') + '. Nothing changed.');
|
|
565
588
|
}, () => toast('Not filed: no answer.'));
|
|
566
589
|
}
|
|
@@ -590,9 +613,11 @@
|
|
|
590
613
|
return;
|
|
591
614
|
}
|
|
592
615
|
S.me = me;
|
|
593
|
-
const [constitution, presets, skills, specialities] = await Promise.all([
|
|
616
|
+
const [constitution, presets, skills, specialities, room] = await Promise.all([
|
|
594
617
|
getJSON('/government/constitution'), getJSON('/government/presets'), getJSON('/me/skill-prefs'), getJSON('/specialities'),
|
|
618
|
+
getJSON('/government/board/items'),
|
|
595
619
|
]);
|
|
620
|
+
S.pending = pendingLaws(room);
|
|
596
621
|
S.presets = presets || null;
|
|
597
622
|
S.constitution = constitution;
|
|
598
623
|
S.skills = skills || null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "MY AGENTS (task 1004685, the living-city prototype) through modules/ui-design/kit/render.js. Every row is filled from GET /me and its prefs, so the floors run against the hall-preview harness, which serves those reads and records the pref writes (scripts/hall-preview/pref-writes.js):\n\n node scripts/hall-preview/server.js --port 4614 --fixture-me --rank archon\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/my-agents.states.json --base-in http://builders.localhost:4614 --base-out http://builders.localhost:4614\n\nAgainst the kit's own stub the reads 404 and the page shows its sign-in line. The 'nobody' state is the same page as a builder who holds no seat (?as=nobody,
|
|
2
|
+
"_": "MY AGENTS (task 1004685, the living-city prototype) through modules/ui-design/kit/render.js. Every row is filled from GET /me and its prefs, so the floors run against the hall-preview harness, which serves those reads and records the pref writes (scripts/hall-preview/pref-writes.js):\n\n node scripts/hall-preview/server.js --port 4614 --fixture-me --rank archon\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/my-agents.states.json --base-in http://builders.localhost:4614 --base-out http://builders.localhost:4614\n\nAgainst the kit's own stub the reads 404 and the page shows its sign-in line. The 'nobody' state is the same page as a builder who holds no seat (?as=nobody, a stand-in that can only take away, gov-positions.js).",
|
|
3
3
|
"page": "/my-agents",
|
|
4
4
|
"surface": "hall-ui",
|
|
5
5
|
"stub": { "prefix": "/builders/" },
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
32
32
|
|
|
33
33
|
const toAuth = () => window.location.assign(`${API}/auth/web/start`);
|
|
34
|
+
// the project's laws on these fields (task 1004724): settings-law-locks.js
|
|
35
|
+
const Laws = window.OTBSettingsLaws || { lockFor: () => null, allows: () => true, lawLine: () => '', tagFor: () => '' };
|
|
34
36
|
|
|
35
37
|
function render(data) {
|
|
36
38
|
const wrap = $('#interaction-rows');
|
|
@@ -45,17 +47,19 @@
|
|
|
45
47
|
// name — four independent single-selects, not one flat list of radios.
|
|
46
48
|
wrap.innerHTML = fields.map((f) => {
|
|
47
49
|
const chosenValue = eff[f.field];
|
|
50
|
+
const lock = Laws.lockFor(data, f.field);
|
|
48
51
|
const rows = (f.values || []).map((v) => {
|
|
49
52
|
const id = `interaction-${f.field}-${v.value}`;
|
|
50
53
|
const chosen = v.value === chosenValue;
|
|
54
|
+
const barred = !Laws.allows(lock, v.value);
|
|
51
55
|
// No "default" tag here, deliberately: interaction-prefs.js already ends
|
|
52
56
|
// every default value's LABEL with "(default)", so adding the row tag the
|
|
53
57
|
// wandering picker uses rendered "Non-technical (default) · DEFAULT".
|
|
54
58
|
// One source for the word, and it is the label the API already ships.
|
|
55
59
|
return (
|
|
56
|
-
`<label class="prow prow--pick" for="${id}">` +
|
|
60
|
+
`<label class="prow prow--pick" for="${id}"${barred ? ' aria-disabled="true"' : ''}>` +
|
|
57
61
|
`<input class="pradio" type="radio" name="interaction-${escapeHtml(f.field)}" id="${id}"` +
|
|
58
|
-
` value="${escapeHtml(v.value)}" data-field="${escapeHtml(f.field)}"${chosen ? ' checked' : ''}>` +
|
|
62
|
+
` value="${escapeHtml(v.value)}" data-field="${escapeHtml(f.field)}"${chosen ? ' checked' : ''}${barred ? ' disabled' : ''}>` +
|
|
59
63
|
'<span class="prow__main">' +
|
|
60
64
|
`<span class="prow__label">${escapeHtml(v.label)}</span>` +
|
|
61
65
|
'</span></label>'
|
|
@@ -65,6 +69,7 @@
|
|
|
65
69
|
`<fieldset class="pgroup" id="interaction-field-${escapeHtml(f.field)}">` +
|
|
66
70
|
`<legend class="prow__label">${escapeHtml(f.label)}</legend>` +
|
|
67
71
|
`<p class="prow__desc">${escapeHtml(f.blurb || '')}</p>` +
|
|
72
|
+
(lock ? `<p class="prow__desc prow__law" data-law="${escapeHtml(String(lock.law_no || ''))}">${escapeHtml(Laws.lawLine(lock))}</p>` : '') +
|
|
68
73
|
rows +
|
|
69
74
|
'</fieldset>'
|
|
70
75
|
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// modules/hall-ui/public/settings-law-locks.js — the project's LAWS on the
|
|
2
|
+
// /builders/settings rows (task 1004724).
|
|
3
|
+
//
|
|
4
|
+
// A law passed by the project's government may lock a personal setting: FIX it
|
|
5
|
+
// to one value, or CAP it. The server enforces the lock (law-locks.js, task
|
|
6
|
+
// 1004707) and every settings read already reports it as `law_locks` — this file
|
|
7
|
+
// is only how the page SAYS so: a value the lock does not allow is a disabled
|
|
8
|
+
// row tagged with the law, and the setting carries one line naming the law.
|
|
9
|
+
// Shared by the "How Claude talks to you" panel (settings-interaction.js) and the
|
|
10
|
+
// wandering picker (settings.js). The page decides nothing; a write the law
|
|
11
|
+
// forbids is refused by the route either way (409 locked_by_law).
|
|
12
|
+
//
|
|
13
|
+
// PURE: no DOM, no fetch. tests/settings_law_locks.mjs requires it in node.
|
|
14
|
+
|
|
15
|
+
(function (root) {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const str = (v) => (v == null ? '' : String(v));
|
|
19
|
+
|
|
20
|
+
// The lock a law puts on `setting`, from a settings read's `law_locks`, or null.
|
|
21
|
+
// Only an ENFORCED lock counts: one naming no real value locks nothing.
|
|
22
|
+
function lockFor(data, setting) {
|
|
23
|
+
const locks = data && Array.isArray(data.law_locks) ? data.law_locks : [];
|
|
24
|
+
return locks.find((l) => l && l.enforced === true && str(l.setting) === str(setting)) || null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// May `value` still be picked under `lock`? No lock: yes.
|
|
28
|
+
function allows(lock, value) {
|
|
29
|
+
if (!lock) return true;
|
|
30
|
+
if (!Array.isArray(lock.allowed)) return true;
|
|
31
|
+
return lock.allowed.map(str).includes(str(value));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The one line the setting carries: which law, and what it does to the row.
|
|
35
|
+
function lawLine(lock) {
|
|
36
|
+
if (!lock) return '';
|
|
37
|
+
const n = lock.law_no != null ? 'Law ' + str(lock.law_no) : 'A law';
|
|
38
|
+
const text = str(lock.law_text).trim();
|
|
39
|
+
const what = Object.prototype.hasOwnProperty.call(lock, 'value')
|
|
40
|
+
? ' sets this for every agent in the project'
|
|
41
|
+
: ' caps this for every agent in the project';
|
|
42
|
+
return n + what + (text ? ': "' + text + '".' : '.') + ' Only a vote can change it.';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The tag a row the lock rules out wears.
|
|
46
|
+
function tagFor(lock) {
|
|
47
|
+
return lock ? 'set by ' + (lock.law_no != null ? 'law ' + str(lock.law_no) : 'a law') : '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const api = { lockFor, allows, lawLine, tagFor };
|
|
51
|
+
root.OTBSettingsLaws = api;
|
|
52
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
53
|
+
}(typeof window !== 'undefined' ? window : globalThis));
|
|
@@ -482,7 +482,8 @@
|
|
|
482
482
|
<!-- shared record overlay (#/idea|blocker/N) — every hall page carries it; this
|
|
483
483
|
page was the one gap (task 2172). -->
|
|
484
484
|
<script src="/builders/task-detail.js?v=2026-07-03-taskdetail"></script>
|
|
485
|
-
<script src="/builders/settings.js?v=2026-
|
|
485
|
+
<script src="/builders/settings-law-locks.js?v=2026-10-17-laws"></script>
|
|
486
|
+
<script src="/builders/settings.js?v=2026-10-17-laws"></script>
|
|
486
487
|
<!-- task 1003341: the Active sessions panel. Its own file rather than more of
|
|
487
488
|
settings.js, which sits close to the 1500-line size ratchet. -->
|
|
488
489
|
<script src="/builders/settings-sessions.js?v=2026-08-29-sessions"></script>
|
|
@@ -490,7 +491,7 @@
|
|
|
490
491
|
<!-- task 1002514: the interaction-profile panel ("How Claude talks to you"), on
|
|
491
492
|
the same precedent and for the same reason — settings.js is against the
|
|
492
493
|
1500-line ratchet, and this panel shares nothing with the rest of the page. -->
|
|
493
|
-
<script src="/builders/settings-interaction.js?v=2026-
|
|
494
|
+
<script src="/builders/settings-interaction.js?v=2026-10-17-laws"></script>
|
|
494
495
|
<script src="/builders/settings-specialities.js?v=2026-09-17-specialities"></script>
|
|
495
496
|
<!-- task 1002518: the card-richness ladder. Own file on the same precedent —
|
|
496
497
|
the panel grew from one switch to a rung picker with a derived default, and
|
|
@@ -413,12 +413,17 @@
|
|
|
413
413
|
// One pick among four: a radio row per level (panel.css `.prow--pick`), the
|
|
414
414
|
// whole row its label. A level above the rank ceiling is a disabled radio
|
|
415
415
|
// that says so in the label voice rather than a dead "Rank-locked" button.
|
|
416
|
+
// a project law may fix or cap the level too (task 1004724, settings-law-locks.js)
|
|
417
|
+
const Laws = window.OTBSettingsLaws || null;
|
|
418
|
+
const lawLock = Laws ? Laws.lockFor(data, 'wandering') : null;
|
|
416
419
|
wrap.innerHTML = levels.map((lv) => {
|
|
417
420
|
const chosen = lv.n === eff;
|
|
418
|
-
const
|
|
421
|
+
const byLaw = lv.allowed && !!lawLock && !Laws.allows(lawLock, lv.key);
|
|
422
|
+
const locked = !lv.allowed || byLaw;
|
|
419
423
|
const id = `wander-${lv.n}`;
|
|
420
424
|
const tags = (lv.n === def ? '<span class="prow__tag">default for your rank</span>' : '') +
|
|
421
|
-
(
|
|
425
|
+
(!lv.allowed ? '<span class="prow__tag">rank-locked</span>' : '') +
|
|
426
|
+
(byLaw ? `<span class="prow__tag">${escapeHtml(Laws.tagFor(lawLock))}</span>` : '');
|
|
422
427
|
return (
|
|
423
428
|
`<label class="prow prow--pick" for="${id}" data-level="${lv.n}"${locked ? ' aria-disabled="true"' : ''}>` +
|
|
424
429
|
`<input class="pradio" type="radio" name="wandering" id="${id}" value="${lv.n}"` +
|
|
@@ -431,9 +436,9 @@
|
|
|
431
436
|
}).join('');
|
|
432
437
|
const note = $('#wandering-note');
|
|
433
438
|
if (note) {
|
|
434
|
-
note.textContent = (data && data.effective_label && data.max_key)
|
|
439
|
+
note.textContent = ((data && data.effective_label && data.max_key)
|
|
435
440
|
? `Now: ${data.effective_label}. Your rank allows up to ${data.max_key}.`
|
|
436
|
-
: '';
|
|
441
|
+
: '') + (lawLock ? ' ' + Laws.lawLine(lawLock) : '');
|
|
437
442
|
}
|
|
438
443
|
}
|
|
439
444
|
|
|
@@ -248,6 +248,10 @@ body[data-page="street"] .street-card { background: var(--gov-glass); border: 1p
|
|
|
248
248
|
.office .desk-name__f { white-space: nowrap; }
|
|
249
249
|
.govx-capsule { height: auto; min-height: 44px; padding: 6px 14px; display: inline-flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 1px; }
|
|
250
250
|
.govx-capsule__how { font-size: 11px; font-weight: 400; color: var(--ink-faint); letter-spacing: 0.04em; }
|
|
251
|
+
/* who should lead (task 1004724): one pick, one chip */
|
|
252
|
+
.govx-appoint__who { min-height: 44px; max-width: 100%; min-width: 0; padding: 0 12px; border-radius: 999px; border: 1px solid var(--street-rule-strong); background: var(--bg); color: var(--ink); font: inherit; font-size: 14px; }
|
|
253
|
+
.govx-appoint__who:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
254
|
+
.govx-appoint__f { font-size: 13px; color: var(--ink-soft); }
|
|
251
255
|
.govx-pic { margin: 0; display: flex; flex-direction: column; gap: 4px; padding: 6px 0 2px; border-top: 1px solid var(--street-rule); border-bottom: 1px solid var(--street-rule); }
|
|
252
256
|
.govx-pic svg { display: block; max-width: 100%; }
|
|
253
257
|
.govx-pic__cap { font-size: 12px; color: var(--ink-faint); }
|
|
@@ -260,6 +260,32 @@
|
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
// ---------- who should lead (task 1004724) ----------
|
|
264
|
+
// The office's "who should lead": any builder who may open a board item (the
|
|
265
|
+
// perm /me reports) may propose one of the project's active people as the
|
|
266
|
+
// building's Lead. A proposal is the existing appoint_lead People matter (POST
|
|
267
|
+
// /government/board/matters); only its sitting passing seats anyone, and the
|
|
268
|
+
// server checks the perm, the person and the building again. null: not offered.
|
|
269
|
+
function appointPlan(living, key, roster, me, os) {
|
|
270
|
+
const off = officeOf(living, key);
|
|
271
|
+
const perms = me && me.permissions;
|
|
272
|
+
if (!off || !(perms && perms['board.item.open'] === true)) return null;
|
|
273
|
+
const leadId = off.lead ? off.lead.id : null;
|
|
274
|
+
const people = (roster && Array.isArray(roster.builders) ? roster.builders : [])
|
|
275
|
+
.filter((b) => b && b.id != null && (b.status == null || b.status === 'active') && str(b.id) !== leadId)
|
|
276
|
+
.map((b) => ({ id: str(b.id), name: str(b.display_name || b.github_login) }))
|
|
277
|
+
.filter((p) => p.name)
|
|
278
|
+
.sort((a, z) => a.name.localeCompare(z.name));
|
|
279
|
+
if (!people.length) return null;
|
|
280
|
+
return { building: key, name: off.name, seated: off.seated, people: people, filed: !!(os && os.leadFiled && os.leadFiled[key]) };
|
|
281
|
+
}
|
|
282
|
+
// The body of POST /government/board/matters for one proposed Lead, or null.
|
|
283
|
+
function appointBody(plan, personId) {
|
|
284
|
+
const p = plan ? plan.people.find((x) => x.id === str(personId)) : null;
|
|
285
|
+
if (!p) return null;
|
|
286
|
+
return { area: 'people', title: 'Appoint ' + p.name + ' as Lead of ' + plan.name, action: { type: 'appoint_lead', building: plan.building, builder_id: Number(p.id) } };
|
|
287
|
+
}
|
|
288
|
+
|
|
263
289
|
// ---------- how strict ----------
|
|
264
290
|
// GET /government/presets (task 1004715): the preset is the one the laws in
|
|
265
291
|
// force amount to (none: "No preset yet"), each carries the locks a switch to
|
|
@@ -281,6 +307,7 @@
|
|
|
281
307
|
officeOf: officeOf, worksOf: worksOf, builtFloors: builtFloors, buildPlan: buildPlan, buildsAlone: buildsAlone,
|
|
282
308
|
isoPicture: isoPicture, lawBook: lawBook, injection: injection,
|
|
283
309
|
treasury: treasury, strictness: strictness, usd: usd, dayWords: dayWords,
|
|
310
|
+
appointPlan: appointPlan, appointBody: appointBody,
|
|
284
311
|
};
|
|
285
312
|
|
|
286
313
|
// =====================================================================
|
|
@@ -292,6 +319,9 @@
|
|
|
292
319
|
const page = (function () {
|
|
293
320
|
let env = null, doc = null;
|
|
294
321
|
const os = blank();
|
|
322
|
+
// the Lead appointments this visit filed, by building (task 1004724); the
|
|
323
|
+
// vote itself is the server's, on the docket
|
|
324
|
+
const leadsFiled = {};
|
|
295
325
|
const deckPage = () => (root.GovDeck ? root.GovDeck.page : null);
|
|
296
326
|
const living = () => { const p = deckPage(); return p ? p.living() : null; };
|
|
297
327
|
const viewerId = () => { const d = env ? env.data() : null; return d && d.docket && d.docket.viewer ? str(d.docket.viewer.id) : null; };
|
|
@@ -575,6 +605,10 @@
|
|
|
575
605
|
off.rooms.forEach((r) => row(r.name, r.proposed ? 'proposed · a vote is open' : r.line));
|
|
576
606
|
worksOf(lv, key).forEach((w) => row(w.label, w.line));
|
|
577
607
|
if (rows.children && rows.children.length) el.appendChild(rows);
|
|
608
|
+
// who should lead: propose a Lead, filed as a People vote (task 1004724)
|
|
609
|
+
const fd = deckPage() ? deckPage().feeds() : {};
|
|
610
|
+
const ap = appointPlan(lv, key, fd.roster, fd.me, { leadFiled: leadsFiled });
|
|
611
|
+
if (ap) el.appendChild(appointEl(ap));
|
|
578
612
|
// build out: a capsule each, one tap. Only the building's holder sees them
|
|
579
613
|
// (GET /government/leads answers; the server holds the same wall).
|
|
580
614
|
if (off.holders && off.holders.includes(str(viewerId()))) {
|
|
@@ -597,6 +631,31 @@
|
|
|
597
631
|
if (c.capsule && c.ask_id) { const card = askHere(c.ask_id, { building: key, change: c.capsule }); if (card) el.appendChild(card); }
|
|
598
632
|
});
|
|
599
633
|
}
|
|
634
|
+
function appointEl(ap) {
|
|
635
|
+
const g = h('div', 'govx-build govx-appoint');
|
|
636
|
+
g.setAttribute('role', 'group');
|
|
637
|
+
g.setAttribute('aria-label', 'who should lead');
|
|
638
|
+
g.appendChild(h('span', 'desk-choices__ask', 'who should lead'));
|
|
639
|
+
if (ap.filed) { g.appendChild(h('span', 'govx-appoint__f', 'proposed · a vote is open')); return g; }
|
|
640
|
+
const sel = h('select', 'govx-appoint__who');
|
|
641
|
+
sel.setAttribute('aria-label', 'who should lead ' + ap.name);
|
|
642
|
+
ap.people.forEach((p) => { const o = h('option', '', p.name); o.setAttribute('value', p.id); sel.appendChild(o); });
|
|
643
|
+
g.appendChild(sel);
|
|
644
|
+
g.appendChild(chip('desk-chip--choice', ap.seated ? 'propose instead' : 'put it to a vote', () => appoint(ap, sel.value)));
|
|
645
|
+
return g;
|
|
646
|
+
}
|
|
647
|
+
// One matter, one place (task 1004724): a book's in-place ask card steps
|
|
648
|
+
// aside when the floor desk below already holds that matter as a row, which
|
|
649
|
+
// acts in place too — the treasury's dry pot used to show twice.
|
|
650
|
+
function settle(govt, on) {
|
|
651
|
+
if (!govt || !on || !govt.querySelectorAll || !on.querySelector) return 0;
|
|
652
|
+
let n = 0;
|
|
653
|
+
Array.prototype.slice.call(govt.querySelectorAll('[data-ask]')).forEach((card) => {
|
|
654
|
+
const id = str(card.getAttribute('data-ask')).replace(/["\\]/g, '');
|
|
655
|
+
if (id && on.querySelector('[data-row="' + id + '"]') && card.parentNode) { card.parentNode.removeChild(card); n++; }
|
|
656
|
+
});
|
|
657
|
+
return n;
|
|
658
|
+
}
|
|
600
659
|
// the live site over the drawing: a tower crane over a floor going up
|
|
601
660
|
function crane(bv) {
|
|
602
661
|
const layer = doc.getElementById('insideFloors');
|
|
@@ -649,6 +708,19 @@
|
|
|
649
708
|
});
|
|
650
709
|
}
|
|
651
710
|
// a switch of preset (task 1004715): ONE laws vote, filed through the board
|
|
711
|
+
function appoint(ap, personId) {
|
|
712
|
+
const body = appointBody(ap, personId);
|
|
713
|
+
if (!body || !env || !env.send) return Promise.resolve();
|
|
714
|
+
return env.send({ method: 'POST', url: '/api/bongos/government/board/matters', body: body }).then((out) => {
|
|
715
|
+
if (preview(out)) { env.say('This preview stores nothing; no vote was filed.', true); return; }
|
|
716
|
+
if (!out.ok) { env.say(refusal(out), true); return; }
|
|
717
|
+
leadsFiled[ap.building] = true;
|
|
718
|
+
env.say('Put to a vote. Nobody leads ' + ap.name + ' until the vote passes.', true);
|
|
719
|
+
if (root.GovFeeds && typeof root.GovFeeds.refresh === 'function') root.GovFeeds.refresh(['docket', 'leads']);
|
|
720
|
+
if (env.load) env.load();
|
|
721
|
+
env.draw();
|
|
722
|
+
});
|
|
723
|
+
}
|
|
652
724
|
function switchPreset(to) {
|
|
653
725
|
const st = strictness(living(), os);
|
|
654
726
|
if (!st || !env.send) return Promise.resolve();
|
|
@@ -696,7 +768,7 @@
|
|
|
696
768
|
p.onChange(() => { if (env && env.inside && env.inside()) env.draw(); });
|
|
697
769
|
}
|
|
698
770
|
|
|
699
|
-
return { mount: mount, input: input, inside: inside, buildOut: buildOut, switchPreset: switchPreset, state: os };
|
|
771
|
+
return { mount: mount, input: input, inside: inside, settle: settle, buildOut: buildOut, appoint: appoint, switchPreset: switchPreset, state: os };
|
|
700
772
|
}());
|
|
701
773
|
|
|
702
774
|
const api = Object.assign({ page: page }, model);
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
14
14
|
<link rel="stylesheet" href="/builders/street.css?v=2026-10-09-living3">
|
|
15
15
|
<link rel="stylesheet" href="/builders/street-card.css?v=2026-10-04-record1">
|
|
16
|
-
<link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-
|
|
16
|
+
<link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-17-tidy">
|
|
17
17
|
</head>
|
|
18
18
|
<!-- THE STREET (task 1004525, goal 1000125, R03). The street view of
|
|
19
19
|
docs/design/mocks/governor-city/Street.dc.html (renderVals' street branch,
|
|
@@ -234,7 +234,7 @@
|
|
|
234
234
|
<script src="/builders/palette.js" defer></script>
|
|
235
235
|
<script src="/builders/gov-living.js?v=2026-10-09-positions1"></script>
|
|
236
236
|
<script src="/builders/gov-feeds.js?v=2026-10-09-living3"></script>
|
|
237
|
-
<script src="/builders/city-draw.js?v=2026-10-
|
|
237
|
+
<script src="/builders/city-draw.js?v=2026-10-17-tidy"></script>
|
|
238
238
|
<script src="/builders/street-draw.js?v=2026-10-09-living3"></script>
|
|
239
239
|
<script src="/builders/street-card.js?v=2026-10-04-record1"></script>
|
|
240
240
|
<script src="/builders/street-inside.js?v=2026-10-09-living3"></script>
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
<script src="/builders/street-desk.js?v=2026-10-04-record1"></script>
|
|
243
243
|
<script src="/builders/street-amend.js?v=2026-10-02-amend1"></script>
|
|
244
244
|
<script src="/builders/street-deck.js?v=2026-10-13-glass1"></script>
|
|
245
|
-
<script src="/builders/street-office.js?v=2026-10-
|
|
246
|
-
<script src="/builders/street.js?v=2026-10-
|
|
245
|
+
<script src="/builders/street-office.js?v=2026-10-17-tidy"></script>
|
|
246
|
+
<script src="/builders/street.js?v=2026-10-17-tidy"></script>
|
|
247
247
|
</body>
|
|
248
248
|
</html>
|
|
@@ -516,7 +516,10 @@
|
|
|
516
516
|
// the chosen floor: two blocks that take turns, so the block re-enters on every pick
|
|
517
517
|
var on = $(bv.flipA ? 'insideOpenA' : 'insideOpenB'), off = $(bv.flipA ? 'insideOpenB' : 'insideOpenA');
|
|
518
518
|
if (off) { off.hidden = true; clear(off); }
|
|
519
|
-
if (on) {
|
|
519
|
+
if (on) {
|
|
520
|
+
on.hidden = false;
|
|
521
|
+
if (!(OP && OP.inside(bv, on, $('insideGovt')))) { fillDesk(on, bv); if (OP && OP.settle) OP.settle($('insideGovt'), on); }
|
|
522
|
+
}
|
|
520
523
|
setText('insideOnFile', bv.onFile);
|
|
521
524
|
var drawers = $('insideDrawers'); clear(drawers);
|
|
522
525
|
bv.drawers.forEach(function (dw) {
|