@bongos/core 1.19.910 → 1.19.911
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 +47 -27
- 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 +62 -2
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +25 -25
- package/docs/copy-registry.json +29 -29
- package/docs/module-api-changelog.md +3 -1
- package/modules/hall-ui/public/ideas.css +25 -0
- package/modules/hall-ui/public/ideas.html +12 -0
- package/modules/hall-ui/public/ideas.js +115 -2
- package/modules/ideas/landing-preview.js +180 -0
- package/modules/ideas/routes/inbox.js +47 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/api-alias-caller-scan.js +213 -0
- package/scripts/gds/fitness-ratchets.js +31 -0
- package/scripts/hall-preview/server.js +14 -1
- package/src/module-api.js +1 -1
- package/tests/api_alias_caller_ratchet.mjs +130 -0
- package/tests/idea_landing_preview.mjs +337 -0
- package/tests/idea_spark_hall.mjs +183 -0
package/docs/copy-registry.json
CHANGED
|
@@ -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:189",
|
|
113
|
+
"modules/hall-ui/public/ideas.js:666",
|
|
114
114
|
"modules/hall-ui/public/watch.js:1242"
|
|
115
115
|
]
|
|
116
116
|
},
|
|
@@ -812,7 +812,7 @@
|
|
|
812
812
|
"occurrences": 2,
|
|
813
813
|
"at": [
|
|
814
814
|
"modules/hall-ui/public/blockers.js:311",
|
|
815
|
-
"modules/hall-ui/public/ideas.html:
|
|
815
|
+
"modules/hall-ui/public/ideas.html:119"
|
|
816
816
|
]
|
|
817
817
|
},
|
|
818
818
|
{
|
|
@@ -1552,7 +1552,7 @@
|
|
|
1552
1552
|
"occurrences": 2,
|
|
1553
1553
|
"at": [
|
|
1554
1554
|
"modules/hall-ui/public/idea.js:210",
|
|
1555
|
-
"modules/hall-ui/public/ideas.js:
|
|
1555
|
+
"modules/hall-ui/public/ideas.js:439"
|
|
1556
1556
|
]
|
|
1557
1557
|
},
|
|
1558
1558
|
{
|
|
@@ -2043,7 +2043,7 @@
|
|
|
2043
2043
|
"surface": "builders-hall",
|
|
2044
2044
|
"text": "A Full Idea answers five questions, so that someone else can deliver against it without having to interview you first. Open to every role — the score below is the only filter, and it counts substance, not who you are.",
|
|
2045
2045
|
"file": "modules/hall-ui/public/ideas.html",
|
|
2046
|
-
"line":
|
|
2046
|
+
"line": 127,
|
|
2047
2047
|
"origin": "html-text",
|
|
2048
2048
|
"confidence": "certain"
|
|
2049
2049
|
},
|
|
@@ -2493,7 +2493,7 @@
|
|
|
2493
2493
|
"surface": "builders-hall",
|
|
2494
2494
|
"text": "Answer the five questions and this spark becomes a Full Idea.",
|
|
2495
2495
|
"file": "modules/hall-ui/public/ideas.js",
|
|
2496
|
-
"line":
|
|
2496
|
+
"line": 805,
|
|
2497
2497
|
"origin": "js-text-assign",
|
|
2498
2498
|
"confidence": "certain"
|
|
2499
2499
|
},
|
|
@@ -3753,7 +3753,7 @@
|
|
|
3753
3753
|
"surface": "builders-hall",
|
|
3754
3754
|
"text": "Could not load idea {…}.",
|
|
3755
3755
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3756
|
-
"line":
|
|
3756
|
+
"line": 848,
|
|
3757
3757
|
"origin": "js-text-assign",
|
|
3758
3758
|
"confidence": "certain"
|
|
3759
3759
|
},
|
|
@@ -3807,7 +3807,7 @@
|
|
|
3807
3807
|
"surface": "builders-hall",
|
|
3808
3808
|
"text": "Could not load the idea template. Reload, or file from the terminal with capture.js.",
|
|
3809
3809
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3810
|
-
"line":
|
|
3810
|
+
"line": 830,
|
|
3811
3811
|
"origin": "js-text-assign",
|
|
3812
3812
|
"confidence": "certain"
|
|
3813
3813
|
},
|
|
@@ -3834,7 +3834,7 @@
|
|
|
3834
3834
|
"surface": "builders-hall",
|
|
3835
3835
|
"text": "Could not load the queue just now.",
|
|
3836
3836
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3837
|
-
"line":
|
|
3837
|
+
"line": 769,
|
|
3838
3838
|
"origin": "js-markup",
|
|
3839
3839
|
"confidence": "certain"
|
|
3840
3840
|
},
|
|
@@ -4275,7 +4275,7 @@
|
|
|
4275
4275
|
"surface": "builders-hall",
|
|
4276
4276
|
"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.",
|
|
4277
4277
|
"file": "modules/hall-ui/public/ideas.html",
|
|
4278
|
-
"line":
|
|
4278
|
+
"line": 187,
|
|
4279
4279
|
"origin": "html-text",
|
|
4280
4280
|
"confidence": "certain"
|
|
4281
4281
|
},
|
|
@@ -4419,7 +4419,7 @@
|
|
|
4419
4419
|
"surface": "builders-hall",
|
|
4420
4420
|
"text": "Develop this idea",
|
|
4421
4421
|
"file": "modules/hall-ui/public/ideas.js",
|
|
4422
|
-
"line":
|
|
4422
|
+
"line": 804,
|
|
4423
4423
|
"origin": "js-text-assign",
|
|
4424
4424
|
"confidence": "certain"
|
|
4425
4425
|
},
|
|
@@ -4428,7 +4428,7 @@
|
|
|
4428
4428
|
"surface": "builders-hall",
|
|
4429
4429
|
"text": "Developing a Big Idea Spark",
|
|
4430
4430
|
"file": "modules/hall-ui/public/ideas.html",
|
|
4431
|
-
"line":
|
|
4431
|
+
"line": 139,
|
|
4432
4432
|
"origin": "html-text",
|
|
4433
4433
|
"confidence": "certain"
|
|
4434
4434
|
},
|
|
@@ -4446,7 +4446,7 @@
|
|
|
4446
4446
|
"surface": "builders-hall",
|
|
4447
4447
|
"text": "Discard draft",
|
|
4448
4448
|
"file": "modules/hall-ui/public/ideas.html",
|
|
4449
|
-
"line":
|
|
4449
|
+
"line": 165,
|
|
4450
4450
|
"origin": "html-text",
|
|
4451
4451
|
"confidence": "certain"
|
|
4452
4452
|
},
|
|
@@ -4608,7 +4608,7 @@
|
|
|
4608
4608
|
"surface": "builders-hall",
|
|
4609
4609
|
"text": "Draft discarded.",
|
|
4610
4610
|
"file": "modules/hall-ui/public/ideas.js",
|
|
4611
|
-
"line":
|
|
4611
|
+
"line": 452,
|
|
4612
4612
|
"origin": "js-notice",
|
|
4613
4613
|
"confidence": "certain"
|
|
4614
4614
|
},
|
|
@@ -4869,7 +4869,7 @@
|
|
|
4869
4869
|
"surface": "builders-hall",
|
|
4870
4870
|
"text": "File it",
|
|
4871
4871
|
"file": "modules/hall-ui/public/ideas.html",
|
|
4872
|
-
"line":
|
|
4872
|
+
"line": 119,
|
|
4873
4873
|
"origin": "html-text",
|
|
4874
4874
|
"confidence": "certain"
|
|
4875
4875
|
},
|
|
@@ -4896,7 +4896,7 @@
|
|
|
4896
4896
|
"surface": "builders-hall",
|
|
4897
4897
|
"text": "File this idea",
|
|
4898
4898
|
"file": "modules/hall-ui/public/ideas.html",
|
|
4899
|
-
"line":
|
|
4899
|
+
"line": 164,
|
|
4900
4900
|
"origin": "html-text",
|
|
4901
4901
|
"confidence": "certain"
|
|
4902
4902
|
},
|
|
@@ -4914,7 +4914,7 @@
|
|
|
4914
4914
|
"surface": "builders-hall",
|
|
4915
4915
|
"text": "Filed to the inbox.",
|
|
4916
4916
|
"file": "modules/hall-ui/public/ideas.js",
|
|
4917
|
-
"line":
|
|
4917
|
+
"line": 669,
|
|
4918
4918
|
"origin": "js-text-assign",
|
|
4919
4919
|
"confidence": "certain"
|
|
4920
4920
|
},
|
|
@@ -5022,7 +5022,7 @@
|
|
|
5022
5022
|
"surface": "builders-hall",
|
|
5023
5023
|
"text": "Five questions, and a score that counts how much of each you actually answered.",
|
|
5024
5024
|
"file": "modules/hall-ui/public/ideas.js",
|
|
5025
|
-
"line":
|
|
5025
|
+
"line": 840,
|
|
5026
5026
|
"origin": "js-text-assign",
|
|
5027
5027
|
"confidence": "certain"
|
|
5028
5028
|
},
|
|
@@ -6246,7 +6246,7 @@
|
|
|
6246
6246
|
"surface": "builders-hall",
|
|
6247
6247
|
"text": "Loading the five questions…",
|
|
6248
6248
|
"file": "modules/hall-ui/public/ideas.html",
|
|
6249
|
-
"line":
|
|
6249
|
+
"line": 158,
|
|
6250
6250
|
"origin": "html-text",
|
|
6251
6251
|
"confidence": "certain"
|
|
6252
6252
|
},
|
|
@@ -6804,7 +6804,7 @@
|
|
|
6804
6804
|
"surface": "builders-hall",
|
|
6805
6805
|
"text": "No goal (homeless)",
|
|
6806
6806
|
"file": "modules/hall-ui/public/ideas.js",
|
|
6807
|
-
"line":
|
|
6807
|
+
"line": 473,
|
|
6808
6808
|
"origin": "js-markup",
|
|
6809
6809
|
"confidence": "certain"
|
|
6810
6810
|
},
|
|
@@ -7182,7 +7182,7 @@
|
|
|
7182
7182
|
"surface": "builders-hall",
|
|
7183
7183
|
"text": "Nothing is waiting. A spark appears here when its author offers a quick idea up for development.",
|
|
7184
7184
|
"file": "modules/hall-ui/public/ideas.js",
|
|
7185
|
-
"line":
|
|
7185
|
+
"line": 747,
|
|
7186
7186
|
"origin": "js-markup",
|
|
7187
7187
|
"confidence": "certain"
|
|
7188
7188
|
},
|
|
@@ -7272,7 +7272,7 @@
|
|
|
7272
7272
|
"surface": "builders-hall",
|
|
7273
7273
|
"text": "One line. This is what the idea is called everywhere else.",
|
|
7274
7274
|
"file": "modules/hall-ui/public/ideas.html",
|
|
7275
|
-
"line":
|
|
7275
|
+
"line": 147,
|
|
7276
7276
|
"origin": "html-text",
|
|
7277
7277
|
"confidence": "certain"
|
|
7278
7278
|
},
|
|
@@ -8136,7 +8136,7 @@
|
|
|
8136
8136
|
"surface": "builders-hall",
|
|
8137
8137
|
"text": "Recent filings",
|
|
8138
8138
|
"file": "modules/hall-ui/public/ideas.html",
|
|
8139
|
-
"line":
|
|
8139
|
+
"line": 113,
|
|
8140
8140
|
"origin": "html-text",
|
|
8141
8141
|
"confidence": "certain"
|
|
8142
8142
|
},
|
|
@@ -9009,7 +9009,7 @@
|
|
|
9009
9009
|
"surface": "builders-hall",
|
|
9010
9010
|
"text": "Showing {…} of {…}.",
|
|
9011
9011
|
"file": "modules/hall-ui/public/ideas.js",
|
|
9012
|
-
"line":
|
|
9012
|
+
"line": 751,
|
|
9013
9013
|
"origin": "js-markup",
|
|
9014
9014
|
"confidence": "certain"
|
|
9015
9015
|
},
|
|
@@ -11106,7 +11106,7 @@
|
|
|
11106
11106
|
"surface": "builders-hall",
|
|
11107
11107
|
"text": "Write a Full Idea",
|
|
11108
11108
|
"file": "modules/hall-ui/public/ideas.html",
|
|
11109
|
-
"line":
|
|
11109
|
+
"line": 126,
|
|
11110
11110
|
"origin": "html-text",
|
|
11111
11111
|
"confidence": "certain"
|
|
11112
11112
|
},
|
|
@@ -12069,7 +12069,7 @@
|
|
|
12069
12069
|
"surface": "builders-hall",
|
|
12070
12070
|
"text": "idea {…}",
|
|
12071
12071
|
"file": "modules/hall-ui/public/ideas.js",
|
|
12072
|
-
"line":
|
|
12072
|
+
"line": 439,
|
|
12073
12073
|
"origin": "js-markup",
|
|
12074
12074
|
"confidence": "certain"
|
|
12075
12075
|
},
|
|
@@ -12717,7 +12717,7 @@
|
|
|
12717
12717
|
"surface": "builders-hall",
|
|
12718
12718
|
"text": "task {…}",
|
|
12719
12719
|
"file": "modules/hall-ui/public/ideas.js",
|
|
12720
|
-
"line":
|
|
12720
|
+
"line": 189,
|
|
12721
12721
|
"origin": "js-markup",
|
|
12722
12722
|
"confidence": "certain"
|
|
12723
12723
|
},
|
|
@@ -12726,7 +12726,7 @@
|
|
|
12726
12726
|
"surface": "builders-hall",
|
|
12727
12727
|
"text": "task {…}",
|
|
12728
12728
|
"file": "modules/hall-ui/public/ideas.js",
|
|
12729
|
-
"line":
|
|
12729
|
+
"line": 666,
|
|
12730
12730
|
"origin": "js-markup",
|
|
12731
12731
|
"confidence": "certain"
|
|
12732
12732
|
},
|
|
@@ -12951,7 +12951,7 @@
|
|
|
12951
12951
|
"surface": "builders-hall",
|
|
12952
12952
|
"text": "what the idea is, in one line",
|
|
12953
12953
|
"file": "modules/hall-ui/public/ideas.html",
|
|
12954
|
-
"line":
|
|
12954
|
+
"line": 150,
|
|
12955
12955
|
"origin": "html-attr:placeholder",
|
|
12956
12956
|
"confidence": "certain"
|
|
12957
12957
|
},
|
|
@@ -2293,6 +2293,8 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2293
2293
|
1.19.909 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2294
2294
|
landed since 1.19.908 with no explicit bump. run 35517426559. (task 1002620)
|
|
2295
2295
|
1.19.910 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2296
|
-
landed since 1.19.909 with no explicit bump. run
|
|
2296
|
+
landed since 1.19.909 with no explicit bump. run 35519966415. (task 1002620)
|
|
2297
|
+
1.19.911 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2298
|
+
landed since 1.19.910 with no explicit bump. run 35520751450. (task 1002620)
|
|
2297
2299
|
---------------------------------------------------------------------------
|
|
2298
2300
|
```
|
|
@@ -71,6 +71,31 @@
|
|
|
71
71
|
|
|
72
72
|
.fi-note { font-size: 13.5px; color: var(--ink-faint); }
|
|
73
73
|
|
|
74
|
+
/* GRAVITY (task 1004083 / spec R02) — the consequence of the picker above it,
|
|
75
|
+
so it sits tight under the select rather than at the form's own rhythm, and
|
|
76
|
+
reads a step louder than .fi-hint: a hint describes a control, this states
|
|
77
|
+
what will happen to the thought.
|
|
78
|
+
|
|
79
|
+
TONE IS THE STATUS VOCABULARY, the same .fi-fieldnote[data-ok] precedent, and
|
|
80
|
+
the three are three different facts rather than three severities:
|
|
81
|
+
lands — it becomes work. --ok, because that IS the verification colour:
|
|
82
|
+
the thought has a home and the system can name it.
|
|
83
|
+
floats — it waits in the inbox. NEUTRAL on purpose — floating is a legal
|
|
84
|
+
outcome the filer may have chosen, not a failure to warn about,
|
|
85
|
+
and painting it as one would push people to pick a goal they do
|
|
86
|
+
not believe in just to make the line go quiet.
|
|
87
|
+
refused — it will not be filed at all (the protected-scope wall). --danger,
|
|
88
|
+
because this one costs the filer the filing. */
|
|
89
|
+
.fi-gravity {
|
|
90
|
+
margin: 8px 0 0;
|
|
91
|
+
max-width: var(--measure);
|
|
92
|
+
font-size: 13.5px;
|
|
93
|
+
line-height: 1.5;
|
|
94
|
+
color: var(--ink-soft);
|
|
95
|
+
}
|
|
96
|
+
.fi-gravity[data-tone="lands"] { color: var(--ok); }
|
|
97
|
+
.fi-gravity[data-tone="refused"] { color: var(--danger); }
|
|
98
|
+
|
|
74
99
|
.fi-err { margin: var(--sp-sm) 0 0; max-width: var(--measure); font-size: 14px; line-height: 1.5; color: var(--danger); }
|
|
75
100
|
.fi-err[data-visible="0"] { display: none; }
|
|
76
101
|
|
|
@@ -89,6 +89,18 @@
|
|
|
89
89
|
</label>
|
|
90
90
|
<select id="fq-goal" class="fi-input"></select>
|
|
91
91
|
|
|
92
|
+
<!-- GRAVITY (task 1004083 / spec R02) — what will actually catch this
|
|
93
|
+
thought, stated before it is released. It sits under the picker
|
|
94
|
+
because it is the picker's consequence, and because the picker's
|
|
95
|
+
own option labels cannot carry it: "No goal (homeless)" is false
|
|
96
|
+
for a bug (ADR 0262 routes it to the maintenance goal), and a
|
|
97
|
+
named goal is false for a judgment kind (routing.js
|
|
98
|
+
NEVER_ROUTE_KINDS). The verdict comes from the server's real
|
|
99
|
+
landing matrix — see POST /inbox/preview-landing — never from a
|
|
100
|
+
second copy of the rules in ideas.js. Hidden until there is
|
|
101
|
+
something true to say. -->
|
|
102
|
+
<p class="fi-gravity" id="fq-gravity" role="status" aria-live="polite" hidden></p>
|
|
103
|
+
|
|
92
104
|
<p class="fi-err" id="fq-err" role="alert"></p>
|
|
93
105
|
<p class="fi-result" id="fq-result" role="status" aria-live="polite"></p>
|
|
94
106
|
|
|
@@ -88,6 +88,12 @@
|
|
|
88
88
|
let goalTouchedByUser = false; // once the builder picks, stop clobbering it with fresh suggestions
|
|
89
89
|
let quickSuggestTimer = null;
|
|
90
90
|
let quickFiling = false;
|
|
91
|
+
// GRAVITY (task 1004083 / spec R02). The landing verdict for the draft as it
|
|
92
|
+
// currently stands, read from POST /inbox/preview-landing. A monotonic token
|
|
93
|
+
// guards against the out-of-order arrival a debounced-but-concurrent read
|
|
94
|
+
// invites: two previews in flight can land in either order, and the older one
|
|
95
|
+
// winning would leave the line describing a draft that no longer exists.
|
|
96
|
+
let gravitySeq = 0;
|
|
91
97
|
|
|
92
98
|
// ---------------------------------------------------------------------------
|
|
93
99
|
// draft persistence
|
|
@@ -501,9 +507,108 @@
|
|
|
501
507
|
}
|
|
502
508
|
}
|
|
503
509
|
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
// gravity — which goal will catch this thought, said before it is released
|
|
512
|
+
// (task 1004083 / spec R02)
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
|
|
515
|
+
const fqGravityEl = () => $('#fq-gravity');
|
|
516
|
+
|
|
517
|
+
// THE SENTENCES, AND NOTHING ELSE. Every branch below READS a field of the
|
|
518
|
+
// server's verdict; not one of them decides a landing. That split is the whole
|
|
519
|
+
// point of the endpoint: modules/ideas/routing.js `decideLanding` stays the one
|
|
520
|
+
// place the matrix lives, and a second copy of it in this file is precisely the
|
|
521
|
+
// failure the #fq-kind comment above already records ("this markup used to
|
|
522
|
+
// spell the whole vocabulary and had gone stale ... a third copy nobody was
|
|
523
|
+
// watching"). If a sentence here ever needs an `if` over kind or rank, the
|
|
524
|
+
// verdict is missing a field and the fix belongs on the server.
|
|
525
|
+
//
|
|
526
|
+
// Returns null for "say nothing" — an unscorable draft, or a read that failed.
|
|
527
|
+
// Silence is the honest degraded state: it leaves the filer exactly where they
|
|
528
|
+
// were before this feature, rather than replacing a quiet lie with a loud one.
|
|
529
|
+
function gravitySentence(v) {
|
|
530
|
+
if (!v || !v.outcome) return null;
|
|
531
|
+
const goalName = v.goal ? (v.goal.title || `goal ${v.goal.id}`) : null;
|
|
532
|
+
const claimable = v.status === 'ready';
|
|
533
|
+
|
|
534
|
+
if (v.outcome === 'refused') {
|
|
535
|
+
// The protected-scope wall. The server wrote the reason for a human; it is
|
|
536
|
+
// the one branch that already reads as a sentence, so it is passed through.
|
|
537
|
+
return { tone: 'refused', text: v.refusal && v.refusal.reason ? v.refusal.reason : `${goalName} will not take this filing.` };
|
|
538
|
+
}
|
|
539
|
+
if (v.outcome === 'routed') {
|
|
540
|
+
return {
|
|
541
|
+
tone: 'lands',
|
|
542
|
+
text: claimable
|
|
543
|
+
? `This lands in ${goalName} as a task anyone can claim.`
|
|
544
|
+
: `This lands in ${goalName}, waiting for a nod before anyone can claim it.`,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
if (v.outcome === 'routed_home') {
|
|
548
|
+
// The label on the picker says "homeless"; since ADR 0262 that is not true
|
|
549
|
+
// of a bug, and this line is the correction. Naming the version rather than
|
|
550
|
+
// the goal is deliberate — the maintenance goal is created by the capture,
|
|
551
|
+
// so the preview has no row to name without making one (landing-preview.js).
|
|
552
|
+
return {
|
|
553
|
+
tone: 'lands',
|
|
554
|
+
text: claimable
|
|
555
|
+
? `A bug always lands. With no goal picked this goes to ${v.version_id}'s maintenance goal, claimable straight away.`
|
|
556
|
+
: `A bug always lands. With no goal picked this goes to ${v.version_id}'s maintenance goal, waiting for a nod.`,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
// outcome === 'floats' — the inbox. Two ways to get here, and they are
|
|
560
|
+
// different facts: nobody named a goal, or a goal was named and the matrix
|
|
561
|
+
// declined it. The second is the one the picker currently lies about.
|
|
562
|
+
if (v.reason === 'judgment_kind') {
|
|
563
|
+
return { tone: 'floats', text: `${goalName} will not catch this — this kind is judged, not queued. It waits in the inbox for triage.` };
|
|
564
|
+
}
|
|
565
|
+
if (v.reason === 'full_idea_never_routes') {
|
|
566
|
+
return { tone: 'floats', text: 'A Full Idea is ratified by the board rather than routed. It waits in the inbox.' };
|
|
567
|
+
}
|
|
568
|
+
return { tone: 'floats', text: 'This floats in the inbox until someone triages it.' };
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function renderGravity(v) {
|
|
572
|
+
const el = fqGravityEl();
|
|
573
|
+
if (!el) return;
|
|
574
|
+
const said = gravitySentence(v);
|
|
575
|
+
// dataset, not setAttribute: it writes the same data-tone the sheet selects
|
|
576
|
+
// on, and it is the half of the element a DOM the page is rendered into and
|
|
577
|
+
// a test stub both really carry.
|
|
578
|
+
if (!said) { el.hidden = true; el.textContent = ''; el.dataset.tone = ''; return; }
|
|
579
|
+
el.textContent = said.text;
|
|
580
|
+
el.dataset.tone = said.tone;
|
|
581
|
+
el.hidden = false;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// The read. Shares the suggester's debounce rather than adding a second clock:
|
|
585
|
+
// the two answer the same question at the same moment ("given what is typed so
|
|
586
|
+
// far, where does this go?"), and spec decision 12 — filing never gets slower —
|
|
587
|
+
// is a reason not to grow the number of things a keystroke sets running.
|
|
588
|
+
//
|
|
589
|
+
// A draft with no title is not previewed: it cannot be filed either, so there
|
|
590
|
+
// is no outcome to state.
|
|
591
|
+
async function refreshGravity() {
|
|
592
|
+
const seq = ++gravitySeq;
|
|
593
|
+
const title = fqTitleEl().value.trim();
|
|
594
|
+
if (!title) { renderGravity(null); return; }
|
|
595
|
+
const goalId = fqGoalEl().value;
|
|
596
|
+
const body = { kind: fqKindEl().value };
|
|
597
|
+
if (goalId) body.goal_id = Number(goalId);
|
|
598
|
+
let r;
|
|
599
|
+
try {
|
|
600
|
+
r = await api.inbox.postInboxPreviewLanding({ body });
|
|
601
|
+
} catch (_) {
|
|
602
|
+
r = null;
|
|
603
|
+
}
|
|
604
|
+
// A stale answer never paints. See gravitySeq above.
|
|
605
|
+
if (seq !== gravitySeq) return;
|
|
606
|
+
renderGravity(r && r.ok ? r.data : null);
|
|
607
|
+
}
|
|
608
|
+
|
|
504
609
|
function onQuickTitleEdit() {
|
|
505
610
|
clearTimeout(quickSuggestTimer);
|
|
506
|
-
quickSuggestTimer = setTimeout(refreshGoalSuggestions, QUICK_SUGGEST_DEBOUNCE_MS);
|
|
611
|
+
quickSuggestTimer = setTimeout(() => { refreshGoalSuggestions(); refreshGravity(); }, QUICK_SUGGEST_DEBOUNCE_MS);
|
|
507
612
|
}
|
|
508
613
|
|
|
509
614
|
function showQuickErr(msg) {
|
|
@@ -570,6 +675,10 @@
|
|
|
570
675
|
fqKindEl().value = 'bug';
|
|
571
676
|
goalTouchedByUser = false;
|
|
572
677
|
renderGoalOptions([]);
|
|
678
|
+
// The form is empty again, so the line has nothing to describe. The RECEIPT
|
|
679
|
+
// above is what survives the filing (task 1004082); gravity is a statement
|
|
680
|
+
// about a draft, and this draft is gone.
|
|
681
|
+
renderGravity(null);
|
|
573
682
|
}
|
|
574
683
|
|
|
575
684
|
async function bootQuickComposer() {
|
|
@@ -577,7 +686,11 @@
|
|
|
577
686
|
renderGoalOptions([]);
|
|
578
687
|
fqTitleEl().addEventListener('input', onQuickTitleEdit);
|
|
579
688
|
fqKindEl().addEventListener('change', onQuickTitleEdit);
|
|
580
|
-
|
|
689
|
+
// Changing the goal is the one edit that changes gravity WITHOUT changing
|
|
690
|
+
// what the suggester would return, so it redraws the line directly rather
|
|
691
|
+
// than going through onQuickTitleEdit. Deliberately undebounced: a select is a
|
|
692
|
+
// deliberate single act, not a stream of keystrokes.
|
|
693
|
+
fqGoalEl().addEventListener('change', () => { goalTouchedByUser = true; refreshGravity(); });
|
|
581
694
|
$('#fq-form').addEventListener('submit', fileQuick);
|
|
582
695
|
|
|
583
696
|
// Before any network call: the receipts are local, so a builder who reloads
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/ideas/landing-preview.js — THE LANDING VERDICT, BEFORE THE WRITE
|
|
4
|
+
// (task 1004083, BV2.R02 — "Gravity is visible before release").
|
|
5
|
+
//
|
|
6
|
+
// THE PROBLEM THIS ANSWERS. The composer's goal `<select>` is not invisible; it
|
|
7
|
+
// is visible and it MISLEADS, in two directions that the filer cannot see:
|
|
8
|
+
//
|
|
9
|
+
// 1. `No goal (homeless)` is FALSE FOR A BUG. Since ADR 0262 a homeless
|
|
10
|
+
// kind='bug' filed through the `api` vector routes into the version's
|
|
11
|
+
// maintenance goal (./homeless-home.js). The filer is told their report
|
|
12
|
+
// will float. It will not.
|
|
13
|
+
// 2. NAMING A GOAL IS FALSE FOR A JUDGMENT KIND. routing.NEVER_ROUTE_KINDS
|
|
14
|
+
// ('criterion-proposal', 'decision') and grade='full' never route, so the
|
|
15
|
+
// select shows a goal that will not catch the thought.
|
|
16
|
+
//
|
|
17
|
+
// So "gravity is visible" is not a restyle of the dropdown — the dropdown's own
|
|
18
|
+
// labels are the thing that is wrong. It is met by stating the matrix's verdict.
|
|
19
|
+
//
|
|
20
|
+
// NO NEW ROUTING, and this file is where that promise is kept. It DECIDES
|
|
21
|
+
// nothing: it calls the same `decideLanding` and `authorizeRoutedLanding` the
|
|
22
|
+
// capture calls, over the same inputs, and reports what they said. A second copy
|
|
23
|
+
// of the matrix in browser JS is the failure mode this exists to avoid — the
|
|
24
|
+
// same one the composer's `#fq-kind` comment already records ("this markup used
|
|
25
|
+
// to spell the whole vocabulary and had gone stale against the DB CHECK ... a
|
|
26
|
+
// third copy nobody was watching"), and the same one `ideas.js` already forbids
|
|
27
|
+
// in prose ("read it, never re-derive the landing matrix here").
|
|
28
|
+
//
|
|
29
|
+
// READ-ONLY IS A HARD CONTRACT, not an intention. Every statement below is a
|
|
30
|
+
// SELECT. In particular it calls `currentBuildingVersionId` and NEVER
|
|
31
|
+
// `ensureMaintenanceGoal` — that one is find-or-CREATE, and the preview runs on
|
|
32
|
+
// every keystroke of a debounced composer. Previewing a homeless bug must not
|
|
33
|
+
// mint a goal as a side effect of typing, so the preview names the version whose
|
|
34
|
+
// maintenance goal will catch it and leaves the row to the real capture.
|
|
35
|
+
//
|
|
36
|
+
// ITS OWN FILE for the reason homeless-home.js gives: inbox.js sits at the
|
|
37
|
+
// fitness line cap with no headroom, and "what will happen if I file this" is
|
|
38
|
+
// one question with one answer that a reader should find as a file.
|
|
39
|
+
|
|
40
|
+
const api = require('../../src/module-api');
|
|
41
|
+
const { pool } = api;
|
|
42
|
+
const { decideLanding, authorizeRoutedLanding } = require('./routing');
|
|
43
|
+
|
|
44
|
+
// The verdict vocabulary the composer renders. Stable machine tokens, never
|
|
45
|
+
// prose: the wording is the UI's business and the copy registry's, and a client
|
|
46
|
+
// that wants a different sentence must not have to string-match English.
|
|
47
|
+
//
|
|
48
|
+
// 'routed' — it becomes a task in a named goal. `status` says whether
|
|
49
|
+
// that task is claimable ('ready') or awaiting a nod
|
|
50
|
+
// ('backlog'); `goal` names the home.
|
|
51
|
+
// 'routed_home' — it becomes a task, but the goal is the MAINTENANCE goal the
|
|
52
|
+
// system picks, not one the filer named (the homeless bug).
|
|
53
|
+
// `version_id` names whose maintenance goal.
|
|
54
|
+
// 'floats' — it waits in the inbox for triage. The honest reading of
|
|
55
|
+
// "no goal", and also of a goal the matrix declined.
|
|
56
|
+
// 'refused' — it will not be filed at all (the protected-scope wall).
|
|
57
|
+
const OUTCOMES = Object.freeze({
|
|
58
|
+
ROUTED: 'routed',
|
|
59
|
+
ROUTED_HOME: 'routed_home',
|
|
60
|
+
FLOATS: 'floats',
|
|
61
|
+
REFUSED: 'refused',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// previewLanding — what pressing "File it" would do, without doing it.
|
|
65
|
+
//
|
|
66
|
+
// Takes exactly what the capture takes, and the caller resolves `filerRank` and
|
|
67
|
+
// `vector` the same way POST /inbox does: the rank off the AUTHENTICATED builder
|
|
68
|
+
// and the vector hardcoded. A preview that read either from the body would let a
|
|
69
|
+
// filer see — and then, by symmetry of argument, demand — a landing they do not
|
|
70
|
+
// hold. It is a read, so being wrong here leaks nothing; being wrong here
|
|
71
|
+
// TEACHES something false, which is the whole defect this task is about.
|
|
72
|
+
//
|
|
73
|
+
// NEVER THROWS. A composer redrawing on every keystroke must degrade to "we
|
|
74
|
+
// cannot say yet", never to a red error under a half-typed title. An outage in
|
|
75
|
+
// any read below collapses to `{ outcome: null }` and the UI says nothing —
|
|
76
|
+
// which leaves today's behaviour exactly as it is, rather than replacing a
|
|
77
|
+
// silent lie with a loud one.
|
|
78
|
+
async function previewLanding(input, deps = {}) {
|
|
79
|
+
// NORMALISE FIRST, DESTRUCTURE AFTER — the trap ./routing.js's `decideLanding`
|
|
80
|
+
// already records, and which the first cut of this function walked straight
|
|
81
|
+
// into. A default in the parameter list fires on `undefined` and NOT on
|
|
82
|
+
// `null`, so `previewLanding(null)` threw from the signature — BEFORE the
|
|
83
|
+
// try/catch below, so the "never throws" contract above was false for the one
|
|
84
|
+
// input a caller spreading an absent object actually produces.
|
|
85
|
+
const { goalId = null, kind = null, grade = null, filerRank = null, vector = null } = input || {};
|
|
86
|
+
const activePool = ((deps || {}).pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
|
|
87
|
+
try {
|
|
88
|
+
const landing = decideLanding({ goalId, kind, grade, filerRank, vector });
|
|
89
|
+
|
|
90
|
+
if (!landing.route) {
|
|
91
|
+
// A goal was named and the matrix declined it — the second lie above. The
|
|
92
|
+
// reason token travels so the UI can say WHY it still floats rather than
|
|
93
|
+
// showing a goal that is not going to catch anything.
|
|
94
|
+
return { outcome: OUTCOMES.FLOATS, reason: landing.reason, status: null, goal: null, version_id: null, refusal: null };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (landing.homeless) {
|
|
98
|
+
// The first lie above. Read-only: name the version, never make the goal.
|
|
99
|
+
const lifecycle = api.resolveOptional && api.resolveOptional('lifecycle');
|
|
100
|
+
const versionId = lifecycle && lifecycle.currentBuildingVersionId
|
|
101
|
+
? await lifecycle.currentBuildingVersionId()
|
|
102
|
+
: null;
|
|
103
|
+
// No building version means the real capture would fail closed
|
|
104
|
+
// (homeless-home.js NO_BUILDING_VERSION), so the preview must not promise
|
|
105
|
+
// a landing. It says nothing rather than naming a home that isn't there.
|
|
106
|
+
if (!versionId) return { outcome: null, reason: landing.reason, status: null, goal: null, version_id: null, refusal: null };
|
|
107
|
+
return {
|
|
108
|
+
outcome: OUTCOMES.ROUTED_HOME,
|
|
109
|
+
reason: landing.reason,
|
|
110
|
+
status: landing.status,
|
|
111
|
+
goal: null,
|
|
112
|
+
version_id: versionId,
|
|
113
|
+
refusal: null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A named goal. The same three facts the transaction checks — the goal is
|
|
118
|
+
// real, open, and on a building version — read WITHOUT the FOR SHARE lock,
|
|
119
|
+
// because a preview holds nothing and races nothing: it describes the world
|
|
120
|
+
// a moment before the write, and the write re-checks under its own lock.
|
|
121
|
+
const { rows } = await activePool.query(
|
|
122
|
+
`SELECT g.id, g.title, g.status, g.version_id, g.scope_modules, v.status AS version_status
|
|
123
|
+
FROM goals g
|
|
124
|
+
JOIN versions v ON v.id = g.version_id
|
|
125
|
+
WHERE g.id = $1`,
|
|
126
|
+
[goalId]
|
|
127
|
+
);
|
|
128
|
+
const goal = rows[0];
|
|
129
|
+
// A goal that is gone, archived, or on a closed version would be refused by
|
|
130
|
+
// the capture. The preview declines to guess a sentence for a state the
|
|
131
|
+
// filer cannot reach from the picker anyway (it lists open goals only), and
|
|
132
|
+
// lets the real 400 name it if they get there by other means.
|
|
133
|
+
if (!goal || goal.status !== 'open' || goal.version_status !== 'building') {
|
|
134
|
+
return { outcome: null, reason: landing.reason, status: null, goal: null, version_id: null, refusal: null };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// The binding gate, verbatim. `touches` is never declared by the composer —
|
|
138
|
+
// a person reporting a bug does not know which files it lives in — so the
|
|
139
|
+
// scope wall has nothing to subset-check and `scopeCheck` is null, exactly
|
|
140
|
+
// as it is for a human filing through POST /inbox. What CAN bite is the
|
|
141
|
+
// protected-scope refusal, and that is a server-only fact: it is the reason
|
|
142
|
+
// this preview cannot live in the browser.
|
|
143
|
+
const authorized = authorizeRoutedLanding({
|
|
144
|
+
landing,
|
|
145
|
+
filerRank,
|
|
146
|
+
scopeProtected: api.moduleScopeMap.scopeIncludesProtected(goal.scope_modules),
|
|
147
|
+
scopeCheck: null,
|
|
148
|
+
});
|
|
149
|
+
// The refusal NAMES the goal, and that is checked rather than assumed: the
|
|
150
|
+
// composer's own picker is filled from `GET /goals?status=open`, which applies
|
|
151
|
+
// no rank filter, so every open goal's title is already on screen for this
|
|
152
|
+
// caller — they picked this one FROM that list. Withholding it here would hide
|
|
153
|
+
// nothing and would cost the refusal its subject. If that read ever becomes
|
|
154
|
+
// rank-scoped, this line has to be revisited with it.
|
|
155
|
+
if (!authorized.ok) {
|
|
156
|
+
return {
|
|
157
|
+
outcome: OUTCOMES.REFUSED,
|
|
158
|
+
reason: landing.reason,
|
|
159
|
+
status: null,
|
|
160
|
+
goal: { id: goal.id, title: goal.title },
|
|
161
|
+
version_id: goal.version_id,
|
|
162
|
+
refusal: { code: authorized.code, reason: authorized.reason },
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
outcome: OUTCOMES.ROUTED,
|
|
168
|
+
reason: landing.reason,
|
|
169
|
+
status: authorized.status,
|
|
170
|
+
goal: { id: goal.id, title: goal.title },
|
|
171
|
+
version_id: goal.version_id,
|
|
172
|
+
refusal: null,
|
|
173
|
+
};
|
|
174
|
+
} catch (_) {
|
|
175
|
+
// See the NEVER THROWS note above.
|
|
176
|
+
return { outcome: null, reason: null, status: null, goal: null, version_id: null, refusal: null };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = { previewLanding, OUTCOMES };
|