@bongos/core 1.21.7 → 1.21.9
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 +98 -38
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +8 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0363-who-decides-what-is-a-table-changed-only-by-amendment.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +182 -3
- package/docs/api-reference.md +4 -2
- package/docs/copy-inventory.md +20 -5
- package/docs/copy-registry.json +163 -16
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +33 -4
- package/docs/page-readings.json +44 -2
- package/modules/discord/board-broadcast.js +2 -0
- package/modules/government/board.js +82 -23
- package/modules/government/db.js +81 -6
- package/modules/government/decision-board.js +300 -0
- package/modules/government/decision-rules.js +312 -0
- package/modules/government/docket.js +49 -9
- package/modules/government/migrations/government_023_decision_rules.sql +190 -0
- package/modules/government/routes/government.js +115 -0
- package/modules/hall-ui/public/city-draw.js +11 -2
- package/modules/hall-ui/public/street-draw.js +585 -0
- package/modules/hall-ui/public/street.css +188 -0
- package/modules/hall-ui/public/street.html +118 -0
- package/modules/hall-ui/public/street.js +364 -0
- package/modules/hall-ui/public/street.states.json +43 -0
- package/modules/hall-ui/records/governor-street.md +14 -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 +6 -0
- package/src/bongos/serve-internal.js +12 -0
- package/src/module-api.js +1 -1
- package/tests/government_abuse_matrix.mjs +3 -0
- package/tests/government_decision_rules.mjs +701 -0
- package/tests/government_routes.mjs +4 -0
- package/tests/government_seed.mjs +1 -0
- package/tests/hall_audit.mjs +9 -1
- package/tests/hall_page_gate_map.mjs +3 -0
- package/tests/hall_street.mjs +521 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// modules/hall-ui/public/street.js — THE STREET, wired (task 1004525, goal
|
|
2
|
+
// 1000125, R03).
|
|
3
|
+
//
|
|
4
|
+
// street-draw.js computes the street as data; this file reads the feeds, hands
|
|
5
|
+
// them to GovStreet.streetModel(), and turns its lists into DOM: SVG paths for
|
|
6
|
+
// the road and the buildings, a span per crate, a name and state line per
|
|
7
|
+
// building, a bracket per group, an <a> door per building and district, a
|
|
8
|
+
// <button> per pin, and an <a> per reading. It owns the light/dark switch, the
|
|
9
|
+
// artboard's fit to the stage, the address (#building= / #district=), and the
|
|
10
|
+
// short matter card a pin opens. No innerHTML anywhere: every node is created,
|
|
11
|
+
// and every docket string lands as text or an attribute.
|
|
12
|
+
//
|
|
13
|
+
// THE FEEDS (all GETs, all gated server-side):
|
|
14
|
+
// /api/bongos/government/docket buildings, pins, lines, readings (task 1004523)
|
|
15
|
+
// /api/bongos/goals the open goals: one district each
|
|
16
|
+
// /api/bongos/goals/rollups each goal's task counts: its buildings
|
|
17
|
+
// /api/bongos/builders/<viewer>/earnings your share this month (after the docket names the viewer)
|
|
18
|
+
// A feed that fails leaves its part out (no pins, no districts, "—" for the
|
|
19
|
+
// share) and the page says the docket could not be read; it never draws sample data.
|
|
20
|
+
//
|
|
21
|
+
// THE SWITCH. The hall's own preference ('hall-theme', else the OS) and the
|
|
22
|
+
// hall's own attribute (html[data-theme]), shared with the city and every
|
|
23
|
+
// hall page. The drawing's faces are drawing data in the mode, so a toggle
|
|
24
|
+
// redraws the street from the last feeds (no refetch).
|
|
25
|
+
//
|
|
26
|
+
// MOTION is CSS only (street.css); nothing here animates. The scale write on
|
|
27
|
+
// resize is layout, not motion.
|
|
28
|
+
|
|
29
|
+
(function () {
|
|
30
|
+
'use strict';
|
|
31
|
+
var S = window.GovStreet;
|
|
32
|
+
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
33
|
+
var THEME_KEY = 'hall-theme';
|
|
34
|
+
var SUN = 'M7 1.5V3M7 11v1.5M1.5 7H3M11 7h1.5M3.1 3.1l1 1M9.9 9.9l1 1M3.1 10.9l1-1M9.9 4.1l1-1M9.5 7A2.5 2.5 0 1 1 4.5 7A2.5 2.5 0 1 1 9.5 7Z';
|
|
35
|
+
var MOON = 'M11.5 8.6A5 5 0 0 1 5.4 2.5A5 5 0 1 0 11.5 8.6Z';
|
|
36
|
+
var FEEDS = {
|
|
37
|
+
docket: '/api/bongos/government/docket',
|
|
38
|
+
goals: '/api/bongos/goals',
|
|
39
|
+
rollups: '/api/bongos/goals/rollups',
|
|
40
|
+
earnings: function (id) { return '/api/bongos/builders/' + encodeURIComponent(id) + '/earnings'; },
|
|
41
|
+
};
|
|
42
|
+
// The artboard's street band: the prototype's 900-high board less its 64 px
|
|
43
|
+
// header and 128 px footer, which this page lays out as real rows.
|
|
44
|
+
var BAND_TOP = 64, BAND_H = 708;
|
|
45
|
+
var CARD_W = 390;
|
|
46
|
+
|
|
47
|
+
var $ = function (id) { return document.getElementById(id); };
|
|
48
|
+
var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
|
49
|
+
var data = { docket: null, goals: [], rollups: [], earnings: null, loaded: false };
|
|
50
|
+
var view = { open: null, opener: null, model: null, scale: 1, mode: null };
|
|
51
|
+
|
|
52
|
+
// ---------- light / dark ----------
|
|
53
|
+
var chosen = null;
|
|
54
|
+
function storedTheme() {
|
|
55
|
+
try { var v = window.localStorage.getItem(THEME_KEY); if (v === 'dark' || v === 'light') return v; } catch (_) { /* blocked */ }
|
|
56
|
+
return chosen;
|
|
57
|
+
}
|
|
58
|
+
function effectiveTheme() { return storedTheme() || (media && media.matches ? 'dark' : 'light'); }
|
|
59
|
+
function paintSwitch(t) {
|
|
60
|
+
var btn = $('streetMode');
|
|
61
|
+
var label = t === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
|
|
62
|
+
if (btn) { btn.setAttribute('aria-label', label); btn.setAttribute('title', label); }
|
|
63
|
+
var icon = $('streetModeIcon');
|
|
64
|
+
if (icon) icon.setAttribute('d', t === 'dark' ? SUN : MOON);
|
|
65
|
+
var word = $('streetModeWord');
|
|
66
|
+
if (word) word.textContent = t === 'dark' ? 'light' : 'dark';
|
|
67
|
+
}
|
|
68
|
+
function applyTheme() {
|
|
69
|
+
var t = effectiveTheme();
|
|
70
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
71
|
+
paintSwitch(t);
|
|
72
|
+
return t;
|
|
73
|
+
}
|
|
74
|
+
function toggleTheme() {
|
|
75
|
+
var next = effectiveTheme() === 'dark' ? 'light' : 'dark';
|
|
76
|
+
chosen = next;
|
|
77
|
+
try { window.localStorage.setItem(THEME_KEY, next); } catch (_) { /* private mode: this page only */ }
|
|
78
|
+
applyTheme();
|
|
79
|
+
draw();
|
|
80
|
+
}
|
|
81
|
+
function currentMode() {
|
|
82
|
+
var t = document.documentElement.getAttribute('data-theme');
|
|
83
|
+
return t === 'light' || t === 'dark' ? t : effectiveTheme();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------- the address ----------
|
|
87
|
+
function here() { return S.parseHash(window.location && window.location.hash); }
|
|
88
|
+
|
|
89
|
+
// ---------- fit the artboard's street band to the stage ----------
|
|
90
|
+
// 1440 x 708 at scale 1 is the prototype exactly. A wider or taller stage
|
|
91
|
+
// grows it, a smaller one shrinks it; a phone keeps it near full size and the
|
|
92
|
+
// street scrolls sideways inside its window, so a pin stays a pin.
|
|
93
|
+
function fit() {
|
|
94
|
+
var win = $('streetWindow'), sizer = $('streetSizer'), board = $('streetBoard');
|
|
95
|
+
if (!win || !sizer || !board) return;
|
|
96
|
+
var W = view.model ? view.model.width : S.ARTBOARD_W;
|
|
97
|
+
var cw = win.clientWidth || window.innerWidth || 1440, ch = win.clientHeight || 708;
|
|
98
|
+
var s = Math.min(cw / W, ch / BAND_H);
|
|
99
|
+
if (cw / W < 0.6) s = Math.min(1, ch / BAND_H);
|
|
100
|
+
s = Math.max(0.4, Math.round(s * 1000) / 1000);
|
|
101
|
+
view.scale = s;
|
|
102
|
+
sizer.style.width = Math.round(W * s) + 'px';
|
|
103
|
+
sizer.style.height = Math.round(BAND_H * s) + 'px';
|
|
104
|
+
board.style.width = W + 'px';
|
|
105
|
+
board.style.setProperty('--street-scale', String(s));
|
|
106
|
+
var svg = $('streetSvg');
|
|
107
|
+
if (svg) { svg.setAttribute('width', String(W)); svg.setAttribute('viewBox', '0 0 ' + W + ' 900'); }
|
|
108
|
+
placeCard();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------- the feeds ----------
|
|
112
|
+
function getJson(url) {
|
|
113
|
+
return fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json' } })
|
|
114
|
+
.then(function (r) { return r && r.ok ? r.json() : null; })
|
|
115
|
+
.catch(function () { return null; });
|
|
116
|
+
}
|
|
117
|
+
function load() {
|
|
118
|
+
return Promise.all([getJson(FEEDS.docket), getJson(FEEDS.goals), getJson(FEEDS.rollups)]).then(function (res) {
|
|
119
|
+
data.docket = res[0] && Array.isArray(res[0].matters) ? res[0] : null;
|
|
120
|
+
data.goals = res[1] && Array.isArray(res[1].goals) ? res[1].goals : [];
|
|
121
|
+
data.rollups = res[2] && Array.isArray(res[2].rollups) ? res[2].rollups : [];
|
|
122
|
+
data.loaded = true;
|
|
123
|
+
draw();
|
|
124
|
+
var viewer = data.docket && data.docket.viewer && data.docket.viewer.id;
|
|
125
|
+
if (viewer == null) return null;
|
|
126
|
+
return getJson(FEEDS.earnings(viewer)).then(function (e) {
|
|
127
|
+
data.earnings = e && Array.isArray(e.days) ? e : null;
|
|
128
|
+
draw();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------- drawing ----------
|
|
134
|
+
function clear(el) { while (el && el.firstChild) el.removeChild(el.firstChild); }
|
|
135
|
+
function pathEl(p) {
|
|
136
|
+
var el = document.createElementNS(SVG_NS, 'path');
|
|
137
|
+
el.setAttribute('d', p.d);
|
|
138
|
+
el.setAttribute('fill', p.f);
|
|
139
|
+
el.setAttribute('stroke', p.s);
|
|
140
|
+
if (p.dash && p.dash !== 'none') el.setAttribute('stroke-dasharray', p.dash);
|
|
141
|
+
el.setAttribute('stroke-width', String(p.w || 1));
|
|
142
|
+
return el;
|
|
143
|
+
}
|
|
144
|
+
function node(tag, cls, text) {
|
|
145
|
+
var el = document.createElement(tag);
|
|
146
|
+
if (cls) el.className = cls;
|
|
147
|
+
if (text != null) el.textContent = String(text);
|
|
148
|
+
return el;
|
|
149
|
+
}
|
|
150
|
+
function at(el, x, y, w, h) {
|
|
151
|
+
el.style.left = x + 'px';
|
|
152
|
+
el.style.top = y + 'px';
|
|
153
|
+
if (w != null) el.style.width = w + 'px';
|
|
154
|
+
if (h != null) el.style.height = h + 'px';
|
|
155
|
+
return el;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function drawPin(p) {
|
|
159
|
+
var el = node('button', 'street-pin street-pin--' + p.state);
|
|
160
|
+
el.setAttribute('type', 'button');
|
|
161
|
+
el.setAttribute('aria-label', p.aria);
|
|
162
|
+
el.setAttribute('aria-expanded', view.open === p.key ? 'true' : 'false');
|
|
163
|
+
el.setAttribute('aria-controls', 'streetCard');
|
|
164
|
+
el.setAttribute('data-matter', p.key);
|
|
165
|
+
el.setAttribute('data-building', p.building);
|
|
166
|
+
at(el, p.x, p.y, 110, p.h);
|
|
167
|
+
var label = node('span', 'street-pin__label', p.label);
|
|
168
|
+
label.setAttribute('title', p.label);
|
|
169
|
+
el.appendChild(label);
|
|
170
|
+
var head = node('span', 'street-pin__head');
|
|
171
|
+
if (p.state === 'needs') head.appendChild(node('span', 'street-pin__ring'));
|
|
172
|
+
if (p.isCount) head.appendChild(node('span', 'street-pin__count', p.count));
|
|
173
|
+
else {
|
|
174
|
+
var svg = document.createElementNS(SVG_NS, 'svg');
|
|
175
|
+
svg.setAttribute('width', '18');
|
|
176
|
+
svg.setAttribute('height', '18');
|
|
177
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
178
|
+
var fill = document.createElementNS(SVG_NS, 'path');
|
|
179
|
+
fill.setAttribute('d', p.gf || '');
|
|
180
|
+
var line = document.createElementNS(SVG_NS, 'path');
|
|
181
|
+
line.setAttribute('d', p.gl || '');
|
|
182
|
+
svg.appendChild(fill);
|
|
183
|
+
svg.appendChild(line);
|
|
184
|
+
head.appendChild(svg);
|
|
185
|
+
}
|
|
186
|
+
el.appendChild(head);
|
|
187
|
+
el.appendChild(node('span', 'street-pin__stem'));
|
|
188
|
+
el.addEventListener('click', function () { openCard(p.key, el); });
|
|
189
|
+
return el;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function drawSign(sg) {
|
|
193
|
+
var a = node('a', 'street-sign' + (sg.hot ? ' street-sign--hot' : '') + (sg.here ? ' street-sign--here' : ''));
|
|
194
|
+
a.setAttribute('href', sg.href);
|
|
195
|
+
a.setAttribute('aria-label', sg.aria);
|
|
196
|
+
a.setAttribute('data-reading', sg.key);
|
|
197
|
+
if (sg.here) a.setAttribute('aria-current', 'location');
|
|
198
|
+
var top = node('span', 'street-sign__top');
|
|
199
|
+
top.appendChild(node('span', 'street-sign__dot'));
|
|
200
|
+
top.appendChild(node('span', 'street-label', sg.label));
|
|
201
|
+
a.appendChild(top);
|
|
202
|
+
a.appendChild(node('span', 'street-sign__figure', sg.figure));
|
|
203
|
+
a.appendChild(node('span', 'street-sign__line', sg.line));
|
|
204
|
+
return a;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function draw() {
|
|
208
|
+
var mode = currentMode();
|
|
209
|
+
view.mode = mode;
|
|
210
|
+
paintSwitch(mode);
|
|
211
|
+
var m = S.streetModel({ docket: data.docket, goals: data.goals, rollups: data.rollups, earnings: data.earnings, mode: mode, now: Date.now(), here: here() });
|
|
212
|
+
view.model = m;
|
|
213
|
+
var flat = $('streetFlat'), blds = $('streetBlds');
|
|
214
|
+
clear(flat); clear(blds);
|
|
215
|
+
m.flat.forEach(function (p) { flat.appendChild(pathEl(p)); });
|
|
216
|
+
m.blds.forEach(function (b) {
|
|
217
|
+
var g = document.createElementNS(SVG_NS, 'g');
|
|
218
|
+
g.setAttribute('data-key', b.key);
|
|
219
|
+
b.polys.forEach(function (p) { g.appendChild(pathEl(p)); });
|
|
220
|
+
blds.appendChild(g);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
var crates = $('streetCrates'); clear(crates);
|
|
224
|
+
m.crates.forEach(function (c) { crates.appendChild(at(node('span', 'street-crate'), c.x, c.y)); });
|
|
225
|
+
|
|
226
|
+
var cols = $('streetCols'); clear(cols);
|
|
227
|
+
m.cols.forEach(function (c) {
|
|
228
|
+
var el = at(node('div', 'street-col'), c.x, 596, c.w);
|
|
229
|
+
var name = node('span', 'street-col__name' + (c.strong ? ' street-col__name--strong' : ''), c.name);
|
|
230
|
+
name.setAttribute('title', c.title);
|
|
231
|
+
el.appendChild(name);
|
|
232
|
+
el.appendChild(node('span', 'street-col__state', c.state));
|
|
233
|
+
cols.appendChild(el);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
var groups = $('streetGroups'); clear(groups);
|
|
237
|
+
m.groups.forEach(function (g) {
|
|
238
|
+
var el = at(node('div', 'street-group'), g.x, 650, g.w);
|
|
239
|
+
el.appendChild(node('span', 'street-group__bracket'));
|
|
240
|
+
el.appendChild(node('span', 'street-label', g.name));
|
|
241
|
+
groups.appendChild(el);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
var doors = $('streetDoors'); clear(doors);
|
|
245
|
+
m.doors.forEach(function (d) {
|
|
246
|
+
var a = at(node('a', 'street-door' + (d.here ? ' street-door--here' : '')), d.x, d.y, d.w, d.h);
|
|
247
|
+
a.setAttribute('href', d.href);
|
|
248
|
+
a.setAttribute('aria-label', d.aria);
|
|
249
|
+
a.setAttribute('data-door', d.key);
|
|
250
|
+
if (d.here) a.setAttribute('aria-current', 'location');
|
|
251
|
+
doors.appendChild(a);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
var pins = $('streetPins'); clear(pins);
|
|
255
|
+
m.pins.forEach(function (p) { pins.appendChild(drawPin(p)); });
|
|
256
|
+
|
|
257
|
+
var signs = $('streetSigns'); clear(signs);
|
|
258
|
+
m.signs.forEach(function (sg) { signs.appendChild(drawSign(sg)); });
|
|
259
|
+
|
|
260
|
+
var hint = $('streetHint');
|
|
261
|
+
if (hint) { hint.textContent = m.hint; hint.hidden = !m.hint || !!view.open; }
|
|
262
|
+
var eve = $('streetEvening');
|
|
263
|
+
if (eve) eve.hidden = !(m.evening && !view.open);
|
|
264
|
+
var eveLine = $('streetEveningLine');
|
|
265
|
+
if (eveLine) eveLine.textContent = m.eveningLine;
|
|
266
|
+
|
|
267
|
+
$('hudNeeds').textContent = m.hud.needs;
|
|
268
|
+
$('hudMoving').textContent = m.hud.moving;
|
|
269
|
+
$('hudShare').textContent = m.hud.share;
|
|
270
|
+
var dot = $('hudDot');
|
|
271
|
+
if (dot) dot.className = 'street-hud__dot' + (m.hud.hot ? ' street-hud__dot--hot' : '');
|
|
272
|
+
var board = $('streetBoard');
|
|
273
|
+
if (board) board.setAttribute('aria-label', data.loaded ? m.label : 'The street, reading the docket');
|
|
274
|
+
var home = $('streetHome');
|
|
275
|
+
if (home) home.setAttribute('href', S.HOME_HREF);
|
|
276
|
+
|
|
277
|
+
fit();
|
|
278
|
+
if (view.open) fillCard();
|
|
279
|
+
return m;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ---------- the short matter card ----------
|
|
283
|
+
function fillCard() {
|
|
284
|
+
var cv = S.cardFor(data.docket, view.open, Date.now());
|
|
285
|
+
if (!cv) { closeCard(false); return null; }
|
|
286
|
+
$('streetCardWhere').textContent = cv.where;
|
|
287
|
+
$('streetCardWhen').textContent = cv.when;
|
|
288
|
+
$('streetCardTitle').textContent = cv.title;
|
|
289
|
+
var meta = $('streetCardMeta');
|
|
290
|
+
var metaText = [cv.proposer, cv.tally].filter(Boolean).join(' · ');
|
|
291
|
+
meta.textContent = metaText;
|
|
292
|
+
meta.hidden = !metaText;
|
|
293
|
+
var lines = $('streetCardLines');
|
|
294
|
+
clear(lines);
|
|
295
|
+
cv.lines.forEach(function (t) { lines.appendChild(node('li', '', t)); });
|
|
296
|
+
lines.hidden = !cv.lines.length;
|
|
297
|
+
$('streetCardResult').textContent = cv.result;
|
|
298
|
+
var open = $('streetCardOpen');
|
|
299
|
+
if (cv.href) open.setAttribute('href', cv.href);
|
|
300
|
+
open.hidden = !cv.href;
|
|
301
|
+
placeCard();
|
|
302
|
+
return cv;
|
|
303
|
+
}
|
|
304
|
+
function placeCard() {
|
|
305
|
+
var card = $('streetCard');
|
|
306
|
+
if (!card || !view.open || !view.model) return;
|
|
307
|
+
var pin = null;
|
|
308
|
+
view.model.pins.forEach(function (p) { if (p.key === view.open) pin = p; });
|
|
309
|
+
var win = $('streetWindow'), sizer = $('streetSizer');
|
|
310
|
+
var stageW = (card.parentNode && card.parentNode.clientWidth) || window.innerWidth || 1440;
|
|
311
|
+
var originX = (sizer && sizer.offsetLeft != null ? sizer.offsetLeft : 0) - (win && win.scrollLeft ? win.scrollLeft : 0);
|
|
312
|
+
var cx = pin ? originX + (pin.x + 55) * view.scale : stageW / 2;
|
|
313
|
+
var x = Math.max(16, Math.min(stageW - CARD_W - 16, Math.round(cx - CARD_W / 2)));
|
|
314
|
+
card.style.setProperty('--street-card-x', x + 'px');
|
|
315
|
+
}
|
|
316
|
+
function openCard(key, opener) {
|
|
317
|
+
view.open = key;
|
|
318
|
+
view.opener = opener || null;
|
|
319
|
+
var card = $('streetCard');
|
|
320
|
+
if (!fillCard()) return;
|
|
321
|
+
card.hidden = false;
|
|
322
|
+
draw();
|
|
323
|
+
var close = $('streetCardClose');
|
|
324
|
+
if (close && close.focus) close.focus();
|
|
325
|
+
}
|
|
326
|
+
function closeCard(restore) {
|
|
327
|
+
var was = view.opener;
|
|
328
|
+
view.open = null;
|
|
329
|
+
view.opener = null;
|
|
330
|
+
var card = $('streetCard');
|
|
331
|
+
if (card) card.hidden = true;
|
|
332
|
+
draw();
|
|
333
|
+
// The pins were redrawn, so focus goes back to the new button for the same matter.
|
|
334
|
+
if (restore !== false && was && was.getAttribute) {
|
|
335
|
+
var key = was.getAttribute('data-matter');
|
|
336
|
+
var pins = $('streetPins').children || [];
|
|
337
|
+
for (var i = 0; i < pins.length; i++) {
|
|
338
|
+
if (pins[i].getAttribute('data-matter') === key) { if (pins[i].focus) pins[i].focus(); break; }
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function onKey(e) {
|
|
343
|
+
if (e.key === 'Escape' && view.open) { e.preventDefault(); closeCard(true); }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function boot() {
|
|
347
|
+
var btn = $('streetMode');
|
|
348
|
+
if (btn) btn.addEventListener('click', toggleTheme);
|
|
349
|
+
var close = $('streetCardClose');
|
|
350
|
+
if (close) close.addEventListener('click', function () { closeCard(true); });
|
|
351
|
+
window.addEventListener('keydown', onKey);
|
|
352
|
+
window.addEventListener('resize', fit);
|
|
353
|
+
window.addEventListener('hashchange', function () { draw(); });
|
|
354
|
+
var win = $('streetWindow');
|
|
355
|
+
if (win) win.addEventListener('scroll', placeCard);
|
|
356
|
+
if (media && media.addEventListener) media.addEventListener('change', function () { if (!storedTheme()) { applyTheme(); draw(); } });
|
|
357
|
+
applyTheme();
|
|
358
|
+
draw();
|
|
359
|
+
return load();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
window.GovStreetPage = { boot: boot, draw: draw, load: load, fit: fit, openCard: openCard, closeCard: closeCard, FEEDS: FEEDS, THEME_KEY: THEME_KEY };
|
|
363
|
+
window.GovStreetPage.ready = boot();
|
|
364
|
+
}());
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "THE STREET (task 1004525, goal 1000125) through modules/ui-design/kit/render.js. The page draws GET /government/docket, GET /goals, GET /goals/rollups and the viewer's GET /builders/:id/earnings; against the kit's own stub none exists, so every state renders the bare street (the nine civic buildings, no districts, no pins) with the readings saying the docket could not be read: a real state the floors must hold in. To look at the populated street, run it against the hall-preview harness, whose government__docket.json fixture carries the reference design's seven sample matters (3 need you, 2 moving, 2 settled) and whose goals fixtures carry the open goals:\n\n node scripts/hall-preview/server.js --port 4625 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/street.states.json --base-in http://localhost:4625 --base-out http://localhost:4625\n\nThe page follows the OS mode until the switch is pressed (then the hall's own 'hall-theme' preference), so the kit's dark/light emulation renders both modes.",
|
|
3
|
+
"page": "/builders/street",
|
|
4
|
+
"surface": "hall-ui",
|
|
5
|
+
"stub": { "prefix": "/builders/" },
|
|
6
|
+
"modeQuery": false,
|
|
7
|
+
"ignoreRequests": ["/api/(gds|bongos)/", "fonts\\.g(oogleapis|static)\\.com"],
|
|
8
|
+
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b403\\b", "\\b404\\b", "Unauthorized", "Forbidden", "Not Found", "load failed", "fonts\\.g(oogleapis|static)\\.com"],
|
|
9
|
+
"states": {
|
|
10
|
+
"street": {
|
|
11
|
+
"auth": true,
|
|
12
|
+
"_": "The street: the header, the buildings in the order work moves with their names, state lines and group brackets, the pins, and the six readings.",
|
|
13
|
+
"actions": [["wait", 900]],
|
|
14
|
+
"expect": { "visible": ["#streetBoard", "#streetSigns", "#streetMode", "#streetHome"], "href": [["#streetHome", "/builders/city"]] }
|
|
15
|
+
},
|
|
16
|
+
"card": {
|
|
17
|
+
"auth": true,
|
|
18
|
+
"_": "A pin pressed: the matter's short card over the street.",
|
|
19
|
+
"actions": [["wait", 900], ["click", ".street-pin"], ["wait", 400]],
|
|
20
|
+
"expect": { "visible": ["#streetCard", "#streetCardClose"] }
|
|
21
|
+
},
|
|
22
|
+
"switched": {
|
|
23
|
+
"auth": true,
|
|
24
|
+
"_": "The light/dark switch pressed once: the other mode, the street redrawn in it.",
|
|
25
|
+
"actions": [["wait", 600], ["click", "#streetMode"], ["wait", 400]],
|
|
26
|
+
"expect": { "visible": ["#streetBoard", "#streetSigns"] }
|
|
27
|
+
},
|
|
28
|
+
"here": {
|
|
29
|
+
"auth": true,
|
|
30
|
+
"_": "Arrived through a door: the address names the Charter, so its door and the readings that open it are marked.",
|
|
31
|
+
"url": "/builders/street#building=charter",
|
|
32
|
+
"actions": [["wait", 900]],
|
|
33
|
+
"expect": { "visible": ["#streetBoard"] }
|
|
34
|
+
},
|
|
35
|
+
"focus": {
|
|
36
|
+
"auth": true,
|
|
37
|
+
"_": "Keyboard: the wordmark back to the city focused, its ring drawn.",
|
|
38
|
+
"actions": [["wait", 600], ["focus", "#streetHome"], ["wait", 100]],
|
|
39
|
+
"expect": { "activeId": "streetHome" }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"reducedMotion": { "auth": true, "actions": [["wait", 600]] }
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# The Governor City's street — /street, the six readings, and the doors to the interiors (task 1004525)
|
|
2
|
+
|
|
3
|
+
**R03 of goal 1000125.** Built 1:1 on the street view of the reference design [`docs/design/mocks/governor-city/Street.dc.html`](../../../docs/design/mocks/governor-city/README.md): one front-on row in the order work moves under five group brackets, a name and one state line under every building, pins with their labels always showing, work queued at Review as crates by its door, the header (wordmark back to the city, needs you, moving, your share this month, the light/dark switch) and the six readings along the bottom. The home city (`/city`, task 1004524) links here from start governing.
|
|
4
|
+
|
|
5
|
+
Files: `street-draw.js` (the prototype's front-on kit and street layout, verbatim, as pure data: `window.GovStreet`, node-require-safe), `street.js` (the page: feeds, nodes, the switch, the address, the short card) and `street.css` (the template's styles on the hall's tokens). It reuses `city-draw.js` for the decision glyphs, the seeded random and `rulesAtWork`, which the city now also uses to light the Rules band.
|
|
6
|
+
|
|
7
|
+
What it settled:
|
|
8
|
+
|
|
9
|
+
- **Standalone, like the city.** No hall sidebar or top bar; it is the second entry in `tests/hall_audit.mjs`'s `STANDALONE_EXEMPT`, with the city's reason. It still loads `style.css` and reads and writes the hall's own `hall-theme`.
|
|
10
|
+
- **The artboard is the street band.** The prototype's 1440 x 900 board less its 64 px header and 128 px footer, which the page lays out as real rows; `street.js` scales the 1440 x 708 band to the window, and on a phone keeps it near full size and lets the street scroll sideways inside its own window, so a pin stays a pin. The header fits the 80 px top-bar floor at 390 and 320 by going to two rows, its three figures as their own sideways strip.
|
|
11
|
+
- **Real data where the prototype had samples.** One district per open goal (up to eight plots; past that seven stand and the eighth is a staked lot that opens the goals page), each goal's tasks drawn in proportion (built, scaffold, the prototype's `ready` slab), Review held while any source that stands in it can be read, its open matters as crates and the dashed "waiting for a tester" count pin, a reading off drawn as a dash and its reason. The full list is the header of `street-draw.js`.
|
|
12
|
+
- **Every building and district is a door** to `/street#building=<key>` or `/street#district=<goal id>`, where task 1004526 builds the interiors; until then the street marks the building the address names. A pin opens the matter's short card (task 1004528 makes it act); its link is offered only for an on-site path.
|
|
13
|
+
|
|
14
|
+
Tests: `tests/hall_street.mjs` holds the kit, the row, the pins, the doors and the crates equal to the prototype by running its own `Component` class beside `street-draw.js` in both modes, then the docket rules and the page itself in a vm. The states file is `public/street.states.json`; render it against the hall-preview harness, whose `government__docket.json` fixture carries the seven sample matters.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.9",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.9",
|
|
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.21.
|
|
3
|
+
"version": "1.21.9",
|
|
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
|
@@ -8794,5 +8794,21 @@
|
|
|
8794
8794
|
"id": "1004503",
|
|
8795
8795
|
"text": "When you start a project, the invite box now waits until your project is actually up, because nobody can join before then. Until it is, one line says when you can invite, and a demo's teammates are kept ready for that moment."
|
|
8796
8796
|
}
|
|
8797
|
+
],
|
|
8798
|
+
"1.21.8": [
|
|
8799
|
+
{
|
|
8800
|
+
"id": "1004525",
|
|
8801
|
+
"text": "The city's start governing button now opens the governor's street. Every part of the project's government stands as a building in one row, in the order work moves, with a building for each open goal. A pin with its label stand"
|
|
8802
|
+
},
|
|
8803
|
+
{
|
|
8804
|
+
"id": "1004441",
|
|
8805
|
+
"text": "Checked and signed off on the live site: artists and ideators now read as their own craft on profiles, the builders list and the status page. Their earnings charts show, and builders can choose their main role in Settings."
|
|
8806
|
+
}
|
|
8807
|
+
],
|
|
8808
|
+
"1.21.9": [
|
|
8809
|
+
{
|
|
8810
|
+
"id": "1004529",
|
|
8811
|
+
"text": "Each project can now set who decides what in each part of its government: the charter, rules, disputes, people, planning, automation, review, release and treasury. For each part it names who decides and how: one person decides"
|
|
8812
|
+
}
|
|
8797
8813
|
]
|
|
8798
8814
|
}
|
|
@@ -281,6 +281,12 @@ const GOVERNMENT_PINS = {
|
|
|
281
281
|
// task 1004523: the governor's docket — every matter awaiting a governor. The
|
|
282
282
|
// coarse gate is the room's; each source inside is scoped to its own atom.
|
|
283
283
|
'GET /government/docket': 'perm:board.vote.cast',
|
|
284
|
+
// task 1004529: who decides what — a change to the decision-rules table is an
|
|
285
|
+
// amendment by another door (same atom as proposing one), and a building's
|
|
286
|
+
// matter is put to its sitting by the same atom. Only a passed amendment
|
|
287
|
+
// writes the table; neither route does.
|
|
288
|
+
'POST /government/board/decision-rules': 'perm:board.item.open',
|
|
289
|
+
'POST /government/board/matters': 'perm:board.item.open',
|
|
284
290
|
};
|
|
285
291
|
|
|
286
292
|
// Module-scoped privileged-rank pins: a privileged route that lives in a FEATURE
|
|
@@ -951,6 +951,14 @@ function mountInternalSurfaces(app) {
|
|
|
951
951
|
// NB as at PROJECT_SETTINGS_PAGE_RE: nothing between the `=` and the literal
|
|
952
952
|
// (tests/hall_page_gate_map.mjs parses this file statically). Prose goes above.
|
|
953
953
|
const CITY_PAGE_RE = /^\/builders\/city(?:\.html)?\/?$/;
|
|
954
|
+
// /street — the Governor City's street (task 1004525), the city's one door.
|
|
955
|
+
// The same atom for the same reason: its governed read is the same docket.
|
|
956
|
+
// Its other reads (goals, rollups, the viewer's own earnings) are
|
|
957
|
+
// requireBuilder, a wider floor.
|
|
958
|
+
//
|
|
959
|
+
// NB as at PROJECT_SETTINGS_PAGE_RE: nothing between the `=` and the literal
|
|
960
|
+
// (tests/hall_page_gate_map.mjs parses this file statically). Prose goes above.
|
|
961
|
+
const STREET_PAGE_RE = /^\/builders\/street(?:\.html)?\/?$/;
|
|
954
962
|
// /deploy — the owner's deploy door (task 1003159, ADR 0293). Gated on the
|
|
955
963
|
// `core.pin.move` atom, the same key its preview and its move both check, so the
|
|
956
964
|
// shell's reach equals the right to press the button. Archon-floor and system:true,
|
|
@@ -1037,6 +1045,10 @@ function mountInternalSurfaces(app) {
|
|
|
1037
1045
|
if (CITY_PAGE_RE.test(norm)) {
|
|
1038
1046
|
return gdsAuth.requireBoardVotePage(req, res, next);
|
|
1039
1047
|
}
|
|
1048
|
+
// task 1004525: the street, on the same atom (see STREET_PAGE_RE).
|
|
1049
|
+
if (STREET_PAGE_RE.test(norm)) {
|
|
1050
|
+
return gdsAuth.requireBoardVotePage(req, res, next);
|
|
1051
|
+
}
|
|
1040
1052
|
// ADR 0293: the deploy door, on the same atom its two routes check.
|
|
1041
1053
|
if (DEPLOY_PAGE_RE.test(norm)) {
|
|
1042
1054
|
return gdsAuth.requireCorePinPage(req, res, next);
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.9'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -120,6 +120,9 @@ const stub = {
|
|
|
120
120
|
unassignRank: async () => true,
|
|
121
121
|
listRanksWithGrants: async () => [],
|
|
122
122
|
listAssignments: async () => [],
|
|
123
|
+
// task 1004529: People's row is read before a rank is given or taken. No rows
|
|
124
|
+
// = every building as today, so this matrix's walls are the route's own.
|
|
125
|
+
listDecisionRules: async () => [],
|
|
123
126
|
};
|
|
124
127
|
const REAL = {};
|
|
125
128
|
for (const k of Object.keys(stub)) {
|