@bongos/core 1.21.35 → 1.21.37
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 +100 -80
- package/clients/bongos-client/index.d.ts +3 -3
- package/docs/api/openapi.json +25 -35
- package/docs/api-reference.md +1 -1
- package/docs/copy-inventory.md +8 -8
- package/docs/copy-registry.json +8 -8
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +569 -523
- package/modules/autonomy/matter-acts.js +41 -0
- package/modules/autonomy/routes/autonomy.js +2 -0
- package/modules/government/board.js +4 -0
- package/modules/government/db.js +14 -0
- package/modules/government/decision-board.js +90 -11
- package/modules/government/docket-vocab.js +5 -0
- package/modules/government/docket.js +11 -0
- package/modules/government/routes/government.js +2 -1
- package/modules/hall-ui/public/atlas.html +3 -3
- package/modules/hall-ui/public/blockers.html +3 -3
- package/modules/hall-ui/public/board-room.html +3 -3
- package/modules/hall-ui/public/city.html +3 -3
- package/modules/hall-ui/public/collab.html +3 -3
- package/modules/hall-ui/public/commands.html +3 -3
- package/modules/hall-ui/public/copy-desk.html +3 -3
- package/modules/hall-ui/public/craft-mode.js +44 -6
- package/modules/hall-ui/public/deploy.html +3 -3
- package/modules/hall-ui/public/diagrams.html +3 -3
- package/modules/hall-ui/public/drachmae.html +3 -3
- package/modules/hall-ui/public/fleet.html +3 -3
- package/modules/hall-ui/public/gate.html +3 -3
- package/modules/hall-ui/public/goal-map.html +3 -3
- package/modules/hall-ui/public/goals.html +3 -3
- package/modules/hall-ui/public/government.html +3 -3
- package/modules/hall-ui/public/idea.html +3 -3
- package/modules/hall-ui/public/ideas.html +3 -3
- package/modules/hall-ui/public/index.html +3 -3
- package/modules/hall-ui/public/modules.html +3 -3
- package/modules/hall-ui/public/primer.html +3 -3
- package/modules/hall-ui/public/profile.html +3 -3
- package/modules/hall-ui/public/project-settings.html +3 -3
- package/modules/hall-ui/public/ranks.html +3 -3
- package/modules/hall-ui/public/roadmap.html +3 -3
- package/modules/hall-ui/public/roster.html +3 -3
- package/modules/hall-ui/public/sessions.html +3 -3
- package/modules/hall-ui/public/settings.html +3 -3
- package/modules/hall-ui/public/street-card.js +62 -2
- package/modules/hall-ui/public/street-desk.js +101 -8
- package/modules/hall-ui/public/street-inside.js +6 -3
- package/modules/hall-ui/public/street.css +15 -0
- package/modules/hall-ui/public/street.html +3 -3
- package/modules/hall-ui/public/street.js +73 -4
- package/modules/hall-ui/public/street.states.json +124 -0
- package/modules/hall-ui/public/studio.html +3 -3
- package/modules/hall-ui/public/style.css +121 -2
- package/modules/hall-ui/public/task.html +3 -3
- package/modules/hall-ui/public/thinking.html +3 -3
- package/modules/hall-ui/public/tweak-editor.html +3 -3
- package/modules/hall-ui/public/watch.html +3 -3
- package/modules/hall-ui/public/work.html +3 -3
- package/modules/hall-ui/records/craft-mode.md +8 -0
- package/modules/ideas/governor-docket.js +91 -0
- package/modules/lifecycle/governor-docket.js +69 -0
- package/modules/lifecycle/matter-acts.js +63 -0
- package/modules/lifecycle/routes/done-when.js +26 -12
- package/modules/lifecycle/routes/gate-approvals.js +74 -59
- package/modules/lifecycle/routes/lifecycle.js +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +16 -0
- package/src/bongos/route-rank-check.js +2 -0
- package/src/bongos/routes/builders.js +56 -1
- package/src/module-api.js +1 -1
- package/tests/craft_mode_looks.mjs +170 -0
- package/tests/craft_mode_nav.mjs +7 -5
- package/tests/governor_matter_acts.mjs +241 -0
- package/tests/hall_street_desk.mjs +244 -4
- package/tests/hall_street_inside.mjs +3 -3
- package/tests/hall_tweak_editor.mjs +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/autonomy/matter-acts.js — what a PASSED Automation matter carries out
|
|
4
|
+
// (task 1004561, goal 1000125). When a project's decision table puts Automation
|
|
5
|
+
// to a sitting, the sitting's passed matter acts here, after the close commits:
|
|
6
|
+
// the switch on or off, or one goal allowed. Each act goes through the SAME db
|
|
7
|
+
// function its direct route calls (POST /autonomy/fence, POST
|
|
8
|
+
// /autonomy/fence/goals), so a matter can do nothing the route could not.
|
|
9
|
+
// The government module only checks the act's shape and calls in through the
|
|
10
|
+
// 'governor.matterAct' contribution point (a module never requires a sibling).
|
|
11
|
+
|
|
12
|
+
const api = require('../../src/module-api');
|
|
13
|
+
const db = require('./db');
|
|
14
|
+
|
|
15
|
+
const log = api.logger('autonomy');
|
|
16
|
+
const POINT = 'governor.matterAct';
|
|
17
|
+
|
|
18
|
+
const why = (ctx) => `a passed Automation matter (matter ${ctx && ctx.matterId})`;
|
|
19
|
+
|
|
20
|
+
async function switchFence(enabled, ctx) {
|
|
21
|
+
const row = await db.setFenceEnabled({ enabled, reason: why(ctx), builderId: ctx.decidedBy });
|
|
22
|
+
if (!row) return { done: false, reason: 'fence_uninitialised' };
|
|
23
|
+
log.info(`[gds] autonomy fence ${enabled ? 'ENABLED' : 'DISABLED'} by ${why(ctx)}, proposed by builder ${ctx.decidedBy}`);
|
|
24
|
+
return { done: true };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ACTS = Object.freeze({
|
|
28
|
+
switch_on: (_action, ctx) => switchFence(true, ctx),
|
|
29
|
+
switch_off: (_action, ctx) => switchFence(false, ctx),
|
|
30
|
+
allow_goal: async (action, ctx) => {
|
|
31
|
+
await db.allowGoal({ goalId: action.goal_id, note: why(ctx), builderId: ctx.decidedBy });
|
|
32
|
+
log.info(`[gds] autonomy fence: goal ${action.goal_id} ALLOWED by ${why(ctx)}`);
|
|
33
|
+
return { done: true };
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function contributeAutomationMatterActs() {
|
|
38
|
+
for (const [type, act] of Object.entries(ACTS)) api.contribute(POINT, { area: 'automation', type, act });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { ACTS, contributeAutomationMatterActs };
|
|
@@ -108,6 +108,8 @@ const governorDocket = require('../governor-docket');
|
|
|
108
108
|
|
|
109
109
|
module.exports = function buildAutonomyRouter() {
|
|
110
110
|
governorDocket.contributeAutonomyToDocket();
|
|
111
|
+
// what a passed Automation matter carries out (task 1004561)
|
|
112
|
+
require('../matter-acts').contributeAutomationMatterActs();
|
|
111
113
|
const router = express.Router();
|
|
112
114
|
|
|
113
115
|
// The one Discord line for a runner idle on a setting (task 1004539). The port
|
|
@@ -1370,6 +1370,9 @@ async function boardPassForIdea(ideaId) {
|
|
|
1370
1370
|
});
|
|
1371
1371
|
}
|
|
1372
1372
|
|
|
1373
|
+
// boardPassForIdea for a list, in one read (task 1004560).
|
|
1374
|
+
const boardPassedIdeaIds = async (ids) => new Set(await db.listPassedSubjectIds('full_idea', ids));
|
|
1375
|
+
|
|
1373
1376
|
// ── the summons: what is waiting on THIS member's vote (task 1003271) ───────
|
|
1374
1377
|
//
|
|
1375
1378
|
// THE PROBLEM THIS EXISTS TO FIX. Nine items sat open for two days with zero
|
|
@@ -1485,6 +1488,7 @@ module.exports = {
|
|
|
1485
1488
|
maybeCloseAfterVote,
|
|
1486
1489
|
latestRejectionForIdea,
|
|
1487
1490
|
boardPassForIdea,
|
|
1491
|
+
boardPassedIdeaIds,
|
|
1488
1492
|
validateProposedConstitution,
|
|
1489
1493
|
proposeAmendment,
|
|
1490
1494
|
boardRoomView,
|
package/modules/government/db.js
CHANGED
|
@@ -712,6 +712,19 @@ async function getLatestPassedItem(subjectType, subjectId) {
|
|
|
712
712
|
return rows[0] || null;
|
|
713
713
|
}
|
|
714
714
|
|
|
715
|
+
// Which of these subjects the board passed, in ONE read (task 1004560: the
|
|
716
|
+
// Ideas-to-ratify desk asks for every open Full Idea at once).
|
|
717
|
+
async function listPassedSubjectIds(subjectType, subjectIds) {
|
|
718
|
+
const ids = (subjectIds || []).map(Number).filter((n) => Number.isInteger(n) && n > 0);
|
|
719
|
+
if (!ids.length) return [];
|
|
720
|
+
const { rows } = await pool.query(
|
|
721
|
+
`SELECT DISTINCT subject_id FROM government_board_items
|
|
722
|
+
WHERE subject_type = $1 AND subject_id = ANY($2::bigint[]) AND outcome = 'passed'`,
|
|
723
|
+
[subjectType, ids],
|
|
724
|
+
);
|
|
725
|
+
return rows.map((r) => String(r.subject_id));
|
|
726
|
+
}
|
|
727
|
+
|
|
715
728
|
// The expiry sweep's read (R11): every OPEN item whose deadline has passed, on
|
|
716
729
|
// the database clock, oldest deadline first. Served by the partial index
|
|
717
730
|
// government_board_items_open_by_deadline (government_003 built it for exactly
|
|
@@ -872,6 +885,7 @@ module.exports = {
|
|
|
872
885
|
getExpiredOpenBoardItems,
|
|
873
886
|
getLatestReturnedItemWithObjection,
|
|
874
887
|
getLatestPassedItem,
|
|
888
|
+
listPassedSubjectIds,
|
|
875
889
|
createAmendment,
|
|
876
890
|
getAmendment,
|
|
877
891
|
listDecisionRules,
|
|
@@ -50,7 +50,9 @@ async function applyPassedDecisionRules(client, closedRow, amendment) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// A passed matter: act where the building can act by itself, and build the
|
|
53
|
-
// announcement. Never throws — the decision already stands.
|
|
53
|
+
// announcement. Never throws — the decision already stands. Runs after the
|
|
54
|
+
// close commits (People since task 1004529; Automation, Release, Review and
|
|
55
|
+
// Treasury since task 1004561, through their owning modules' acts).
|
|
54
56
|
async function actOnPassedMatter(closed) {
|
|
55
57
|
const area = decisionRules.areaForSubjectType(closed.subject_type);
|
|
56
58
|
let matter = null;
|
|
@@ -71,11 +73,30 @@ async function actOnPassedMatter(closed) {
|
|
|
71
73
|
await db.unassignRank({ builderId: action.builder_id, rankKey: action.rank_key });
|
|
72
74
|
result = { ...result, action: action.type, done: true, builder_id: String(action.builder_id), rank_key: action.rank_key };
|
|
73
75
|
}
|
|
76
|
+
} else if (area !== 'people' && action && MATTER_ACTIONS[area]) {
|
|
77
|
+
// The owning module carries it out, through its direct route's own
|
|
78
|
+
// function (task 1004561). Re-validated first: the stored row was checked
|
|
79
|
+
// when opened and is immutable since, so a refusal means it was altered.
|
|
80
|
+
const v = validateMatterAction(area, action);
|
|
81
|
+
const doer = v.ok ? matterActFor(area, v.action.type) : null;
|
|
82
|
+
if (!v.ok) {
|
|
83
|
+
result = { ...result, action: action.type || null, done: false, reason: 'bad_action' };
|
|
84
|
+
} else if (!doer) {
|
|
85
|
+
result = { ...result, action: v.action.type, done: false, reason: 'not_wired' };
|
|
86
|
+
} else {
|
|
87
|
+
const out = (await doer.act(v.action, { decidedBy: matter.proposed_by, matterId: String(matter.id) })) || {};
|
|
88
|
+
result = { ...result, action: v.action.type, done: out.done === true, ...(out.reason ? { reason: String(out.reason) } : {}) };
|
|
89
|
+
}
|
|
74
90
|
}
|
|
75
91
|
} catch (err) {
|
|
76
92
|
log.error(`[government] matter ${closed.subject_id} passed at ${area} but its act did NOT run (re-drivable from the passed item): ${err && err.message}`);
|
|
77
93
|
result = { ...result, done: false, reason: 'act_failed' };
|
|
78
94
|
}
|
|
95
|
+
// A refusal (already decided elsewhere, the module off) leaves the decision
|
|
96
|
+
// standing; it is said loudly here and carried as `acted: false`.
|
|
97
|
+
if (result.action && !result.done && result.reason !== 'act_failed') {
|
|
98
|
+
log.warn(`[government] matter ${closed.subject_id} passed at ${area} but its act was refused: ${result.reason || 'not done'}`);
|
|
99
|
+
}
|
|
79
100
|
return {
|
|
80
101
|
result,
|
|
81
102
|
payload: {
|
|
@@ -153,22 +174,74 @@ async function decisionRulesView() {
|
|
|
153
174
|
// the whole point — People decided by a council's consent sits as exactly that.
|
|
154
175
|
const MATTER_TITLE_MAX = 200;
|
|
155
176
|
const MATTER_DETAIL_MAX = 5000;
|
|
156
|
-
|
|
177
|
+
// What a passed matter may carry out, per building (task 1004561). People acts
|
|
178
|
+
// here, in this module; every other act is carried out by the module that owns
|
|
179
|
+
// the building's direct route, through the same function that route calls,
|
|
180
|
+
// contributed to 'governor.matterAct' as { area, type, act }. This table is only
|
|
181
|
+
// the SHAPE of each act (field → kind), checked when the matter is opened and
|
|
182
|
+
// again before it acts. 'id' is a positive integer, 'rank' a custom rank key,
|
|
183
|
+
// 'usd' a non-negative amount or null (no limit).
|
|
184
|
+
const MATTER_ACTIONS = Object.freeze({
|
|
185
|
+
people: Object.freeze({
|
|
186
|
+
assign_rank: Object.freeze({ builder_id: 'id', rank_key: 'rank' }),
|
|
187
|
+
unassign_rank: Object.freeze({ builder_id: 'id', rank_key: 'rank' }),
|
|
188
|
+
}),
|
|
189
|
+
automation: Object.freeze({
|
|
190
|
+
switch_on: Object.freeze({}),
|
|
191
|
+
switch_off: Object.freeze({}),
|
|
192
|
+
allow_goal: Object.freeze({ goal_id: 'id' }),
|
|
193
|
+
}),
|
|
194
|
+
release: Object.freeze({
|
|
195
|
+
approve_override: Object.freeze({ request_id: 'id' }),
|
|
196
|
+
approve_gate: Object.freeze({ pr: 'id' }),
|
|
197
|
+
}),
|
|
198
|
+
review: Object.freeze({
|
|
199
|
+
satisfy_criterion: Object.freeze({ criterion_id: 'id' }),
|
|
200
|
+
}),
|
|
201
|
+
treasury: Object.freeze({
|
|
202
|
+
set_budget: Object.freeze({ builder_id: 'id', monthly_usd: 'usd' }),
|
|
203
|
+
}),
|
|
204
|
+
});
|
|
205
|
+
const MATTER_ACT_POINT = 'governor.matterAct';
|
|
206
|
+
const MAX_BUDGET_USD = 100000;
|
|
207
|
+
|
|
208
|
+
function fieldValue(kind, raw) {
|
|
209
|
+
if (kind === 'id') {
|
|
210
|
+
const n = Number(raw);
|
|
211
|
+
return Number.isSafeInteger(n) && n > 0 && raw !== null && raw !== '' && typeof raw !== 'boolean' ? { ok: true, v: n } : { ok: false };
|
|
212
|
+
}
|
|
213
|
+
if (kind === 'rank') return typeof raw === 'string' && /^[a-z][a-z0-9-]*$/.test(raw) && raw.length <= 64 ? { ok: true, v: raw } : { ok: false };
|
|
214
|
+
if (kind === 'usd') {
|
|
215
|
+
if (raw === null) return { ok: true, v: null };
|
|
216
|
+
return typeof raw === 'number' && Number.isFinite(raw) && raw >= 0 && raw <= MAX_BUDGET_USD ? { ok: true, v: raw } : { ok: false };
|
|
217
|
+
}
|
|
218
|
+
return { ok: false };
|
|
219
|
+
}
|
|
157
220
|
|
|
158
221
|
function validateMatterAction(area, action) {
|
|
159
222
|
if (action == null) return { ok: true, action: null };
|
|
160
|
-
|
|
223
|
+
const acts = MATTER_ACTIONS[area];
|
|
224
|
+
if (!acts) return { ok: false, reason: 'action_not_supported_here', expected: `a matter acts by itself only at ${Object.keys(MATTER_ACTIONS).join(', ')}` };
|
|
161
225
|
if (typeof action !== 'object' || Array.isArray(action)) return { ok: false, reason: 'bad_action' };
|
|
226
|
+
const shape = acts[action.type];
|
|
227
|
+
if (!shape) return { ok: false, reason: 'bad_action', expected: Object.keys(acts) };
|
|
162
228
|
for (const k of Object.keys(action)) {
|
|
163
|
-
if (
|
|
229
|
+
if (k !== 'type' && !Object.prototype.hasOwnProperty.call(shape, k)) return { ok: false, reason: 'unknown_field', field: `action.${k}` };
|
|
164
230
|
}
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return { ok: false, reason: 'bad_action', field:
|
|
231
|
+
const out = { type: action.type };
|
|
232
|
+
for (const [k, kind] of Object.entries(shape)) {
|
|
233
|
+
if (!Object.prototype.hasOwnProperty.call(action, k)) return { ok: false, reason: 'bad_action', field: `action.${k}` };
|
|
234
|
+
const f = fieldValue(kind, action[k]);
|
|
235
|
+
if (!f.ok) return { ok: false, reason: 'bad_action', field: `action.${k}` };
|
|
236
|
+
out[k] = f.v;
|
|
170
237
|
}
|
|
171
|
-
return { ok: true, action:
|
|
238
|
+
return { ok: true, action: out };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// The owning module's act for (area, type), or null when that module is off.
|
|
242
|
+
function matterActFor(area, type) {
|
|
243
|
+
return api.contributions(MATTER_ACT_POINT)
|
|
244
|
+
.find((c) => c && c.area === area && c.type === type && typeof c.act === 'function') || null;
|
|
172
245
|
}
|
|
173
246
|
|
|
174
247
|
async function openMatter({ area, title, detailMd = null, action = null, proposedBy } = {}) {
|
|
@@ -190,7 +263,10 @@ async function openMatter({ area, title, detailMd = null, action = null, propose
|
|
|
190
263
|
};
|
|
191
264
|
}
|
|
192
265
|
if (!resolved.sitting) return { ok: false, reason: 'no_one_holds_this', area, what_this_means: `No one holds ${resolved.name} yet, so nothing there can be decided.` };
|
|
193
|
-
if (a.action) {
|
|
266
|
+
if (a.action && area !== 'people' && !matterActFor(area, a.action.type)) {
|
|
267
|
+
return { ok: false, reason: 'action_not_wired', area, what_this_means: `Nothing at ${resolved.name} can carry that out right now, so it would pass and do nothing. Turn on the module that owns ${resolved.name}, or file the matter without an act.` };
|
|
268
|
+
}
|
|
269
|
+
if (a.action && area === 'people') {
|
|
194
270
|
const rank = await db.getRankByKey(a.action.rank_key);
|
|
195
271
|
if (!rank) return { ok: false, reason: 'unknown_rank', rank_key: a.action.rank_key };
|
|
196
272
|
if (rank.is_system) return { ok: false, reason: 'seeded_rank', rank_key: a.action.rank_key, expected: 'a custom rank — a seeded rank follows the builder\'s rank' };
|
|
@@ -295,6 +371,9 @@ module.exports = {
|
|
|
295
371
|
proposeDecisionRules,
|
|
296
372
|
decisionRulesView,
|
|
297
373
|
validateMatterAction,
|
|
374
|
+
matterActFor,
|
|
375
|
+
MATTER_ACTIONS,
|
|
376
|
+
MATTER_ACT_POINT,
|
|
298
377
|
openMatter,
|
|
299
378
|
checkActAlone,
|
|
300
379
|
};
|
|
@@ -101,6 +101,11 @@ const DESKS = Object.freeze([
|
|
|
101
101
|
['automation.switch', 'automation'], ['automation.goals', 'automation'], ['automation.runners', 'automation'],
|
|
102
102
|
['people.invite', 'people'], ['release.core', 'release'],
|
|
103
103
|
['rules.settings', 'rules'], ['rules.modules', 'rules'], ['treasury.backup', 'treasury'],
|
|
104
|
+
// the Planning desks (task 1004560): the goals in their worked order, and the
|
|
105
|
+
// ideas a ratifier may take from here
|
|
106
|
+
['planning.order', 'planning'], ['planning.ratify', 'planning'],
|
|
107
|
+
// task 1004561: a builder's monthly spending limit, and a sign-off undone
|
|
108
|
+
['treasury.budgets', 'treasury'], ['review.signed', 'review'],
|
|
104
109
|
].map(([key, building]) => Object.freeze({ key, building })));
|
|
105
110
|
|
|
106
111
|
module.exports = {
|
|
@@ -250,8 +250,19 @@ function normalizeDeskRow(raw, { desk, building }) {
|
|
|
250
250
|
opened_at: iso(raw.opened_at),
|
|
251
251
|
ref: raw.ref && typeof raw.ref === 'object' ? { type: String(raw.ref.type), id: String(raw.ref.id) } : null,
|
|
252
252
|
acts,
|
|
253
|
+
...deskExtras(raw),
|
|
253
254
|
};
|
|
254
255
|
}
|
|
256
|
+
// Two optional shapes a desk row may carry (task 1004560): `pips`, promises kept
|
|
257
|
+
// of total drawn as squares (at most 12), and `set`, the ordered set the row sits
|
|
258
|
+
// in (a version id) so the desk can reorder its rows and save the order whole.
|
|
259
|
+
function deskExtras(raw) {
|
|
260
|
+
const out = {};
|
|
261
|
+
const p = raw.pips;
|
|
262
|
+
if (p && Number.isInteger(p.of) && p.of > 0 && p.of <= 12 && Number.isInteger(p.on) && p.on >= 0 && p.on <= p.of) out.pips = { on: p.on, of: p.of };
|
|
263
|
+
if (typeof raw.set === 'string' && /^[A-Za-z0-9._-]{1,64}$/.test(raw.set)) out.set = raw.set;
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
255
266
|
|
|
256
267
|
// One floor as a contributor handed it → { count, line, lines, hot }. Pure.
|
|
257
268
|
// Anything that is not a plain object is not a floor (→ null, read as
|
|
@@ -110,6 +110,7 @@ const governmentPort = {
|
|
|
110
110
|
// is no counterpart that records an outcome, and there must never be one: an
|
|
111
111
|
// outcome is written by a close, never by the module reading it.
|
|
112
112
|
boardPassForIdea: board.boardPassForIdea,
|
|
113
|
+
boardPassedIdeaIds: board.boardPassedIdeaIds,
|
|
113
114
|
// task 1004429 (BV2.PS04): a finished genesis stage is put to the board — the
|
|
114
115
|
// genesis home (task 1004423) calls this when a stage's tasks are done. The
|
|
115
116
|
// finished-tasks gate lives inside board.js; the item is an ordinary one under
|
|
@@ -789,7 +790,7 @@ module.exports = function buildGovernmentRouter() {
|
|
|
789
790
|
proposedBy: req.builder.id,
|
|
790
791
|
});
|
|
791
792
|
if (!out.ok) {
|
|
792
|
-
if (out.reason === 'decided_by_gate' || out.reason === 'no_one_holds_this') {
|
|
793
|
+
if (out.reason === 'decided_by_gate' || out.reason === 'no_one_holds_this' || out.reason === 'action_not_wired') {
|
|
793
794
|
return res.fail(out.reason, 409, { area: out.area, ...(out.gate ? { gate: out.gate } : {}), what_this_means: out.what_this_means });
|
|
794
795
|
}
|
|
795
796
|
return res.fail('invalid_matter', 400, {
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
|
|
92
92
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
93
93
|
|
|
94
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
95
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
96
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
94
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
95
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
96
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
97
97
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
98
98
|
<script src="/builders/palette.js" defer></script>
|
|
99
99
|
<script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
54
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
55
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
53
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
54
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
55
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
56
56
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
57
57
|
<script src="/builders/palette.js" defer></script>
|
|
58
58
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
|
|
61
61
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
62
62
|
|
|
63
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
64
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
65
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
63
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
64
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
65
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
66
66
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
67
67
|
<script src="/builders/palette.js" defer></script>
|
|
68
68
|
<script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
|
|
@@ -83,9 +83,9 @@
|
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
85
85
|
|
|
86
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
87
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
88
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
86
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
87
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
88
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
89
89
|
<script src="/builders/palette.js" defer></script>
|
|
90
90
|
<script src="/builders/city-draw.js?v=2026-10-02-city1"></script>
|
|
91
91
|
<script src="/builders/city.js?v=2026-10-02-city1"></script>
|
|
@@ -148,9 +148,9 @@
|
|
|
148
148
|
|
|
149
149
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
150
150
|
|
|
151
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
152
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
153
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
151
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
152
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
153
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
154
154
|
<script src="/builders/palette.js" defer></script>
|
|
155
155
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
156
156
|
<script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
|
|
55
55
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
56
56
|
|
|
57
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
58
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
59
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
57
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
58
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
59
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
60
60
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
61
61
|
<script src="/builders/palette.js" defer></script>
|
|
62
62
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -177,9 +177,9 @@
|
|
|
177
177
|
|
|
178
178
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
179
179
|
|
|
180
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
181
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
182
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
180
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
181
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
182
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
183
183
|
<script src="/builders/palette.js" defer></script>
|
|
184
184
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
185
185
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
//
|
|
9
9
|
// It is a MODE the existing hall wears, never a second hall (the founding-mode
|
|
10
10
|
// precedent, docs/design/project-startup-direction.md). The nav follows the mode
|
|
11
|
-
// (task 1004493, MODE_TABS below)
|
|
12
|
-
// own tasks (
|
|
11
|
+
// (task 1004493, MODE_TABS below), and so does the look (task 1004495, LOOKS
|
|
12
|
+
// below); the mode tools are their own tasks (1004496 onward). Legacy mode is the
|
|
13
|
+
// hall exactly as it was.
|
|
13
14
|
//
|
|
14
15
|
// NO MODE is the default and the fallback: a reader who closes the picker, a
|
|
15
16
|
// browser whose storage throws, a page that opts out — none of them get the
|
|
@@ -44,15 +45,15 @@
|
|
|
44
45
|
|
|
45
46
|
// ---------- each mode's tabs (task 1004493) ----------
|
|
46
47
|
// A mode shows only the nav items its craft needs, by NAV id (shell.js). ALWAYS
|
|
47
|
-
// is the owner's list of tabs no mode may hide (2026-10-01: Builders first
|
|
48
|
-
// every mode's list is ALWAYS plus its own. A mode with no entry here — Legacy,
|
|
48
|
+
// is the owner's list of tabs no mode may hide (2026-10-01: Builders first;
|
|
49
|
+
// 2026-10-03: Collab and Profile too), and every mode's list is ALWAYS plus its own. A mode with no entry here — Legacy,
|
|
49
50
|
// or no mode at all — shows every tab, exactly as the hall did before.
|
|
50
51
|
// Government lives in Governing alone, and Governing is the shortest list.
|
|
51
52
|
// Hiding is cosmetic: a hidden tab's page still opens by its URL (ADR 0016).
|
|
52
|
-
var ALWAYS_TABS = ['roster'];
|
|
53
|
+
var ALWAYS_TABS = ['roster', 'collab', 'profile'];
|
|
53
54
|
var MODE_TABS = {
|
|
54
55
|
building: ['home', 'roadmap', 'goals', 'goal-map', 'work', 'blockers', 'thinking', 'studio',
|
|
55
|
-
'standing',
|
|
56
|
+
'standing',
|
|
56
57
|
'primer', 'ranks', 'economy', 'commands', 'diagrams', 'atlas', 'modules', 'apidocs'],
|
|
57
58
|
artistry: ['studio', 'goals', 'thinking'],
|
|
58
59
|
ideating: ['goal-map', 'thinking', 'goals'],
|
|
@@ -91,6 +92,40 @@
|
|
|
91
92
|
else head.removeAttribute('data-craft-off');
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
// ---------- each mode's look (task 1004495) ----------
|
|
96
|
+
// Artistry, Ideating and Governing each wear a look: the colour tokens in
|
|
97
|
+
// style.css (keyed on html[data-craft]), plus two things only this script can
|
|
98
|
+
// draw — the mode's words beside the page title, and its door's scene framed
|
|
99
|
+
// under the brand (artHtml, the picker's own art, so the two never drift).
|
|
100
|
+
// Building, Legacy and no mode have no entry here and wear nothing.
|
|
101
|
+
var LOOKS = { artistry: 'the studio · night', ideating: 'the sky', governing: 'the city' };
|
|
102
|
+
function remove(el) { if (el && el.parentNode) el.parentNode.removeChild(el); }
|
|
103
|
+
function applyLook(mode) {
|
|
104
|
+
var look = Object.prototype.hasOwnProperty.call(LOOKS, mode) ? LOOKS[mode] : null;
|
|
105
|
+
var plate = document.getElementById('craft-plate');
|
|
106
|
+
if (plate && plate.getAttribute('data-plate') !== (look ? mode : null)) { remove(plate); plate = null; }
|
|
107
|
+
var nav = document.getElementById('app-sidebar');
|
|
108
|
+
var brand = look && nav && nav.querySelector ? nav.querySelector('.app-sidebar__brand') : null;
|
|
109
|
+
if (!plate && brand && brand.parentNode) {
|
|
110
|
+
plate = document.createElement('div');
|
|
111
|
+
plate.id = 'craft-plate';
|
|
112
|
+
plate.className = 'craft-plate';
|
|
113
|
+
plate.setAttribute('data-plate', mode);
|
|
114
|
+
plate.setAttribute('aria-hidden', 'true');
|
|
115
|
+
plate.innerHTML = artHtml(mode);
|
|
116
|
+
brand.parentNode.insertBefore(plate, brand.nextSibling);
|
|
117
|
+
}
|
|
118
|
+
var mark = document.getElementById('craft-eyebrow');
|
|
119
|
+
if (!look) { remove(mark); return; }
|
|
120
|
+
var title = document.getElementById('topbar-title');
|
|
121
|
+
if (!mark && title && title.parentNode) {
|
|
122
|
+
mark = document.createElement('span');
|
|
123
|
+
mark.id = 'craft-eyebrow';
|
|
124
|
+
mark.className = 'craft-eyebrow';
|
|
125
|
+
title.parentNode.insertBefore(mark, title.nextSibling);
|
|
126
|
+
}
|
|
127
|
+
if (mark) mark.textContent = look;
|
|
128
|
+
}
|
|
94
129
|
// sessionStorage: this session's mode, or that the picker was already shown.
|
|
95
130
|
var MODE_KEY = 'hall-craft';
|
|
96
131
|
var ASKED_KEY = 'hall-craft-asked';
|
|
@@ -138,6 +173,7 @@
|
|
|
138
173
|
var sw = document.getElementById('craft-switch');
|
|
139
174
|
if (sw) sw.classList.toggle('craft-switch--unset', !mode);
|
|
140
175
|
applyNav();
|
|
176
|
+
applyLook(mode);
|
|
141
177
|
try { document.dispatchEvent(new CustomEvent('otb:craft', { detail: { mode: mode } })); } catch (_) { /* old engine */ }
|
|
142
178
|
}
|
|
143
179
|
|
|
@@ -344,6 +380,8 @@
|
|
|
344
380
|
ALWAYS_TABS: ALWAYS_TABS.slice(),
|
|
345
381
|
tabsFor: tabsFor,
|
|
346
382
|
applyNav: applyNav,
|
|
383
|
+
LOOKS: LOOKS,
|
|
384
|
+
artHtml: artHtml,
|
|
347
385
|
switchHtml: switchHtml,
|
|
348
386
|
mount: mount,
|
|
349
387
|
onAccess: onAccess,
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
|
|
75
75
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
76
76
|
|
|
77
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
78
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
79
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
77
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
78
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
79
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
80
80
|
<script src="/builders/palette.js" defer></script>
|
|
81
81
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
82
82
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -97,9 +97,9 @@
|
|
|
97
97
|
|
|
98
98
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
99
99
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
100
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
101
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
102
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
100
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
101
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
102
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
103
103
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
104
104
|
<script src="/builders/palette.js" defer></script>
|
|
105
105
|
<script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
|
|
@@ -98,9 +98,9 @@
|
|
|
98
98
|
|
|
99
99
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
100
100
|
|
|
101
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
102
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
103
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
101
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
102
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
103
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
104
104
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
105
105
|
<script src="/builders/palette.js" defer></script>
|
|
106
106
|
<script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
|
|
55
55
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
56
56
|
|
|
57
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
58
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
59
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
57
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
58
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
59
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
60
60
|
<script src="/builders/palette.js" defer></script>
|
|
61
61
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
62
62
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -77,9 +77,9 @@
|
|
|
77
77
|
<div id="gate-modal-root"></div>
|
|
78
78
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
79
79
|
|
|
80
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
81
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
82
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
80
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
81
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
82
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
83
83
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
84
84
|
<script src="/builders/palette.js" defer></script>
|
|
85
85
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -137,9 +137,9 @@
|
|
|
137
137
|
</div>
|
|
138
138
|
</div>
|
|
139
139
|
|
|
140
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
141
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
142
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
140
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
141
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
142
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
143
143
|
<script src="/builders/palette.js" defer></script>
|
|
144
144
|
|
|
145
145
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -230,9 +230,9 @@
|
|
|
230
230
|
|
|
231
231
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
232
232
|
|
|
233
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
234
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
235
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
233
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
234
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
235
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
236
236
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
237
237
|
<script src="/builders/palette.js" defer></script>
|
|
238
238
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -134,9 +134,9 @@
|
|
|
134
134
|
|
|
135
135
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
136
136
|
|
|
137
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
138
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
139
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
137
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
138
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
139
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
140
140
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
141
141
|
<script src="/builders/palette.js" defer></script>
|
|
142
142
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
|
|
51
51
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
52
52
|
|
|
53
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
54
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
55
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
53
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
54
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
55
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
56
56
|
<script src="/builders/palette.js" defer></script>
|
|
57
57
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
|
|
58
58
|
<!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
|