@bongos/core 1.19.934 → 1.19.936
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 +43 -33
- package/clients/bongos-client/index.d.ts +3 -3
- package/docs/api/openapi.json +12 -0
- package/docs/api-reference.md +3 -3
- package/docs/copy-inventory.md +36 -34
- package/docs/copy-registry.json +56 -38
- package/docs/module-api-changelog.md +4 -0
- package/modules/government/board.js +4 -0
- package/modules/government/db.js +8 -1
- package/modules/hall-ui/public/board-room.css +6 -0
- package/modules/hall-ui/public/board-room.js +31 -0
- package/modules/hall-ui/public/ideas.css +27 -0
- package/modules/hall-ui/public/ideas.html +7 -0
- package/modules/hall-ui/public/ideas.js +99 -3
- package/modules/ideas/grades.js +43 -14
- package/modules/ideas/inbox.js +7 -0
- package/modules/ideas/migrations/ideas_009_recommendation.sql +62 -0
- package/modules/ideas/projection.js +45 -2
- package/modules/ideas/routes/inbox.js +72 -1
- package/modules/ideas/templates.js +38 -10
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/government_board_view.mjs +5 -1
- package/tests/idea_completeness.mjs +31 -5
- package/tests/idea_develop_spark.mjs +6 -1
- package/tests/idea_full_capture.mjs +7 -1
- package/tests/idea_recommendation.mjs +381 -0
- package/tests/idea_templates.mjs +34 -0
package/docs/copy-registry.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"generator": "scripts/gds/copy-inventory.js",
|
|
4
4
|
"goal": 1000074,
|
|
5
5
|
"counts": {
|
|
6
|
-
"strings":
|
|
7
|
-
"unique_ids":
|
|
8
|
-
"certain":
|
|
6
|
+
"strings": 1891,
|
|
7
|
+
"unique_ids": 1634,
|
|
8
|
+
"certain": 1891,
|
|
9
9
|
"heuristic": 0,
|
|
10
10
|
"duplicate_ids": 177
|
|
11
11
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"builders-hall": {
|
|
14
14
|
"at": "modules/hall-ui/public",
|
|
15
15
|
"files": 89,
|
|
16
|
-
"strings":
|
|
16
|
+
"strings": 1401
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
19
19
|
"at": "modules/status-ui/public",
|
|
@@ -109,8 +109,8 @@
|
|
|
109
109
|
"modules/hall-ui/public/gate.js:157",
|
|
110
110
|
"modules/hall-ui/public/hall-render.js:669",
|
|
111
111
|
"modules/hall-ui/public/idea.js:207",
|
|
112
|
-
"modules/hall-ui/public/ideas.js:
|
|
113
|
-
"modules/hall-ui/public/ideas.js:
|
|
112
|
+
"modules/hall-ui/public/ideas.js:193",
|
|
113
|
+
"modules/hall-ui/public/ideas.js:762",
|
|
114
114
|
"modules/hall-ui/public/watch.js:1242"
|
|
115
115
|
]
|
|
116
116
|
},
|
|
@@ -247,7 +247,7 @@
|
|
|
247
247
|
"text": "Restricted.",
|
|
248
248
|
"occurrences": 4,
|
|
249
249
|
"at": [
|
|
250
|
-
"modules/hall-ui/public/board-room.js:
|
|
250
|
+
"modules/hall-ui/public/board-room.js:521",
|
|
251
251
|
"modules/hall-ui/public/gate.js:234",
|
|
252
252
|
"modules/hall-ui/public/government.js:879",
|
|
253
253
|
"modules/hall-ui/public/watch.js:1271"
|
|
@@ -1615,7 +1615,7 @@
|
|
|
1615
1615
|
"occurrences": 2,
|
|
1616
1616
|
"at": [
|
|
1617
1617
|
"modules/hall-ui/public/idea.js:210",
|
|
1618
|
-
"modules/hall-ui/public/ideas.js:
|
|
1618
|
+
"modules/hall-ui/public/ideas.js:534"
|
|
1619
1619
|
]
|
|
1620
1620
|
},
|
|
1621
1621
|
{
|
|
@@ -2048,7 +2048,7 @@
|
|
|
2048
2048
|
"surface": "builders-hall",
|
|
2049
2049
|
"text": ", // membership",
|
|
2050
2050
|
"file": "modules/hall-ui/public/board-room.js",
|
|
2051
|
-
"line":
|
|
2051
|
+
"line": 300,
|
|
2052
2052
|
"origin": "js-markup",
|
|
2053
2053
|
"confidence": "certain"
|
|
2054
2054
|
},
|
|
@@ -2633,7 +2633,7 @@
|
|
|
2633
2633
|
"surface": "builders-hall",
|
|
2634
2634
|
"text": "Answer the five questions and this spark becomes a Full Idea.",
|
|
2635
2635
|
"file": "modules/hall-ui/public/ideas.js",
|
|
2636
|
-
"line":
|
|
2636
|
+
"line": 901,
|
|
2637
2637
|
"origin": "js-text-assign",
|
|
2638
2638
|
"confidence": "certain"
|
|
2639
2639
|
},
|
|
@@ -3200,7 +3200,7 @@
|
|
|
3200
3200
|
"surface": "builders-hall",
|
|
3201
3201
|
"text": "Cast objection",
|
|
3202
3202
|
"file": "modules/hall-ui/public/board-room.js",
|
|
3203
|
-
"line":
|
|
3203
|
+
"line": 363,
|
|
3204
3204
|
"origin": "js-markup",
|
|
3205
3205
|
"confidence": "certain"
|
|
3206
3206
|
},
|
|
@@ -3911,7 +3911,7 @@
|
|
|
3911
3911
|
"surface": "builders-hall",
|
|
3912
3912
|
"text": "Could not load idea {…}.",
|
|
3913
3913
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3914
|
-
"line":
|
|
3914
|
+
"line": 944,
|
|
3915
3915
|
"origin": "js-text-assign",
|
|
3916
3916
|
"confidence": "certain"
|
|
3917
3917
|
},
|
|
@@ -3965,7 +3965,7 @@
|
|
|
3965
3965
|
"surface": "builders-hall",
|
|
3966
3966
|
"text": "Could not load the idea template. Reload, or file from the terminal with capture.js.",
|
|
3967
3967
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3968
|
-
"line":
|
|
3968
|
+
"line": 926,
|
|
3969
3969
|
"origin": "js-text-assign",
|
|
3970
3970
|
"confidence": "certain"
|
|
3971
3971
|
},
|
|
@@ -3992,7 +3992,7 @@
|
|
|
3992
3992
|
"surface": "builders-hall",
|
|
3993
3993
|
"text": "Could not load the queue just now.",
|
|
3994
3994
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3995
|
-
"line":
|
|
3995
|
+
"line": 865,
|
|
3996
3996
|
"origin": "js-markup",
|
|
3997
3997
|
"confidence": "certain"
|
|
3998
3998
|
},
|
|
@@ -4397,7 +4397,7 @@
|
|
|
4397
4397
|
"surface": "builders-hall",
|
|
4398
4398
|
"text": "Could not vote: {…}",
|
|
4399
4399
|
"file": "modules/hall-ui/public/board-room.js",
|
|
4400
|
-
"line":
|
|
4400
|
+
"line": 391,
|
|
4401
4401
|
"origin": "js-notice",
|
|
4402
4402
|
"confidence": "certain"
|
|
4403
4403
|
},
|
|
@@ -4433,7 +4433,7 @@
|
|
|
4433
4433
|
"surface": "builders-hall",
|
|
4434
4434
|
"text": "Counted, not judged: each question is worth the same, and earns its share in proportion to how much real text it carries. Whitespace and padding score nothing.",
|
|
4435
4435
|
"file": "modules/hall-ui/public/ideas.html",
|
|
4436
|
-
"line":
|
|
4436
|
+
"line": 194,
|
|
4437
4437
|
"origin": "html-text",
|
|
4438
4438
|
"confidence": "certain"
|
|
4439
4439
|
},
|
|
@@ -4577,7 +4577,7 @@
|
|
|
4577
4577
|
"surface": "builders-hall",
|
|
4578
4578
|
"text": "Develop this idea",
|
|
4579
4579
|
"file": "modules/hall-ui/public/ideas.js",
|
|
4580
|
-
"line":
|
|
4580
|
+
"line": 900,
|
|
4581
4581
|
"origin": "js-text-assign",
|
|
4582
4582
|
"confidence": "certain"
|
|
4583
4583
|
},
|
|
@@ -4604,7 +4604,7 @@
|
|
|
4604
4604
|
"surface": "builders-hall",
|
|
4605
4605
|
"text": "Discard draft",
|
|
4606
4606
|
"file": "modules/hall-ui/public/ideas.html",
|
|
4607
|
-
"line":
|
|
4607
|
+
"line": 172,
|
|
4608
4608
|
"origin": "html-text",
|
|
4609
4609
|
"confidence": "certain"
|
|
4610
4610
|
},
|
|
@@ -4766,7 +4766,7 @@
|
|
|
4766
4766
|
"surface": "builders-hall",
|
|
4767
4767
|
"text": "Draft discarded.",
|
|
4768
4768
|
"file": "modules/hall-ui/public/ideas.js",
|
|
4769
|
-
"line":
|
|
4769
|
+
"line": 548,
|
|
4770
4770
|
"origin": "js-notice",
|
|
4771
4771
|
"confidence": "certain"
|
|
4772
4772
|
},
|
|
@@ -5090,7 +5090,7 @@
|
|
|
5090
5090
|
"surface": "builders-hall",
|
|
5091
5091
|
"text": "File this idea",
|
|
5092
5092
|
"file": "modules/hall-ui/public/ideas.html",
|
|
5093
|
-
"line":
|
|
5093
|
+
"line": 171,
|
|
5094
5094
|
"origin": "html-text",
|
|
5095
5095
|
"confidence": "certain"
|
|
5096
5096
|
},
|
|
@@ -5108,7 +5108,7 @@
|
|
|
5108
5108
|
"surface": "builders-hall",
|
|
5109
5109
|
"text": "Filed to the inbox.",
|
|
5110
5110
|
"file": "modules/hall-ui/public/ideas.js",
|
|
5111
|
-
"line":
|
|
5111
|
+
"line": 765,
|
|
5112
5112
|
"origin": "js-text-assign",
|
|
5113
5113
|
"confidence": "certain"
|
|
5114
5114
|
},
|
|
@@ -5216,7 +5216,7 @@
|
|
|
5216
5216
|
"surface": "builders-hall",
|
|
5217
5217
|
"text": "Five questions, and a score that counts how much of each you actually answered.",
|
|
5218
5218
|
"file": "modules/hall-ui/public/ideas.js",
|
|
5219
|
-
"line":
|
|
5219
|
+
"line": 936,
|
|
5220
5220
|
"origin": "js-text-assign",
|
|
5221
5221
|
"confidence": "certain"
|
|
5222
5222
|
},
|
|
@@ -6044,7 +6044,7 @@
|
|
|
6044
6044
|
"surface": "builders-hall",
|
|
6045
6045
|
"text": "It cleared the completeness grade at {…}/100.",
|
|
6046
6046
|
"file": "modules/hall-ui/public/board-room.js",
|
|
6047
|
-
"line":
|
|
6047
|
+
"line": 326,
|
|
6048
6048
|
"origin": "js-markup",
|
|
6049
6049
|
"confidence": "certain"
|
|
6050
6050
|
},
|
|
@@ -6084,6 +6084,15 @@
|
|
|
6084
6084
|
"origin": "js-markup",
|
|
6085
6085
|
"confidence": "certain"
|
|
6086
6086
|
},
|
|
6087
|
+
{
|
|
6088
|
+
"id": "a21788f84e29",
|
|
6089
|
+
"surface": "builders-hall",
|
|
6090
|
+
"text": "Its author gave no scope recommendation.",
|
|
6091
|
+
"file": "modules/hall-ui/public/board-room.js",
|
|
6092
|
+
"line": 295,
|
|
6093
|
+
"origin": "js-markup",
|
|
6094
|
+
"confidence": "certain"
|
|
6095
|
+
},
|
|
6087
6096
|
{
|
|
6088
6097
|
"id": "b0bcb3f844ec",
|
|
6089
6098
|
"surface": "builders-hall",
|
|
@@ -6093,6 +6102,15 @@
|
|
|
6093
6102
|
"origin": "js-markup",
|
|
6094
6103
|
"confidence": "certain"
|
|
6095
6104
|
},
|
|
6105
|
+
{
|
|
6106
|
+
"id": "680491549e47",
|
|
6107
|
+
"surface": "builders-hall",
|
|
6108
|
+
"text": "Its author recommends this becomes",
|
|
6109
|
+
"file": "modules/hall-ui/public/board-room.js",
|
|
6110
|
+
"line": 294,
|
|
6111
|
+
"origin": "js-markup",
|
|
6112
|
+
"confidence": "certain"
|
|
6113
|
+
},
|
|
6096
6114
|
{
|
|
6097
6115
|
"id": "b7719e855009",
|
|
6098
6116
|
"surface": "builders-hall",
|
|
@@ -6395,7 +6413,7 @@
|
|
|
6395
6413
|
"surface": "builders-hall",
|
|
6396
6414
|
"text": "Loading sittings…",
|
|
6397
6415
|
"file": "modules/hall-ui/public/board-room.js",
|
|
6398
|
-
"line":
|
|
6416
|
+
"line": 539,
|
|
6399
6417
|
"origin": "js-text-assign",
|
|
6400
6418
|
"confidence": "certain"
|
|
6401
6419
|
},
|
|
@@ -7016,7 +7034,7 @@
|
|
|
7016
7034
|
"surface": "builders-hall",
|
|
7017
7035
|
"text": "No goal (homeless)",
|
|
7018
7036
|
"file": "modules/hall-ui/public/ideas.js",
|
|
7019
|
-
"line":
|
|
7037
|
+
"line": 569,
|
|
7020
7038
|
"origin": "js-markup",
|
|
7021
7039
|
"confidence": "certain"
|
|
7022
7040
|
},
|
|
@@ -7403,7 +7421,7 @@
|
|
|
7403
7421
|
"surface": "builders-hall",
|
|
7404
7422
|
"text": "Nothing is waiting. A spark appears here when its author offers a quick idea up for development.",
|
|
7405
7423
|
"file": "modules/hall-ui/public/ideas.js",
|
|
7406
|
-
"line":
|
|
7424
|
+
"line": 843,
|
|
7407
7425
|
"origin": "js-markup",
|
|
7408
7426
|
"confidence": "certain"
|
|
7409
7427
|
},
|
|
@@ -8744,7 +8762,7 @@
|
|
|
8744
8762
|
"surface": "builders-hall",
|
|
8745
8763
|
"text": "Restricted.",
|
|
8746
8764
|
"file": "modules/hall-ui/public/board-room.js",
|
|
8747
|
-
"line":
|
|
8765
|
+
"line": 521,
|
|
8748
8766
|
"origin": "js-text-assign",
|
|
8749
8767
|
"confidence": "certain"
|
|
8750
8768
|
},
|
|
@@ -9284,7 +9302,7 @@
|
|
|
9284
9302
|
"surface": "builders-hall",
|
|
9285
9303
|
"text": "Showing {…} of {…}.",
|
|
9286
9304
|
"file": "modules/hall-ui/public/ideas.js",
|
|
9287
|
-
"line":
|
|
9305
|
+
"line": 847,
|
|
9288
9306
|
"origin": "js-markup",
|
|
9289
9307
|
"confidence": "certain"
|
|
9290
9308
|
},
|
|
@@ -9914,7 +9932,7 @@
|
|
|
9914
9932
|
"surface": "builders-hall",
|
|
9915
9933
|
"text": "The Board Room is open to the builders this project’s constitution seats on its board.",
|
|
9916
9934
|
"file": "modules/hall-ui/public/board-room.js",
|
|
9917
|
-
"line":
|
|
9935
|
+
"line": 524,
|
|
9918
9936
|
"origin": "js-text-assign",
|
|
9919
9937
|
"confidence": "certain"
|
|
9920
9938
|
},
|
|
@@ -10742,7 +10760,7 @@
|
|
|
10742
10760
|
"surface": "builders-hall",
|
|
10743
10761
|
"text": "This sends the idea back to its author. Say why, and which section.",
|
|
10744
10762
|
"file": "modules/hall-ui/public/board-room.js",
|
|
10745
|
-
"line":
|
|
10763
|
+
"line": 363,
|
|
10746
10764
|
"origin": "js-markup",
|
|
10747
10765
|
"confidence": "certain"
|
|
10748
10766
|
},
|
|
@@ -10967,7 +10985,7 @@
|
|
|
10967
10985
|
"surface": "builders-hall",
|
|
10968
10986
|
"text": "Vote yes",
|
|
10969
10987
|
"file": "modules/hall-ui/public/board-room.js",
|
|
10970
|
-
"line":
|
|
10988
|
+
"line": 363,
|
|
10971
10989
|
"origin": "js-markup",
|
|
10972
10990
|
"confidence": "certain"
|
|
10973
10991
|
},
|
|
@@ -11363,7 +11381,7 @@
|
|
|
11363
11381
|
"surface": "builders-hall",
|
|
11364
11382
|
"text": "Why — the reason the author will act on",
|
|
11365
11383
|
"file": "modules/hall-ui/public/board-room.js",
|
|
11366
|
-
"line":
|
|
11384
|
+
"line": 363,
|
|
11367
11385
|
"origin": "js-markup",
|
|
11368
11386
|
"confidence": "certain"
|
|
11369
11387
|
},
|
|
@@ -11615,7 +11633,7 @@
|
|
|
11615
11633
|
"surface": "builders-hall",
|
|
11616
11634
|
"text": "You {…}. One vote per member.",
|
|
11617
11635
|
"file": "modules/hall-ui/public/board-room.js",
|
|
11618
|
-
"line":
|
|
11636
|
+
"line": 435,
|
|
11619
11637
|
"origin": "js-markup",
|
|
11620
11638
|
"confidence": "certain"
|
|
11621
11639
|
},
|
|
@@ -11741,7 +11759,7 @@
|
|
|
11741
11759
|
"surface": "builders-hall",
|
|
11742
11760
|
"text": "Your own idea. You may vote, but it can’t be decided by your vote alone, and a vote on your own idea earns no karma.",
|
|
11743
11761
|
"file": "modules/hall-ui/public/board-room.js",
|
|
11744
|
-
"line":
|
|
11762
|
+
"line": 361,
|
|
11745
11763
|
"origin": "js-markup",
|
|
11746
11764
|
"confidence": "certain"
|
|
11747
11765
|
},
|
|
@@ -12569,7 +12587,7 @@
|
|
|
12569
12587
|
"surface": "builders-hall",
|
|
12570
12588
|
"text": "idea {…}",
|
|
12571
12589
|
"file": "modules/hall-ui/public/ideas.js",
|
|
12572
|
-
"line":
|
|
12590
|
+
"line": 534,
|
|
12573
12591
|
"origin": "js-markup",
|
|
12574
12592
|
"confidence": "certain"
|
|
12575
12593
|
},
|
|
@@ -13289,7 +13307,7 @@
|
|
|
13289
13307
|
"surface": "builders-hall",
|
|
13290
13308
|
"text": "task {…}",
|
|
13291
13309
|
"file": "modules/hall-ui/public/ideas.js",
|
|
13292
|
-
"line":
|
|
13310
|
+
"line": 193,
|
|
13293
13311
|
"origin": "js-markup",
|
|
13294
13312
|
"confidence": "certain"
|
|
13295
13313
|
},
|
|
@@ -13298,7 +13316,7 @@
|
|
|
13298
13316
|
"surface": "builders-hall",
|
|
13299
13317
|
"text": "task {…}",
|
|
13300
13318
|
"file": "modules/hall-ui/public/ideas.js",
|
|
13301
|
-
"line":
|
|
13319
|
+
"line": 762,
|
|
13302
13320
|
"origin": "js-markup",
|
|
13303
13321
|
"confidence": "certain"
|
|
13304
13322
|
},
|
|
@@ -14036,7 +14054,7 @@
|
|
|
14036
14054
|
"surface": "builders-hall",
|
|
14037
14055
|
"text": "{…} · who votes, and how it {…}",
|
|
14038
14056
|
"file": "modules/hall-ui/public/board-room.js",
|
|
14039
|
-
"line":
|
|
14057
|
+
"line": 444,
|
|
14040
14058
|
"origin": "js-markup",
|
|
14041
14059
|
"confidence": "certain"
|
|
14042
14060
|
},
|
|
@@ -2342,5 +2342,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2342
2342
|
landed since 1.19.932 with no explicit bump. run 35555853466. (task 1002620)
|
|
2343
2343
|
1.19.934 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2344
2344
|
landed since 1.19.933 with no explicit bump. run 35556739619. (task 1002620)
|
|
2345
|
+
1.19.935 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2346
|
+
landed since 1.19.934 with no explicit bump. run 35558269194. (task 1002620)
|
|
2347
|
+
1.19.936 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2348
|
+
landed since 1.19.935 with no explicit bump. run 35561285958. (task 1002620)
|
|
2345
2349
|
---------------------------------------------------------------------------
|
|
2346
2350
|
```
|
|
@@ -861,6 +861,10 @@ async function boardRoomView({ closedLimit = 20 } = {}) {
|
|
|
861
861
|
field_values: r.field_values || null,
|
|
862
862
|
template_key: r.template_key || null,
|
|
863
863
|
completeness_score: r.completeness_score ?? null,
|
|
864
|
+
// BV2.R13: the author's recommendation, in front of the voter with
|
|
865
|
+
// the sections. A RECOMMENDATION, NOT A DECISION — the board is
|
|
866
|
+
// deciding it, and nothing on this path may treat it as settled.
|
|
867
|
+
recommendation: r.recommendation ?? null,
|
|
864
868
|
} : null;
|
|
865
869
|
})()
|
|
866
870
|
: (() => { const r = amendmentById.get(String(item.subject_id)); return r ? { proposed: r.proposed, rationale_md: r.rationale_md, author_id: r.proposed_by == null ? null : String(r.proposed_by) } : null; })();
|
package/modules/government/db.js
CHANGED
|
@@ -509,7 +509,14 @@ async function getIdeaSubjects(ideaIds) {
|
|
|
509
509
|
// in the hall rather than on a reaction. LEFT JOIN: an idea whose grade row
|
|
510
510
|
// is somehow gone still shows its title.
|
|
511
511
|
const { rows } = await pool.query(
|
|
512
|
-
`
|
|
512
|
+
// `g.recommendation` (task 1004092 / BV2.R13, spec R22): what the author
|
|
513
|
+
// thinks their idea turned out to be — a goal, or a criterion inside one.
|
|
514
|
+
// It rides with the sections for exactly the same reason they do: the board
|
|
515
|
+
// is being asked to decide that question, and deciding it without seeing
|
|
516
|
+
// what was proposed is the gap this closes. NULL for every Full Idea filed
|
|
517
|
+
// before the question existed, which the room renders honestly.
|
|
518
|
+
`SELECT i.id, i.title, i.captured_by, g.field_values, g.template_key, g.completeness_score,
|
|
519
|
+
g.recommendation
|
|
513
520
|
FROM idea_inbox i
|
|
514
521
|
LEFT JOIN ideas_grades g ON g.idea_id = i.id
|
|
515
522
|
WHERE i.id = ANY($1::bigint[])`,
|
|
@@ -40,6 +40,12 @@
|
|
|
40
40
|
.gov-item__title .fact-pill { vertical-align: middle; margin-left: 10px; text-shadow: none; }
|
|
41
41
|
.gov-item__state { display: inline-flex; align-items: center; gap: 10px; font-size: 12.5px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
|
|
42
42
|
.gov-item__amend { margin: 0 0 12px; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); max-width: var(--measure); overflow-wrap: anywhere; }
|
|
43
|
+
/* The author's scope recommendation (BV2.R13). Sits between the ballot and the
|
|
44
|
+
idea's own words, at the weight of a stated fact rather than a badge — it is
|
|
45
|
+
what the board is deciding, not something already decided. */
|
|
46
|
+
.gov-item__rec { margin: 12px 0 0; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); max-width: var(--measure); }
|
|
47
|
+
.gov-item__rec strong { font-weight: 500; color: var(--ink); }
|
|
48
|
+
.gov-item__rec--none { color: var(--ink-faint); }
|
|
43
49
|
.gov-item__act { margin: 8px 0 4px; }
|
|
44
50
|
.gov-item__cast { margin: 0; font-size: 13.5px; color: var(--ink-soft); }
|
|
45
51
|
.gov-item__meta { margin: var(--sp-sm) 0 0; border-top: var(--line) solid var(--rule-soft); }
|
|
@@ -265,6 +265,36 @@
|
|
|
265
265
|
: emptyStateHtml('This idea has no written sections.');
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
// The author's scope recommendation, in front of the voter WITH the sections
|
|
269
|
+
// (task 1004092 / BV2.R13, spec R22).
|
|
270
|
+
//
|
|
271
|
+
// THE WORDING CARRIES THE WHOLE POINT, so it is not shortened to a pill. The
|
|
272
|
+
// ideator recommends and the board decides (spec decision 9) — a bare badge
|
|
273
|
+
// reading "goal" next to a ballot reads as a fact already settled, which is
|
|
274
|
+
// the one thing this value is not. The line says who said it and who decides.
|
|
275
|
+
//
|
|
276
|
+
// AN ABSENT RECOMMENDATION IS STATED, NOT HIDDEN. Every Full Idea filed before
|
|
277
|
+
// this question existed carries NULL, and so does a terse CLI filing; there is
|
|
278
|
+
// no backfill and no default, because inventing a scope opinion is putting
|
|
279
|
+
// words in an author's mouth. Rendering nothing would leave a voter unable to
|
|
280
|
+
// tell "recommended as a criterion" from "never asked".
|
|
281
|
+
const RECOMMENDATION_WORDS = {
|
|
282
|
+
goal: 'a goal of its own',
|
|
283
|
+
criterion: 'a criterion inside an existing goal',
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
function recommendationHtml(item) {
|
|
287
|
+
if (item.subject_type !== 'full_idea' || !item.subject) return '';
|
|
288
|
+
const rec = item.subject.recommendation;
|
|
289
|
+
// An unknown value can only mean the vocabulary widened after this page
|
|
290
|
+
// shipped. Print it rather than dropping it — the same honesty rule the
|
|
291
|
+
// unlabelled-section tail follows.
|
|
292
|
+
const words = rec ? (RECOMMENDATION_WORDS[rec] || rec) : null;
|
|
293
|
+
return words
|
|
294
|
+
? `<p class="gov-item__rec">Its author recommends this becomes <strong>${escapeHtml(words)}</strong>. The board decides.</p>`
|
|
295
|
+
: '<p class="gov-item__rec gov-item__rec--none">Its author gave no scope recommendation.</p>';
|
|
296
|
+
}
|
|
297
|
+
|
|
268
298
|
// ---- the sitting's rules, in plain words (task 1003270) --------------------
|
|
269
299
|
|
|
270
300
|
// The card used to carry `tally: 0 yes · 0 no — sitting under <first_ratifier>,
|
|
@@ -412,6 +442,7 @@
|
|
|
412
442
|
</div>
|
|
413
443
|
${amendmentDetail}
|
|
414
444
|
${action ? `<div class="gov-item__act">${action}</div>` : ''}
|
|
445
|
+
${isOpen ? recommendationHtml(item) : ''}
|
|
415
446
|
${isOpen ? ideaSectionsHtml(item) : ''}
|
|
416
447
|
<details class="gov-item__meta">
|
|
417
448
|
<summary class="gov-item__summary">${tally} · who votes, and how it ${isOpen ? 'passes' : 'passed'}</summary>
|
|
@@ -71,6 +71,33 @@
|
|
|
71
71
|
|
|
72
72
|
.fi-note { font-size: 13.5px; color: var(--ink-faint); }
|
|
73
73
|
|
|
74
|
+
/* ---- the scope recommendation (BV2.R13) ----------------------------------
|
|
75
|
+
A pick, so it is radios — the settings family's `.prow--pick` grammar, not a
|
|
76
|
+
toggle: a toggle says on/off and this says which of two. The fieldset draws
|
|
77
|
+
no box (a bordered group inside a composer panel is the card-in-a-card the
|
|
78
|
+
world removed) and the legend takes the label voice the five questions wear,
|
|
79
|
+
so the sixth question does not read as a different kind of thing.
|
|
80
|
+
|
|
81
|
+
The 24px floor is on the LABEL, not the 13px input: the whole option row is
|
|
82
|
+
the target, so the box stays the size it is drawn while the thing a finger
|
|
83
|
+
lands on is the sentence beside it. */
|
|
84
|
+
.fi-rec__set { margin: 0; padding: 0; border: 0; max-width: var(--measure); }
|
|
85
|
+
.fi-rec__set > .fi-label { margin-bottom: 0; padding: 0; }
|
|
86
|
+
.fi-rec__hint { margin-bottom: 6px; }
|
|
87
|
+
.fi-rec__opt {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: 10px;
|
|
91
|
+
min-height: 24px;
|
|
92
|
+
padding: 3px 0;
|
|
93
|
+
font-size: 14px;
|
|
94
|
+
line-height: 1.4;
|
|
95
|
+
color: var(--ink);
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
}
|
|
98
|
+
.fi-rec__opt input { flex: none; margin: 0; }
|
|
99
|
+
.fi-rec__opt input:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
|
|
100
|
+
|
|
74
101
|
/* GRAVITY (task 1004083 / spec R02) — the consequence of the picker above it,
|
|
75
102
|
so it sits tight under the select rather than at the form's own rhythm, and
|
|
76
103
|
reads a step louder than .fi-hint: a hint describes a control, this states
|
|
@@ -158,6 +158,13 @@
|
|
|
158
158
|
<p class="fi-note">Loading the five questions…</p>
|
|
159
159
|
</div>
|
|
160
160
|
|
|
161
|
+
<!-- The scope recommendation (BV2.R13). Rendered from the same
|
|
162
|
+
GET /inbox/template read as the fields above, and for the same
|
|
163
|
+
reason: the two values are a closed vocabulary the database
|
|
164
|
+
enforces, so a hand-written pair of radios here would be a
|
|
165
|
+
second copy of it, free to drift. -->
|
|
166
|
+
<div id="fi-rec" class="fi-rec"></div>
|
|
167
|
+
|
|
161
168
|
<p class="fi-err" id="fi-err" role="alert"></p>
|
|
162
169
|
|
|
163
170
|
<div class="fi-actions">
|
|
@@ -119,6 +119,10 @@
|
|
|
119
119
|
template_key: template.key,
|
|
120
120
|
title: titleEl().value,
|
|
121
121
|
field_values: collect(),
|
|
122
|
+
// BV2.R13: a half-written thought includes the scope its author has in
|
|
123
|
+
// mind for it; losing that on navigation is the same loss as losing a
|
|
124
|
+
// paragraph.
|
|
125
|
+
recommendation: collectRecommendation(),
|
|
122
126
|
at: new Date().toISOString(),
|
|
123
127
|
}));
|
|
124
128
|
note('Draft saved in this browser.');
|
|
@@ -258,6 +262,8 @@
|
|
|
258
262
|
}
|
|
259
263
|
titleEl().addEventListener('input', onEdit);
|
|
260
264
|
|
|
265
|
+
renderRecommendation(draft);
|
|
266
|
+
|
|
261
267
|
$('#fi-meter').hidden = false;
|
|
262
268
|
renderMeterSkeleton();
|
|
263
269
|
}
|
|
@@ -268,6 +274,83 @@
|
|
|
268
274
|
return out;
|
|
269
275
|
}
|
|
270
276
|
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// the scope recommendation (task 1004092 / BV2.R13, spec R22)
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
//
|
|
281
|
+
// NOT A SIXTH TEXTAREA, and deliberately not collected into `field_values`:
|
|
282
|
+
// the five answers are prose the scorer measures, and this is a two-value
|
|
283
|
+
// choice nothing scores. It travels as its own body field, so the meter
|
|
284
|
+
// above never moves when it is picked.
|
|
285
|
+
//
|
|
286
|
+
// THE OPTIONS COME FROM THE SERVER, like the fields. The vocabulary is a
|
|
287
|
+
// database CHECK constraint; hard-coding the pair here would be the "second
|
|
288
|
+
// copy of the rules" defect this whole file is organised against, and would
|
|
289
|
+
// silently omit any value a later migration admits.
|
|
290
|
+
//
|
|
291
|
+
// REQUIRED HERE, OPTIONAL AT THE API — which is not a contradiction. The
|
|
292
|
+
// hall is where the question is actually put to a person, so filing from it
|
|
293
|
+
// without answering would be filing an unanswered question. The route stays
|
|
294
|
+
// permissive because hundreds of already-filed Full Ideas carry no
|
|
295
|
+
// recommendation and a terse CLI filing may carry none either; every reader
|
|
296
|
+
// therefore has to render the absence honestly regardless, and a required
|
|
297
|
+
// body field would 400 every already-shipped client.
|
|
298
|
+
|
|
299
|
+
function recSpec() {
|
|
300
|
+
return (template && template.recommendation) || null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function renderRecommendation(draft) {
|
|
304
|
+
const host = $('#fi-rec');
|
|
305
|
+
if (!host) return;
|
|
306
|
+
const spec = recSpec();
|
|
307
|
+
// An instance whose template read predates this question simply has no
|
|
308
|
+
// control — the composer keeps working and files without one.
|
|
309
|
+
if (!spec || !Array.isArray(spec.options) || !spec.options.length) {
|
|
310
|
+
host.innerHTML = '';
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const picked = draft && typeof draft.recommendation === 'string' ? draft.recommendation : '';
|
|
314
|
+
host.innerHTML = `
|
|
315
|
+
<fieldset class="fi-rec__set">
|
|
316
|
+
<legend class="fi-label">${escapeHtml(spec.label || 'What do you think this is')}</legend>
|
|
317
|
+
${spec.hint ? `<p class="fi-hint fi-rec__hint">${escapeHtml(spec.hint)}</p>` : ''}
|
|
318
|
+
${spec.options.map((o) => `
|
|
319
|
+
<label class="fi-rec__opt">
|
|
320
|
+
<input type="radio" name="fi-rec-choice" value="${escapeHtml(o.value)}"${o.value === picked ? ' checked' : ''}>
|
|
321
|
+
<span>${escapeHtml(o.label || o.value)}</span>
|
|
322
|
+
</label>`).join('')}
|
|
323
|
+
</fieldset>`;
|
|
324
|
+
// onEdit, not a bespoke handler: picking an option has to save the draft
|
|
325
|
+
// and re-evaluate the file button exactly as typing does. It cannot change
|
|
326
|
+
// the score, and scoreNow is a no-op for it beyond one wasted call — which
|
|
327
|
+
// the debounce already collapses.
|
|
328
|
+
for (const el of host.querySelectorAll('input[name="fi-rec-choice"]')) {
|
|
329
|
+
el.addEventListener('change', onEdit);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function collectRecommendation() {
|
|
334
|
+
const host = $('#fi-rec');
|
|
335
|
+
const picked = host && host.querySelector('input[name="fi-rec-choice"]:checked');
|
|
336
|
+
return picked ? picked.value : '';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function clearRecommendation() {
|
|
340
|
+
const host = $('#fi-rec');
|
|
341
|
+
if (!host) return;
|
|
342
|
+
for (const el of host.querySelectorAll('input[name="fi-rec-choice"]')) el.checked = false;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Whether the composer may file. Only true when the server actually offered
|
|
346
|
+
// the choice — an instance that serves no `recommendation` spec must not have
|
|
347
|
+
// its file button held hostage by a control that was never rendered.
|
|
348
|
+
function recommendationMissing() {
|
|
349
|
+
const spec = recSpec();
|
|
350
|
+
if (!spec || !Array.isArray(spec.options) || !spec.options.length) return false;
|
|
351
|
+
return !collectRecommendation();
|
|
352
|
+
}
|
|
353
|
+
|
|
271
354
|
// ---------------------------------------------------------------------------
|
|
272
355
|
// the meter — every number in it came from the server
|
|
273
356
|
// ---------------------------------------------------------------------------
|
|
@@ -316,7 +399,13 @@
|
|
|
316
399
|
// does NOT enforce the substance floor, because a thin answer is a scoring
|
|
317
400
|
// problem, not a capture error). Matching that exactly means the button never
|
|
318
401
|
// promises an outcome the server will refuse, and never blocks one it accepts.
|
|
319
|
-
|
|
402
|
+
//
|
|
403
|
+
// THE SCOPE RECOMMENDATION IS THE ONE PLACE THIS BUTTON IS STRICTER THAN
|
|
404
|
+
// THE ROUTE (BV2.R13). The route accepts a Full Idea without one, because
|
|
405
|
+
// the historical rows have none; the hall does not, because the hall is
|
|
406
|
+
// where the question was actually asked. See renderRecommendation.
|
|
407
|
+
const missingRequired = template.fields.some((f) => f.required && !fieldEl(f.key).value.trim())
|
|
408
|
+
|| recommendationMissing();
|
|
320
409
|
// In develop mode the title is the existing idea's and is not editable, so it
|
|
321
410
|
// cannot be a reason to keep the button off.
|
|
322
411
|
$('#fi-submit').disabled = filing || missingRequired || (!DEVELOP_ID && !titleEl().value.trim());
|
|
@@ -342,7 +431,8 @@
|
|
|
342
431
|
if (lastScore) renderScore(lastScore);
|
|
343
432
|
else $('#fi-submit').disabled = filing
|
|
344
433
|
|| (!DEVELOP_ID && !titleEl().value.trim())
|
|
345
|
-
|| template.fields.some((f) => f.required && !fieldEl(f.key).value.trim())
|
|
434
|
+
|| template.fields.some((f) => f.required && !fieldEl(f.key).value.trim())
|
|
435
|
+
|| recommendationMissing();
|
|
346
436
|
}
|
|
347
437
|
|
|
348
438
|
// ---------------------------------------------------------------------------
|
|
@@ -386,10 +476,13 @@
|
|
|
386
476
|
// The one behavioural fork between the two modes. Developing is an act on an
|
|
387
477
|
// EXISTING idea, so it carries no title and no grade: the row already has
|
|
388
478
|
// both, and R146 preserves them along with its author.
|
|
479
|
+
// BV2.R13: the recommendation travels on BOTH paths, and only when one was
|
|
480
|
+
// actually picked — sending '' would be asserting an opinion nobody gave.
|
|
481
|
+
const rec = collectRecommendation();
|
|
389
482
|
const r = DEVELOP_ID
|
|
390
483
|
? await api.inbox.postInboxIdDevelop({
|
|
391
484
|
id: DEVELOP_ID,
|
|
392
|
-
body: { field_values: collect(), template_key: template.key },
|
|
485
|
+
body: { field_values: collect(), template_key: template.key, ...(rec ? { recommendation: rec } : {}) },
|
|
393
486
|
})
|
|
394
487
|
: await api.inbox.postInbox({
|
|
395
488
|
body: {
|
|
@@ -397,6 +490,7 @@
|
|
|
397
490
|
grade: 'full',
|
|
398
491
|
template_key: template.key,
|
|
399
492
|
field_values: collect(),
|
|
493
|
+
...(rec ? { recommendation: rec } : {}),
|
|
400
494
|
},
|
|
401
495
|
});
|
|
402
496
|
|
|
@@ -429,6 +523,7 @@
|
|
|
429
523
|
|
|
430
524
|
if (!DEVELOP_ID) titleEl().value = '';
|
|
431
525
|
for (const f of template.fields) fieldEl(f.key).value = '';
|
|
526
|
+
clearRecommendation();
|
|
432
527
|
lastScore = null;
|
|
433
528
|
renderScore(null);
|
|
434
529
|
note('');
|
|
@@ -445,6 +540,7 @@
|
|
|
445
540
|
clearDraft();
|
|
446
541
|
titleEl().value = '';
|
|
447
542
|
for (const f of template.fields) fieldEl(f.key).value = '';
|
|
543
|
+
clearRecommendation();
|
|
448
544
|
lastScore = null;
|
|
449
545
|
renderScore(null);
|
|
450
546
|
showErr('');
|