@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
|
@@ -82,7 +82,8 @@
|
|
|
82
82
|
var view = { open: null, opener: null, model: null, scale: 1, mode: null, inside: null, insideModel: null };
|
|
83
83
|
// the desk's own state: rows you acted on this visit, what you did per floor,
|
|
84
84
|
// the floors you cleared, the row whose facts or chips are open
|
|
85
|
-
|
|
85
|
+
// order: the Priorities desk's order as the viewer has moved it, per floor (task 1004560)
|
|
86
|
+
var desk = { done: {}, mine: {}, cleared: {}, order: {}, open: null, ask: null, busy: false, roomAsked: false, model: null };
|
|
86
87
|
var TODAY_KEY = 'gov-desk-today';
|
|
87
88
|
var today = { day: '', n: 0 };
|
|
88
89
|
|
|
@@ -553,7 +554,7 @@
|
|
|
553
554
|
var D = window.GovDesk, st = view.inside;
|
|
554
555
|
if (!D || !st) return;
|
|
555
556
|
var k = st.key + ':' + bv.selFloor;
|
|
556
|
-
var dm = D.deskModel({ key: st.key, floor: bv.selFloor, docket: data.docket, goals: (data.goals || []).filter(function (g) { return g && g.status === 'open'; }), now: Date.now(), room: data.room, constitution: data.constitution, done: desk.done, mine: desk.mine[k] || [], cleared: !!desk.cleared[k], today: today.n, sym: bv.sym });
|
|
557
|
+
var dm = D.deskModel({ key: st.key, floor: bv.selFloor, docket: data.docket, goals: (data.goals || []).filter(function (g) { return g && g.status === 'open'; }), now: Date.now(), room: data.room, constitution: data.constitution, done: desk.done, mine: desk.mine[k] || [], cleared: !!desk.cleared[k], today: today.n, sym: bv.sym, order: desk.order[k] || null });
|
|
557
558
|
desk.model = dm;
|
|
558
559
|
el.className = 'inside-open desk' + (dm.quiet ? ' desk--quiet' : '');
|
|
559
560
|
el.setAttribute('data-desk', dm.key);
|
|
@@ -595,6 +596,14 @@
|
|
|
595
596
|
dm.rows.forEach(function (r) { list.appendChild(deskRow(r)); });
|
|
596
597
|
el.appendChild(list);
|
|
597
598
|
}
|
|
599
|
+
// a changed order: what saving does, then undo and save (task 1004560)
|
|
600
|
+
if (dm.save) {
|
|
601
|
+
var bar = node('div', 'desk-save');
|
|
602
|
+
bar.appendChild(node('span', 'desk-save__line', dm.save.line));
|
|
603
|
+
bar.appendChild(chipBtn('desk-chip--quiet', 'undo', function () { desk.order[k] = null; draw(); }));
|
|
604
|
+
bar.appendChild(chipBtn('desk-chip--primary', dm.save.label, function () { saveOrder(dm, k); }));
|
|
605
|
+
el.appendChild(bar);
|
|
606
|
+
}
|
|
598
607
|
// the record line: what you just did here, then what this building closed
|
|
599
608
|
var rec = node('div', 'desk-record');
|
|
600
609
|
rec.appendChild(node('span', 'desk-record__label', 'record'));
|
|
@@ -606,6 +615,7 @@
|
|
|
606
615
|
var row = node('div', 'desk-row' + (r.needs ? ' desk-row--needs' : ''));
|
|
607
616
|
row.setAttribute('data-row', r.key);
|
|
608
617
|
var main = node('div', 'desk-row__main');
|
|
618
|
+
if (r.pos) main.appendChild(node('span', 'desk-pos', String(r.pos)));
|
|
609
619
|
main.appendChild(node('span', 'desk-ini', r.ini));
|
|
610
620
|
var name = node('button', 'desk-name');
|
|
611
621
|
name.setAttribute('type', 'button');
|
|
@@ -615,10 +625,33 @@
|
|
|
615
625
|
name.appendChild(node('span', 'desk-name__f', r.fact));
|
|
616
626
|
name.addEventListener('click', function () { desk.open = desk.open === r.key ? null : r.key; desk.ask = null; draw(); focusRow(r.key, 'name'); });
|
|
617
627
|
main.appendChild(name);
|
|
628
|
+
// promises kept of total, or a vote's seats: one pip each
|
|
629
|
+
if (r.pips && r.pips.length) {
|
|
630
|
+
var pips = node('span', 'desk-pips');
|
|
631
|
+
pips.setAttribute('aria-hidden', 'true');
|
|
632
|
+
r.pips.forEach(function (p) { pips.appendChild(node('span', 'desk-pip' + (p.on ? ' desk-pip--on' : ''))); });
|
|
633
|
+
main.appendChild(pips);
|
|
634
|
+
}
|
|
618
635
|
var acts = node('span', 'desk-acts');
|
|
619
636
|
if (r.wait) acts.appendChild(node('span', 'desk-wait', r.wait));
|
|
637
|
+
if (r.order) {
|
|
638
|
+
// the order's two arrows: the move is the viewer's until saved
|
|
639
|
+
[['up', -1, 'M6 15l6-6 6 6'], ['down', 1, 'M6 9l6 6 6-6']].forEach(function (m) {
|
|
640
|
+
if (!r.actions.some(function (a) { return a.key === m[0]; })) return;
|
|
641
|
+
var b = node('button', 'desk-arrow');
|
|
642
|
+
b.setAttribute('type', 'button');
|
|
643
|
+
b.setAttribute('aria-label', 'move ' + r.name + ' ' + m[0]);
|
|
644
|
+
// the top goal cannot go up, nor the last down
|
|
645
|
+
if (desk.busy || (m[1] < 0 && r.top) || (m[1] > 0 && r.bottom)) b.setAttribute('disabled', '');
|
|
646
|
+
b.appendChild(icon(m[2], null, 14));
|
|
647
|
+
b.addEventListener('click', function () { moveRow(r, m[1]); });
|
|
648
|
+
acts.appendChild(b);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
620
651
|
// the primary button sits rightmost, as on the board
|
|
621
|
-
|
|
652
|
+
// a row whose chips stand open is decided by its chip alone: no button for it
|
|
653
|
+
if (!r.order) r.actions.slice().reverse().forEach(function (a) {
|
|
654
|
+
if (r.show && a.key === r.show) return;
|
|
622
655
|
acts.appendChild(chipBtn('desk-chip--' + a.tone, a.label, function () {
|
|
623
656
|
if (a.go) { window.location.href = a.go; return; }
|
|
624
657
|
if (a.ask) { var at = r.key + '|' + a.key; desk.ask = desk.ask === at ? null : at; desk.open = null; draw(); focusRow(r.key, 'chip'); return; }
|
|
@@ -633,6 +666,8 @@
|
|
|
633
666
|
row.appendChild(facts);
|
|
634
667
|
}
|
|
635
668
|
var asked = desk.ask && desk.ask.indexOf(r.key + '|') === 0 ? (r.choices || {})[desk.ask.slice(r.key.length + 1)] : null;
|
|
669
|
+
// a blocker's options stand open: deciding it is picking one
|
|
670
|
+
if (!asked && r.show) asked = (r.choices || {})[r.show] || null;
|
|
636
671
|
if (asked) {
|
|
637
672
|
var chips = node('div', 'desk-choices');
|
|
638
673
|
chips.appendChild(node('span', 'desk-choices__ask', asked.ask));
|
|
@@ -653,6 +688,35 @@
|
|
|
653
688
|
}
|
|
654
689
|
return row;
|
|
655
690
|
}
|
|
691
|
+
// The Priorities desk (task 1004560): a move is kept in this visit until saved.
|
|
692
|
+
function moveRow(r, by) {
|
|
693
|
+
var D = window.GovDesk, st = view.inside;
|
|
694
|
+
if (!D || !st || !desk.model) return;
|
|
695
|
+
var k = st.key + ':' + (view.insideModel ? view.insideModel.selFloor : 0);
|
|
696
|
+
var now = desk.model.rows.filter(function (x) { return x.order; }).map(function (x) { return x.key; });
|
|
697
|
+
desk.order[k] = D.moved(now, r.key, by);
|
|
698
|
+
draw();
|
|
699
|
+
focusRow(r.key, 'arrow');
|
|
700
|
+
}
|
|
701
|
+
// Save: the order goes to POST /goals/reorder, or is filed for the council;
|
|
702
|
+
// the server's own checks decide either way.
|
|
703
|
+
function saveOrder(dm, k) {
|
|
704
|
+
var C = window.GovMatterCard;
|
|
705
|
+
var req = C && C.orderRequest ? C.orderRequest(dm.save) : null;
|
|
706
|
+
if (!req || desk.busy) return Promise.resolve(false);
|
|
707
|
+
desk.busy = true;
|
|
708
|
+
draw();
|
|
709
|
+
return send(req).then(function (out) {
|
|
710
|
+
desk.busy = false;
|
|
711
|
+
if (!out.ok) { say(C.refusal(out.status, out.body)); draw(); return false; }
|
|
712
|
+
desk.order[k] = null;
|
|
713
|
+
desk.mine[k] = [dm.save.council ? 'order sent to council' : 'order set'].concat(desk.mine[k] || []);
|
|
714
|
+
bumpToday();
|
|
715
|
+
say(dm.save.said);
|
|
716
|
+
draw();
|
|
717
|
+
return load().then(function () { return true; });
|
|
718
|
+
});
|
|
719
|
+
}
|
|
656
720
|
function deskRowsEl() {
|
|
657
721
|
var st = view.insideModel;
|
|
658
722
|
var on = $(st && st.flipA ? 'insideOpenA' : 'insideOpenB');
|
|
@@ -667,6 +731,7 @@
|
|
|
667
731
|
if (rows[i].getAttribute('data-row') !== key) continue;
|
|
668
732
|
var main = rows[i].children[0];
|
|
669
733
|
var target = what === 'chip' && rows[i].children[1] ? rows[i].children[1].children[1] : main && main.children[1];
|
|
734
|
+
if (what === 'arrow' && main) target = main.querySelector ? main.querySelector('.desk-arrow') : target;
|
|
670
735
|
if (target && target.focus) target.focus();
|
|
671
736
|
return;
|
|
672
737
|
}
|
|
@@ -685,6 +750,8 @@
|
|
|
685
750
|
switch_on: 'switched on', switch_off: 'switched off', allow: 'allowed', disallow: 'disallowed', first: 'put first',
|
|
686
751
|
pause: 'paused', resume: 'resumed', tier: 'new tier', deactivate: 'deactivated', give: 'gave', take: 'took back',
|
|
687
752
|
check: 'checked', rot: 'set', module_on: 'turned on', module_off: 'turned off', backup: 'backed up', invite: 'invited',
|
|
753
|
+
decide: 'decided', ratify: 'ratified', ratify_goal: 'made a goal of',
|
|
754
|
+
budget: 'set the limit of', unsign: 'undid',
|
|
688
755
|
};
|
|
689
756
|
// One act from a row: checked, sent through the card's own request (the
|
|
690
757
|
// server's permission checks decide), then the row clears, the floor counts
|
|
@@ -699,7 +766,9 @@
|
|
|
699
766
|
var req = C.request(r.card, key, input);
|
|
700
767
|
var settle = function (body) {
|
|
701
768
|
var a = C.answer(r.card, key, body);
|
|
702
|
-
|
|
769
|
+
// an act at a building a sitting decides went to it as a matter (task 1004561)
|
|
770
|
+
var sent = r.card.subject && r.card.subject.council && req && /\/government\/board\/matters$/.test(req.url);
|
|
771
|
+
desk.mine[k] = [(sent ? 'sent to council:' : DONE_WORDS[key]) + ' ' + r.name].concat(desk.mine[k] || []);
|
|
703
772
|
desk.done[r.matter] = true;
|
|
704
773
|
desk.open = null; desk.ask = null;
|
|
705
774
|
if (desk.model && !desk.model.rows.some(function (x) { return x.matter !== r.matter; })) desk.cleared[k] = true;
|
|
@@ -681,6 +681,130 @@
|
|
|
681
681
|
".desk-rows"
|
|
682
682
|
]
|
|
683
683
|
}
|
|
684
|
+
},
|
|
685
|
+
"desk-planning-priorities": {
|
|
686
|
+
"auth": true,
|
|
687
|
+
"_": "Task 1004560: Planning, Priorities. The open goals in order with promise pips and two arrows; one goal moved down, so the save bar says the new order goes to the council and how many yes it needs, with undo.",
|
|
688
|
+
"url": "/builders/street#building=planning",
|
|
689
|
+
"actions": [
|
|
690
|
+
[
|
|
691
|
+
"wait",
|
|
692
|
+
900
|
|
693
|
+
],
|
|
694
|
+
[
|
|
695
|
+
"clickIfVisible",
|
|
696
|
+
"[data-craft-close]"
|
|
697
|
+
],
|
|
698
|
+
[
|
|
699
|
+
"wait",
|
|
700
|
+
300
|
|
701
|
+
],
|
|
702
|
+
[
|
|
703
|
+
"click",
|
|
704
|
+
"[data-floor=\"0\"]"
|
|
705
|
+
],
|
|
706
|
+
[
|
|
707
|
+
"wait",
|
|
708
|
+
600
|
|
709
|
+
],
|
|
710
|
+
[
|
|
711
|
+
"click",
|
|
712
|
+
".desk-arrow[aria-label$=\" down\"]"
|
|
713
|
+
],
|
|
714
|
+
[
|
|
715
|
+
"wait",
|
|
716
|
+
400
|
|
717
|
+
]
|
|
718
|
+
],
|
|
719
|
+
"expect": {
|
|
720
|
+
"visible": [
|
|
721
|
+
".desk-rows",
|
|
722
|
+
".desk-pips",
|
|
723
|
+
".desk-save"
|
|
724
|
+
]
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
"desk-planning-ideas": {
|
|
728
|
+
"auth": true,
|
|
729
|
+
"_": "Task 1004560: Planning, Ideas to ratify. A Full Idea in its vote with seat pips, a promise to ratify in one tap, and a carried Full Idea made a goal from a version chip.",
|
|
730
|
+
"url": "/builders/street#building=planning",
|
|
731
|
+
"actions": [
|
|
732
|
+
[
|
|
733
|
+
"wait",
|
|
734
|
+
900
|
|
735
|
+
],
|
|
736
|
+
[
|
|
737
|
+
"clickIfVisible",
|
|
738
|
+
"[data-craft-close]"
|
|
739
|
+
],
|
|
740
|
+
[
|
|
741
|
+
"wait",
|
|
742
|
+
300
|
|
743
|
+
],
|
|
744
|
+
[
|
|
745
|
+
"click",
|
|
746
|
+
"[data-floor=\"1\"]"
|
|
747
|
+
],
|
|
748
|
+
[
|
|
749
|
+
"wait",
|
|
750
|
+
600
|
|
751
|
+
],
|
|
752
|
+
[
|
|
753
|
+
"focus",
|
|
754
|
+
".desk-rows .desk-row:last-child .desk-name"
|
|
755
|
+
],
|
|
756
|
+
[
|
|
757
|
+
"wait",
|
|
758
|
+
300
|
|
759
|
+
]
|
|
760
|
+
],
|
|
761
|
+
"expect": {
|
|
762
|
+
"visible": [
|
|
763
|
+
".desk-rows"
|
|
764
|
+
]
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
"desk-planning-blockers": {
|
|
768
|
+
"auth": true,
|
|
769
|
+
"_": "Task 1004560: Planning, Waiting on you. Each blocker decided by one of its options as chips standing open; go ahead, not now, need more when it lists none.",
|
|
770
|
+
"url": "/builders/street#building=planning",
|
|
771
|
+
"actions": [
|
|
772
|
+
[
|
|
773
|
+
"wait",
|
|
774
|
+
900
|
|
775
|
+
],
|
|
776
|
+
[
|
|
777
|
+
"clickIfVisible",
|
|
778
|
+
"[data-craft-close]"
|
|
779
|
+
],
|
|
780
|
+
[
|
|
781
|
+
"wait",
|
|
782
|
+
300
|
|
783
|
+
],
|
|
784
|
+
[
|
|
785
|
+
"click",
|
|
786
|
+
"[data-floor=\"2\"]"
|
|
787
|
+
],
|
|
788
|
+
[
|
|
789
|
+
"wait",
|
|
790
|
+
600
|
|
791
|
+
],
|
|
792
|
+
[
|
|
793
|
+
"focus",
|
|
794
|
+
".desk-rows .desk-row:last-child .desk-name"
|
|
795
|
+
],
|
|
796
|
+
[
|
|
797
|
+
"wait",
|
|
798
|
+
300
|
|
799
|
+
]
|
|
800
|
+
],
|
|
801
|
+
"expect": {
|
|
802
|
+
"visible": [
|
|
803
|
+
".desk-rows",
|
|
804
|
+
".desk-choices",
|
|
805
|
+
".desk-status--hot"
|
|
806
|
+
]
|
|
807
|
+
}
|
|
684
808
|
}
|
|
685
809
|
},
|
|
686
810
|
"reducedMotion": {
|
|
@@ -176,9 +176,9 @@
|
|
|
176
176
|
|
|
177
177
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
178
178
|
|
|
179
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
180
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
181
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
179
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
180
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
181
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
182
182
|
<script src="/builders/palette.js" defer></script>
|
|
183
183
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
184
184
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -1206,8 +1206,8 @@ html[data-fullscreen="on"][data-fullscreen-plain="on"] .app-content { padding-to
|
|
|
1206
1206
|
/* THE CRAFT MODE (task 1004492, craft-mode.js). The top bar's switch: one pill
|
|
1207
1207
|
holding the four crafts and Legacy, the current one lit. Narrower windows
|
|
1208
1208
|
drop the words, and a phone shows only the current mode (or "Mode"), which
|
|
1209
|
-
opens the picker. The picker below asks once per session.
|
|
1210
|
-
|
|
1209
|
+
opens the picker. The picker below asks once per session. Each mode's look
|
|
1210
|
+
(its colour on the current pill) is THE CRAFT MODE LOOKS, further down. */
|
|
1211
1211
|
.craft-switch {
|
|
1212
1212
|
display: inline-flex;
|
|
1213
1213
|
align-items: center;
|
|
@@ -1431,6 +1431,125 @@ html.craft-picker-open { overflow: hidden; }
|
|
|
1431
1431
|
.craft-picker__legacy { height: auto; min-height: 40px; white-space: normal; text-align: left; }
|
|
1432
1432
|
}
|
|
1433
1433
|
|
|
1434
|
+
/* THE CRAFT MODE LOOKS (task 1004495; direction docs/design/craft-mode-direction.md).
|
|
1435
|
+
Three modes wear a look: Artistry, Ideating, Governing. Building, Legacy and no
|
|
1436
|
+
mode wear none — nothing below names them, so they render as the hall always
|
|
1437
|
+
did (tests/craft_mode_looks.mjs pins it). --craft marks WHERE you are, never
|
|
1438
|
+
what to press: the accent stays the action colour in every mode. Light values
|
|
1439
|
+
clear 5.4:1 on the hall's paper; the dark values are the prototype's. Light
|
|
1440
|
+
keeps the paper ground; only dark changes the ground and the glass. */
|
|
1441
|
+
html[data-craft="artistry"] { --craft: #2e3c6e; --craft-on: #f1e6d2; }
|
|
1442
|
+
html[data-craft="ideating"] { --craft: #7a5a0c; --craft-on: #fdf6dd; }
|
|
1443
|
+
html[data-craft="governing"] { --craft: #33479e; --craft-on: #eef1ff; }
|
|
1444
|
+
html[data-theme="dark"][data-craft="artistry"] {
|
|
1445
|
+
--craft: #f1e6d2; --craft-on: #0c1122;
|
|
1446
|
+
--bg: #07080d; --bg-frame: #0a0b12;
|
|
1447
|
+
--glass: rgba(150, 170, 230, .11); --glass-strong: rgba(170, 190, 240, .17);
|
|
1448
|
+
--glass-edge: rgba(241, 230, 210, .16);
|
|
1449
|
+
}
|
|
1450
|
+
html[data-theme="dark"][data-craft="ideating"] {
|
|
1451
|
+
--craft: #f2d27a; --craft-on: #1d1604;
|
|
1452
|
+
--bg: #05060d;
|
|
1453
|
+
--glass: rgba(140, 150, 225, .10); --glass-strong: rgba(160, 170, 240, .16);
|
|
1454
|
+
--glass-edge: rgba(242, 210, 122, .16);
|
|
1455
|
+
}
|
|
1456
|
+
html[data-theme="dark"][data-craft="governing"] {
|
|
1457
|
+
--craft: #aabeff; --craft-on: #0a1030;
|
|
1458
|
+
--glass-edge: rgba(170, 190, 255, .16);
|
|
1459
|
+
}
|
|
1460
|
+
/* The shared treatment: the switch's current pill, the current tab and the top
|
|
1461
|
+
bar's hairline take the mode colour. */
|
|
1462
|
+
html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing"]) .craft-switch__opt[aria-pressed="true"] {
|
|
1463
|
+
background: var(--craft); color: var(--craft-on);
|
|
1464
|
+
}
|
|
1465
|
+
html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing"]) .nav-item--active {
|
|
1466
|
+
background: color-mix(in srgb, var(--craft) 14%, transparent); color: var(--craft);
|
|
1467
|
+
}
|
|
1468
|
+
html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing"]) .nav-item--active .nav-icon,
|
|
1469
|
+
html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing"]) .nav-item--active .nav-item__label { color: var(--craft); }
|
|
1470
|
+
html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing"]) .nav-item--active::before { background: var(--craft); }
|
|
1471
|
+
html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing"]) .app-topbar {
|
|
1472
|
+
border-bottom-color: color-mix(in srgb, var(--craft) 30%, transparent);
|
|
1473
|
+
}
|
|
1474
|
+
/* The header mark: the mode's own words beside the page title (craft-mode.js
|
|
1475
|
+
applyLook). Hidden on a phone — the switch's one pill already says the mode,
|
|
1476
|
+
and the bar has no room for both. */
|
|
1477
|
+
.craft-eyebrow {
|
|
1478
|
+
font-family: var(--label-face);
|
|
1479
|
+
font-size: 11px;
|
|
1480
|
+
font-weight: 500;
|
|
1481
|
+
letter-spacing: .14em;
|
|
1482
|
+
text-transform: uppercase;
|
|
1483
|
+
color: var(--craft);
|
|
1484
|
+
white-space: nowrap;
|
|
1485
|
+
flex-shrink: 0;
|
|
1486
|
+
}
|
|
1487
|
+
@media (max-width: 640px) { .craft-eyebrow { display: none; } }
|
|
1488
|
+
/* The signature: the mode's door scene (craft-mode.js artHtml, the same art the
|
|
1489
|
+
picker draws) framed under the brand, 40px in the closed rail and 64px open.
|
|
1490
|
+
The 110px-tall scene is scaled to the plate's height and widened to fill it,
|
|
1491
|
+
never redrawn: each scene's pieces are placed from its edges or its centre. */
|
|
1492
|
+
.craft-plate {
|
|
1493
|
+
position: relative;
|
|
1494
|
+
height: 40px;
|
|
1495
|
+
margin: 10px 8px 0;
|
|
1496
|
+
border-radius: var(--radius-sm);
|
|
1497
|
+
overflow: hidden;
|
|
1498
|
+
flex-shrink: 0;
|
|
1499
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--craft) 30%, transparent);
|
|
1500
|
+
}
|
|
1501
|
+
.craft-plate .craft-art {
|
|
1502
|
+
position: absolute;
|
|
1503
|
+
left: 0;
|
|
1504
|
+
top: 0;
|
|
1505
|
+
width: calc(100% / .364);
|
|
1506
|
+
height: 110px;
|
|
1507
|
+
transform: scale(.364);
|
|
1508
|
+
transform-origin: 0 0;
|
|
1509
|
+
}
|
|
1510
|
+
.craft-plate .craft-art__city { top: 0; }
|
|
1511
|
+
.app-sidebar:hover .craft-plate,
|
|
1512
|
+
.app-sidebar:focus-within .craft-plate { height: 64px; margin: 10px 16px 0; }
|
|
1513
|
+
.app-sidebar:hover .craft-plate .craft-art,
|
|
1514
|
+
.app-sidebar:focus-within .craft-plate .craft-art { width: calc(100% / .582); transform: scale(.582); }
|
|
1515
|
+
@media (max-width: 768px) {
|
|
1516
|
+
.craft-plate { height: 64px; margin: 10px 16px 0; }
|
|
1517
|
+
.craft-plate .craft-art { width: calc(100% / .582); transform: scale(.582); }
|
|
1518
|
+
}
|
|
1519
|
+
/* The plate follows the theme (owner, 2026-10-03): light shows each scene's day. */
|
|
1520
|
+
html[data-theme="light"] .craft-plate .craft-art--artistry { background: #dfe3ef; }
|
|
1521
|
+
html[data-theme="light"] .craft-plate .craft-art__dot--star { background: #2e3c6e; }
|
|
1522
|
+
html[data-theme="light"] .craft-plate .craft-art__moon { background: #f3c98b; box-shadow: 0 0 0 9px rgba(243, 201, 139, .28); }
|
|
1523
|
+
html[data-theme="light"] .craft-plate .craft-art__ground { background: #c3c9dc; }
|
|
1524
|
+
html[data-theme="light"] .craft-plate .craft-art__house { background: #8d97b8; }
|
|
1525
|
+
html[data-theme="light"] .craft-plate .craft-art--ideating { background: #f6f0dc; }
|
|
1526
|
+
html[data-theme="light"] .craft-plate .craft-art__dot--gold { background: #c9a23a; }
|
|
1527
|
+
html[data-theme="light"] .craft-plate .craft-art__dot--glow { background: #b8891c; box-shadow: 0 0 10px rgba(184, 137, 28, .45); }
|
|
1528
|
+
html[data-theme="light"] .craft-plate .craft-art__dot--proto { box-shadow: inset 0 0 0 1px rgba(122, 90, 12, .6); }
|
|
1529
|
+
html[data-theme="light"] .craft-plate .craft-art__ring { box-shadow: inset 0 0 0 1px rgba(122, 90, 12, .35); }
|
|
1530
|
+
html[data-theme="light"] .craft-plate .craft-art--governing { background: #e9ecf6; }
|
|
1531
|
+
html[data-theme="light"] .craft-plate .craft-art__city { filter: invert(1) hue-rotate(180deg); }
|
|
1532
|
+
/* Ideating's star dust: a few faint fixed specks across the rail itself. */
|
|
1533
|
+
html[data-craft="ideating"] .app-sidebar {
|
|
1534
|
+
background-image:
|
|
1535
|
+
radial-gradient(1px 1px at 14px 40px, rgba(242, 210, 122, .5), transparent),
|
|
1536
|
+
radial-gradient(1px 1px at 46px 130px, rgba(242, 210, 122, .35), transparent),
|
|
1537
|
+
radial-gradient(1.5px 1.5px at 120px 90px, rgba(242, 210, 122, .4), transparent),
|
|
1538
|
+
radial-gradient(1px 1px at 200px 190px, rgba(242, 210, 122, .35), transparent),
|
|
1539
|
+
radial-gradient(1px 1px at 30px 230px, rgba(242, 210, 122, .3), transparent),
|
|
1540
|
+
radial-gradient(1.5px 1.5px at 170px 260px, rgba(242, 210, 122, .3), transparent);
|
|
1541
|
+
background-size: 246px 280px;
|
|
1542
|
+
}
|
|
1543
|
+
html[data-theme="light"][data-craft="ideating"] .app-sidebar {
|
|
1544
|
+
background-image:
|
|
1545
|
+
radial-gradient(1px 1px at 14px 40px, rgba(184, 137, 28, .45), transparent),
|
|
1546
|
+
radial-gradient(1px 1px at 46px 130px, rgba(184, 137, 28, .3), transparent),
|
|
1547
|
+
radial-gradient(1.5px 1.5px at 120px 90px, rgba(184, 137, 28, .35), transparent),
|
|
1548
|
+
radial-gradient(1px 1px at 200px 190px, rgba(184, 137, 28, .3), transparent),
|
|
1549
|
+
radial-gradient(1px 1px at 30px 230px, rgba(184, 137, 28, .25), transparent),
|
|
1550
|
+
radial-gradient(1.5px 1.5px at 170px 260px, rgba(184, 137, 28, .25), transparent);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1434
1553
|
/* THE BACK LINK (task 1004430): at the head of the bar when the page was
|
|
1435
1554
|
reached from inside another hall page. 24px floor on both axes. */
|
|
1436
1555
|
.topbar-back {
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
|
|
55
55
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
56
56
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
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
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -138,9 +138,9 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
142
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
143
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
141
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
142
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
143
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
144
144
|
<script src="/builders/palette.js" defer></script>
|
|
145
145
|
|
|
146
146
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -142,9 +142,9 @@
|
|
|
142
142
|
|
|
143
143
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
144
144
|
|
|
145
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
146
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
147
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
145
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
146
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
147
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
148
148
|
<script src="/builders/palette.js" defer></script>
|
|
149
149
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
150
150
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
@@ -162,9 +162,9 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
166
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
167
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
165
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
166
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
167
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
168
168
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
169
169
|
<script src="/builders/palette.js" defer></script>
|
|
170
170
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,9 +171,9 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell-trail.js?v=2026-10-03-
|
|
175
|
-
<script src="/builders/craft-mode.js?v=2026-10-03-
|
|
176
|
-
<script src="/builders/shell.js?v=2026-10-03-
|
|
174
|
+
<script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
|
|
175
|
+
<script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
|
|
176
|
+
<script src="/builders/shell.js?v=2026-10-03-looks"></script>
|
|
177
177
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
178
178
|
<script src="/builders/palette.js" defer></script>
|
|
179
179
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
|
@@ -27,3 +27,11 @@ Covers: every shelled hall page (`craft-mode.js`, `shell.js`, `style.css`), and
|
|
|
27
27
|
- **Government shows only in Governing**, and outside that group Governing is the shortest list (Blockers, Board Room). Building is every tab except Government and the Board Room; Artistry is Studio, Goals, Your thinking; Ideating is Sky, Your thinking, Goals. The prototype names rooms that do not exist yet (Store, Page reviews, Treasury); a list names only real NAV items, and `tests/craft_mode_nav.mjs` requires every NAV item to sit in some mode.
|
|
28
28
|
- **The mode only ever takes away, and never through `hidden`.** The access gate owns `hidden`; `applyNav()` marks what the mode leaves out with `[data-craft-off]`, which `style.css` hides. So a mode can never show a tab the gate hid, and switching mode just moves the attribute. A group head goes too once nothing under it shows.
|
|
29
29
|
- **Two callers.** `apply()` runs it on every mode change (live, no reload), and `shell.js` `applyAccess` runs it right after its gate loop, so a later `/me` answer (a promotion, a permission) brings a head back with its tabs. Hidden tabs still open by their URL; nothing is enforced (ADR 0016).
|
|
30
|
+
|
|
31
|
+
## Each mode's look (task 1004495)
|
|
32
|
+
|
|
33
|
+
- **Three modes wear a look; Building, Legacy and no mode wear none.** Artistry, Ideating and Governing each set `--craft` (where you are) and `--craft-on` (text on it) on `html[data-craft="<mode>"]`, with a dark twin on `html[data-theme="dark"][data-craft="<mode>"]` that also darkens the ground and tints the glass (Artistry, Ideating). Values and contrast are the direction's, `docs/design/craft-mode-direction.md`. No rule in `style.css` names `building` or `legacy`, and none matches any mode at all; `tests/craft_mode_looks.mjs` pins both, and re-measures every colour pair.
|
|
34
|
+
- **The accent stays the action colour.** `--craft` paints only the switch's current pill, the current tab (tint, icon, label, marker) and the top bar's hairline.
|
|
35
|
+
- **Two pieces are drawn by `craft-mode.js` `applyLook()`, on every mode change:** the eyebrow beside `#topbar-title` (`LOOKS`: "the studio · night", "the sky", "the city"; hidden at <=640px, where it overflowed phones) and the scene plate under `.app-sidebar__brand` (40px closed rail, 64px open). The plate's markup IS `artHtml(mode)`, the picker's door art, exported rather than copied; light mode recolours it to each scene's day (the city is the dark one through `invert(1) hue-rotate(180deg)`). It does not animate.
|
|
36
|
+
- **Always-present tabs widened** (owner, 2026-10-03): `ALWAYS_TABS` is Builders, Collab and Profile, so every mode keeps the You and Community groups.
|
|
37
|
+
- **Governing's header tallies** ("needs you · moving") are task 1004563, not this look.
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
const api = require('../../src/module-api');
|
|
21
21
|
const blockers = require('./blockers');
|
|
22
|
+
const ratify = require('./ratify');
|
|
22
23
|
|
|
23
24
|
const POINT = 'governor.docket';
|
|
24
25
|
const DOCKET_BLOCKER_LIMIT = 50;
|
|
@@ -41,9 +42,96 @@ function blockerDocketMatter(b, mayResolve) {
|
|
|
41
42
|
tally: null,
|
|
42
43
|
href: `/blockers#blocker-${b.id}`,
|
|
43
44
|
ref: { type: 'blocker', id: b.id },
|
|
45
|
+
// the floor desk's one act (task 1004560): decide, by one of its options
|
|
46
|
+
...(mayResolve ? { acts: [{ key: 'decide', options: blockerOptions(b.body_md) }] } : {}),
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
// A blocker's options as one-tap chips (task 1004560). Its own, when its body
|
|
51
|
+
// lists them as "Option A: …" lines (two to five of them); otherwise the three
|
|
52
|
+
// answers every blocker takes. The chosen key becomes the resolution note, so
|
|
53
|
+
// a key is the option's own words (at most 64 characters) and the label its
|
|
54
|
+
// first words. Pure.
|
|
55
|
+
const DEFAULT_CHOICES = Object.freeze(['go ahead', 'not now', 'need more']);
|
|
56
|
+
const OPTION_LINE = /^\s*(?:[-*]|\d+[.)])?\s*(?:\*\*)?option\s+[A-Za-z0-9]{1,2}(?:\*\*)?\s*[:.)—-]+\s*(?:\*\*)?(.+)$/i;
|
|
57
|
+
function plainWords(s) { return String(s).replace(/[*_`[\]]+/g, '').replace(/\([^)]*\)/g, '').replace(/\s+/g, ' ').trim(); }
|
|
58
|
+
function shortLabel(s) {
|
|
59
|
+
let out = '';
|
|
60
|
+
for (const w of s.split(' ')) { const next = out ? out + ' ' + w : w; if (next.length > 16) break; out = next; }
|
|
61
|
+
return out || s.slice(0, 16);
|
|
62
|
+
}
|
|
63
|
+
function blockerOptions(bodyMd) {
|
|
64
|
+
const found = [];
|
|
65
|
+
for (const line of String(bodyMd || '').split(/\r?\n/)) {
|
|
66
|
+
const m = OPTION_LINE.exec(line);
|
|
67
|
+
if (!m) continue;
|
|
68
|
+
const text = plainWords(m[1]).split(/\s[—–-]\s|[.;:]\s/)[0].trim();
|
|
69
|
+
if (text && !found.some((o) => o.key === text.slice(0, 64))) found.push({ key: text.slice(0, 64), label: shortLabel(text) });
|
|
70
|
+
}
|
|
71
|
+
if (found.length >= 2 && found.length <= 5) return found;
|
|
72
|
+
return DEFAULT_CHOICES.map((t) => ({ key: t, label: t }));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The Ideas-to-ratify desk (task 1004560): the ideas a ratifier may take from
|
|
76
|
+
// the floor, under idea.ratify (both ratify routes' atom). A criterion proposal
|
|
77
|
+
// ratifies in one tap (POST /inbox/:id/ratify). A Full Idea the board has
|
|
78
|
+
// carried becomes a goal (POST /inbox/:id/ratify-goal) with its target version
|
|
79
|
+
// as a chip — only when its answers compose a goal on their own; one that needs
|
|
80
|
+
// the ratifier to write its criterion opens its page instead. A Full Idea still
|
|
81
|
+
// in its sitting is not here: it is the board's vote row (ADR 0175 §8). Pure.
|
|
82
|
+
function ratifyDeskRows(ideas, versions, passed) {
|
|
83
|
+
const chips = (versions || []).slice(0, 5).map((v) => ({ key: String(v.id), label: String(v.id) }));
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const i of ideas || []) {
|
|
86
|
+
if (!i || i.status !== 'open') continue;
|
|
87
|
+
const href = `/idea/${i.id}`;
|
|
88
|
+
if (i.kind === 'criterion-proposal') {
|
|
89
|
+
let p = null;
|
|
90
|
+
try { p = ratify.parseProposalBody(i.body_md); } catch (_) { p = null; }
|
|
91
|
+
if (!p || !p.target_version) continue;
|
|
92
|
+
out.push({
|
|
93
|
+
id: `p${i.id}`, name: String(i.title || 'a promise'), ref: { type: 'criterion_proposal', id: i.id }, href, opened_at: i.created_at,
|
|
94
|
+
fact: `a promise for ${p.target_version}`, facts: [String(p.criterion_md || '').slice(0, 80)],
|
|
95
|
+
acts: [{ key: 'ratify' }, { key: 'open' }],
|
|
96
|
+
});
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (i.grade !== 'full' || !passed.has(String(i.id))) continue;
|
|
100
|
+
const composed = ratify.composeGoalFromFullIdea({ ideaId: i.id, templateKey: i.template_key, fieldValues: i.field_values });
|
|
101
|
+
const fits = composed && composed.ok && chips.length > 0;
|
|
102
|
+
out.push({
|
|
103
|
+
id: `g${i.id}`, name: String(i.title || 'a full idea'), ref: { type: 'full_idea', id: i.id }, href, opened_at: i.created_at,
|
|
104
|
+
fact: 'the board carried it', facts: [fits ? 'becomes a goal' : 'needs its page to finish'],
|
|
105
|
+
acts: fits ? [{ key: 'ratify_goal', options: chips }, { key: 'open' }] : [{ key: 'open' }],
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
// The desk reads the oldest 30 open proposals and Full Ideas (older first, so
|
|
111
|
+
// nothing waits forever behind newer ones); which Full Ideas the board carried
|
|
112
|
+
// is one read for all of them (government.boardPassedIdeaIds).
|
|
113
|
+
const RATIFY_IDEAS_LIMIT = 30;
|
|
114
|
+
async function readRatifyDesk() {
|
|
115
|
+
const { rows: ideas } = await api.pool.query(
|
|
116
|
+
`SELECT i.id, i.title, i.kind, i.status, i.body_md, i.created_at,
|
|
117
|
+
COALESCE(g.grade, 'quick') AS grade, g.template_key, g.field_values
|
|
118
|
+
FROM idea_inbox i LEFT JOIN ideas_grades g ON g.idea_id = i.id
|
|
119
|
+
WHERE i.status = 'open' AND (i.kind = 'criterion-proposal' OR g.grade = 'full')
|
|
120
|
+
ORDER BY i.created_at, i.id LIMIT $1`,
|
|
121
|
+
[RATIFY_IDEAS_LIMIT],
|
|
122
|
+
);
|
|
123
|
+
const government = api.resolveOptional && api.resolveOptional('government');
|
|
124
|
+
const passed = new Set();
|
|
125
|
+
const full = ideas.filter((i) => i.grade === 'full').map((i) => i.id);
|
|
126
|
+
if (full.length && government && typeof government.boardPassedIdeaIds === 'function') {
|
|
127
|
+
(await government.boardPassedIdeaIds(full)).forEach((id) => passed.add(String(id)));
|
|
128
|
+
}
|
|
129
|
+
const { rows: versions } = passed.size
|
|
130
|
+
? await api.pool.query(`SELECT id, name FROM versions WHERE status IN ('building', 'planning') ORDER BY (status = 'building') DESC, id LIMIT 5`)
|
|
131
|
+
: { rows: [] };
|
|
132
|
+
return ratifyDeskRows(ideas, versions, passed);
|
|
133
|
+
}
|
|
134
|
+
|
|
47
135
|
// The rules-at-work reading from this month's routed filings. Pure.
|
|
48
136
|
function rulesReading(settledThisMonth) {
|
|
49
137
|
const n = Number(settledThisMonth) || 0;
|
|
@@ -114,6 +202,7 @@ function contributeIdeasToDocket() {
|
|
|
114
202
|
return rows.map((b) => blockerDocketMatter(b, mayResolve));
|
|
115
203
|
},
|
|
116
204
|
});
|
|
205
|
+
api.contribute(POINT, { desk: 'planning.ratify', gate: { permission: 'idea.ratify' }, read: () => readRatifyDesk() });
|
|
117
206
|
// A count, no rows: any builder may read it.
|
|
118
207
|
api.contribute(POINT, {
|
|
119
208
|
reading: 'rules',
|
|
@@ -127,6 +216,8 @@ function contributeIdeasToDocket() {
|
|
|
127
216
|
module.exports = {
|
|
128
217
|
contributeIdeasToDocket,
|
|
129
218
|
blockerDocketMatter,
|
|
219
|
+
blockerOptions,
|
|
220
|
+
ratifyDeskRows,
|
|
130
221
|
rulesReading,
|
|
131
222
|
rulesFloor,
|
|
132
223
|
rulesHistory,
|