@bongos/core 1.21.37 → 1.21.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +51 -36
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +77 -3
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +6 -7
- package/docs/copy-registry.json +8 -17
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +310 -318
- package/modules/government/board-withdraw.js +38 -0
- package/modules/government/board.js +1 -0
- package/modules/government/db.js +20 -0
- package/modules/government/migrations/government_026_board_outcome_withdrawn.sql +29 -0
- package/modules/government/routes/government.js +26 -0
- package/modules/hall-ui/public/street-card.js +16 -0
- package/modules/hall-ui/public/street-desk.js +2 -0
- package/modules/hall-ui/public/street-inside.js +3 -13
- package/modules/hall-ui/public/street.css +2 -6
- package/modules/hall-ui/public/street.html +0 -4
- package/modules/hall-ui/public/street.js +3 -18
- package/modules/hall-ui/records/governor-street.md +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/bongos/route-rank-check.js +4 -0
- package/src/module-api.js +1 -1
- package/tests/government_board_withdraw.mjs +144 -0
- package/tests/government_seed.mjs +1 -0
- package/tests/hall_street_desk.mjs +37 -0
- package/tests/hall_street_inside.mjs +18 -16
|
@@ -202,6 +202,43 @@ test('a row\'s buttons are the card\'s acts under the board\'s words, and send t
|
|
|
202
202
|
assert.equal(D.rowsFor('charter', 0, dk, { now: NOW })[0].wait, 'reading…');
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
+
// task 1004564: the proposer's own sitting, before anyone has voted, is one row
|
|
206
|
+
// with one button — withdraw — sent to the withdraw route. A vote takes it away.
|
|
207
|
+
test('your own sitting with no vote on it: one row, withdraw, sent to its route; a vote or another viewer takes it away', () => {
|
|
208
|
+
const ms = [matter({ id: 'board:1', source: 'board', building: 'charter', state: 'moving', ref: { type: 'board_item', id: '1' } })];
|
|
209
|
+
const dk = docket(ms);
|
|
210
|
+
const room = (votes) => ({ open: [{ id: '1', subject_type: 'constitutional_amendment', subject: { title: LONG, author_id: '95' }, votes }], closed: [] });
|
|
211
|
+
const mine = D.rowsFor('charter', 0, dk, { room: room([]), now: NOW, viewerId: '95' })[0];
|
|
212
|
+
assert.deepEqual(mine.actions.map((a) => [a.key, a.label]), [['withdraw', 'withdraw']]);
|
|
213
|
+
assert.equal(mine.chips, null, 'withdraw asks nothing: no chips');
|
|
214
|
+
assert.deepEqual(j(C.request(mine.card, 'withdraw')), { method: 'POST', url: '/api/bongos/government/board/items/1/withdraw', body: {} });
|
|
215
|
+
assert.match(C.answer(mine.card, 'withdraw', { item_closed: true, outcome: 'withdrawn' }).toast, /^Withdrawn/);
|
|
216
|
+
assert.equal(C.refusal(409, { error: { code: 'votes_cast' } }), 'Someone has voted on it, so it is the sitting\'s to decide now.');
|
|
217
|
+
// one vote — anyone's — and the row is the sitting's again
|
|
218
|
+
const voted = D.rowsFor('charter', 0, dk, { room: room([{ voter_id: '7', direction: 'yes', name: 'Ana' }]), now: NOW, viewerId: '95' })[0];
|
|
219
|
+
assert.ok(!voted.actions.some((a) => a.key === 'withdraw'), 'no withdraw once a vote is cast');
|
|
220
|
+
// someone else's sitting never offers it
|
|
221
|
+
const theirs = D.rowsFor('charter', 0, dk, { room: room([]), now: NOW, viewerId: '7' })[0];
|
|
222
|
+
assert.ok(!theirs.actions.some((a) => a.key === 'withdraw'), 'only the proposer sees withdraw');
|
|
223
|
+
// a proposer who may also vote keeps yes first, withdraw second — still two buttons
|
|
224
|
+
const needs = D.rowsFor('charter', 0, docket([{ ...ms[0], state: 'needs' }]), { room: room([]), now: NOW, viewerId: '95' })[0];
|
|
225
|
+
assert.deepEqual(needs.actions.map((a) => a.key), ['for', 'withdraw']);
|
|
226
|
+
// and the line rule holds on every floor with the proposer looking
|
|
227
|
+
const own = { ...ROOM, open: ROOM.open.map((it) => it.subject_type === 'full_idea' ? it : { ...it, subject: { ...it.subject, author_id: '95' } }) };
|
|
228
|
+
const desks = everyDesk(docket(longMatters()), { room: own });
|
|
229
|
+
assert.ok(desks.flatMap((d) => d.rows).some((r) => r.actions.some((a) => a.key === 'withdraw')), 'the stress docket reached a withdraw row');
|
|
230
|
+
assert.deepEqual(desks.flatMap((d) => D.breaks(d)), []);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('a withdrawn sitting reads as withdrawn on the card, never as sent back to revise', () => {
|
|
234
|
+
const dk = docket([matter({ id: 'board:1', source: 'board', building: 'charter', state: 'settled', ref: { type: 'board_item', id: '1' } })]);
|
|
235
|
+
const room = { open: [], closed: [{ id: '1', subject_type: 'constitutional_amendment', subject: { title: 'Shorter windows', author_id: '95' }, votes: [], closed_at: '2026-10-03T10:00:00.000Z', outcome: 'withdrawn' }] };
|
|
236
|
+
const cv = (viewerId) => C.model({ docket: dk, key: 'board:1', room, now: NOW, viewerId });
|
|
237
|
+
assert.equal(cv('95').result, 'You withdrew it before anyone voted.');
|
|
238
|
+
assert.match(cv('7').result, /^Withdrawn by .* before anyone voted\.$/);
|
|
239
|
+
assert.equal(cv('95').readOnly, true);
|
|
240
|
+
});
|
|
241
|
+
|
|
205
242
|
// ── the page: the desk acts in place, and the game layer answers ──────────────
|
|
206
243
|
|
|
207
244
|
function fakeDom() {
|
|
@@ -218,7 +218,7 @@ test('a source that is off, coming, not yours or unreadable never shows a number
|
|
|
218
218
|
const none = I.civicInfo('release', { docket: null, now: NOW });
|
|
219
219
|
assert.ok(none.floors.every((f) => f.count === '' && f.status !== 'ok' && f.status !== 'pending'), 'no docket: no numbers anywhere');
|
|
220
220
|
assert.equal(none.onFile, 'the docket could not be read just now');
|
|
221
|
-
assert.deepEqual(none.
|
|
221
|
+
assert.deepEqual(none.record, [], 'no docket: nothing filed');
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
test('a floor bound to a reading shows its figure and line when live; Planning counts the open goals', () => {
|
|
@@ -237,12 +237,12 @@ test('a floor bound to a reading shows its figure and line when live; Planning c
|
|
|
237
237
|
assert.ok(m.texts.some((t) => t.v === '5' && t.s === 20), 'only open goals are counted on the wall');
|
|
238
238
|
});
|
|
239
239
|
|
|
240
|
-
test('
|
|
241
|
-
const ms = [matter({ id: 'board:1', source: 'board', building: 'charter', state: 'moving', title: 'B'
|
|
240
|
+
test('no separate list of matters: the floor desks carry every matter, so the model keeps no table (task 1004565)', () => {
|
|
241
|
+
const ms = [matter({ id: 'board:1', source: 'board', building: 'charter', state: 'moving', title: 'B' }), matter({ id: 'board:2', source: 'board', building: 'charter', state: 'needs', title: 'A' })];
|
|
242
242
|
const m = I.buildingModel({ key: 'charter', docket: docket(ms), mode: 'light', now: NOW });
|
|
243
|
-
assert.
|
|
244
|
-
assert.
|
|
245
|
-
assert.
|
|
243
|
+
assert.ok(!('table' in m) && !('hasTable' in m), 'no table fields on the model');
|
|
244
|
+
assert.doesNotMatch(m.label, /on the table/, 'the building\'s label no longer counts a table');
|
|
245
|
+
assert.match(m.label, /floors in use\.$/);
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
test('the record files the building\'s matters by the month each was opened, newest drawer first', () => {
|
|
@@ -524,7 +524,8 @@ const FEEDS = (ms, extra = {}) => ({
|
|
|
524
524
|
|
|
525
525
|
test('the markup carries the interior: one persistent lift, two flip blocks, the panel and the back link', () => {
|
|
526
526
|
const html = read('street.html').replace(/<!--[\s\S]*?-->/g, '');
|
|
527
|
-
for (const id of ['streetInside', 'insideBack', 'insideSvg', 'insideFloors', 'insideLift', 'insideAnnex', 'insidePanel', 'insideName', 'insideAmend', 'insideAmendNote', 'insideFill', '
|
|
527
|
+
for (const id of ['streetInside', 'insideBack', 'insideSvg', 'insideFloors', 'insideLift', 'insideAnnex', 'insidePanel', 'insideName', 'insideAmend', 'insideAmendNote', 'insideFill', 'insideOpenA', 'insideOpenB', 'insideDrawers', 'insideDrawerItems', 'insideDName', 'insideLegend', 'insideItems']) assert.match(html, new RegExp(`id="${id}"`), id);
|
|
528
|
+
assert.doesNotMatch(html, /insideTable|on the table here/i, 'the "on the table here" list is gone (task 1004565)');
|
|
528
529
|
assert.equal((html.match(/<h1\b/g) || []).length, 1, 'the street keeps its one h1; the interior names are h2');
|
|
529
530
|
assert.match(html, /<section class="inside" id="streetInside"[^>]*hidden>/, 'hidden until an address names a building or a district');
|
|
530
531
|
assert.match(html, /<button type="button" class="inside-textbtn" id="insideAmend"/);
|
|
@@ -539,8 +540,7 @@ test('a building address draws the interior in place of the street, from the doc
|
|
|
539
540
|
assert.equal(p.byId.get('insideFloors').children.length, 7, 'a button per floor in use');
|
|
540
541
|
assert.equal(p.byId.get('insideFloors').children[4].attrs['aria-pressed'], 'true', 'the floor being decided is chosen');
|
|
541
542
|
assert.equal(p.byId.get('insideLift').style.transform, 'translateY(-208px)');
|
|
542
|
-
assert.equal(p.byId.get('insideTable')
|
|
543
|
-
assert.equal(p.byId.get('insideTable').children[0].className, 'inside-row inside-row--needs');
|
|
543
|
+
assert.equal(p.byId.get('insideTable'), undefined, 'no separate list of matters');
|
|
544
544
|
assert.equal(p.byId.get('insideSeats').children[0].className, 'inside-seat inside-seat--open');
|
|
545
545
|
assert.equal(p.focused.el, null, 'a page opened at the address keeps the browser\'s own start');
|
|
546
546
|
p.location.hash = '#building=rules';
|
|
@@ -575,15 +575,17 @@ test('picking a floor moves the SAME lift node and re-enters the chosen block; d
|
|
|
575
575
|
assert.match(p.byId.get('insideAmendNote').textContent, /could not be read/);
|
|
576
576
|
});
|
|
577
577
|
|
|
578
|
-
test('a
|
|
578
|
+
test('a matter is a desk row: its name opens it in place; back to the street returns to the door', async () => {
|
|
579
579
|
const ms = [matter({ id: 'board:1', source: 'board', building: 'charter', state: 'needs', title: '<b>x</b>' })];
|
|
580
580
|
const p = await bootPage({ hash: '#building=charter', feeds: FEEDS(ms) });
|
|
581
|
-
p.byId.get('
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
assert.equal(
|
|
586
|
-
|
|
581
|
+
const desk = () => (p.byId.get('insideOpenA').hidden ? p.byId.get('insideOpenB') : p.byId.get('insideOpenA'));
|
|
582
|
+
const find = (el, pred) => { if (pred(el)) return el; for (const c of el.children) { const hit = find(c, pred); if (hit) return hit; } return null; };
|
|
583
|
+
const nameOf = () => find(desk(), (el) => el.attrs && el.attrs['data-matter'] === 'board:1');
|
|
584
|
+
assert.ok(nameOf(), 'the desk carries the matter as a row');
|
|
585
|
+
assert.equal(nameOf().attrs['aria-expanded'], 'false');
|
|
586
|
+
nameOf().fire('click');
|
|
587
|
+
assert.equal(nameOf().attrs['aria-expanded'], 'true', 'the row opens in place');
|
|
588
|
+
assert.equal(p.byId.get('streetCard').hidden, true, 'no card is needed inside: the desk acts');
|
|
587
589
|
p.byId.get('insideBack').fire('click');
|
|
588
590
|
assert.deepEqual(p.history, ['/builders/street']);
|
|
589
591
|
assert.equal(p.byId.get('streetInside').hidden, true);
|