@bongos/core 1.21.38 → 1.21.40
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 +33 -33
- package/docs/copy-inventory.md +5 -7
- package/docs/copy-registry.json +7 -25
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +14 -2
- package/docs/page-readings.json +387 -348
- package/modules/government/docket.js +14 -0
- package/modules/hall-ui/public/street-card.css +21 -2
- package/modules/hall-ui/public/street-card.js +198 -64
- package/modules/hall-ui/public/street-card.states.json +495 -7
- package/modules/hall-ui/public/street-desk.js +47 -2
- package/modules/hall-ui/public/street-inside.js +3 -13
- package/modules/hall-ui/public/street.css +3 -9
- package/modules/hall-ui/public/street.html +0 -5
- package/modules/hall-ui/public/street.js +44 -32
- package/modules/hall-ui/records/governor-street.md +2 -2
- package/modules/ideas/governor-docket.js +2 -0
- package/modules/lifecycle/governor-docket.js +4 -0
- package/modules/onboarding/routes/access-requests.js +2 -0
- package/modules/security/routes/reports.js +3 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/bongos/module-rechecks.js +12 -1
- package/src/module-api.js +1 -1
- package/tests/governor_docket.mjs +20 -0
- package/tests/hall_matter_card.mjs +143 -18
- package/tests/hall_street.mjs +1 -1
- package/tests/hall_street_inside.mjs +18 -16
|
@@ -161,10 +161,8 @@ html[data-fullscreen="on"] .street-top { padding-right: 184px; }
|
|
|
161
161
|
.street-card__lines { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
|
|
162
162
|
.street-card__lines li { padding: 8px 0; border-top: 1px solid var(--street-rule); font-size: 14px; line-height: 1.4; }
|
|
163
163
|
.street-card__result { margin: 0; font-size: 15px; line-height: 1.45; }
|
|
164
|
-
.street-card__open { align-self: flex-start; display: inline-flex; align-items: center; min-height: 24px; padding: 6px 0; font-size: 14px; color: var(--street-accent-text); text-decoration: underline; text-underline-offset: 4px; }
|
|
165
|
-
.street-card__open[hidden] { display: none; }
|
|
166
164
|
|
|
167
|
-
.street-mark:focus-visible, .street-mode:focus-visible, .street-pin:focus-visible, .street-door:focus-visible, .street-sign:focus-visible, .street-card__close:focus-visible
|
|
165
|
+
.street-mark:focus-visible, .street-mode:focus-visible, .street-pin:focus-visible, .street-door:focus-visible, .street-sign:focus-visible, .street-card__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
168
166
|
|
|
169
167
|
/* narrower: the strip tightens, then wraps; the readings scroll sideways */
|
|
170
168
|
@media (max-width: 1180px) {
|
|
@@ -195,7 +193,7 @@ html[data-fullscreen="on"] .street-top { padding-right: 184px; }
|
|
|
195
193
|
on the hall's tokens. Under 900 px the artboard gives way to a column: the
|
|
196
194
|
drawing in its own sideways strip, the panel below it. */
|
|
197
195
|
.inside { position: absolute; inset: 0; display: flex; overflow-x: auto; overflow-y: auto; background: var(--bg); }
|
|
198
|
-
.inside[hidden], .inside-annex[hidden], .inside-dhead[hidden], .inside-legend[hidden], .inside-
|
|
196
|
+
.inside[hidden], .inside-annex[hidden], .inside-dhead[hidden], .inside-legend[hidden], .inside-open[hidden], .inside-note[hidden], .inside-panel[hidden], .inside-more[hidden] { display: none; }
|
|
199
197
|
.inside-sizer { position: relative; margin: auto; flex: none; overflow: hidden; }
|
|
200
198
|
.inside-board { position: absolute; left: 0; top: 0; width: 1440px; height: 708px; transform-origin: 0 0; transform: scale(var(--inside-scale, 1)); color: var(--ink); }
|
|
201
199
|
.inside-head { position: absolute; left: 32px; top: 16px; display: flex; align-items: center; gap: 16px; z-index: 2; }
|
|
@@ -265,10 +263,6 @@ html[data-fullscreen="on"] .street-top { padding-right: 184px; }
|
|
|
265
263
|
.inside-meter__fill { height: 6px; width: 0; background: color-mix(in srgb, var(--ink) 30%, var(--bg)); }
|
|
266
264
|
.inside-meter__fill--full { background: var(--accent); }
|
|
267
265
|
.inside-meter__line { font-size: 13px; color: var(--ink-soft); line-height: 1.45; }
|
|
268
|
-
.inside-table { display: flex; flex-direction: column; gap: 8px; }
|
|
269
|
-
.inside-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; width: 100%; background: none; border: 0; border-top: 1px solid var(--street-rule); padding: 10px 0; cursor: pointer; font-family: var(--font-body); font-size: 15px; text-align: left; color: var(--ink); }
|
|
270
|
-
.inside-row--needs { color: var(--street-accent-text); }
|
|
271
|
-
.inside-row__status { font-size: 12.5px; color: var(--ink-faint); white-space: nowrap; }
|
|
272
266
|
|
|
273
267
|
/* the chosen floor, its sign large; the block re-enters on every pick */
|
|
274
268
|
.inside-open { display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -362,7 +356,7 @@ html[data-fullscreen="on"] .street-top { padding-right: 184px; }
|
|
|
362
356
|
.inside-ditem { display: flex; justify-content: space-between; gap: 14px; padding: 8px 0; border-top: 1px solid var(--street-rule); font-size: 14px; line-height: 1.4; }
|
|
363
357
|
.inside-ditem__meta { color: var(--ink-faint); white-space: nowrap; }
|
|
364
358
|
|
|
365
|
-
.inside-back:focus-visible, .inside-textbtn:focus-visible, .inside-
|
|
359
|
+
.inside-back:focus-visible, .inside-textbtn:focus-visible, .inside-drawer:focus-visible, .inside-item:focus-visible, .inside-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
366
360
|
.inside-floor:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
|
|
367
361
|
.inside-name:focus { outline: none; }
|
|
368
362
|
.inside-name:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
|
|
@@ -166,10 +166,6 @@
|
|
|
166
166
|
<span class="inside-meter__line" id="insideGrowth"></span>
|
|
167
167
|
</div>
|
|
168
168
|
</div>
|
|
169
|
-
<div class="inside-table" id="insideTableWrap" hidden>
|
|
170
|
-
<span class="street-label">on the table here</span>
|
|
171
|
-
<div id="insideTable"></div>
|
|
172
|
-
</div>
|
|
173
169
|
<div class="inside-open" id="insideOpenA"></div>
|
|
174
170
|
<div class="inside-open" id="insideOpenB" hidden></div>
|
|
175
171
|
<div class="inside-record">
|
|
@@ -199,7 +195,6 @@
|
|
|
199
195
|
</div>
|
|
200
196
|
<h2 class="street-card__title" id="streetCardTitle"></h2>
|
|
201
197
|
<div class="street-card__body" id="streetCardBody"></div>
|
|
202
|
-
<a class="street-card__open" id="streetCardOpen" href="/builders/street" hidden>open it where it lives</a>
|
|
203
198
|
</div>
|
|
204
199
|
|
|
205
200
|
<div class="street-toast" id="streetToast" role="status" aria-live="polite" hidden></div>
|
|
@@ -78,7 +78,9 @@
|
|
|
78
78
|
var $ = function (id) { return document.getElementById(id); };
|
|
79
79
|
var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
|
80
80
|
var data = { docket: null, goals: [], rollups: [], earnings: null, loaded: false, tasks: {}, room: null, constitution: null };
|
|
81
|
-
|
|
81
|
+
// ask: the act whose chips stand open; more: the detail is open in place;
|
|
82
|
+
// pick: which of the review queue the queue's card shows (task 1004566)
|
|
83
|
+
var card = { cv: null, form: null, ask: null, more: false, pick: null, error: '', busy: false, fresh: null, answer: null };
|
|
82
84
|
var view = { open: null, opener: null, model: null, scale: 1, mode: null, inside: null, insideModel: null };
|
|
83
85
|
// the desk's own state: rows you acted on this visit, what you did per floor,
|
|
84
86
|
// the floors you cleared, the row whose facts or chips are open
|
|
@@ -493,19 +495,6 @@
|
|
|
493
495
|
var fill = $('insideFill');
|
|
494
496
|
if (fill) { fill.style.width = bv.fill + '%'; fill.className = 'inside-meter__fill' + (bv.fill >= 80 ? ' inside-meter__fill--full' : ''); }
|
|
495
497
|
setText('insideGrowth', bv.growth);
|
|
496
|
-
show('insideTableWrap', bv.hasTable);
|
|
497
|
-
var table = $('insideTable'); clear(table);
|
|
498
|
-
bv.table.forEach(function (r) {
|
|
499
|
-
var b = node('button', 'inside-row' + (r.state === 'needs' ? ' inside-row--needs' : ''));
|
|
500
|
-
b.setAttribute('type', 'button');
|
|
501
|
-
b.setAttribute('data-matter', r.matter);
|
|
502
|
-
b.setAttribute('aria-controls', 'streetCard');
|
|
503
|
-
b.setAttribute('aria-expanded', view.open === r.matter ? 'true' : 'false');
|
|
504
|
-
b.appendChild(node('span', 'inside-row__label', r.label));
|
|
505
|
-
b.appendChild(node('span', 'inside-row__status', r.status));
|
|
506
|
-
b.addEventListener('click', function () { openCard(r.matter, b); });
|
|
507
|
-
if (table) table.appendChild(b);
|
|
508
|
-
});
|
|
509
498
|
// the chosen floor: two blocks that take turns, so the block re-enters on every pick
|
|
510
499
|
var on = $(bv.flipA ? 'insideOpenA' : 'insideOpenB'), off = $(bv.flipA ? 'insideOpenB' : 'insideOpenA');
|
|
511
500
|
if (off) { off.hidden = true; clear(off); }
|
|
@@ -1054,21 +1043,42 @@
|
|
|
1054
1043
|
|
|
1055
1044
|
|
|
1056
1045
|
// ---------- the matter card (task 1004528: it acts) ----------
|
|
1046
|
+
// Its acts are the desk row's for the same item (GovDesk.cardFor, task
|
|
1047
|
+
// 1004566): the same words, chips, routes and permission checks. "more" grows
|
|
1048
|
+
// the card in place; nothing on it opens an older page to decide.
|
|
1057
1049
|
function fillCard() {
|
|
1058
|
-
var C = window.GovMatterCard;
|
|
1059
|
-
var
|
|
1050
|
+
var C = window.GovMatterCard, D = window.GovDesk;
|
|
1051
|
+
var ctx = { room: data.room, constitution: data.constitution, now: Date.now(), goals: (data.goals || []).filter(function (g) { return g && g.status === 'open'; }), pick: card.pick };
|
|
1052
|
+
var cv = D && D.cardFor ? D.cardFor(view.open, data.docket, ctx) : C ? C.model({ docket: data.docket, key: view.open, room: data.room, constitution: data.constitution, now: Date.now() }) : null;
|
|
1060
1053
|
if (!cv) { closeCard(false); return null; }
|
|
1061
1054
|
card.cv = cv;
|
|
1062
1055
|
$('streetCardWhere').textContent = cv.where;
|
|
1063
1056
|
$('streetCardWhen').textContent = cv.when;
|
|
1064
1057
|
$('streetCardTitle').textContent = cv.title;
|
|
1065
|
-
C.render(document, $('streetCardBody'), cv, { act: actOn, openForm: openForm }, { form: card.form, error: card.error, busy: card.busy, fresh: card.fresh });
|
|
1066
|
-
var
|
|
1067
|
-
if (cv.href) open.setAttribute('href', cv.href);
|
|
1068
|
-
open.hidden = !cv.href;
|
|
1058
|
+
C.render(document, $('streetCardBody'), cv, { act: actOn, openForm: openForm, ask: askFor, more: toggleMore, pick: pickQueued }, { form: card.form, ask: card.ask, more: card.more, error: card.error, busy: card.busy, fresh: card.fresh });
|
|
1059
|
+
var frame = $('streetCard'); if (frame) frame.setAttribute('data-more', card.more ? 'open' : 'closed');
|
|
1069
1060
|
placeCard();
|
|
1070
1061
|
return cv;
|
|
1071
1062
|
}
|
|
1063
|
+
function askFor(key) {
|
|
1064
|
+
card.ask = card.ask === key ? null : key;
|
|
1065
|
+
card.error = '';
|
|
1066
|
+
fillCard();
|
|
1067
|
+
var body = $('streetCardBody'), first = body && body.querySelector ? body.querySelector('.mcard-chip, .mcard-chips__field') : null;
|
|
1068
|
+
if (card.ask && first && first.focus) first.focus();
|
|
1069
|
+
}
|
|
1070
|
+
function toggleMore() {
|
|
1071
|
+
card.more = !card.more;
|
|
1072
|
+
fillCard();
|
|
1073
|
+
var mb = $('streetCardBody'), t = mb && mb.querySelector ? mb.querySelector('[data-act="more"]') : null;
|
|
1074
|
+
if (t && t.focus) t.focus();
|
|
1075
|
+
}
|
|
1076
|
+
function pickQueued(key) {
|
|
1077
|
+
card.pick = key; card.ask = null; card.more = false; card.form = null; card.error = '';
|
|
1078
|
+
fillCard();
|
|
1079
|
+
var close = $('streetCardClose');
|
|
1080
|
+
if (close && close.focus) close.focus();
|
|
1081
|
+
}
|
|
1072
1082
|
function placeCard() {
|
|
1073
1083
|
var card = $('streetCard');
|
|
1074
1084
|
if (!card || !view.open || !view.model) return;
|
|
@@ -1084,7 +1094,7 @@
|
|
|
1084
1094
|
function openCard(key, opener) {
|
|
1085
1095
|
view.open = key;
|
|
1086
1096
|
view.opener = opener || null;
|
|
1087
|
-
card.form = null; card.error = ''; card.fresh = null;
|
|
1097
|
+
card.form = null; card.ask = null; card.more = false; card.pick = null; card.error = ''; card.fresh = null;
|
|
1088
1098
|
var el = $('streetCard');
|
|
1089
1099
|
if (!fillCard()) return;
|
|
1090
1100
|
el.hidden = false;
|
|
@@ -1097,19 +1107,17 @@
|
|
|
1097
1107
|
var was = view.opener;
|
|
1098
1108
|
view.open = null;
|
|
1099
1109
|
view.opener = null;
|
|
1100
|
-
card.cv = null; card.form = null; card.error = ''; card.busy = false; card.fresh = null;
|
|
1110
|
+
card.cv = null; card.form = null; card.ask = null; card.more = false; card.pick = null; card.error = ''; card.busy = false; card.fresh = null;
|
|
1101
1111
|
var el = $('streetCard');
|
|
1102
1112
|
if (el) el.hidden = true;
|
|
1103
1113
|
draw();
|
|
1104
1114
|
// The pins were redrawn, so focus goes back to the new button for the same matter.
|
|
1105
1115
|
if (restore !== false && was && was.getAttribute) {
|
|
1106
1116
|
var key = was.getAttribute('data-matter');
|
|
1107
|
-
// the pins
|
|
1117
|
+
// the pins were redrawn: focus the new pin for the same matter
|
|
1108
1118
|
// .children is an HTMLCollection, which concat does not spread: copy it first
|
|
1109
1119
|
var list = function (id) { var el = $(id); return el && el.children ? Array.prototype.slice.call(el.children) : []; };
|
|
1110
|
-
var
|
|
1111
|
-
var rows = list('insideTable');
|
|
1112
|
-
var pool = view.inside ? [].concat(rows, pins) : [].concat(pins, rows);
|
|
1120
|
+
var pool = list('streetPins');
|
|
1113
1121
|
for (var i = 0; i < pool.length; i++) {
|
|
1114
1122
|
if (pool[i].getAttribute('data-matter') === key) { if (pool[i].focus) pool[i].focus(); break; }
|
|
1115
1123
|
}
|
|
@@ -1138,32 +1146,36 @@
|
|
|
1138
1146
|
card.form = name || null;
|
|
1139
1147
|
card.error = '';
|
|
1140
1148
|
fillCard();
|
|
1141
|
-
var f = name === '
|
|
1149
|
+
var f = name === 'revise' ? $('mcardRevise') : $('streetCardClose');
|
|
1142
1150
|
if (f && f.focus) f.focus();
|
|
1143
1151
|
}
|
|
1144
1152
|
// One act: checked, sent to its own route (the server's permission checks
|
|
1145
1153
|
// decide), then answered. An act that records nothing (abstain, hold) is
|
|
1146
1154
|
// answered without a request.
|
|
1155
|
+
// An act from the card goes with the desk row's own card when a row stands
|
|
1156
|
+
// behind it (its subject: which route, the council's), else the model's.
|
|
1147
1157
|
function actOn(key, input) {
|
|
1148
1158
|
var C = window.GovMatterCard, cv = card.cv;
|
|
1149
1159
|
if (!C || !cv || card.busy) return Promise.resolve(false);
|
|
1150
|
-
var
|
|
1160
|
+
var subj = cv.row && cv.row.card ? cv.row.card : cv;
|
|
1161
|
+
var refused = C.check(subj, key, input);
|
|
1151
1162
|
if (refused) { card.error = refused; fillCard(); return Promise.resolve(false); }
|
|
1152
1163
|
card.error = '';
|
|
1153
|
-
var req = C.request(
|
|
1154
|
-
if (!req) return Promise.resolve(answered(
|
|
1164
|
+
var req = C.request(subj, key, input);
|
|
1165
|
+
if (!req) return Promise.resolve(answered(subj, key, {}, false));
|
|
1155
1166
|
card.busy = true;
|
|
1156
1167
|
fillCard();
|
|
1157
1168
|
return send(req).then(function (out) {
|
|
1158
1169
|
card.busy = false;
|
|
1159
1170
|
if (!out.ok) { card.error = C.refusal(out.status, out.body); if (view.open) fillCard(); return false; }
|
|
1160
|
-
return answered(
|
|
1171
|
+
return answered(subj, key, out.body, true);
|
|
1161
1172
|
});
|
|
1162
1173
|
}
|
|
1163
1174
|
function answered(cv, key, body, recorded) {
|
|
1164
1175
|
var a = window.GovMatterCard.answer(cv, key, body);
|
|
1165
1176
|
say(a.toast);
|
|
1166
|
-
card.form = null;
|
|
1177
|
+
card.form = null; card.ask = null;
|
|
1178
|
+
card.pick = null;
|
|
1167
1179
|
card.fresh = key === 'for' || key === 'object' ? 'you' : null;
|
|
1168
1180
|
if (a.building && a.kind) card.answer = { building: a.building, matter: cv.key, kind: a.kind, at: Date.now() };
|
|
1169
1181
|
if (a.close) closeCard(true);
|
|
@@ -21,12 +21,12 @@ Built 1:1 on `buildingView()` and `districtView()` of the same prototype. The in
|
|
|
21
21
|
Files: `street-inside.js` (`window.GovInside`, node-require-safe, pure): the prototype's `crown()`, `workItem()`, `floorSymbol()`, `symbolPath()` (all forty symbols) and the section drawing itself, verbatim, plus the real-data adapters. `street.js` builds the nodes; `street.css` carries the interior's styles under the `inside-` prefix.
|
|
22
22
|
|
|
23
23
|
- **The building in section.** Every storey a real floor (slab, cut walls with glazing, three lamps, a partition with a doorway, the framed sign with the floor's symbol, its name and state lettered on the wall, its count, its level number), dark empty floors up to the building's capacity, the crown per building, the lift in its shaft, the orange frame and orange sign on the chosen floor, and the annex lot once the building is full. An unheld Review is the dashed ghost (`GovStreet.reviewHeldOf()`, so the street and the interior agree).
|
|
24
|
-
- **The right panel.** Name and what it holds, who holds it (seat chips), how it decides (the rule glyph), **change how this works**, the fill meter with its growth line,
|
|
24
|
+
- **The right panel.** Name and what it holds, who holds it (seat chips), how it decides (the rule glyph), **change how this works**, the fill meter with its growth line, the chosen floor with its sign large, and the record as monthly filing drawers.
|
|
25
25
|
- **Floors are the prototype's structure; a number appears only when the docket holds it.** `GovInside.PLAN` keeps each building's floors, order and capacity. A floor bound to a docket source shows that source's open matters at this building (count, line, needs you, the lines); one bound to a reading shows its figure; Planning's Goals floor counts the open goals. Every other floor says "not recorded yet" on the wall, lettered faint, with no number. A source that is off, coming, not yours or unreadable says which, never a figure. Filling every floor with real data is task 1004527, through `floorFor()` and `PLAN`.
|
|
26
26
|
- **Who holds it.** The docket names no holders yet (task 1004529), so the seat is an empty dashed chip and says "who holds it is not set on this project yet". The rule is the one the building's open matters are decided by, else the design's default, which the words call "by default". `holderOf()` reads a docket building's `holders` / `rule` / `who` first, so 1004529 lands without touching the drawing.
|
|
27
27
|
- **The lift and the flip.** The lift is one node kept across draws, so picking a floor slides it by a CSS transform transition; the chosen floor's block is two blocks that take turns (`flipA` / `flipB`), so it re-enters on every pick. Both are dead under reduced motion.
|
|
28
28
|
- **Change how this works** is present and honest: pressed, it says drafting an amendment from here is not built yet (task 1004530 wires it).
|
|
29
|
-
- **
|
|
29
|
+
- **No "on the table here" list.** The prototype's list of the building's open matters was removed on the owner's direction (task 1004565): the floor desks carry every matter as an actionable row, so the list was duplication. Inside a building a matter is its desk row; the short card opens from the street's pins.
|
|
30
30
|
- **Districts.** The goal's tasks (`GET /tasks?goal_id=N`, read once per goal): shape from the discipline (tower engineer, studio artist, glass front UI, pavilion ideator; none is a tower), stage from the status (backlog and blocked a staked plot, ready a slab, active a scaffold, completed and confirmed built with a flag, shipped lit), label the kind; height from the estimate. Up to ten stand on the road, work in flight first; the rest are counted with a link to the goal. Each one opens its task.
|
|
31
31
|
- **Narrow.** Under 900 px the board becomes a column: the drawing in its own sideways strip (never scaled under 0.6, so a floor stays a 24 px target), then the panel.
|
|
32
32
|
|
|
@@ -42,6 +42,8 @@ function blockerDocketMatter(b, mayResolve) {
|
|
|
42
42
|
tally: null,
|
|
43
43
|
href: `/blockers#blocker-${b.id}`,
|
|
44
44
|
ref: { type: 'blocker', id: b.id },
|
|
45
|
+
// the card's "more" (task 1004566): the body GET /blockers shows everyone
|
|
46
|
+
detail: b.body_md || null,
|
|
45
47
|
// the floor desk's one act (task 1004560): decide, by one of its options
|
|
46
48
|
...(mayResolve ? { acts: [{ key: 'decide', options: blockerOptions(b.body_md) }] } : {}),
|
|
47
49
|
};
|
|
@@ -41,6 +41,8 @@ function overrideMatter(r, mayDecide) {
|
|
|
41
41
|
tally: null,
|
|
42
42
|
href: `/task/${encodeURIComponent(r.task_id)}`,
|
|
43
43
|
ref: { type: 'override_request', id: r.id },
|
|
44
|
+
// the card's "more" (task 1004566): the reason GET /override-requests shows
|
|
45
|
+
detail: r.rationale || null,
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -58,6 +60,8 @@ function removalMatter(r) {
|
|
|
58
60
|
tally: null,
|
|
59
61
|
href: `/task/${encodeURIComponent(r.proposal_task_id)}`,
|
|
60
62
|
ref: { type: 'task', id: r.proposal_task_id },
|
|
63
|
+
// the card's "more" (task 1004566): the proposal's own text, from the same read
|
|
64
|
+
detail: r.description || null,
|
|
61
65
|
// POST /removal-proposals/:id/decide, the docket source's own atom (task 1004559)
|
|
62
66
|
acts: [{ key: 'remove' }, { key: 'keep' }],
|
|
63
67
|
};
|
|
@@ -236,6 +236,8 @@ function accessRequestDocketMatter(r) {
|
|
|
236
236
|
tally: null,
|
|
237
237
|
href: '/watch',
|
|
238
238
|
ref: { type: 'access_request', id: r.id },
|
|
239
|
+
// the card's "more" (task 1004566): their note, as GET /access-requests shows it
|
|
240
|
+
detail: r.note || null,
|
|
239
241
|
};
|
|
240
242
|
}
|
|
241
243
|
|
|
@@ -56,6 +56,9 @@ function redteamDocketMatter(r) {
|
|
|
56
56
|
tally: null,
|
|
57
57
|
href: '/watch',
|
|
58
58
|
ref: { type: 'vulnerability_report', id: r.id },
|
|
59
|
+
// the card's "more" (task 1004566): what GET /security/reports shows this
|
|
60
|
+
// same atom — what it targets and the report's description
|
|
61
|
+
detail: [r.target ? `Target: ${r.target}` : '', r.description || ''].filter(Boolean).join('\n\n') || null,
|
|
59
62
|
// The floor desk's acts (task 1004559): the docket only lists this for a
|
|
60
63
|
// holder of the adjudicate atom, which both routes check.
|
|
61
64
|
acts: [{ key: 'confirm' }, { key: 'dispute' }],
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.40",
|
|
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.40",
|
|
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.40",
|
|
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
|
@@ -8992,5 +8992,17 @@
|
|
|
8992
8992
|
"id": "1004564",
|
|
8993
8993
|
"text": "If you put something up for a vote and nobody has voted yet, you can now withdraw it with one tap on your desk. Once anyone votes, it stays with the vote. A small database change makes \"withdrawn\" a valid way for a sitting to"
|
|
8994
8994
|
}
|
|
8995
|
+
],
|
|
8996
|
+
"1.21.39": [
|
|
8997
|
+
{
|
|
8998
|
+
"id": "1004565",
|
|
8999
|
+
"text": "Inside each building on the governor street, the separate \"on the table here\" list is gone. The floor desks already show every matter as a row you can act on, so the list only repeated them. The rest of the building panel look"
|
|
9000
|
+
}
|
|
9001
|
+
],
|
|
9002
|
+
"1.21.40": [
|
|
9003
|
+
{
|
|
9004
|
+
"id": "1004566",
|
|
9005
|
+
"text": "Every card on the governor street now acts where it opens. Pressing a pin gives the same buttons as that item's row at its building desk: confirm or dispute a security report, remove or keep, decide a blocker by tapping an opt"
|
|
9006
|
+
}
|
|
8995
9007
|
]
|
|
8996
9008
|
}
|
|
@@ -53,6 +53,13 @@ async function listModuleRechecks({ limit = RECHECK_LIMIT, db = null } = {}) {
|
|
|
53
53
|
|
|
54
54
|
// One version as a docket matter. Pure. A failed gate needs a governor who may
|
|
55
55
|
// ask for a re-check; a pending part is a re-check already moving.
|
|
56
|
+
// The one-tap reasons a re-check may give (POST /store/modules/:key/versions/
|
|
57
|
+
// :version/reassess requires one; task 1004566).
|
|
58
|
+
const RECHECK_REASONS = Object.freeze([
|
|
59
|
+
{ key: 'fixed', label: 'fixed since' },
|
|
60
|
+
{ key: 'misfired', label: 'check misfired' },
|
|
61
|
+
{ key: 'again', label: 'look again' },
|
|
62
|
+
]);
|
|
56
63
|
function moduleRecheckMatter(r, mayRerun) {
|
|
57
64
|
const failed = r.security_failed === true;
|
|
58
65
|
const pending = Array.isArray(r.pending_parts) ? r.pending_parts.filter(Boolean) : [];
|
|
@@ -70,7 +77,11 @@ function moduleRecheckMatter(r, mayRerun) {
|
|
|
70
77
|
proposer: null,
|
|
71
78
|
tally: null,
|
|
72
79
|
href: '/modules',
|
|
73
|
-
|
|
80
|
+
// "<key>@<version>": the reassess route's two path parts (task 1004566)
|
|
81
|
+
ref: { type: 'module_version', id: `${r.module_key}@${r.version}` },
|
|
82
|
+
// the one act a governor takes on it, under the reassess route's own atom,
|
|
83
|
+
// only when its security check failed (a pending check is still running)
|
|
84
|
+
...(failed && mayRerun ? { acts: [{ key: 'recheck', options: RECHECK_REASONS }] } : {}),
|
|
74
85
|
};
|
|
75
86
|
}
|
|
76
87
|
|
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.40'; // 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');
|
|
@@ -101,6 +101,26 @@ test('normalizeMatter: the one shape, prefixed id, ISO dates', () => {
|
|
|
101
101
|
assert.deepEqual(Object.keys(m).sort(), ['building', 'deadline', 'href', 'id', 'opened_at', 'proposer', 'ref', 'rule', 'short', 'source', 'state', 'tally', 'title', 'where'].sort());
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test('normalizeMatter carries a matter\'s detail for the card\'s "more" only as bounded text (task 1004566)', () => {
|
|
105
|
+
const base = { id: 1, title: 't' };
|
|
106
|
+
const at = { source: 'blockers', building: 'planning' };
|
|
107
|
+
assert.equal(docket.normalizeMatter({ ...base, detail: ' Option A: Render\n\nOption B: Fly ' }, at).detail, 'Option A: Render\n\nOption B: Fly', 'lines kept, ends trimmed');
|
|
108
|
+
assert.equal(docket.normalizeMatter({ ...base, detail: 'x'.repeat(5000) }, at).detail.length, 4000);
|
|
109
|
+
for (const bad of [null, '', ' ', 42, { md: 'x' }, ['x']]) assert.ok(!('detail' in docket.normalizeMatter({ ...base, detail: bad }, at)), JSON.stringify(bad));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('the sources hand the detail their own list route shows, and a failed module check its one act (task 1004566)', () => {
|
|
113
|
+
const r = reportsRoute.redteamDocketMatter({ id: 2, title: 'x', status: 'open', target: '/login', description: 'No limit on tries.' });
|
|
114
|
+
assert.equal(r.detail, 'Target: /login\n\nNo limit on tries.');
|
|
115
|
+
assert.equal(reportsRoute.redteamDocketMatter({ id: 2, title: 'x', status: 'open' }).detail, null);
|
|
116
|
+
const failed = moduleRechecks.moduleRecheckMatter({ version_id: 7, module_key: 'image-tools', version: '1.2.0', security_failed: true }, true);
|
|
117
|
+
assert.deepEqual(failed.ref, { type: 'module_version', id: 'image-tools@1.2.0' }, 'the reassess route\'s two path parts');
|
|
118
|
+
assert.deepEqual(failed.acts.map((a) => a.key), ['recheck']);
|
|
119
|
+
assert.ok(failed.acts[0].options.length >= 2 && failed.acts[0].options.every((o) => o.label.length <= 16), 'reasons are one-tap chips');
|
|
120
|
+
assert.ok(!('acts' in moduleRechecks.moduleRecheckMatter({ version_id: 7, module_key: 'm', version: '1.0.0', security_failed: true }, false)), 'not without the reassess atom');
|
|
121
|
+
assert.ok(!('acts' in moduleRechecks.moduleRecheckMatter({ version_id: 8, module_key: 'm', version: '1.0.0', security_failed: false, pending_parts: ['tests'] }, true)), 'not while its check is still running');
|
|
122
|
+
});
|
|
123
|
+
|
|
104
124
|
test('normalizeMatter fails SAFE: unknown state → moving (never a false "needs you"), unknown rule → null', () => {
|
|
105
125
|
const m = docket.normalizeMatter({ id: 1, title: 't', state: 'urgent!!', rule: 'vibes', building: 'castle' }, { source: 's', building: 'review' });
|
|
106
126
|
assert.equal(m.state, 'moving');
|