reqctl 0.1.38__py3-none-any.whl
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.
- reqctl/__init__.py +0 -0
- reqctl/baseline.py +380 -0
- reqctl/cite.py +1229 -0
- reqctl/cli.py +1514 -0
- reqctl/corpus.py +1283 -0
- reqctl/fields.py +361 -0
- reqctl/graph.py +207 -0
- reqctl/history.py +175 -0
- reqctl/portal.html +2213 -0
- reqctl/portal.py +131 -0
- reqctl/reach.py +425 -0
- reqctl/resolve.py +74 -0
- reqctl/schemas/baseline.schema.yaml +42 -0
- reqctl/schemas/dictionary.schema.yaml +256 -0
- reqctl/schemas/guard.schema.yaml +131 -0
- reqctl/schemas/requirement.schema.yaml +152 -0
- reqctl/settings.py +183 -0
- reqctl/typescript.py +143 -0
- reqctl/uncited.py +257 -0
- reqctl/validate.py +1929 -0
- reqctl/write.py +2004 -0
- reqctl-0.1.38.dist-info/METADATA +11 -0
- reqctl-0.1.38.dist-info/RECORD +26 -0
- reqctl-0.1.38.dist-info/WHEEL +5 -0
- reqctl-0.1.38.dist-info/entry_points.txt +2 -0
- reqctl-0.1.38.dist-info/top_level.txt +1 -0
reqctl/portal.html
ADDED
|
@@ -0,0 +1,2213 @@
|
|
|
1
|
+
<!-- @req+ REQ-97091321@hE76bTks4vSq q4ltgt -->
|
|
2
|
+
<!-- @req+ REQ-57825543@C4KnNdXCYQZk trs27c -->
|
|
3
|
+
<!-- @req+ REQ-49576265@pFihs7WdIwGS eop5i3 -->
|
|
4
|
+
<!doctype html>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7
|
+
<title>requirements</title>
|
|
8
|
+
<!-- @req- eop5i3 -->
|
|
9
|
+
<!-- @req+ REQ-16308184@e6WlysjEauGV kb7lir -->
|
|
10
|
+
<!-- @req+ REQ-20134540@2OtJ-s-dnjF1 225qqr -->
|
|
11
|
+
<!-- @req+ REQ-20643668@eOsy33VzidrR bn2cye -->
|
|
12
|
+
<!-- @req+ REQ-28090849@CJNtdzigrsx0 3qdht5 -->
|
|
13
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S gww2ip -->
|
|
14
|
+
<!-- @req+ REQ-39359341@uRF-58MwVPtQ g4sv6a -->
|
|
15
|
+
<!-- @req+ REQ-40281745@Ifnq5GBzdwaj ofruun -->
|
|
16
|
+
<!-- @req+ REQ-41307666@x3Cru_vnJl9T 42kdi2 -->
|
|
17
|
+
<!-- @req+ REQ-42771008@O9ZIKWRPqITt xntksp -->
|
|
18
|
+
<!-- @req+ REQ-43631070@Qebxq4sD9bJ6 6qcrzv -->
|
|
19
|
+
<!-- @req+ REQ-49900441@vzcVI-Jl7ucP eysdzo -->
|
|
20
|
+
<!-- @req+ REQ-51482370@AzceAvr9ffaW pfx5in -->
|
|
21
|
+
<!-- @req+ REQ-51994852@TKuebjB9MeY2 vko7hf -->
|
|
22
|
+
<!-- @req+ REQ-58911635@cSn29GquJ0YA 57ylsd -->
|
|
23
|
+
<!-- @req+ REQ-59399209@DMSM7UOh_uQ4 pxwude -->
|
|
24
|
+
<!-- @req+ REQ-62729430@uBypjG0c4OWO 4kotn3 -->
|
|
25
|
+
<!-- @req+ REQ-62925340@nMvWkQNQyn32 x7ws4p -->
|
|
26
|
+
<!-- @req+ REQ-63880283@cB0-6eSJUN4B co634p -->
|
|
27
|
+
<!-- @req+ REQ-69283350@vYYldulaXQvc oitmfb -->
|
|
28
|
+
<!-- @req+ REQ-70227145@poKfbXXrCK1C uahp4a -->
|
|
29
|
+
<!-- @req+ REQ-71989308@UpVZOmFxqw4a 4tlpr5 -->
|
|
30
|
+
<!-- @req+ REQ-77791826@G38_IHLTaGc7 nc6dvv -->
|
|
31
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 gdthnl -->
|
|
32
|
+
<!-- @req+ REQ-81220228@DAytEkvTUdTO rw6kcg -->
|
|
33
|
+
<!-- @req+ REQ-84459416@ZlrZpYNBUh8Q o4qqa2 -->
|
|
34
|
+
<!-- @req+ REQ-84590169@qDZMPHwd6mWm hudo2y -->
|
|
35
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM i7bu3e -->
|
|
36
|
+
<!-- @req+ REQ-87847146@3MkrB9EtSIEF 2grs7y -->
|
|
37
|
+
<!-- @req+ REQ-88874370@H8CRzlKTmNrt ose27n -->
|
|
38
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho qpm2bl -->
|
|
39
|
+
<!-- @req+ REQ-90181246@mRCzSBaTKCgT hopmdg -->
|
|
40
|
+
<!-- @req+ REQ-94312778@9nojWLshVfkX jieypy -->
|
|
41
|
+
<style>
|
|
42
|
+
:root {
|
|
43
|
+
color-scheme: light dark;
|
|
44
|
+
--bg: #fbfbfa; --panel: #fff; --line: #e4e2dd; --ink: #1a1917;
|
|
45
|
+
--mut: #565049; --dim: #8a8279; --accent: #00a4a4; --warn: #be7c1c;
|
|
46
|
+
--bad: #b4392f; --ok: #3f7d3a;
|
|
47
|
+
}
|
|
48
|
+
@media (prefers-color-scheme: dark) {
|
|
49
|
+
:root {
|
|
50
|
+
--bg: #16161a; --panel: #1e1e24; --line: #2f2f38; --ink: #e8e6e1;
|
|
51
|
+
--mut: #a8a29a; --dim: #736d65; --accent: #4fd0d0; --warn: #d99a3e;
|
|
52
|
+
--bad: #e0685c; --ok: #6fbf68;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
* { box-sizing: border-box; }
|
|
56
|
+
body {
|
|
57
|
+
margin: 0; background: var(--bg); color: var(--ink);
|
|
58
|
+
font: 14px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
59
|
+
}
|
|
60
|
+
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
61
|
+
button, input, select, textarea {
|
|
62
|
+
font: inherit; color: inherit; background: var(--panel);
|
|
63
|
+
border: 1px solid var(--line); border-radius: 6px; padding: 7px 9px;
|
|
64
|
+
}
|
|
65
|
+
button { cursor: pointer; }
|
|
66
|
+
button:hover { border-color: var(--accent); }
|
|
67
|
+
button.go { background: var(--accent); color: #08201f; border-color: var(--accent);
|
|
68
|
+
font-weight: 600; }
|
|
69
|
+
button.go:disabled { opacity: .5; cursor: default; }
|
|
70
|
+
header {
|
|
71
|
+
position: sticky; top: 0; z-index: 5; background: var(--panel);
|
|
72
|
+
border-bottom: 1px solid var(--line); padding: 10px 14px;
|
|
73
|
+
display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
|
|
74
|
+
}
|
|
75
|
+
header h1 { font-size: 14px; margin: 0; font-weight: 650; letter-spacing: .02em; }
|
|
76
|
+
header .grow { flex: 1 1 160px; }
|
|
77
|
+
header input[type=search] { width: 100%; }
|
|
78
|
+
.facets { display: flex; gap: 6px; flex-wrap: wrap; padding: 10px 14px 0; }
|
|
79
|
+
.facet {
|
|
80
|
+
border: 1px solid var(--line); background: var(--panel); border-radius: 999px;
|
|
81
|
+
padding: 4px 11px; font-size: 12px; color: var(--mut);
|
|
82
|
+
}
|
|
83
|
+
.facet[aria-pressed=true] { border-color: var(--accent); color: var(--ink); font-weight: 600; }
|
|
84
|
+
main { display: grid; grid-template-columns: 1fr; gap: 14px; padding: 12px 14px 40px; }
|
|
85
|
+
@media (min-width: 900px) {
|
|
86
|
+
main { grid-template-columns: minmax(280px, 380px) 1fr; align-items: start; }
|
|
87
|
+
#detail { position: sticky; top: 62px; max-height: calc(100vh - 76px); overflow: auto; }
|
|
88
|
+
}
|
|
89
|
+
.rows { display: flex; flex-direction: column; gap: 6px; }
|
|
90
|
+
.row {
|
|
91
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
|
|
92
|
+
padding: 9px 11px; cursor: pointer; text-align: left; width: 100%;
|
|
93
|
+
}
|
|
94
|
+
.row:hover, .row[aria-current=true] { border-color: var(--accent); }
|
|
95
|
+
.row .uid { font-size: 11px; color: var(--dim); }
|
|
96
|
+
.row .txt {
|
|
97
|
+
margin-top: 2px; display: -webkit-box; -webkit-line-clamp: 3;
|
|
98
|
+
-webkit-box-orient: vertical; overflow: hidden;
|
|
99
|
+
}
|
|
100
|
+
.row.gone .txt { text-decoration: line-through; color: var(--dim); }
|
|
101
|
+
#detail { background: var(--panel); border: 1px solid var(--line);
|
|
102
|
+
border-radius: 10px; padding: 16px; }
|
|
103
|
+
.label { font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
|
|
104
|
+
color: var(--dim); margin: 16px 0 6px; }
|
|
105
|
+
.card { border: 1px solid var(--line); border-radius: 7px; padding: 8px 10px;
|
|
106
|
+
margin-bottom: 6px; }
|
|
107
|
+
.chip { display: inline-block; font-size: 11px; border-radius: 999px;
|
|
108
|
+
padding: 1px 8px; border: 1px solid var(--line); color: var(--mut); }
|
|
109
|
+
.chip.suspect { border-color: var(--warn); color: var(--warn); }
|
|
110
|
+
.chip.missing { border-color: var(--bad); color: var(--bad); }
|
|
111
|
+
.crit { border-left: 2px solid var(--line); padding-left: 10px; margin-bottom: 8px; }
|
|
112
|
+
.crit div { display: flex; gap: 8px; }
|
|
113
|
+
.crit b { color: var(--dim); font-size: 11px; min-width: 44px; }
|
|
114
|
+
a { color: var(--accent); }
|
|
115
|
+
.uidlink { cursor: pointer; text-decoration: underline dotted; }
|
|
116
|
+
.empty { color: var(--dim); }
|
|
117
|
+
.setup { max-width: 620px; margin: 8vh auto; padding: 0 18px; }
|
|
118
|
+
.setup ol { padding-left: 20px; color: var(--mut); }
|
|
119
|
+
.setup li { margin-bottom: 7px; }
|
|
120
|
+
.setup input { width: 100%; margin: 10px 0; }
|
|
121
|
+
.note { border-left: 3px solid var(--warn); padding: 8px 12px; color: var(--mut);
|
|
122
|
+
background: var(--panel); border-radius: 0 7px 7px 0; margin: 14px 0; }
|
|
123
|
+
dialog {
|
|
124
|
+
border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
|
|
125
|
+
color: var(--ink); max-width: 620px; width: calc(100vw - 28px); padding: 18px;
|
|
126
|
+
}
|
|
127
|
+
dialog::backdrop { background: rgba(0,0,0,.5); }
|
|
128
|
+
.field { margin-bottom: 12px; }
|
|
129
|
+
.field label { display: block; font-size: 12px; color: var(--mut); margin-bottom: 4px; }
|
|
130
|
+
.field input, .field select, .field textarea { width: 100%; }
|
|
131
|
+
.field textarea { min-height: 80px; resize: vertical; }
|
|
132
|
+
.status { padding: 10px 14px; font-size: 13px; color: var(--mut); }
|
|
133
|
+
.status.bad { color: var(--bad); }
|
|
134
|
+
button.thin { padding: 2px 8px; font-size: 11px; }
|
|
135
|
+
.pair { display: flex; gap: 6px; margin-bottom: 6px; }
|
|
136
|
+
.pair input { flex: 1 1 0; min-width: 0; }
|
|
137
|
+
.pick { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
|
|
138
|
+
dialog.wide { max-width: none; height: calc(100vh - 28px);
|
|
139
|
+
display: flex; flex-direction: column; }
|
|
140
|
+
.treebox { flex: 1 1 auto; overflow: auto; margin-top: 10px; }
|
|
141
|
+
.tree { position: relative; }
|
|
142
|
+
.tree svg { position: absolute; inset: 0; pointer-events: none; }
|
|
143
|
+
.tree svg > path { fill: none; stroke: var(--line); stroke-width: 1.5; }
|
|
144
|
+
.tree svg > path.side { stroke: var(--warn); stroke-dasharray: 4 3; }
|
|
145
|
+
.tree marker path { fill: var(--warn); }
|
|
146
|
+
.node { position: absolute; width: 168px; height: 54px; padding: 5px 7px;
|
|
147
|
+
text-align: left; overflow: hidden; }
|
|
148
|
+
.node .uid { display: block; font-size: 10px; color: var(--dim); }
|
|
149
|
+
.node .txt { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
|
150
|
+
overflow: hidden; font-size: 11px; line-height: 1.35; }
|
|
151
|
+
.node.here { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
|
|
152
|
+
.node.gone .txt { text-decoration: line-through; color: var(--dim); }
|
|
153
|
+
</style>
|
|
154
|
+
<!-- @req- jieypy -->
|
|
155
|
+
<!-- @req- hopmdg -->
|
|
156
|
+
<!-- @req- qpm2bl -->
|
|
157
|
+
<!-- @req- ose27n -->
|
|
158
|
+
<!-- @req- 2grs7y -->
|
|
159
|
+
<!-- @req- i7bu3e -->
|
|
160
|
+
<!-- @req- hudo2y -->
|
|
161
|
+
<!-- @req- o4qqa2 -->
|
|
162
|
+
<!-- @req- rw6kcg -->
|
|
163
|
+
<!-- @req- gdthnl -->
|
|
164
|
+
<!-- @req- nc6dvv -->
|
|
165
|
+
<!-- @req- 4tlpr5 -->
|
|
166
|
+
<!-- @req- uahp4a -->
|
|
167
|
+
<!-- @req- oitmfb -->
|
|
168
|
+
<!-- @req- co634p -->
|
|
169
|
+
<!-- @req- x7ws4p -->
|
|
170
|
+
<!-- @req- 4kotn3 -->
|
|
171
|
+
<!-- @req- pxwude -->
|
|
172
|
+
<!-- @req- 57ylsd -->
|
|
173
|
+
<!-- @req- vko7hf -->
|
|
174
|
+
<!-- @req- pfx5in -->
|
|
175
|
+
<!-- @req- eysdzo -->
|
|
176
|
+
<!-- @req- 6qcrzv -->
|
|
177
|
+
<!-- @req- xntksp -->
|
|
178
|
+
<!-- @req- 42kdi2 -->
|
|
179
|
+
<!-- @req- ofruun -->
|
|
180
|
+
<!-- @req- g4sv6a -->
|
|
181
|
+
<!-- @req- gww2ip -->
|
|
182
|
+
<!-- @req- 3qdht5 -->
|
|
183
|
+
<!-- @req- bn2cye -->
|
|
184
|
+
<!-- @req- 225qqr -->
|
|
185
|
+
<!-- @req- kb7lir -->
|
|
186
|
+
<!-- @req- trs27c -->
|
|
187
|
+
|
|
188
|
+
<!-- @req> REQ-49576265@pFihs7WdIwGS lt4z6w -->
|
|
189
|
+
<div id="app"></div>
|
|
190
|
+
<!-- @req- q4ltgt -->
|
|
191
|
+
|
|
192
|
+
<!-- @req> REQ-28090849@CJNtdzigrsx0 tt67oq -->
|
|
193
|
+
<!-- @req> REQ-87847146@3MkrB9EtSIEF yes37y -->
|
|
194
|
+
<script type="application/json" id="kind-fields"></script>
|
|
195
|
+
<!-- @req> REQ-84459416@ZlrZpYNBUh8Q a3ct2q -->
|
|
196
|
+
<script type="application/json" id="token-permissions"></script>
|
|
197
|
+
<!-- @req> REQ-49576265@pFihs7WdIwGS lutz7u -->
|
|
198
|
+
<script>
|
|
199
|
+
<!-- @req+ REQ-13671144@gYp6g1_8U5dA x4yeo7 -->
|
|
200
|
+
<!-- @req+ REQ-51994852@TKuebjB9MeY2 o6bnhc -->
|
|
201
|
+
<!-- @req+ REQ-58928387@e-BYRqijg-8x 75zy5s -->
|
|
202
|
+
<!-- @req+ REQ-62729430@uBypjG0c4OWO 62vyej -->
|
|
203
|
+
<!-- @req+ REQ-94065353@13TDTQioAe4K 3k7dmz -->
|
|
204
|
+
<!-- @req+ REQ-42771008@O9ZIKWRPqITt v64xk5 -->
|
|
205
|
+
<!-- @req+ REQ-11203679@LKGrEB6ZNVCv haro54 -->
|
|
206
|
+
<!-- @req+ REQ-12062416@zyf6QGAMGROf z4x25z -->
|
|
207
|
+
<!-- @req+ REQ-15307279@nS5Wtd_cnICT lfsv3a -->
|
|
208
|
+
<!-- @req+ REQ-16910419@bcXxnTl6FuGy 43m7de -->
|
|
209
|
+
<!-- @req+ REQ-17919170@qG4QtddUE5fI tkswuu -->
|
|
210
|
+
<!-- @req+ REQ-20643668@eOsy33VzidrR chucux -->
|
|
211
|
+
<!-- @req+ REQ-23350269@Wy4vkFUwo_eF v4vh4l -->
|
|
212
|
+
<!-- @req+ REQ-35173659@6-uptMskrE4L 6jr6b6 -->
|
|
213
|
+
<!-- @req+ REQ-45005873@aGI8YILcA3YW phnvew -->
|
|
214
|
+
<!-- @req+ REQ-45207510@UCnNHap8pwx6 cmqcmy -->
|
|
215
|
+
<!-- @req+ REQ-58466257@Mw_PWSEOU-gb 3f6vet -->
|
|
216
|
+
<!-- @req+ REQ-59774469@2L7AroyhuVFM lgwexf -->
|
|
217
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 g4p6im -->
|
|
218
|
+
<!-- @req+ REQ-83033927@K3q-IpHctnCp oeu6q2 -->
|
|
219
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe u3proa -->
|
|
220
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho 7ivqaa -->
|
|
221
|
+
<!-- @req+ REQ-90392012@aPwZvbqtP1TA qwwwop -->
|
|
222
|
+
<!-- @req+ REQ-97091321@hE76bTks4vSq 6gvrj4 -->
|
|
223
|
+
const VIEW = "corpus-view";
|
|
224
|
+
<!-- @req+ REQ-64005208@VjZ0V0VbMEZR vw64dq -->
|
|
225
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY 6ibyne -->
|
|
226
|
+
<!-- @req+ REQ-70227145@poKfbXXrCK1C gt4u7z -->
|
|
227
|
+
<!-- @req+ REQ-71407357@EUXcMYpw10y- 4ntfss -->
|
|
228
|
+
<!-- @req+ REQ-71729610@6No-zvUcqtY0 jz7diw -->
|
|
229
|
+
<!-- @req+ REQ-77248647@YMbLJ7it09qa ne2i4o -->
|
|
230
|
+
const FLOW = "corpus-write.yml";
|
|
231
|
+
<!-- @req+ REQ-11095894@-kE72Zroi1iE rv76dd -->
|
|
232
|
+
<!-- @req+ REQ-38789214@OMx9lVswwRCe stfwyt -->
|
|
233
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM m4kxk4 -->
|
|
234
|
+
<!-- @req> REQ-84459416@ZlrZpYNBUh8Q 3nbbwi -->
|
|
235
|
+
const KEY = "reqportal-token";
|
|
236
|
+
<!-- @req> REQ-17728624@TD_EImR1TLzY ifrqxw -->
|
|
237
|
+
<!-- @req> REQ-53764133@s638VvRMfEgi jyj6oy -->
|
|
238
|
+
<!-- @req> REQ-90709998@v1VFnSWnrebX 2hgf6r -->
|
|
239
|
+
const PLACE = "reqportal-repo";
|
|
240
|
+
<!-- @req- m4kxk4 -->
|
|
241
|
+
<!-- @req- stfwyt -->
|
|
242
|
+
<!-- @req- rv76dd -->
|
|
243
|
+
<!-- @req- ne2i4o -->
|
|
244
|
+
<!-- @req- jz7diw -->
|
|
245
|
+
<!-- @req- 4ntfss -->
|
|
246
|
+
<!-- @req- gt4u7z -->
|
|
247
|
+
<!-- @req- 6ibyne -->
|
|
248
|
+
<!-- @req- vw64dq -->
|
|
249
|
+
const REPO_FORM = /^[A-Za-z0-9-]+\/[A-Za-z0-9._-]+$/;
|
|
250
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S krjsg5 -->
|
|
251
|
+
<!-- @req> REQ-38893754@LAv4UgvQ-csC ituxi7 -->
|
|
252
|
+
<!-- @req> REQ-67241140@WfIP6H42c1yv mnsek2 -->
|
|
253
|
+
const BATCH = "reqportal-batch";
|
|
254
|
+
|
|
255
|
+
<!-- @req+ REQ-49900441@vzcVI-Jl7ucP dollsi -->
|
|
256
|
+
<!-- @req+ REQ-90181246@mRCzSBaTKCgT oco6m2 -->
|
|
257
|
+
<!-- @req+ REQ-16513812@sNNqo8EtRKkZ bvvw6s -->
|
|
258
|
+
<!-- @req+ REQ-20134540@2OtJ-s-dnjF1 hj65ee -->
|
|
259
|
+
<!-- @req+ REQ-36903196@Qp4pMTYCC1T0 xhspco -->
|
|
260
|
+
<!-- @req+ REQ-41307666@x3Cru_vnJl9T pwbizx -->
|
|
261
|
+
<!-- @req+ REQ-51361575@NQI9dJRNPz0T 7tn3kn -->
|
|
262
|
+
<!-- @req+ REQ-54076552@G1Ej0m1RlGGS 2ibqvq -->
|
|
263
|
+
<!-- @req+ REQ-61064763@7M2NL93SN4jQ vkpz5p -->
|
|
264
|
+
<!-- @req+ REQ-81220228@DAytEkvTUdTO qiahra -->
|
|
265
|
+
<!-- @req+ REQ-40692737@dc9rXL3Rwd-E apoc5a -->
|
|
266
|
+
<!-- @req+ REQ-94312778@9nojWLshVfkX khkqwg -->
|
|
267
|
+
<!-- @req> REQ-28090849@CJNtdzigrsx0 mpnidw -->
|
|
268
|
+
<!-- @req> REQ-55866158@mH5CUkZ3Wx6H 5odkld -->
|
|
269
|
+
<!-- @req> REQ-84590169@qDZMPHwd6mWm omiq2k -->
|
|
270
|
+
let corpus = [];
|
|
271
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY md4w5w -->
|
|
272
|
+
<!-- @req+ REQ-77248647@YMbLJ7it09qa cpjif4 -->
|
|
273
|
+
<!-- @req+ REQ-21513166@zjiX-rVW8oFK jgxbh7 -->
|
|
274
|
+
<!-- @req+ REQ-44810466@bLJV-4isqQ5j hhtajz -->
|
|
275
|
+
<!-- @req+ REQ-45797944@wXgeXQtMt7tG phld57 -->
|
|
276
|
+
<!-- @req> REQ-16308184@e6WlysjEauGV glzlle -->
|
|
277
|
+
<!-- @req> REQ-39359341@uRF-58MwVPtQ u6nyi3 -->
|
|
278
|
+
<!-- @req> REQ-54337106@fv9NfknIlFJO xoppim -->
|
|
279
|
+
<!-- @req> REQ-63880283@cB0-6eSJUN4B l3otzi -->
|
|
280
|
+
<!-- @req> REQ-74219001@x7rQkCaRklIp s6cidu -->
|
|
281
|
+
<!-- @req> REQ-80416166@fZFXqf9ggCT8 4k33q6 -->
|
|
282
|
+
let byUid = {};
|
|
283
|
+
<!-- @req- khkqwg -->
|
|
284
|
+
<!-- @req- apoc5a -->
|
|
285
|
+
<!-- @req> REQ-21522236@jupFEITq3Nms x3g5y4 -->
|
|
286
|
+
<!-- @req> REQ-28090849@CJNtdzigrsx0 q5bxdy -->
|
|
287
|
+
<!-- @req> REQ-50159837@eYoI-qFNXpXN wgd3pz -->
|
|
288
|
+
<!-- @req> REQ-87847146@3MkrB9EtSIEF hel35z -->
|
|
289
|
+
let kind = "requirement";
|
|
290
|
+
<!-- @req- phld57 -->
|
|
291
|
+
<!-- @req- hhtajz -->
|
|
292
|
+
<!-- @req- jgxbh7 -->
|
|
293
|
+
<!-- @req- qiahra -->
|
|
294
|
+
<!-- @req- vkpz5p -->
|
|
295
|
+
<!-- @req- 2ibqvq -->
|
|
296
|
+
<!-- @req- 7tn3kn -->
|
|
297
|
+
<!-- @req- pwbizx -->
|
|
298
|
+
<!-- @req- xhspco -->
|
|
299
|
+
<!-- @req- hj65ee -->
|
|
300
|
+
<!-- @req- bvvw6s -->
|
|
301
|
+
<!-- @req> REQ-64005208@VjZ0V0VbMEZR v7h4os -->
|
|
302
|
+
<!-- @req> REQ-70227145@poKfbXXrCK1C x65pvo -->
|
|
303
|
+
<!-- @req> REQ-71407357@EUXcMYpw10y- o4rzai -->
|
|
304
|
+
<!-- @req> REQ-71729610@6No-zvUcqtY0 fdpr2r -->
|
|
305
|
+
let query = "";
|
|
306
|
+
<!-- @req- cpjif4 -->
|
|
307
|
+
<!-- @req- md4w5w -->
|
|
308
|
+
<!-- @req> REQ-16308184@e6WlysjEauGV bzgse2 -->
|
|
309
|
+
<!-- @req> REQ-39359341@uRF-58MwVPtQ b2e6zu -->
|
|
310
|
+
<!-- @req> REQ-43631070@Qebxq4sD9bJ6 6w5zit -->
|
|
311
|
+
<!-- @req> REQ-63880283@cB0-6eSJUN4B qg6n5b -->
|
|
312
|
+
<!-- @req> REQ-72352942@q7_Ju8MGy5CF dbtyom -->
|
|
313
|
+
<!-- @req> REQ-74219001@x7rQkCaRklIp g4mdwf -->
|
|
314
|
+
let chosen = null;
|
|
315
|
+
<!-- @req+ REQ-38893754@LAv4UgvQ-csC 4x347z -->
|
|
316
|
+
<!-- @req> REQ-54337106@fv9NfknIlFJO pl74bo -->
|
|
317
|
+
<!-- @req> REQ-64005208@VjZ0V0VbMEZR p6oc3j -->
|
|
318
|
+
<!-- @req> REQ-67241140@WfIP6H42c1yv 3qebzl -->
|
|
319
|
+
<!-- @req> REQ-71407357@EUXcMYpw10y- 5duekv -->
|
|
320
|
+
<!-- @req> REQ-76958744@G6gCTCrSsHet 7qmhzu -->
|
|
321
|
+
<!-- @req> REQ-77248647@YMbLJ7it09qa uckr7g -->
|
|
322
|
+
<!-- @req> REQ-84641100@hkDOGJ16SKpM tvcyhl -->
|
|
323
|
+
let batch = [];
|
|
324
|
+
let notice = "";
|
|
325
|
+
<!-- @req- 4x347z -->
|
|
326
|
+
<!-- @req- oco6m2 -->
|
|
327
|
+
<!-- @req- dollsi -->
|
|
328
|
+
<!-- @req- krjsg5 -->
|
|
329
|
+
<!-- @req- 6gvrj4 -->
|
|
330
|
+
<!-- @req- qwwwop -->
|
|
331
|
+
<!-- @req- 7ivqaa -->
|
|
332
|
+
<!-- @req- u3proa -->
|
|
333
|
+
<!-- @req- oeu6q2 -->
|
|
334
|
+
<!-- @req- g4p6im -->
|
|
335
|
+
<!-- @req- lgwexf -->
|
|
336
|
+
<!-- @req- 3f6vet -->
|
|
337
|
+
<!-- @req- cmqcmy -->
|
|
338
|
+
<!-- @req- phnvew -->
|
|
339
|
+
<!-- @req- 6jr6b6 -->
|
|
340
|
+
<!-- @req- v4vh4l -->
|
|
341
|
+
<!-- @req- chucux -->
|
|
342
|
+
<!-- @req- tkswuu -->
|
|
343
|
+
<!-- @req- 43m7de -->
|
|
344
|
+
<!-- @req- lfsv3a -->
|
|
345
|
+
<!-- @req- z4x25z -->
|
|
346
|
+
<!-- @req- haro54 -->
|
|
347
|
+
<!-- @req> REQ-55866158@mH5CUkZ3Wx6H ukotq7 -->
|
|
348
|
+
<!-- @req> REQ-58911635@cSn29GquJ0YA 4z4zug -->
|
|
349
|
+
<!-- @req> REQ-59399209@DMSM7UOh_uQ4 t5k2x6 -->
|
|
350
|
+
<!-- @req> REQ-94312778@9nojWLshVfkX yo6y3o -->
|
|
351
|
+
let showLinks = false;
|
|
352
|
+
|
|
353
|
+
<!-- @req+ REQ-71407357@EUXcMYpw10y- nodqc4 -->
|
|
354
|
+
<!-- @req+ REQ-11203679@LKGrEB6ZNVCv 3ce4ro -->
|
|
355
|
+
<!-- @req+ REQ-12062416@zyf6QGAMGROf rixqvx -->
|
|
356
|
+
<!-- @req+ REQ-15307279@nS5Wtd_cnICT m75p4r -->
|
|
357
|
+
<!-- @req+ REQ-16910419@bcXxnTl6FuGy 33y4sp -->
|
|
358
|
+
<!-- @req+ REQ-17919170@qG4QtddUE5fI cxrgj3 -->
|
|
359
|
+
<!-- @req+ REQ-20643668@eOsy33VzidrR xuun3q -->
|
|
360
|
+
<!-- @req+ REQ-23350269@Wy4vkFUwo_eF nsepba -->
|
|
361
|
+
<!-- @req+ REQ-35173659@6-uptMskrE4L gtwilw -->
|
|
362
|
+
<!-- @req+ REQ-45005873@aGI8YILcA3YW rojbbp -->
|
|
363
|
+
<!-- @req+ REQ-45207510@UCnNHap8pwx6 2s4m6u -->
|
|
364
|
+
<!-- @req+ REQ-58466257@Mw_PWSEOU-gb 7ag777 -->
|
|
365
|
+
<!-- @req+ REQ-59774469@2L7AroyhuVFM wpmovn -->
|
|
366
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 7hy7gs -->
|
|
367
|
+
<!-- @req+ REQ-83033927@K3q-IpHctnCp qwhi5p -->
|
|
368
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe werde5 -->
|
|
369
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho d3fh2o -->
|
|
370
|
+
<!-- @req+ REQ-90392012@aPwZvbqtP1TA eexfhj -->
|
|
371
|
+
<!-- @req+ REQ-97091321@hE76bTks4vSq 5jlmyo -->
|
|
372
|
+
<!-- @req+ REQ-64005208@VjZ0V0VbMEZR igma3y -->
|
|
373
|
+
const MINTED = /^\$(\d+)$/;
|
|
374
|
+
|
|
375
|
+
const shifted = (held, by) => {
|
|
376
|
+
if (Array.isArray(held)) return held.map((each) => shifted(each, by));
|
|
377
|
+
const found = MINTED.exec(String(held));
|
|
378
|
+
return found ? `$${Number(found[1]) + by}` : held;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const offsetSteps = (steps, by) => steps.map((step) => ({
|
|
382
|
+
...step,
|
|
383
|
+
...(step.args && { args: shifted(step.args, by) }),
|
|
384
|
+
}));
|
|
385
|
+
|
|
386
|
+
const batched = () => batch.flatMap((held, at) =>
|
|
387
|
+
offsetSteps(held.steps, batch.slice(0, at).reduce((n, e) => n + e.steps.length, 0)));
|
|
388
|
+
<!-- @req- igma3y -->
|
|
389
|
+
<!-- @req- nodqc4 -->
|
|
390
|
+
|
|
391
|
+
<!-- @req+ REQ-11095894@-kE72Zroi1iE 55w77m -->
|
|
392
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM yu5qjl -->
|
|
393
|
+
<!-- @req+ REQ-84459416@ZlrZpYNBUh8Q zyczm3 -->
|
|
394
|
+
<!-- @req+ REQ-16308184@e6WlysjEauGV dl3kc7 -->
|
|
395
|
+
<!-- @req+ REQ-16513812@sNNqo8EtRKkZ dcsotu -->
|
|
396
|
+
<!-- @req+ REQ-19183794@dRHS3YJCEEPV egtrgf -->
|
|
397
|
+
<!-- @req+ REQ-20134540@2OtJ-s-dnjF1 q4bylm -->
|
|
398
|
+
<!-- @req+ REQ-22950334@CvELqCF22whm cmzvr2 -->
|
|
399
|
+
<!-- @req+ REQ-25073861@ARYmbcnvBhhh vkgozd -->
|
|
400
|
+
<!-- @req+ REQ-36903196@Qp4pMTYCC1T0 q7baps -->
|
|
401
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S qcw3p4 -->
|
|
402
|
+
<!-- @req+ REQ-39359341@uRF-58MwVPtQ y6nm6x -->
|
|
403
|
+
<!-- @req+ REQ-40281745@Ifnq5GBzdwaj qejvrs -->
|
|
404
|
+
<!-- @req+ REQ-41307666@x3Cru_vnJl9T d2pvdm -->
|
|
405
|
+
<!-- @req+ REQ-43631070@Qebxq4sD9bJ6 da45vj -->
|
|
406
|
+
<!-- @req+ REQ-49900441@vzcVI-Jl7ucP ntalgb -->
|
|
407
|
+
<!-- @req+ REQ-51361575@NQI9dJRNPz0T wusayj -->
|
|
408
|
+
<!-- @req+ REQ-51482370@AzceAvr9ffaW q3lvpr -->
|
|
409
|
+
<!-- @req+ REQ-54076552@G1Ej0m1RlGGS wnejt4 -->
|
|
410
|
+
<!-- @req+ REQ-55866158@mH5CUkZ3Wx6H txb5oo -->
|
|
411
|
+
<!-- @req+ REQ-58911635@cSn29GquJ0YA gs7mu4 -->
|
|
412
|
+
<!-- @req+ REQ-59399209@DMSM7UOh_uQ4 rqauol -->
|
|
413
|
+
<!-- @req+ REQ-61064763@7M2NL93SN4jQ do3xn2 -->
|
|
414
|
+
<!-- @req+ REQ-67532924@r8wZQAAKL9Ye ab6fto -->
|
|
415
|
+
<!-- @req+ REQ-69283350@vYYldulaXQvc ilgotb -->
|
|
416
|
+
<!-- @req+ REQ-72352942@q7_Ju8MGy5CF aemw4e -->
|
|
417
|
+
<!-- @req+ REQ-76958744@G6gCTCrSsHet bapbkq -->
|
|
418
|
+
<!-- @req+ REQ-81220228@DAytEkvTUdTO awzple -->
|
|
419
|
+
<!-- @req+ REQ-84590169@qDZMPHwd6mWm gmhnff -->
|
|
420
|
+
<!-- @req+ REQ-87847146@3MkrB9EtSIEF i6s7we -->
|
|
421
|
+
<!-- @req+ REQ-90181246@mRCzSBaTKCgT sgzvge -->
|
|
422
|
+
<!-- @req+ REQ-94312778@9nojWLshVfkX o4ux7f -->
|
|
423
|
+
<!-- @req+ REQ-94410924@VNuUThWMV1Ne obmpm4 -->
|
|
424
|
+
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) =>
|
|
425
|
+
({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
426
|
+
<!-- @req- obmpm4 -->
|
|
427
|
+
<!-- @req- o4ux7f -->
|
|
428
|
+
<!-- @req- sgzvge -->
|
|
429
|
+
<!-- @req- i6s7we -->
|
|
430
|
+
<!-- @req- gmhnff -->
|
|
431
|
+
<!-- @req- awzple -->
|
|
432
|
+
<!-- @req- bapbkq -->
|
|
433
|
+
<!-- @req- aemw4e -->
|
|
434
|
+
<!-- @req- ilgotb -->
|
|
435
|
+
<!-- @req- ab6fto -->
|
|
436
|
+
<!-- @req- do3xn2 -->
|
|
437
|
+
<!-- @req- rqauol -->
|
|
438
|
+
<!-- @req- gs7mu4 -->
|
|
439
|
+
<!-- @req- txb5oo -->
|
|
440
|
+
<!-- @req- wnejt4 -->
|
|
441
|
+
<!-- @req- q3lvpr -->
|
|
442
|
+
<!-- @req- wusayj -->
|
|
443
|
+
<!-- @req- ntalgb -->
|
|
444
|
+
<!-- @req- da45vj -->
|
|
445
|
+
<!-- @req- d2pvdm -->
|
|
446
|
+
<!-- @req- qejvrs -->
|
|
447
|
+
<!-- @req- y6nm6x -->
|
|
448
|
+
<!-- @req- qcw3p4 -->
|
|
449
|
+
<!-- @req- q7baps -->
|
|
450
|
+
<!-- @req- vkgozd -->
|
|
451
|
+
<!-- @req- cmzvr2 -->
|
|
452
|
+
<!-- @req- q4bylm -->
|
|
453
|
+
<!-- @req- egtrgf -->
|
|
454
|
+
<!-- @req- dcsotu -->
|
|
455
|
+
<!-- @req- dl3kc7 -->
|
|
456
|
+
|
|
457
|
+
<!-- @req+ REQ-64005208@VjZ0V0VbMEZR qrpj4k -->
|
|
458
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY clwkn3 -->
|
|
459
|
+
<!-- @req+ REQ-70227145@poKfbXXrCK1C bowvwm -->
|
|
460
|
+
<!-- @req+ REQ-71407357@EUXcMYpw10y- wocuxf -->
|
|
461
|
+
<!-- @req+ REQ-71729610@6No-zvUcqtY0 n4zrm6 -->
|
|
462
|
+
<!-- @req+ REQ-77248647@YMbLJ7it09qa fcovgx -->
|
|
463
|
+
<!-- @req> REQ-38789214@OMx9lVswwRCe msay4e -->
|
|
464
|
+
const token = () => localStorage.getItem(KEY) || "";
|
|
465
|
+
<!-- @req- zyczm3 -->
|
|
466
|
+
<!-- @req> REQ-90709998@v1VFnSWnrebX yh7ilp -->
|
|
467
|
+
const repo = () => localStorage.getItem(PLACE) || "";
|
|
468
|
+
<!-- @req- fcovgx -->
|
|
469
|
+
<!-- @req- n4zrm6 -->
|
|
470
|
+
<!-- @req- wocuxf -->
|
|
471
|
+
<!-- @req- bowvwm -->
|
|
472
|
+
<!-- @req- clwkn3 -->
|
|
473
|
+
<!-- @req- qrpj4k -->
|
|
474
|
+
<!-- @req- yu5qjl -->
|
|
475
|
+
<!-- @req- 55w77m -->
|
|
476
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S sw53tv -->
|
|
477
|
+
<!-- @req+ REQ-67241140@WfIP6H42c1yv f2e4lc -->
|
|
478
|
+
function keepBatch() {
|
|
479
|
+
try {
|
|
480
|
+
localStorage.setItem(BATCH, JSON.stringify(batch));
|
|
481
|
+
return "";
|
|
482
|
+
} catch (broken) {
|
|
483
|
+
return batch.length
|
|
484
|
+
? `the browser would not keep the batch, so a reload loses it: ${broken.message}`
|
|
485
|
+
: `the browser would not clear the batch, so a reload brings back what was `
|
|
486
|
+
+ `already sent or discarded: ${broken.message}`;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
<!-- @req- f2e4lc -->
|
|
490
|
+
<!-- @req- sw53tv -->
|
|
491
|
+
|
|
492
|
+
<!-- @req+ REQ-11095894@-kE72Zroi1iE ttobks -->
|
|
493
|
+
<!-- @req+ REQ-64005208@VjZ0V0VbMEZR yevwgz -->
|
|
494
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY 55sayd -->
|
|
495
|
+
<!-- @req+ REQ-70227145@poKfbXXrCK1C zrjite -->
|
|
496
|
+
<!-- @req+ REQ-71407357@EUXcMYpw10y- x6miqm -->
|
|
497
|
+
<!-- @req+ REQ-71729610@6No-zvUcqtY0 xttpau -->
|
|
498
|
+
<!-- @req+ REQ-77248647@YMbLJ7it09qa ysub5f -->
|
|
499
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM stynhm -->
|
|
500
|
+
async function api(path, options = {}) {
|
|
501
|
+
const sent = await fetch(`https://api.github.com${path}`, {
|
|
502
|
+
...options,
|
|
503
|
+
headers: {
|
|
504
|
+
Accept: options.raw ? "application/vnd.github.raw" : "application/vnd.github+json",
|
|
505
|
+
Authorization: `Bearer ${token()}`,
|
|
506
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
507
|
+
...(options.body ? { "Content-Type": "application/json" } : {}),
|
|
508
|
+
...options.headers,
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
<!-- @req+ REQ-37024518@N98xbJLgeCP0 wnvnry -->
|
|
512
|
+
if (sent.status === 401 || sent.status === 403) {
|
|
513
|
+
throw new Error(`GitHub refused the token (${sent.status}). Check its scopes and expiry.`);
|
|
514
|
+
}
|
|
515
|
+
<!-- @req- wnvnry -->
|
|
516
|
+
<!-- @req+ REQ-38773785@JODPq3htgqq5 g4geco -->
|
|
517
|
+
if (!sent.ok) {
|
|
518
|
+
throw new Error(`${path}: ${sent.status} ${sent.statusText}`);
|
|
519
|
+
}
|
|
520
|
+
<!-- @req- g4geco -->
|
|
521
|
+
if (sent.status === 204) return null;
|
|
522
|
+
return options.raw ? sent.text() : sent.json();
|
|
523
|
+
}
|
|
524
|
+
<!-- @req- stynhm -->
|
|
525
|
+
<!-- @req- ysub5f -->
|
|
526
|
+
<!-- @req- xttpau -->
|
|
527
|
+
<!-- @req- x6miqm -->
|
|
528
|
+
<!-- @req- zrjite -->
|
|
529
|
+
<!-- @req- 55sayd -->
|
|
530
|
+
<!-- @req- yevwgz -->
|
|
531
|
+
<!-- @req- ttobks -->
|
|
532
|
+
|
|
533
|
+
<!-- @req+ REQ-16308184@e6WlysjEauGV rkomcw -->
|
|
534
|
+
<!-- @req+ REQ-19183794@dRHS3YJCEEPV bbzwtg -->
|
|
535
|
+
<!-- @req+ REQ-22950334@CvELqCF22whm se6wnb -->
|
|
536
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S vxbgig -->
|
|
537
|
+
<!-- @req+ REQ-39359341@uRF-58MwVPtQ idtdx5 -->
|
|
538
|
+
<!-- @req+ REQ-43631070@Qebxq4sD9bJ6 qwnyvn -->
|
|
539
|
+
<!-- @req+ REQ-49900441@vzcVI-Jl7ucP 63mita -->
|
|
540
|
+
<!-- @req+ REQ-51482370@AzceAvr9ffaW iy3usc -->
|
|
541
|
+
<!-- @req+ REQ-67532924@r8wZQAAKL9Ye 67a2sq -->
|
|
542
|
+
<!-- @req+ REQ-72352942@q7_Ju8MGy5CF odvnku -->
|
|
543
|
+
<!-- @req+ REQ-90181246@mRCzSBaTKCgT m2qqee -->
|
|
544
|
+
<!-- @req+ REQ-94410924@VNuUThWMV1Ne fhsc6i -->
|
|
545
|
+
<!-- @req> REQ-94312778@9nojWLshVfkX mqf5c6 -->
|
|
546
|
+
const LINKED = /\[([^\]]+)\]\((?:#)?([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)?)\)/g;
|
|
547
|
+
|
|
548
|
+
function prose(text) {
|
|
549
|
+
return esc(text ?? "").replace(LINKED,
|
|
550
|
+
(_, word, uid) => `<span class="uidlink" data-uid="${esc(uid)}">${word}</span>`);
|
|
551
|
+
}
|
|
552
|
+
<!-- @req- fhsc6i -->
|
|
553
|
+
<!-- @req- m2qqee -->
|
|
554
|
+
<!-- @req- odvnku -->
|
|
555
|
+
<!-- @req- 67a2sq -->
|
|
556
|
+
<!-- @req- iy3usc -->
|
|
557
|
+
<!-- @req- 63mita -->
|
|
558
|
+
<!-- @req- qwnyvn -->
|
|
559
|
+
<!-- @req- idtdx5 -->
|
|
560
|
+
<!-- @req- vxbgig -->
|
|
561
|
+
<!-- @req- se6wnb -->
|
|
562
|
+
<!-- @req- bbzwtg -->
|
|
563
|
+
<!-- @req- rkomcw -->
|
|
564
|
+
<!-- @req- 5jlmyo -->
|
|
565
|
+
<!-- @req- eexfhj -->
|
|
566
|
+
<!-- @req- d3fh2o -->
|
|
567
|
+
<!-- @req- werde5 -->
|
|
568
|
+
<!-- @req- qwhi5p -->
|
|
569
|
+
<!-- @req- 7hy7gs -->
|
|
570
|
+
<!-- @req- wpmovn -->
|
|
571
|
+
<!-- @req- 7ag777 -->
|
|
572
|
+
<!-- @req- 2s4m6u -->
|
|
573
|
+
<!-- @req- rojbbp -->
|
|
574
|
+
<!-- @req- gtwilw -->
|
|
575
|
+
<!-- @req- nsepba -->
|
|
576
|
+
<!-- @req- xuun3q -->
|
|
577
|
+
<!-- @req- cxrgj3 -->
|
|
578
|
+
<!-- @req- 33y4sp -->
|
|
579
|
+
<!-- @req- m75p4r -->
|
|
580
|
+
<!-- @req- rixqvx -->
|
|
581
|
+
<!-- @req- 3ce4ro -->
|
|
582
|
+
|
|
583
|
+
<!-- @req> REQ-94312778@9nojWLshVfkX jogx7p -->
|
|
584
|
+
const plain = (text) => String(text ?? "").replace(LINKED, "$1");
|
|
585
|
+
|
|
586
|
+
<!-- @req+ REQ-11203679@LKGrEB6ZNVCv t6hbu5 -->
|
|
587
|
+
<!-- @req+ REQ-12062416@zyf6QGAMGROf 5cjjbg -->
|
|
588
|
+
<!-- @req+ REQ-15307279@nS5Wtd_cnICT oikknz -->
|
|
589
|
+
<!-- @req+ REQ-16910419@bcXxnTl6FuGy fmspgt -->
|
|
590
|
+
<!-- @req+ REQ-17919170@qG4QtddUE5fI 267jqv -->
|
|
591
|
+
<!-- @req+ REQ-20643668@eOsy33VzidrR qpynsl -->
|
|
592
|
+
<!-- @req+ REQ-23350269@Wy4vkFUwo_eF e7yobg -->
|
|
593
|
+
<!-- @req+ REQ-35173659@6-uptMskrE4L 5anyfp -->
|
|
594
|
+
<!-- @req+ REQ-45005873@aGI8YILcA3YW 6ucjoj -->
|
|
595
|
+
<!-- @req+ REQ-45207510@UCnNHap8pwx6 bw7cv5 -->
|
|
596
|
+
<!-- @req+ REQ-58466257@Mw_PWSEOU-gb i56nrq -->
|
|
597
|
+
<!-- @req+ REQ-59774469@2L7AroyhuVFM gfjspv -->
|
|
598
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 xzswzb -->
|
|
599
|
+
<!-- @req+ REQ-83033927@K3q-IpHctnCp bv4ypd -->
|
|
600
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe nuikqk -->
|
|
601
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho 2mz4gb -->
|
|
602
|
+
<!-- @req+ REQ-90392012@aPwZvbqtP1TA e5dav5 -->
|
|
603
|
+
<!-- @req+ REQ-97091321@hE76bTks4vSq y2g6tv -->
|
|
604
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S tg66iu -->
|
|
605
|
+
<!-- @req+ REQ-49900441@vzcVI-Jl7ucP 3wgzhv -->
|
|
606
|
+
<!-- @req+ REQ-90181246@mRCzSBaTKCgT p5ayte -->
|
|
607
|
+
<!-- @req+ REQ-16308184@e6WlysjEauGV e2tju7 -->
|
|
608
|
+
<!-- @req+ REQ-19183794@dRHS3YJCEEPV sfxdjl -->
|
|
609
|
+
<!-- @req+ REQ-22950334@CvELqCF22whm 4paxqu -->
|
|
610
|
+
<!-- @req+ REQ-39359341@uRF-58MwVPtQ ov62ly -->
|
|
611
|
+
<!-- @req+ REQ-43631070@Qebxq4sD9bJ6 qoga4z -->
|
|
612
|
+
<!-- @req+ REQ-67532924@r8wZQAAKL9Ye dnfi6k -->
|
|
613
|
+
<!-- @req+ REQ-72352942@q7_Ju8MGy5CF eoafrt -->
|
|
614
|
+
<!-- @req+ REQ-94410924@VNuUThWMV1Ne bsw3ky -->
|
|
615
|
+
<!-- @req> REQ-16513812@sNNqo8EtRKkZ sh6kkb -->
|
|
616
|
+
<!-- @req> REQ-20134540@2OtJ-s-dnjF1 e64kgr -->
|
|
617
|
+
<!-- @req> REQ-36903196@Qp4pMTYCC1T0 3p6rse -->
|
|
618
|
+
<!-- @req> REQ-41307666@x3Cru_vnJl9T qzrbju -->
|
|
619
|
+
<!-- @req> REQ-45797944@wXgeXQtMt7tG wbxdeg -->
|
|
620
|
+
<!-- @req> REQ-51361575@NQI9dJRNPz0T s7it47 -->
|
|
621
|
+
<!-- @req> REQ-51482370@AzceAvr9ffaW ldbwk5 -->
|
|
622
|
+
<!-- @req> REQ-54076552@G1Ej0m1RlGGS 36sf6i -->
|
|
623
|
+
<!-- @req> REQ-61064763@7M2NL93SN4jQ 2zc74m -->
|
|
624
|
+
<!-- @req> REQ-64610303@tFQ0fV9lxwXY go2g2y -->
|
|
625
|
+
<!-- @req> REQ-69300019@zmqcHuaUXUje nuxeei -->
|
|
626
|
+
<!-- @req> REQ-81220228@DAytEkvTUdTO bvmkfv -->
|
|
627
|
+
<!-- @req> REQ-84590169@qDZMPHwd6mWm 3ylmlk -->
|
|
628
|
+
<!-- @req> REQ-89218328@0_EQrNIm_cPA camr4j -->
|
|
629
|
+
const word = (held) => Object.keys(held.entries ?? {})[0] ?? "";
|
|
630
|
+
const handle = (item) => item.kind === "requirement" ? "" : (item.name ?? item.term ?? word(item));
|
|
631
|
+
|
|
632
|
+
<!-- @req+ REQ-94312778@9nojWLshVfkX tv2ch5 -->
|
|
633
|
+
function summary(item) {
|
|
634
|
+
if (item.kind === "term") return item.definition ?? item.text ?? "";
|
|
635
|
+
return item.text ?? "";
|
|
636
|
+
}
|
|
637
|
+
<!-- @req- tv2ch5 -->
|
|
638
|
+
<!-- @req- bsw3ky -->
|
|
639
|
+
<!-- @req- eoafrt -->
|
|
640
|
+
<!-- @req- dnfi6k -->
|
|
641
|
+
<!-- @req- qoga4z -->
|
|
642
|
+
<!-- @req- ov62ly -->
|
|
643
|
+
<!-- @req- 4paxqu -->
|
|
644
|
+
<!-- @req- sfxdjl -->
|
|
645
|
+
<!-- @req- e2tju7 -->
|
|
646
|
+
|
|
647
|
+
function matches(item) {
|
|
648
|
+
if (item.kind !== kind) return false;
|
|
649
|
+
const hay = `${item.uid} ${handle(item)} ${summary(item)} ${item.status ?? ""}`;
|
|
650
|
+
return !query || hay.toLowerCase().includes(query.toLowerCase());
|
|
651
|
+
}
|
|
652
|
+
<!-- @req- v64xk5 -->
|
|
653
|
+
|
|
654
|
+
<!-- @req+ REQ-28090849@CJNtdzigrsx0 7luynt -->
|
|
655
|
+
<!-- @req+ REQ-42771008@O9ZIKWRPqITt suyt4d -->
|
|
656
|
+
function drawFacets() {
|
|
657
|
+
return `<div class="facets">${Object.keys(FIELDS).map((k) => {
|
|
658
|
+
const n = corpus.filter((i) => i.kind === k).length;
|
|
659
|
+
return `<button class="facet" data-kind="${k}" aria-pressed="${k === kind}">${k} ${n}</button>`;
|
|
660
|
+
}).join("")}</div>`;
|
|
661
|
+
}
|
|
662
|
+
<!-- @req- suyt4d -->
|
|
663
|
+
<!-- @req- 7luynt -->
|
|
664
|
+
|
|
665
|
+
<!-- @req+ REQ-42771008@O9ZIKWRPqITt ni5i2y -->
|
|
666
|
+
function drawRows() {
|
|
667
|
+
const shown = corpus.filter(matches);
|
|
668
|
+
<!-- @req> REQ-77791826@G38_IHLTaGc7 3u5ilh -->
|
|
669
|
+
if (!shown.length) return `<p class="empty">Nothing matches.</p>`;
|
|
670
|
+
<!-- @req+ REQ-43631070@Qebxq4sD9bJ6 6kyqm5 -->
|
|
671
|
+
<!-- @req+ REQ-72352942@q7_Ju8MGy5CF bm2w55 -->
|
|
672
|
+
return shown.map((item) => `
|
|
673
|
+
<button class="row ${item.status === "deprecated" ? "gone" : ""}"
|
|
674
|
+
data-uid="${esc(item.uid)}" aria-current="${item.uid === chosen}">
|
|
675
|
+
<div class="uid mono">${esc(item.uid)} · ${esc(item.status ?? "")}${
|
|
676
|
+
item.suspect_links ? ` · <span class="chip suspect">${item.suspect_links} suspect</span>` : ""
|
|
677
|
+
}</div>
|
|
678
|
+
<div class="txt">${esc(handle(item) ? `${handle(item)} — ` : "")}${prose(summary(item))}</div>
|
|
679
|
+
</button>`).join("");
|
|
680
|
+
}
|
|
681
|
+
<!-- @req- bm2w55 -->
|
|
682
|
+
|
|
683
|
+
<!-- @req+ REQ-16308184@e6WlysjEauGV 4oexgl -->
|
|
684
|
+
<!-- @req+ REQ-39359341@uRF-58MwVPtQ twawsu -->
|
|
685
|
+
<!-- @req+ REQ-19183794@dRHS3YJCEEPV zqdvvw -->
|
|
686
|
+
<!-- @req+ REQ-22950334@CvELqCF22whm oef2n7 -->
|
|
687
|
+
<!-- @req+ REQ-67532924@r8wZQAAKL9Ye i2ebgo -->
|
|
688
|
+
<!-- @req+ REQ-94410924@VNuUThWMV1Ne v5vblu -->
|
|
689
|
+
<!-- @req+ REQ-25073861@ARYmbcnvBhhh dl7f7h -->
|
|
690
|
+
<!-- @req+ REQ-62925340@nMvWkQNQyn32 lfamqq -->
|
|
691
|
+
const flags = (row) =>
|
|
692
|
+
`${row.missing ? `<span class="chip missing">missing</span>` : ""}${
|
|
693
|
+
row.suspect ? `<span class="chip suspect">suspect</span>` : ""}`;
|
|
694
|
+
<!-- @req- lfamqq -->
|
|
695
|
+
<!-- @req- dl7f7h -->
|
|
696
|
+
<!-- @req- 6kyqm5 -->
|
|
697
|
+
|
|
698
|
+
const acking = (row) => row.suspect
|
|
699
|
+
? ` <button class="thin" data-act="ack" data-ack="${esc(row.uid)}">Still holds</button>`
|
|
700
|
+
: "";
|
|
701
|
+
|
|
702
|
+
const empty = (v) => v == null || v === "" || (Array.isArray(v) && !v.length)
|
|
703
|
+
|| (typeof v === "object" && !Array.isArray(v) && !Object.keys(v).length);
|
|
704
|
+
|
|
705
|
+
function render(value) {
|
|
706
|
+
if (Array.isArray(value)) return prose(value.join(", "));
|
|
707
|
+
if (value && typeof value === "object") {
|
|
708
|
+
return `<span class="mono">${esc(JSON.stringify(value))}</span>`;
|
|
709
|
+
}
|
|
710
|
+
return prose(value);
|
|
711
|
+
}
|
|
712
|
+
<!-- @req- v5vblu -->
|
|
713
|
+
<!-- @req- i2ebgo -->
|
|
714
|
+
<!-- @req- oef2n7 -->
|
|
715
|
+
<!-- @req- zqdvvw -->
|
|
716
|
+
|
|
717
|
+
<!-- @req+ REQ-19896380@lvCPXiVYZ6ud uoavfi -->
|
|
718
|
+
const SHOWN = new Set(["uid", "kind", "status", "text", "name", "term",
|
|
719
|
+
"definition",
|
|
720
|
+
"aliases", "tag", "type", "verification", "priority", "acceptance_criteria",
|
|
721
|
+
"rationale", "value", "unit", "value_type", "default", "entries",
|
|
722
|
+
"references", "parameters", "concepts", "outgoing", "incoming",
|
|
723
|
+
"implementation", "tests", "stamp", "suspect_links"]);
|
|
724
|
+
<!-- @req- uoavfi -->
|
|
725
|
+
|
|
726
|
+
<!-- @req+ REQ-19183794@dRHS3YJCEEPV h77vw7 -->
|
|
727
|
+
<!-- @req+ REQ-22950334@CvELqCF22whm 3aj6pu -->
|
|
728
|
+
<!-- @req+ REQ-67532924@r8wZQAAKL9Ye qsgumw -->
|
|
729
|
+
<!-- @req+ REQ-94410924@VNuUThWMV1Ne mf2236 -->
|
|
730
|
+
function chips(held) {
|
|
731
|
+
return Object.entries(held ?? {})
|
|
732
|
+
.filter(([, value]) => !empty(value))
|
|
733
|
+
.map(([name, value]) => `<span class="chip">${esc(name)}: ${
|
|
734
|
+
Array.isArray(value) ? esc(value.join(", ")) : esc(String(value))}</span>`)
|
|
735
|
+
.join(" ");
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function entries(item) {
|
|
739
|
+
const all = item.entries ?? {};
|
|
740
|
+
const names = item.value ?? Object.keys(all);
|
|
741
|
+
return names.map((name) => `
|
|
742
|
+
<div class="card">
|
|
743
|
+
<div style="display:flex;align-items:baseline;gap:8px;flex-wrap:wrap">
|
|
744
|
+
<b class="mono">${esc(name)}</b> ${chips(all[name])}
|
|
745
|
+
<span style="margin-left:auto;display:flex;gap:6px">
|
|
746
|
+
<button class="thin" data-act="entry" data-entry="${esc(name)}">Edit</button>
|
|
747
|
+
<button class="thin" data-act="drop-entry" data-entry="${esc(name)}">Drop</button>
|
|
748
|
+
</span>
|
|
749
|
+
</div>
|
|
750
|
+
${all[name]?.text ? `<div style="color:var(--mut);margin-top:4px">${prose(all[name].text)}</div>` : ""}
|
|
751
|
+
</div>`).join("");
|
|
752
|
+
}
|
|
753
|
+
<!-- @req- mf2236 -->
|
|
754
|
+
<!-- @req- qsgumw -->
|
|
755
|
+
<!-- @req- 3aj6pu -->
|
|
756
|
+
<!-- @req- h77vw7 -->
|
|
757
|
+
|
|
758
|
+
function drawDetail() {
|
|
759
|
+
<!-- @req+ REQ-63880283@cB0-6eSJUN4B xkbevh -->
|
|
760
|
+
const item = byUid[chosen];
|
|
761
|
+
if (!item) return `<p class="empty">Select an item.</p>`;
|
|
762
|
+
<!-- @req- xkbevh -->
|
|
763
|
+
const part = (name, body) => body ? `<div class="label">${name}</div>${body}` : "";
|
|
764
|
+
<!-- @req+ REQ-25073861@ARYmbcnvBhhh w2xjya -->
|
|
765
|
+
const links = (rows, key) => rows?.length ? rows.map((r) => `
|
|
766
|
+
<div class="card"><span class="uidlink mono" data-uid="${esc(r[key])}">${esc(r[key])}</span>
|
|
767
|
+
· ${esc(r.relation)} ${flags(r)}${key === "target"
|
|
768
|
+
? ` <button class="thin" data-act="unrelate" data-target="${esc(r.target)}">Drop</button>`
|
|
769
|
+
: ""}</div>`).join("") : "";
|
|
770
|
+
<!-- @req- w2xjya -->
|
|
771
|
+
const rest = Object.entries(item).filter(([k, v]) => !SHOWN.has(k) && !empty(v));
|
|
772
|
+
|
|
773
|
+
<!-- @req+ REQ-19183794@dRHS3YJCEEPV 7akna4 -->
|
|
774
|
+
<!-- @req+ REQ-22950334@CvELqCF22whm tsgjqq -->
|
|
775
|
+
<!-- @req+ REQ-67532924@r8wZQAAKL9Ye eoxln7 -->
|
|
776
|
+
<!-- @req+ REQ-94410924@VNuUThWMV1Ne 3da2fk -->
|
|
777
|
+
return `
|
|
778
|
+
<div class="mono" style="font-size:11px;color:var(--dim)">
|
|
779
|
+
${esc(item.uid)} · ${esc(item.status ?? "")}
|
|
780
|
+
${item.stamp ? `· ${esc(String(item.stamp).slice(0, 12))}` : ""}
|
|
781
|
+
</div>
|
|
782
|
+
${handle(item) ? `<h2 style="margin:6px 0 0;font-size:17px">${esc(handle(item))}</h2>` : ""}
|
|
783
|
+
<p style="margin:8px 0 0">${prose(summary(item))}</p>
|
|
784
|
+
<div style="margin-top:10px">${chips({
|
|
785
|
+
type: item.type, verification: item.verification, priority: item.priority,
|
|
786
|
+
value_type: item.value_type, unit: item.unit, default: item.default,
|
|
787
|
+
aliases: item.aliases,
|
|
788
|
+
})}</div>
|
|
789
|
+
<div style="margin-top:14px;display:flex;gap:8px;flex-wrap:wrap">
|
|
790
|
+
<button data-act="revise">Revise</button>
|
|
791
|
+
${item.kind === "requirement" ? `<button data-act="relate">Relate</button>
|
|
792
|
+
<button data-act="family">Family</button>` : ""}
|
|
793
|
+
<button data-act="deprecate">Deprecate</button>
|
|
794
|
+
${item.status === "draft" ? `<button data-act="delete">Delete</button>` : ""}
|
|
795
|
+
</div>
|
|
796
|
+
${part("Rationale", item.rationale ? `<p>${prose(item.rationale)}</p>` : "")}
|
|
797
|
+
${part("Acceptance criteria", (item.acceptance_criteria ?? []).map((c) => `
|
|
798
|
+
<div class="crit">
|
|
799
|
+
<div><b>GIVEN</b><span>${prose(c.given)}</span></div>
|
|
800
|
+
<div><b>WHEN</b><span>${prose(c.when)}</span></div>
|
|
801
|
+
<div><b>THEN</b><span>${prose(c.then)}</span></div>
|
|
802
|
+
</div>`).join(""))}
|
|
803
|
+
${part("Members", item.entries || item.value
|
|
804
|
+
? `${entries(item)}<button data-act="new-entry">Add a member</button>`
|
|
805
|
+
: "")}
|
|
806
|
+
${part("Leans on", links(item.outgoing, "target"))}
|
|
807
|
+
${part("Leaned on by", links(item.incoming, "source"))}
|
|
808
|
+
${part("Concepts", (item.concepts ?? []).map((t) => `
|
|
809
|
+
<div class="card"><span class="uidlink mono" data-uid="${esc(t.uid)}">${esc(t.uid)}</span>
|
|
810
|
+
— <b>${esc(t.word)}</b> ${flags(t)}${acking(t)}
|
|
811
|
+
<div style="color:var(--mut);margin-top:4px">${prose(t.definition)}</div></div>`).join(""))}
|
|
812
|
+
${part("Parameters", (item.parameters ?? []).map((p) => `
|
|
813
|
+
<div class="card"><span class="uidlink mono" data-uid="${esc(p.uid)}">${esc(p.uid)}</span>
|
|
814
|
+
— <b>${esc(p.name)}</b> ${flags(p)}${acking(p)}</div>`).join(""))}
|
|
815
|
+
${part("Implemented by", (item.implementation ?? []).map((p) =>
|
|
816
|
+
`<div class="card mono">${esc(typeof p === "string" ? p : JSON.stringify(p))}</div>`).join(""))}
|
|
817
|
+
${part("References", (item.references ?? []).map((r) =>
|
|
818
|
+
`<div class="card">${render(r)}</div>`).join(""))}
|
|
819
|
+
${rest.map(([k, v]) => part(k, `<div class="card">${render(v)}</div>`)).join("")}
|
|
820
|
+
`;
|
|
821
|
+
}
|
|
822
|
+
<!-- @req- 3da2fk -->
|
|
823
|
+
<!-- @req- eoxln7 -->
|
|
824
|
+
<!-- @req- tsgjqq -->
|
|
825
|
+
<!-- @req- 7akna4 -->
|
|
826
|
+
<!-- @req- twawsu -->
|
|
827
|
+
<!-- @req- 4oexgl -->
|
|
828
|
+
<!-- @req- p5ayte -->
|
|
829
|
+
<!-- @req- 3wgzhv -->
|
|
830
|
+
<!-- @req- tg66iu -->
|
|
831
|
+
|
|
832
|
+
<!-- @req+ REQ-45797944@wXgeXQtMt7tG ixopsn -->
|
|
833
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY k52m4e -->
|
|
834
|
+
<!-- @req> REQ-16513812@sNNqo8EtRKkZ axnadk -->
|
|
835
|
+
<!-- @req> REQ-20134540@2OtJ-s-dnjF1 x2py7h -->
|
|
836
|
+
<!-- @req> REQ-21522236@jupFEITq3Nms uufzrb -->
|
|
837
|
+
<!-- @req> REQ-36903196@Qp4pMTYCC1T0 47p3ni -->
|
|
838
|
+
<!-- @req> REQ-41307666@x3Cru_vnJl9T xdk4pm -->
|
|
839
|
+
<!-- @req> REQ-50159837@eYoI-qFNXpXN kkvate -->
|
|
840
|
+
<!-- @req> REQ-51361575@NQI9dJRNPz0T ft7jyl -->
|
|
841
|
+
<!-- @req> REQ-54076552@G1Ej0m1RlGGS nxwiaz -->
|
|
842
|
+
<!-- @req> REQ-61064763@7M2NL93SN4jQ oizlhj -->
|
|
843
|
+
<!-- @req> REQ-81220228@DAytEkvTUdTO epyef5 -->
|
|
844
|
+
<!-- @req> REQ-87847146@3MkrB9EtSIEF agfl3b -->
|
|
845
|
+
<!-- @req> relation_kinds@mGWwOsxeDmi5 o62btc -->
|
|
846
|
+
const RELATIONS = ["derives_from", "depends_on", "constrains", "supersedes", "conflicts_with"];
|
|
847
|
+
|
|
848
|
+
<!-- @req> REQ-21513166@zjiX-rVW8oFK riddjp -->
|
|
849
|
+
<!-- @req> REQ-44810466@bLJV-4isqQ5j jvr6yj -->
|
|
850
|
+
const kindOf = (uid) => byUid[uid]?.kind;
|
|
851
|
+
<!-- @req- k52m4e -->
|
|
852
|
+
<!-- @req- ixopsn -->
|
|
853
|
+
|
|
854
|
+
<!-- @req+ REQ-16513812@sNNqo8EtRKkZ lltj4h -->
|
|
855
|
+
<!-- @req+ REQ-20134540@2OtJ-s-dnjF1 2dixf5 -->
|
|
856
|
+
<!-- @req+ REQ-36903196@Qp4pMTYCC1T0 eqql5f -->
|
|
857
|
+
<!-- @req+ REQ-41307666@x3Cru_vnJl9T lyjc2o -->
|
|
858
|
+
<!-- @req+ REQ-51361575@NQI9dJRNPz0T iffnbf -->
|
|
859
|
+
<!-- @req+ REQ-54076552@G1Ej0m1RlGGS gotytf -->
|
|
860
|
+
<!-- @req+ REQ-61064763@7M2NL93SN4jQ hggs4x -->
|
|
861
|
+
<!-- @req+ REQ-81220228@DAytEkvTUdTO d5s5b3 -->
|
|
862
|
+
<!-- @req+ REQ-87847146@3MkrB9EtSIEF rtr2jk -->
|
|
863
|
+
const MINTED_STATUS = ["approved", "draft"];
|
|
864
|
+
|
|
865
|
+
<!-- @req+ REQ-45797944@wXgeXQtMt7tG ffmfen -->
|
|
866
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY kwyjbq -->
|
|
867
|
+
<!-- @req+ REQ-21522236@jupFEITq3Nms uhlk5r -->
|
|
868
|
+
<!-- @req+ REQ-50159837@eYoI-qFNXpXN ubaqkq -->
|
|
869
|
+
const FORMS = {
|
|
870
|
+
revise: { title: "Revise" },
|
|
871
|
+
relate: { title: "Relate", fields: [{ flag: "relation", choices: RELATIONS },
|
|
872
|
+
{ flag: "target", choices: [] }] },
|
|
873
|
+
deprecate: { title: "Deprecate", fields: [] },
|
|
874
|
+
new: { title: "New" },
|
|
875
|
+
entry: { title: "Edit member", fields: [] },
|
|
876
|
+
"new-entry": { title: "Add a member", fields: [] },
|
|
877
|
+
"drop-entry": { title: "Drop member", fields: [] },
|
|
878
|
+
ack: { title: "Acknowledge", fields: [] },
|
|
879
|
+
unrelate: { title: "Drop relation", fields: [] },
|
|
880
|
+
delete: { title: "Delete", fields: [] },
|
|
881
|
+
};
|
|
882
|
+
<!-- @req- ubaqkq -->
|
|
883
|
+
<!-- @req- uhlk5r -->
|
|
884
|
+
<!-- @req- rtr2jk -->
|
|
885
|
+
|
|
886
|
+
<!-- @req+ REQ-80416166@fZFXqf9ggCT8 dqdos5 -->
|
|
887
|
+
const ENTRY_ACTS = new Set(["entry", "new-entry", "drop-entry"]);
|
|
888
|
+
|
|
889
|
+
const fieldText = (value) =>
|
|
890
|
+
typeof value === "string" ? value : JSON.stringify(value);
|
|
891
|
+
<!-- @req- dqdos5 -->
|
|
892
|
+
<!-- @req- kwyjbq -->
|
|
893
|
+
<!-- @req- ffmfen -->
|
|
894
|
+
|
|
895
|
+
function pairsHtml(held) {
|
|
896
|
+
const rows = Object.entries(held ?? {});
|
|
897
|
+
if (!rows.length) rows.push(["", ""]);
|
|
898
|
+
return rows.map(([name, value]) => `
|
|
899
|
+
<div class="pair">
|
|
900
|
+
<input value="${esc(name)}" placeholder="field">
|
|
901
|
+
<input value="${esc(fieldText(value))}" placeholder="value">
|
|
902
|
+
<button type="button" class="thin drop" title="drop this field">×</button>
|
|
903
|
+
</div>`).join("");
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
<!-- @req+ REQ-87847146@3MkrB9EtSIEF hmmycl -->
|
|
907
|
+
<!-- @req+ REQ-45797944@wXgeXQtMt7tG u2hcup -->
|
|
908
|
+
<!-- @req+ REQ-50159837@eYoI-qFNXpXN sd6snz -->
|
|
909
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY 7rwv53 -->
|
|
910
|
+
<!-- @req> REQ-21522236@jupFEITq3Nms iee36j -->
|
|
911
|
+
<!-- @req> REQ-28090849@CJNtdzigrsx0 yph2du -->
|
|
912
|
+
<!-- @req> REQ-37379566@bnI8OsUtcA0S efswro -->
|
|
913
|
+
<!-- @req> REQ-49900441@vzcVI-Jl7ucP 5jgbge -->
|
|
914
|
+
<!-- @req> REQ-90181246@mRCzSBaTKCgT 7e6cv5 -->
|
|
915
|
+
const FIELDS = JSON.parse(document.getElementById("kind-fields").textContent);
|
|
916
|
+
|
|
917
|
+
<!-- @req+ REQ-21522236@jupFEITq3Nms fuavre -->
|
|
918
|
+
function shapeOf(act, kind) {
|
|
919
|
+
return FORMS[act].fields ?? FIELDS[kind][act];
|
|
920
|
+
}
|
|
921
|
+
<!-- @req- 7rwv53 -->
|
|
922
|
+
<!-- @req- sd6snz -->
|
|
923
|
+
<!-- @req- u2hcup -->
|
|
924
|
+
<!-- @req- fuavre -->
|
|
925
|
+
<!-- @req- hmmycl -->
|
|
926
|
+
|
|
927
|
+
<!-- @req+ REQ-21513166@zjiX-rVW8oFK 4siht6 -->
|
|
928
|
+
const criteriaText = (item) => (item?.acceptance_criteria ?? [])
|
|
929
|
+
.map((c) => `${c.given} | ${c.when} | ${c.then}`).join("\n");
|
|
930
|
+
|
|
931
|
+
<!-- @req+ REQ-45797944@wXgeXQtMt7tG espvu6 -->
|
|
932
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY qxhzlu -->
|
|
933
|
+
<!-- @req+ REQ-87847146@3MkrB9EtSIEF szrrfg -->
|
|
934
|
+
function heldBy(item, field) {
|
|
935
|
+
if (!item) return "";
|
|
936
|
+
const held = item[field.flag] ?? item[field.name];
|
|
937
|
+
if (held === null || held === undefined) return "";
|
|
938
|
+
<!-- @req+ REQ-59010401@pkTwqJqZIeSw 2my5p2 -->
|
|
939
|
+
if (typeof held === "object" && !Array.isArray(held)) {
|
|
940
|
+
return Object.entries(held).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
941
|
+
}
|
|
942
|
+
<!-- @req- 2my5p2 -->
|
|
943
|
+
if (!Array.isArray(held)) return String(held);
|
|
944
|
+
const lines = held.map((each) => field.parts.length
|
|
945
|
+
? field.parts.map((part) => each[part]).join(" | ") : String(each));
|
|
946
|
+
if (field.repeated) return lines.join("\n");
|
|
947
|
+
return lines.length === 1 ? lines[0] : `[${lines.join(", ")}]`;
|
|
948
|
+
}
|
|
949
|
+
<!-- @req- szrrfg -->
|
|
950
|
+
<!-- @req- qxhzlu -->
|
|
951
|
+
<!-- @req- espvu6 -->
|
|
952
|
+
<!-- @req- 4siht6 -->
|
|
953
|
+
|
|
954
|
+
const LINKED_WORD = /\[([^\]\n]+)\]\(([a-z][a-z0-9_]*)\)/g;
|
|
955
|
+
|
|
956
|
+
function proseOf(item) {
|
|
957
|
+
const held = [item.text, item.definition, item.rationale];
|
|
958
|
+
held.push(criteriaText(item));
|
|
959
|
+
for (const fields of Object.values(item.entries ?? {})) {
|
|
960
|
+
for (const [name, value] of Object.entries(fields ?? {})) {
|
|
961
|
+
if (name !== "aliases") held.push(...(Array.isArray(value) ? value : [value]));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return held.filter((each) => typeof each === "string");
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function wordsShown(uid) {
|
|
968
|
+
const seen = new Map();
|
|
969
|
+
for (const item of corpus) {
|
|
970
|
+
for (const text of proseOf(item)) {
|
|
971
|
+
for (const [, word, target] of text.matchAll(LINKED_WORD)) {
|
|
972
|
+
if (target === uid) seen.set(word, (seen.get(word) ?? 0) + 1);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return seen;
|
|
977
|
+
}
|
|
978
|
+
<!-- @req- d5s5b3 -->
|
|
979
|
+
<!-- @req- hggs4x -->
|
|
980
|
+
<!-- @req- gotytf -->
|
|
981
|
+
<!-- @req- iffnbf -->
|
|
982
|
+
<!-- @req- lyjc2o -->
|
|
983
|
+
<!-- @req- eqql5f -->
|
|
984
|
+
<!-- @req- 2dixf5 -->
|
|
985
|
+
<!-- @req- lltj4h -->
|
|
986
|
+
|
|
987
|
+
<!-- @req+ REQ-84590169@qDZMPHwd6mWm gde6nt -->
|
|
988
|
+
<!-- @req> REQ-46529187@6VkOhD1CUEjS faz7v6 -->
|
|
989
|
+
const PICKING = /\/([\w -]*)$/;
|
|
990
|
+
|
|
991
|
+
function pickable() {
|
|
992
|
+
return corpus.flatMap((item) => {
|
|
993
|
+
if (item.kind === "term") {
|
|
994
|
+
return [item.term, ...(item.aliases ?? [])].filter(Boolean)
|
|
995
|
+
.map((word) => ({ show: word, insert: `[[${word}]]`, kind: item.kind }));
|
|
996
|
+
}
|
|
997
|
+
if (!item.name) return [];
|
|
998
|
+
return [{ show: item.name, insert: `\${${item.name}}`, kind: item.kind }];
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
<!-- @req- gde6nt -->
|
|
1002
|
+
|
|
1003
|
+
<!-- @req+ REQ-61064763@7M2NL93SN4jQ 7sls5s -->
|
|
1004
|
+
<!-- @req+ REQ-16513812@sNNqo8EtRKkZ 44xjly -->
|
|
1005
|
+
<!-- @req+ REQ-20134540@2OtJ-s-dnjF1 wmcojq -->
|
|
1006
|
+
<!-- @req+ REQ-36903196@Qp4pMTYCC1T0 mefj5c -->
|
|
1007
|
+
<!-- @req+ REQ-41307666@x3Cru_vnJl9T 4ucul4 -->
|
|
1008
|
+
<!-- @req+ REQ-51361575@NQI9dJRNPz0T ffy3xg -->
|
|
1009
|
+
<!-- @req+ REQ-54076552@G1Ej0m1RlGGS e35j5l -->
|
|
1010
|
+
<!-- @req+ REQ-81220228@DAytEkvTUdTO zen2yh -->
|
|
1011
|
+
function rewordHtml(uid, item) {
|
|
1012
|
+
const own = item?.term ?? "";
|
|
1013
|
+
const sentence = own && own[0].toUpperCase() + own.slice(1);
|
|
1014
|
+
const rows = [...wordsShown(uid)]
|
|
1015
|
+
.filter(([word]) => word !== own && word !== sentence);
|
|
1016
|
+
if (!rows.length) return "";
|
|
1017
|
+
return `<div class="field"><label>other words these links show</label>
|
|
1018
|
+
${rows.map(([word, count]) => `
|
|
1019
|
+
<div class="pair">
|
|
1020
|
+
<div class="mono" style="flex:1 1 0;min-width:0">${esc(word)}
|
|
1021
|
+
<span class="chip">${count}</span></div>
|
|
1022
|
+
<input data-word="${esc(word)}" placeholder="leave blank to keep it">
|
|
1023
|
+
</div>`).join("")}</div>`;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
<!-- @req+ REQ-87847146@3MkrB9EtSIEF z2svqa -->
|
|
1027
|
+
function fieldsHtml(act, kind, item) {
|
|
1028
|
+
return shapeOf(act, kind).map((field) => {
|
|
1029
|
+
<!-- @req+ REQ-69283350@vYYldulaXQvc dmpy2p -->
|
|
1030
|
+
const needed = act === "new" && field.required;
|
|
1031
|
+
const label = `<label for="f-field-${field.flag}">${esc(field.flag)}${
|
|
1032
|
+
needed ? ` <span style="color:var(--warn)">required</span>` : ""}</label>`;
|
|
1033
|
+
<!-- @req- dmpy2p -->
|
|
1034
|
+
const held = heldBy(item, field);
|
|
1035
|
+
if (field.choices.length) {
|
|
1036
|
+
const blank = act === "new" ? "(default)" : "(none)";
|
|
1037
|
+
const choices = act === "relate" ? field.choices : ["", ...field.choices];
|
|
1038
|
+
const offered = act === "new" && field.flag === "status" ? MINTED_STATUS
|
|
1039
|
+
: choices.filter((v) => v !== "" || !held);
|
|
1040
|
+
const list = held && !offered.includes(held) ? [held, ...offered] : offered;
|
|
1041
|
+
return `<div class="field">${label}<select id="f-field-${field.flag}">${
|
|
1042
|
+
list.map((v) => `<option value="${esc(v)}"${v === held ? " selected" : ""}>${
|
|
1043
|
+
esc(v || blank)}</option>`).join("")
|
|
1044
|
+
}</select></div>`;
|
|
1045
|
+
}
|
|
1046
|
+
if (field.repeated || field.prose) {
|
|
1047
|
+
<!-- @req+ REQ-40281745@Ifnq5GBzdwaj rjpgoh -->
|
|
1048
|
+
const parts = field.form ?? field.parts.join(" | ").toUpperCase();
|
|
1049
|
+
const hint = field.repeated
|
|
1050
|
+
? ` placeholder="${esc(parts ? `${parts}, one per line` : "one per line")}"` : "";
|
|
1051
|
+
<!-- @req- rjpgoh -->
|
|
1052
|
+
return `<div class="field">${label}<textarea id="f-field-${field.flag}"${hint}>${
|
|
1053
|
+
esc(held)}</textarea></div>`;
|
|
1054
|
+
}
|
|
1055
|
+
return `<div class="field">${label}<input id="f-field-${field.flag}" value="${
|
|
1056
|
+
esc(held)}"></div>`;
|
|
1057
|
+
}).join("");
|
|
1058
|
+
}
|
|
1059
|
+
<!-- @req- zen2yh -->
|
|
1060
|
+
<!-- @req- e35j5l -->
|
|
1061
|
+
<!-- @req- ffy3xg -->
|
|
1062
|
+
<!-- @req- 4ucul4 -->
|
|
1063
|
+
<!-- @req- mefj5c -->
|
|
1064
|
+
<!-- @req- wmcojq -->
|
|
1065
|
+
<!-- @req- 44xjly -->
|
|
1066
|
+
<!-- @req- z2svqa -->
|
|
1067
|
+
|
|
1068
|
+
function formHtml(act, uid, kind, entry) {
|
|
1069
|
+
const heading = act === "new" ? "New"
|
|
1070
|
+
: ENTRY_ACTS.has(act) ? FORMS[act].title
|
|
1071
|
+
: `${FORMS[act].title} this ${kind}`;
|
|
1072
|
+
const picker = act !== "new" ? "" : `
|
|
1073
|
+
<div class="field"><label for="f-kind">kind</label>
|
|
1074
|
+
<select id="f-kind">${Object.keys(FIELDS).map((k) =>
|
|
1075
|
+
`<option value="${esc(k)}"${k === kind ? " selected" : ""}>${esc(k)}</option>`
|
|
1076
|
+
).join("")}</select></div>`;
|
|
1077
|
+
<!-- @req+ REQ-16513812@sNNqo8EtRKkZ hiauof -->
|
|
1078
|
+
<!-- @req+ REQ-20134540@2OtJ-s-dnjF1 ujknid -->
|
|
1079
|
+
<!-- @req+ REQ-36903196@Qp4pMTYCC1T0 wv4bsz -->
|
|
1080
|
+
<!-- @req+ REQ-41307666@x3Cru_vnJl9T 2q25b4 -->
|
|
1081
|
+
<!-- @req+ REQ-51361575@NQI9dJRNPz0T kljmlu -->
|
|
1082
|
+
<!-- @req+ REQ-54076552@G1Ej0m1RlGGS w2kufl -->
|
|
1083
|
+
<!-- @req+ REQ-81220228@DAytEkvTUdTO or7amx -->
|
|
1084
|
+
<!-- @req+ REQ-87847146@3MkrB9EtSIEF 72acrd -->
|
|
1085
|
+
return `
|
|
1086
|
+
<form method="dialog">
|
|
1087
|
+
<h3 style="margin:0 0 4px">${esc(heading)}</h3>
|
|
1088
|
+
<div class="mono" style="font-size:11px;color:var(--dim);margin-bottom:12px">${
|
|
1089
|
+
esc(uid ?? "reqctl mints the uid")}</div>
|
|
1090
|
+
${picker}
|
|
1091
|
+
${act === "deprecate"
|
|
1092
|
+
? `<p>Mark <span class="mono">${esc(uid)}</span> deprecated. The corpus states it; nothing is removed.</p>`
|
|
1093
|
+
: act === "unrelate"
|
|
1094
|
+
? `<p>Remove the relation from <span class="mono">${esc(uid)}</span> to
|
|
1095
|
+
<span class="mono">${esc(entry)}</span>. Both statements stay; only the
|
|
1096
|
+
claim that one leans on the other goes.</p>`
|
|
1097
|
+
: act === "delete"
|
|
1098
|
+
? `<p>Remove <span class="mono">${esc(uid)}</span> entirely. reqctl allows this
|
|
1099
|
+
only while it is a draft nothing points at; once the corpus has stated
|
|
1100
|
+
something, it is deprecated rather than removed.</p>`
|
|
1101
|
+
: act === "ack"
|
|
1102
|
+
? `<p>Pin <span class="mono">${esc(entry)}</span> at what it now says, and
|
|
1103
|
+
record that <span class="mono">${esc(uid)}</span> still holds against it.</p>
|
|
1104
|
+
<p class="status">The suspicion is there because the reference moved. Read
|
|
1105
|
+
it first: if the statement no longer holds, revise the statement instead.</p>`
|
|
1106
|
+
: act === "drop-entry"
|
|
1107
|
+
? `<p>Remove <span class="mono">${esc(entry)}</span> from <span class="mono">${esc(uid)}</span>. Its fields go with it.</p>`
|
|
1108
|
+
: ENTRY_ACTS.has(act)
|
|
1109
|
+
? `${act === "new-entry"
|
|
1110
|
+
? `<div class="field"><label for="f-entry-key">member</label>
|
|
1111
|
+
<input id="f-entry-key" placeholder="a name for it"></div>`
|
|
1112
|
+
: `<div class="field"><label>member</label>
|
|
1113
|
+
<div class="mono">${esc(entry)}</div></div>`}
|
|
1114
|
+
<div class="field"><label>fields</label>
|
|
1115
|
+
<div id="pairs">${pairsHtml(byUid[uid]?.entries?.[entry])}</div>
|
|
1116
|
+
<button type="button" id="another" class="thin">Another field</button></div>`
|
|
1117
|
+
: `<div id="fields">${fieldsHtml(act, kind, byUid[uid])}</div>`}
|
|
1118
|
+
${act === "revise" ? rewordHtml(uid, byUid[uid]) : ""}
|
|
1119
|
+
${act === "revise" ? `<p class="status">Criteria are replaced by what is written
|
|
1120
|
+
above, not added to; emptying the box removes them all.</p>` : ""}
|
|
1121
|
+
${act === "new" ? `<p class="status">Everything reqctl mints is a draft. Approved
|
|
1122
|
+
here means this same pull request approves it.</p>` : ""}
|
|
1123
|
+
<div class="field"><label for="f-title">pull request title</label>
|
|
1124
|
+
<input id="f-title" value="${esc(defaultTitle(act, uid, kind, entry))}"></div>
|
|
1125
|
+
<div style="display:flex;gap:8px;justify-content:flex-end">
|
|
1126
|
+
<button value="close">Close</button>
|
|
1127
|
+
<button id="hold" value="hold">Add to batch</button>
|
|
1128
|
+
<button class="go" id="send" value="send">Open a pull request</button>
|
|
1129
|
+
</div>
|
|
1130
|
+
</form>`;
|
|
1131
|
+
<!-- @req- 72acrd -->
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY 7i23d4 -->
|
|
1135
|
+
<!-- @req+ REQ-77248647@YMbLJ7it09qa jfupnu -->
|
|
1136
|
+
const defaultTitle = (act, uid, kind, entry) => ({
|
|
1137
|
+
revise: `refactor(${uid}): revise the ${kind}`,
|
|
1138
|
+
relate: `feat(${uid}): record a relation`,
|
|
1139
|
+
deprecate: `refactor(${uid}): deprecate`,
|
|
1140
|
+
new: `feat(corpus): propose a ${kind}`,
|
|
1141
|
+
entry: `refactor(${uid}): revise ${entry}`,
|
|
1142
|
+
"new-entry": `feat(${uid}): add a member`,
|
|
1143
|
+
"drop-entry": `refactor(${uid}): drop ${entry}`,
|
|
1144
|
+
ack: `refactor(${uid}): pin ${entry} where it now stands`,
|
|
1145
|
+
unrelate: `refactor(${uid}): drop the relation to ${entry}`,
|
|
1146
|
+
delete: `refactor(${uid}): remove the draft`,
|
|
1147
|
+
})[act];
|
|
1148
|
+
<!-- @req- jfupnu -->
|
|
1149
|
+
<!-- @req- or7amx -->
|
|
1150
|
+
<!-- @req- w2kufl -->
|
|
1151
|
+
<!-- @req- kljmlu -->
|
|
1152
|
+
<!-- @req- 2q25b4 -->
|
|
1153
|
+
<!-- @req- wv4bsz -->
|
|
1154
|
+
<!-- @req- ujknid -->
|
|
1155
|
+
<!-- @req- hiauof -->
|
|
1156
|
+
<!-- @req- 7sls5s -->
|
|
1157
|
+
|
|
1158
|
+
<!-- @req> REQ-34331811@C-JfQjkX5I_Q chlwag -->
|
|
1159
|
+
const UNCHANGED = "Nothing was changed: change a field first";
|
|
1160
|
+
|
|
1161
|
+
<!-- @req+ REQ-66342679@YF3W8dKIgpQE jm5gcq -->
|
|
1162
|
+
function refusal(...faults) {
|
|
1163
|
+
return Object.assign(new Error(faults.map(([text]) => text).join("; ")), { faults });
|
|
1164
|
+
}
|
|
1165
|
+
<!-- @req- jm5gcq -->
|
|
1166
|
+
|
|
1167
|
+
<!-- @req+ REQ-45797944@wXgeXQtMt7tG ib7sx5 -->
|
|
1168
|
+
function payloadFor(act, uid, read, held) {
|
|
1169
|
+
if (act === "unrelate") {
|
|
1170
|
+
return { steps: [{ command: "unrelate", args: [uid, held.entry] }] };
|
|
1171
|
+
}
|
|
1172
|
+
if (act === "delete") {
|
|
1173
|
+
return { steps: [{ command: "delete", args: [uid] }] };
|
|
1174
|
+
}
|
|
1175
|
+
if (act === "ack") {
|
|
1176
|
+
return { steps: [{ command: "revise", args: [uid],
|
|
1177
|
+
options: { ack: held.entry } }] };
|
|
1178
|
+
}
|
|
1179
|
+
if (act === "drop-entry") {
|
|
1180
|
+
return { steps: [{ command: "revise", args: [uid],
|
|
1181
|
+
options: { "drop-entry": held.entry } }] };
|
|
1182
|
+
}
|
|
1183
|
+
<!-- @req> REQ-80416166@fZFXqf9ggCT8 anxpfm -->
|
|
1184
|
+
if (ENTRY_ACTS.has(act)) {
|
|
1185
|
+
<!-- @req+ REQ-65499760@Kmwfnj7-rVbi hiu7iq -->
|
|
1186
|
+
const key = act === "new-entry" ? read("entry-key").trim() : held.entry;
|
|
1187
|
+
<!-- @req> REQ-34331811@C-JfQjkX5I_Q ogtux4 -->
|
|
1188
|
+
if (!key) throw refusal(["The member needs a name: enter one", "#f-entry-key"]);
|
|
1189
|
+
<!-- @req- hiu7iq -->
|
|
1190
|
+
<!-- @req> REQ-80416166@fZFXqf9ggCT8 rw36xl -->
|
|
1191
|
+
const before = act === "new-entry" ? {} : (byUid[uid]?.entries?.[key] ?? {});
|
|
1192
|
+
<!-- @req+ REQ-91036410@EFhAQqWmHFZP twk5vc -->
|
|
1193
|
+
const named = held.pairs().map(([name]) => name);
|
|
1194
|
+
const twice = named.findIndex((name, at) => name && named.indexOf(name) !== at);
|
|
1195
|
+
<!-- @req+ REQ-34331811@C-JfQjkX5I_Q 6c2djp -->
|
|
1196
|
+
if (twice >= 0) {
|
|
1197
|
+
throw refusal([`${named[twice]} is listed twice: rename or drop one of the two`,
|
|
1198
|
+
`#pairs .pair:nth-child(${twice + 1}) input`]);
|
|
1199
|
+
}
|
|
1200
|
+
<!-- @req- 6c2djp -->
|
|
1201
|
+
<!-- @req- twk5vc -->
|
|
1202
|
+
<!-- @req+ REQ-80416166@fZFXqf9ggCT8 b6wjwi -->
|
|
1203
|
+
const now = new Map(held.pairs().filter(([name]) => name));
|
|
1204
|
+
const set = [...now]
|
|
1205
|
+
.filter(([name, value]) => !(name in before)
|
|
1206
|
+
|| fieldText(before[name]) !== value)
|
|
1207
|
+
.map(([name, value]) => `${name}=${value}`);
|
|
1208
|
+
const unset = Object.keys(before).filter((name) => !now.has(name));
|
|
1209
|
+
<!-- @req- b6wjwi -->
|
|
1210
|
+
<!-- @req> REQ-11466654@hJPYOKZDTuvq tyghs2 -->
|
|
1211
|
+
<!-- @req> REQ-34331811@C-JfQjkX5I_Q nqzorq -->
|
|
1212
|
+
if (!set.length && !unset.length) throw refusal([UNCHANGED]);
|
|
1213
|
+
<!-- @req+ REQ-80416166@fZFXqf9ggCT8 q3utqe -->
|
|
1214
|
+
const flag = act === "new-entry" ? "new-entry" : "entry";
|
|
1215
|
+
return { steps: [{ command: "revise", args: [uid],
|
|
1216
|
+
options: { [flag]: key, ...(set.length ? { set } : {}),
|
|
1217
|
+
...(unset.length ? { unset } : {}) } }] };
|
|
1218
|
+
}
|
|
1219
|
+
<!-- @req- q3utqe -->
|
|
1220
|
+
if (act === "deprecate") {
|
|
1221
|
+
return { steps: [{ command: "revise", args: [uid], options: { status: "deprecated" } }] };
|
|
1222
|
+
}
|
|
1223
|
+
if (act === "relate") {
|
|
1224
|
+
<!-- @req+ REQ-84269679@uQ500_HZ6_7e iasyxk -->
|
|
1225
|
+
const target = read("field-target").trim();
|
|
1226
|
+
<!-- @req+ REQ-34331811@C-JfQjkX5I_Q jojmbu -->
|
|
1227
|
+
if (!/^REQ-\d+$/.test(target)) {
|
|
1228
|
+
throw refusal(["The target must be a requirement: enter the uid of one",
|
|
1229
|
+
"#f-field-target"]);
|
|
1230
|
+
}
|
|
1231
|
+
<!-- @req- jojmbu -->
|
|
1232
|
+
<!-- @req- iasyxk -->
|
|
1233
|
+
return { steps: [{ command: "relate", args: [uid, read("field-relation"), target] }] };
|
|
1234
|
+
}
|
|
1235
|
+
<!-- @req+ REQ-21513166@zjiX-rVW8oFK fep7un -->
|
|
1236
|
+
<!-- @req+ REQ-44810466@bLJV-4isqQ5j vneanq -->
|
|
1237
|
+
const kind = act === "new" ? read("kind") : kindOf(uid);
|
|
1238
|
+
const item = act === "new" ? undefined : byUid[uid];
|
|
1239
|
+
const options = {};
|
|
1240
|
+
const stuck = [];
|
|
1241
|
+
<!-- @req- vneanq -->
|
|
1242
|
+
<!-- @req- fep7un -->
|
|
1243
|
+
<!-- @req+ REQ-87847146@3MkrB9EtSIEF lsvu3b -->
|
|
1244
|
+
for (const field of shapeOf(act, kind)) {
|
|
1245
|
+
const typed = read(`field-${field.flag}`).trim();
|
|
1246
|
+
if (item && typed === heldBy(item, field).trim()) continue;
|
|
1247
|
+
<!-- @req+ REQ-14679866@V0QsDEPmgOq3 c3qlcl -->
|
|
1248
|
+
if (!typed) {
|
|
1249
|
+
if (item && field.clears) options[field.clears] = null;
|
|
1250
|
+
else if (item) stuck.push(field.flag);
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
<!-- @req- c3qlcl -->
|
|
1254
|
+
options[field.flag] = field.repeated
|
|
1255
|
+
? typed.split("\n").map((line) => line.trim()).filter(Boolean)
|
|
1256
|
+
: typed;
|
|
1257
|
+
<!-- @req+ REQ-51798986@Xa9cAPHBctJ6 l7hwbf -->
|
|
1258
|
+
if (item && field.drops) {
|
|
1259
|
+
const held = item[field.flag] ?? item[field.name];
|
|
1260
|
+
if (held == null) {
|
|
1261
|
+
throw new Error(`the published corpus carries no ${field.flag} for ${item.uid}; `
|
|
1262
|
+
+ "republish it with this reqctl, then reload");
|
|
1263
|
+
}
|
|
1264
|
+
const phrases = options[field.flag]
|
|
1265
|
+
.map((line) => line.split("=")[0].trim().replace(/\s+/g, " "));
|
|
1266
|
+
const twice = phrases.find((phrase, at) => phrases.indexOf(phrase) !== at);
|
|
1267
|
+
<!-- @req+ REQ-34331811@C-JfQjkX5I_Q uakc7z -->
|
|
1268
|
+
if (twice) {
|
|
1269
|
+
throw refusal([`${twice} is listed twice: remove one of the two lines`,
|
|
1270
|
+
`#f-field-${field.flag}`]);
|
|
1271
|
+
}
|
|
1272
|
+
<!-- @req- uakc7z -->
|
|
1273
|
+
const gone = Object.keys(held).filter((key) => !phrases.includes(key));
|
|
1274
|
+
if (gone.length) options[field.drops] = gone;
|
|
1275
|
+
}
|
|
1276
|
+
<!-- @req- l7hwbf -->
|
|
1277
|
+
}
|
|
1278
|
+
<!-- @req- lsvu3b -->
|
|
1279
|
+
<!-- @req> REQ-44810466@bLJV-4isqQ5j 4hw3xa -->
|
|
1280
|
+
if (act === "new") {
|
|
1281
|
+
<!-- @req+ REQ-50159837@eYoI-qFNXpXN ymfeaw -->
|
|
1282
|
+
const missing = shapeOf(act, kind)
|
|
1283
|
+
.filter((field) => field.required && !options[field.flag]).map((field) => field.flag);
|
|
1284
|
+
<!-- @req+ REQ-34331811@C-JfQjkX5I_Q 46owta -->
|
|
1285
|
+
if (missing.length) {
|
|
1286
|
+
throw refusal(...missing.map((flag) =>
|
|
1287
|
+
[`A ${kind} needs ${flag}: enter it`, `#f-field-${flag}`]));
|
|
1288
|
+
}
|
|
1289
|
+
<!-- @req- 46owta -->
|
|
1290
|
+
<!-- @req- ymfeaw -->
|
|
1291
|
+
<!-- @req+ REQ-44810466@bLJV-4isqQ5j f2cqu4 -->
|
|
1292
|
+
const { status, ...rest } = options;
|
|
1293
|
+
const steps = [{ command: "new", args: [kind], options: rest }];
|
|
1294
|
+
<!-- @req- f2cqu4 -->
|
|
1295
|
+
<!-- @req+ REQ-66276549@8ixw_ZGqp0Dw mx4ill -->
|
|
1296
|
+
if (status === "approved") {
|
|
1297
|
+
steps.push({ command: "revise", args: ["$0"], options: { status } });
|
|
1298
|
+
}
|
|
1299
|
+
<!-- @req- mx4ill -->
|
|
1300
|
+
<!-- @req+ REQ-44810466@bLJV-4isqQ5j 2ehqos -->
|
|
1301
|
+
return { steps };
|
|
1302
|
+
}
|
|
1303
|
+
<!-- @req- 2ehqos -->
|
|
1304
|
+
<!-- @req+ REQ-14679866@V0QsDEPmgOq3 vpkyye -->
|
|
1305
|
+
<!-- @req+ REQ-34331811@C-JfQjkX5I_Q bt755a -->
|
|
1306
|
+
if (stuck.length) {
|
|
1307
|
+
throw refusal(...stuck.map((flag) =>
|
|
1308
|
+
[`reqctl cannot empty ${flag}: put it back, or deprecate the item`,
|
|
1309
|
+
`#f-field-${flag}`]));
|
|
1310
|
+
}
|
|
1311
|
+
<!-- @req- bt755a -->
|
|
1312
|
+
<!-- @req- vpkyye -->
|
|
1313
|
+
<!-- @req+ REQ-69300019@zmqcHuaUXUje gr6trq -->
|
|
1314
|
+
<!-- @req+ REQ-89218328@0_EQrNIm_cPA 5v5cpw -->
|
|
1315
|
+
const reword = held.rewords().map(([word, becomes]) => {
|
|
1316
|
+
<!-- @req+ REQ-34331811@C-JfQjkX5I_Q wtmile -->
|
|
1317
|
+
if (word.includes("=")) {
|
|
1318
|
+
throw refusal([`${word} cannot be reworded, as reqctl reads old=new up to the `
|
|
1319
|
+
+ `first = and this word carries one: clear its replacement`,
|
|
1320
|
+
`[data-word="${CSS.escape(word)}"]`]);
|
|
1321
|
+
}
|
|
1322
|
+
<!-- @req- wtmile -->
|
|
1323
|
+
return `${word}=${becomes}`;
|
|
1324
|
+
});
|
|
1325
|
+
<!-- @req- 5v5cpw -->
|
|
1326
|
+
if (reword.length) options.reword = reword;
|
|
1327
|
+
<!-- @req- gr6trq -->
|
|
1328
|
+
<!-- @req> REQ-11466654@hJPYOKZDTuvq i35p6w -->
|
|
1329
|
+
<!-- @req> REQ-34331811@C-JfQjkX5I_Q l36icd -->
|
|
1330
|
+
if (!Object.keys(options).length) throw refusal([UNCHANGED]);
|
|
1331
|
+
<!-- @req+ REQ-21513166@zjiX-rVW8oFK 45zzp6 -->
|
|
1332
|
+
return { steps: [{ command: "revise", args: [uid], options }] };
|
|
1333
|
+
}
|
|
1334
|
+
<!-- @req- 45zzp6 -->
|
|
1335
|
+
<!-- @req- ib7sx5 -->
|
|
1336
|
+
<!-- @req+ REQ-71729610@6No-zvUcqtY0 duwcld -->
|
|
1337
|
+
<!-- @req+ REQ-64005208@VjZ0V0VbMEZR w4oace -->
|
|
1338
|
+
<!-- @req+ REQ-71407357@EUXcMYpw10y- 7boad5 -->
|
|
1339
|
+
<!-- @req+ REQ-77248647@YMbLJ7it09qa fu2xtw -->
|
|
1340
|
+
<!-- @req+ REQ-70227145@poKfbXXrCK1C ey5djg -->
|
|
1341
|
+
const runs = (query) =>
|
|
1342
|
+
api(`/repos/${repo()}/actions/workflows/${FLOW}/runs?${query}`);
|
|
1343
|
+
<!-- @req- ey5djg -->
|
|
1344
|
+
|
|
1345
|
+
async function dispatch(change, title, say) {
|
|
1346
|
+
const seen = await runs("per_page=1");
|
|
1347
|
+
const floor = seen.workflow_runs?.[0]?.id ?? 0;
|
|
1348
|
+
<!-- @req+ REQ-70227145@poKfbXXrCK1C cmjjbq -->
|
|
1349
|
+
say("Dispatching…");
|
|
1350
|
+
await api(`/repos/${repo()}/actions/workflows/${FLOW}/dispatches`, {
|
|
1351
|
+
method: "POST",
|
|
1352
|
+
body: JSON.stringify({ ref: "main", inputs: { change: JSON.stringify(change), title } }),
|
|
1353
|
+
});
|
|
1354
|
+
say("Waiting for the run to start…");
|
|
1355
|
+
let run = null;
|
|
1356
|
+
for (let tries = 0; tries < 40 && !run; tries++) {
|
|
1357
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
1358
|
+
const found = await runs("event=workflow_dispatch&per_page=10");
|
|
1359
|
+
run = (found.workflow_runs ?? []).find((r) => r.id > floor) ?? null;
|
|
1360
|
+
}
|
|
1361
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM kjh7h3 -->
|
|
1362
|
+
if (!run) throw new Error("the run never appeared; check the Actions tab");
|
|
1363
|
+
say(`Run ${run.id} started. reqctl is writing the corpus…`);
|
|
1364
|
+
<!-- @req- cmjjbq -->
|
|
1365
|
+
for (let tries = 0; tries < 100; tries++) {
|
|
1366
|
+
run = await api(`/repos/${repo()}/actions/runs/${run.id}`);
|
|
1367
|
+
if (run.status === "completed") break;
|
|
1368
|
+
await new Promise((r) => setTimeout(r, 4000));
|
|
1369
|
+
}
|
|
1370
|
+
if (run.conclusion !== "success") {
|
|
1371
|
+
throw new Error(`the run ${run.conclusion ?? "did not finish"} — ${run.html_url}`);
|
|
1372
|
+
}
|
|
1373
|
+
<!-- @req+ REQ-11095894@-kE72Zroi1iE xzj74d -->
|
|
1374
|
+
const pulls = await api(
|
|
1375
|
+
`/repos/${repo()}/pulls?state=open&head=${repo().split("/")[0]}:corpus/${run.id}`);
|
|
1376
|
+
if (!pulls.length) {
|
|
1377
|
+
throw new Error(`the run succeeded but opened no pull request — ${run.html_url}`);
|
|
1378
|
+
}
|
|
1379
|
+
<!-- @req- kjh7h3 -->
|
|
1380
|
+
return pulls[0].html_url;
|
|
1381
|
+
<!-- @req- xzj74d -->
|
|
1382
|
+
}
|
|
1383
|
+
<!-- @req- fu2xtw -->
|
|
1384
|
+
<!-- @req- 7boad5 -->
|
|
1385
|
+
<!-- @req- w4oace -->
|
|
1386
|
+
<!-- @req- 7i23d4 -->
|
|
1387
|
+
<!-- @req- y2g6tv -->
|
|
1388
|
+
<!-- @req- e5dav5 -->
|
|
1389
|
+
<!-- @req- 2mz4gb -->
|
|
1390
|
+
<!-- @req- nuikqk -->
|
|
1391
|
+
<!-- @req- bv4ypd -->
|
|
1392
|
+
<!-- @req- xzswzb -->
|
|
1393
|
+
<!-- @req- gfjspv -->
|
|
1394
|
+
<!-- @req- i56nrq -->
|
|
1395
|
+
<!-- @req- bw7cv5 -->
|
|
1396
|
+
<!-- @req- 6ucjoj -->
|
|
1397
|
+
<!-- @req- 5anyfp -->
|
|
1398
|
+
<!-- @req- e7yobg -->
|
|
1399
|
+
<!-- @req- qpynsl -->
|
|
1400
|
+
<!-- @req- 267jqv -->
|
|
1401
|
+
<!-- @req- fmspgt -->
|
|
1402
|
+
<!-- @req- oikknz -->
|
|
1403
|
+
<!-- @req- 5cjjbg -->
|
|
1404
|
+
<!-- @req- t6hbu5 -->
|
|
1405
|
+
<!-- @req- duwcld -->
|
|
1406
|
+
|
|
1407
|
+
<!-- @req+ REQ-94312778@9nojWLshVfkX u6xjos -->
|
|
1408
|
+
const KIN = { w: 168, h: 54, gapx: 20, gapy: 46 };
|
|
1409
|
+
|
|
1410
|
+
function kinship() {
|
|
1411
|
+
const up = {}, down = {}, side = {};
|
|
1412
|
+
for (const item of corpus) {
|
|
1413
|
+
for (const link of item.outgoing ?? []) {
|
|
1414
|
+
if (link.relation === "derives_from") {
|
|
1415
|
+
(up[item.uid] ??= []).push(link.target);
|
|
1416
|
+
(down[link.target] ??= []).push(item.uid);
|
|
1417
|
+
} else (side[item.uid] ??= []).push(link);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return { up, down, side };
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function family(uid, { up, down }) {
|
|
1424
|
+
const seen = new Set(), stack = [uid];
|
|
1425
|
+
while (stack.length) {
|
|
1426
|
+
const at = stack.pop();
|
|
1427
|
+
if (seen.has(at)) continue;
|
|
1428
|
+
seen.add(at);
|
|
1429
|
+
stack.push(...(up[at] ?? []), ...(down[at] ?? []));
|
|
1430
|
+
}
|
|
1431
|
+
return seen;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function generations(clan, up) {
|
|
1435
|
+
const gen = Object.fromEntries([...clan].map((at) => [at, 0]));
|
|
1436
|
+
for (let pass = 0; pass < clan.size; pass++) {
|
|
1437
|
+
let moved = false;
|
|
1438
|
+
for (const at of clan) {
|
|
1439
|
+
for (const over of up[at] ?? []) {
|
|
1440
|
+
if (clan.has(over) && gen[over] + 1 > gen[at]) {
|
|
1441
|
+
gen[at] = gen[over] + 1;
|
|
1442
|
+
moved = true;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
if (!moved) break;
|
|
1447
|
+
}
|
|
1448
|
+
return gen;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function arrange(clan, up) {
|
|
1452
|
+
const gen = generations(clan, up);
|
|
1453
|
+
const under = {}, roots = [];
|
|
1454
|
+
for (const at of [...clan].sort()) {
|
|
1455
|
+
const over = (up[at] ?? []).filter((p) => clan.has(p) && gen[p] < gen[at])
|
|
1456
|
+
.sort((a, b) => gen[b] - gen[a] || a.localeCompare(b))[0];
|
|
1457
|
+
if (over) (under[over] ??= []).push(at);
|
|
1458
|
+
else roots.push(at);
|
|
1459
|
+
}
|
|
1460
|
+
const x = {};
|
|
1461
|
+
let slot = 0;
|
|
1462
|
+
const place = (at) => {
|
|
1463
|
+
const kids = under[at] ?? [];
|
|
1464
|
+
if (!kids.length) return void (x[at] = slot++);
|
|
1465
|
+
kids.forEach((kid) => place(kid));
|
|
1466
|
+
x[at] = (x[kids[0]] + x[kids[kids.length - 1]]) / 2;
|
|
1467
|
+
};
|
|
1468
|
+
for (const root of roots) {
|
|
1469
|
+
place(root);
|
|
1470
|
+
slot++;
|
|
1471
|
+
}
|
|
1472
|
+
return { gen, x };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function familyHtml(uid) {
|
|
1476
|
+
const kin = kinship();
|
|
1477
|
+
const clan = family(uid, kin);
|
|
1478
|
+
const { gen, x } = arrange(clan, kin.up);
|
|
1479
|
+
const { w, h, gapx, gapy } = KIN;
|
|
1480
|
+
const spot = (at) => [x[at] * (w + gapx), gen[at] * (h + gapy)];
|
|
1481
|
+
const wide = Math.max(...[...clan].map((at) => spot(at)[0])) + w;
|
|
1482
|
+
<!-- @req+ REQ-58911635@cSn29GquJ0YA hv46ec -->
|
|
1483
|
+
const elbow = (over, under) => {
|
|
1484
|
+
const [ax, ay] = spot(over), [bx, by] = spot(under);
|
|
1485
|
+
return `M${ax + w / 2} ${ay + h} V${ay + h + gapy / 2} H${bx + w / 2} V${by}`;
|
|
1486
|
+
};
|
|
1487
|
+
<!-- @req- hv46ec -->
|
|
1488
|
+
<!-- @req+ REQ-59399209@DMSM7UOh_uQ4 cy57og -->
|
|
1489
|
+
const arc = (from, to) => {
|
|
1490
|
+
const [ax, ay] = spot(from), [bx, by] = spot(to);
|
|
1491
|
+
const [x1, y1] = [ax + w / 2, ay + h], [x2, y2] = [bx + w / 2, by + h];
|
|
1492
|
+
const cy = Math.max(y1, y2) + Math.min(160, gapy / 2 + Math.abs(x2 - x1) / 5);
|
|
1493
|
+
const at = (cy - y1) / (2 * cy - y1 - y2);
|
|
1494
|
+
return [`M${x1} ${y1} Q${(x1 + x2) / 2} ${cy} ${x2} ${y2}`,
|
|
1495
|
+
(1 - at) ** 2 * y1 + 2 * at * (1 - at) * cy + at ** 2 * y2];
|
|
1496
|
+
};
|
|
1497
|
+
<!-- @req- cy57og -->
|
|
1498
|
+
<!-- @req+ REQ-58911635@cSn29GquJ0YA 64zila -->
|
|
1499
|
+
const lines = [...clan].flatMap((at) => (kin.up[at] ?? [])
|
|
1500
|
+
.filter((over) => clan.has(over))
|
|
1501
|
+
.map((over) => `<path d="${elbow(over, at)}"/>`));
|
|
1502
|
+
<!-- @req- 64zila -->
|
|
1503
|
+
<!-- @req+ REQ-59399209@DMSM7UOh_uQ4 7psheo -->
|
|
1504
|
+
const sides = [];
|
|
1505
|
+
let low = 0;
|
|
1506
|
+
for (const at of showLinks ? clan : []) {
|
|
1507
|
+
for (const link of kin.side[at] ?? []) {
|
|
1508
|
+
if (!clan.has(link.target)) continue;
|
|
1509
|
+
const [drawn, reach] = arc(at, link.target);
|
|
1510
|
+
low = Math.max(low, reach);
|
|
1511
|
+
sides.push(`<path class="side" marker-end="url(#tip)" d="${drawn}"
|
|
1512
|
+
><title>${esc(at)} ${esc(link.relation)} ${esc(link.target)}</title></path>`);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
<!-- @req- 7psheo -->
|
|
1516
|
+
const tall = Math.max(low + 4, ...[...clan].map((at) => spot(at)[1] + h));
|
|
1517
|
+
<!-- @req+ REQ-55866158@mH5CUkZ3Wx6H tjyb43 -->
|
|
1518
|
+
const away = corpus.flatMap((item) => (kin.side[item.uid] ?? [])
|
|
1519
|
+
.map((link) => [item.uid, link.target]))
|
|
1520
|
+
.filter(([from, to]) => clan.has(from) !== clan.has(to)).length;
|
|
1521
|
+
<!-- @req- tjyb43 -->
|
|
1522
|
+
const deep = Math.max(...Object.values(gen)) + 1;
|
|
1523
|
+
const box = (at) => {
|
|
1524
|
+
const item = byUid[at];
|
|
1525
|
+
const [left, top] = spot(at);
|
|
1526
|
+
return `<button class="node${at === uid ? " here" : ""}${
|
|
1527
|
+
item.status === "deprecated" ? " gone" : ""}" data-uid="${esc(at)}"
|
|
1528
|
+
style="left:${left}px;top:${top}px">
|
|
1529
|
+
<span class="uid mono">${esc(at)} · ${esc(item.status ?? "")}</span>
|
|
1530
|
+
<span class="txt">${esc(plain(summary(item)))}</span>
|
|
1531
|
+
</button>`;
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
<!-- @req+ REQ-55866158@mH5CUkZ3Wx6H 3fp2af -->
|
|
1535
|
+
<!-- @req+ REQ-58911635@cSn29GquJ0YA dmuwve -->
|
|
1536
|
+
<!-- @req+ REQ-59399209@DMSM7UOh_uQ4 qvsqyt -->
|
|
1537
|
+
return `
|
|
1538
|
+
<form method="dialog" style="display:flex;gap:10px;align-items:baseline;flex-wrap:wrap">
|
|
1539
|
+
<b class="mono">${esc(uid)}</b>
|
|
1540
|
+
<span style="color:var(--mut)">${clan.size} in this family · ${
|
|
1541
|
+
deep} generation${deep === 1 ? "" : "s"}</span>
|
|
1542
|
+
<button type="button" class="facet" id="sidelinks" aria-pressed="${showLinks}"
|
|
1543
|
+
>depends_on and constrains</button>
|
|
1544
|
+
<span style="margin-left:auto"><button value="close">Close</button></span>
|
|
1545
|
+
</form>
|
|
1546
|
+
${clan.size === 1
|
|
1547
|
+
? `<p class="status">Nothing derives from this, and it derives from nothing.</p>`
|
|
1548
|
+
: ""}
|
|
1549
|
+
${showLinks && away
|
|
1550
|
+
? `<p class="status">Not drawn: ${away} joining this family to a requirement outside it.</p>`
|
|
1551
|
+
: ""}
|
|
1552
|
+
<div class="treebox"><div class="tree" style="width:${wide}px;height:${tall}px">
|
|
1553
|
+
<svg width="${wide}" height="${tall}"><defs><marker id="tip" viewBox="0 0 8 8"
|
|
1554
|
+
refX="7" refY="4" markerWidth="6" markerHeight="6" orient="auto"
|
|
1555
|
+
><path d="M0 0 L8 4 L0 8 z"/></marker></defs>${lines.join("")}${sides.join("")}</svg>
|
|
1556
|
+
${[...clan].map(box).join("")}
|
|
1557
|
+
</div></div>`;
|
|
1558
|
+
}
|
|
1559
|
+
<!-- @req- qvsqyt -->
|
|
1560
|
+
<!-- @req- dmuwve -->
|
|
1561
|
+
<!-- @req- 3fp2af -->
|
|
1562
|
+
<!-- @req- u6xjos -->
|
|
1563
|
+
|
|
1564
|
+
function openFamily() {
|
|
1565
|
+
const box = document.createElement("dialog");
|
|
1566
|
+
box.className = "wide";
|
|
1567
|
+
document.body.append(box);
|
|
1568
|
+
const paint = () => {
|
|
1569
|
+
box.innerHTML = familyHtml(chosen);
|
|
1570
|
+
box.querySelector(".node.here").scrollIntoView({ block: "center", inline: "center" });
|
|
1571
|
+
};
|
|
1572
|
+
box.addEventListener("click", (event) => {
|
|
1573
|
+
<!-- @req+ REQ-59399209@DMSM7UOh_uQ4 igkrjd -->
|
|
1574
|
+
if (event.target.id === "sidelinks") {
|
|
1575
|
+
showLinks = !showLinks;
|
|
1576
|
+
return paint();
|
|
1577
|
+
}
|
|
1578
|
+
<!-- @req- igkrjd -->
|
|
1579
|
+
<!-- @req+ REQ-17919170@qG4QtddUE5fI s6qmbk -->
|
|
1580
|
+
const one = event.target.closest(".node");
|
|
1581
|
+
if (!one || one.dataset.uid === chosen) return;
|
|
1582
|
+
event.stopPropagation();
|
|
1583
|
+
chosen = one.dataset.uid;
|
|
1584
|
+
<!-- @req> REQ-58928387@e-BYRqijg-8x j366nt -->
|
|
1585
|
+
kind = byUid[chosen].kind;
|
|
1586
|
+
draw();
|
|
1587
|
+
paint();
|
|
1588
|
+
<!-- @req- s6qmbk -->
|
|
1589
|
+
});
|
|
1590
|
+
box.addEventListener("close", () => box.remove());
|
|
1591
|
+
paint();
|
|
1592
|
+
box.showModal();
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
<!-- @req+ REQ-58466257@Mw_PWSEOU-gb ei5gqg -->
|
|
1596
|
+
<!-- @req+ REQ-11203679@LKGrEB6ZNVCv stdpm2 -->
|
|
1597
|
+
<!-- @req+ REQ-15307279@nS5Wtd_cnICT h3nmbz -->
|
|
1598
|
+
<!-- @req+ REQ-16910419@bcXxnTl6FuGy 54yzc7 -->
|
|
1599
|
+
<!-- @req+ REQ-17919170@qG4QtddUE5fI 6lt63c -->
|
|
1600
|
+
<!-- @req+ REQ-20643668@eOsy33VzidrR wbec45 -->
|
|
1601
|
+
<!-- @req+ REQ-23350269@Wy4vkFUwo_eF qdillp -->
|
|
1602
|
+
<!-- @req+ REQ-35173659@6-uptMskrE4L ybcc2f -->
|
|
1603
|
+
<!-- @req+ REQ-45005873@aGI8YILcA3YW b5ynwh -->
|
|
1604
|
+
<!-- @req+ REQ-45207510@UCnNHap8pwx6 pcdkio -->
|
|
1605
|
+
<!-- @req+ REQ-59774469@2L7AroyhuVFM kjszsz -->
|
|
1606
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 tmvsti -->
|
|
1607
|
+
<!-- @req+ REQ-83033927@K3q-IpHctnCp gp5s5q -->
|
|
1608
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho nj3n23 -->
|
|
1609
|
+
<!-- @req+ REQ-90392012@aPwZvbqtP1TA ixf5l5 -->
|
|
1610
|
+
<!-- @req+ REQ-97091321@hE76bTks4vSq u3w4cp -->
|
|
1611
|
+
<!-- @req+ REQ-12062416@zyf6QGAMGROf szdcfo -->
|
|
1612
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe onxtnl -->
|
|
1613
|
+
function openForm(act, entry) {
|
|
1614
|
+
const uid = act === "new" ? null : chosen;
|
|
1615
|
+
let making = act === "new" ? kind : kindOf(uid);
|
|
1616
|
+
const box = document.createElement("dialog");
|
|
1617
|
+
box.innerHTML = formHtml(act, uid, making, entry);
|
|
1618
|
+
document.body.append(box);
|
|
1619
|
+
<!-- @req+ REQ-80416166@fZFXqf9ggCT8 xfxj5o -->
|
|
1620
|
+
const pairs = () => [...box.querySelectorAll(".pair")].map((row) => {
|
|
1621
|
+
const [name, value] = row.querySelectorAll("input");
|
|
1622
|
+
return [name.value.trim(), value.value];
|
|
1623
|
+
});
|
|
1624
|
+
<!-- @req- xfxj5o -->
|
|
1625
|
+
<!-- @req+ REQ-54076552@G1Ej0m1RlGGS dus5s4 -->
|
|
1626
|
+
<!-- @req+ REQ-69300019@zmqcHuaUXUje plnzrv -->
|
|
1627
|
+
const rewords = () => [...box.querySelectorAll("[data-word]")]
|
|
1628
|
+
.map((held) => [held.dataset.word, held.value.trim()])
|
|
1629
|
+
.filter(([, becomes]) => becomes);
|
|
1630
|
+
<!-- @req- plnzrv -->
|
|
1631
|
+
<!-- @req- dus5s4 -->
|
|
1632
|
+
<!-- @req+ REQ-20134540@2OtJ-s-dnjF1 uj6tab -->
|
|
1633
|
+
box.querySelector("#another")?.addEventListener("click", () => {
|
|
1634
|
+
box.querySelector("#pairs").insertAdjacentHTML("beforeend", pairsHtml(null));
|
|
1635
|
+
});
|
|
1636
|
+
box.querySelector("#pairs")?.addEventListener("click", (event) => {
|
|
1637
|
+
if (event.target.classList.contains("drop")) event.target.closest(".pair").remove();
|
|
1638
|
+
});
|
|
1639
|
+
<!-- @req- uj6tab -->
|
|
1640
|
+
<!-- @req+ REQ-84590169@qDZMPHwd6mWm d3mwig -->
|
|
1641
|
+
const offer = (area) => {
|
|
1642
|
+
if (area?.tagName !== "TEXTAREA") return;
|
|
1643
|
+
const panel = area.nextElementSibling?.classList.contains("pick")
|
|
1644
|
+
? area.nextElementSibling
|
|
1645
|
+
: area.insertAdjacentElement("afterend",
|
|
1646
|
+
Object.assign(document.createElement("div"), { className: "pick" }));
|
|
1647
|
+
const asked = PICKING.exec(area.value.slice(0, area.selectionStart));
|
|
1648
|
+
const found = !asked ? [] : pickable()
|
|
1649
|
+
.filter((held) => held.show.toLowerCase().includes(asked[1].toLowerCase()))
|
|
1650
|
+
<!-- @req> pick_offers@g8aSBqpW9hhR m2ikuj -->
|
|
1651
|
+
.slice(0, 8);
|
|
1652
|
+
panel.hidden = !found.length;
|
|
1653
|
+
panel.innerHTML = found.map((held) =>
|
|
1654
|
+
`<button type="button" class="thin pickone" data-insert="${esc(held.insert)}"
|
|
1655
|
+
>${esc(held.show)} <span class="chip">${esc(held.kind)}</span></button>`
|
|
1656
|
+
).join("");
|
|
1657
|
+
};
|
|
1658
|
+
for (const when of ["input", "keyup", "click"]) {
|
|
1659
|
+
box.addEventListener(when, (event) => offer(event.target));
|
|
1660
|
+
}
|
|
1661
|
+
<!-- @req- d3mwig -->
|
|
1662
|
+
<!-- @req+ REQ-46529187@6VkOhD1CUEjS 5vveoe -->
|
|
1663
|
+
box.addEventListener("click", (event) => {
|
|
1664
|
+
const one = event.target.closest(".pickone");
|
|
1665
|
+
if (!one) return;
|
|
1666
|
+
const panel = one.closest(".pick");
|
|
1667
|
+
const area = panel.previousElementSibling;
|
|
1668
|
+
const at = area.selectionStart;
|
|
1669
|
+
const asked = PICKING.exec(area.value.slice(0, at));
|
|
1670
|
+
panel.hidden = true;
|
|
1671
|
+
if (!asked) return area.focus();
|
|
1672
|
+
const kept = area.value.slice(0, asked.index) + one.dataset.insert;
|
|
1673
|
+
area.value = kept + area.value.slice(at);
|
|
1674
|
+
area.selectionStart = area.selectionEnd = kept.length;
|
|
1675
|
+
area.focus();
|
|
1676
|
+
});
|
|
1677
|
+
<!-- @req- 5vveoe -->
|
|
1678
|
+
<!-- @req+ REQ-61064763@7M2NL93SN4jQ zirqsf -->
|
|
1679
|
+
box.querySelector("#f-kind")?.addEventListener("change", (event) => {
|
|
1680
|
+
making = event.target.value;
|
|
1681
|
+
box.querySelector("#fields").innerHTML = fieldsHtml(act, making, byUid[uid]);
|
|
1682
|
+
<!-- @req- zirqsf -->
|
|
1683
|
+
<!-- @req+ REQ-51361575@NQI9dJRNPz0T kxnu3e -->
|
|
1684
|
+
box.querySelector("#f-title").value = defaultTitle(act, uid, making);
|
|
1685
|
+
});
|
|
1686
|
+
<!-- @req- kxnu3e -->
|
|
1687
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM hoajjx -->
|
|
1688
|
+
const note = document.createElement("p");
|
|
1689
|
+
note.className = "status";
|
|
1690
|
+
note.id = "say";
|
|
1691
|
+
box.append(note);
|
|
1692
|
+
const say = (text, bad = false) => {
|
|
1693
|
+
note.textContent = text;
|
|
1694
|
+
note.classList.toggle("bad", bad);
|
|
1695
|
+
};
|
|
1696
|
+
<!-- @req- hoajjx -->
|
|
1697
|
+
<!-- @req+ REQ-66342679@YF3W8dKIgpQE lvox6c -->
|
|
1698
|
+
const state = (broken) => {
|
|
1699
|
+
note.classList.add("bad");
|
|
1700
|
+
note.replaceChildren(...(broken.faults ?? [[broken.message]]).map(([text, at]) => {
|
|
1701
|
+
const line = document.createElement("div");
|
|
1702
|
+
if (!at) return Object.assign(line, { textContent: text });
|
|
1703
|
+
const link = Object.assign(document.createElement("a"), { href: "#", textContent: text });
|
|
1704
|
+
const field = box.querySelector(at);
|
|
1705
|
+
link.addEventListener("click", (event) => {
|
|
1706
|
+
event.preventDefault();
|
|
1707
|
+
field.focus();
|
|
1708
|
+
});
|
|
1709
|
+
line.append(link);
|
|
1710
|
+
return line;
|
|
1711
|
+
}));
|
|
1712
|
+
};
|
|
1713
|
+
<!-- @req- lvox6c -->
|
|
1714
|
+
<!-- @req> REQ-67558098@GdMrvliz8Kv0 uo53u3 -->
|
|
1715
|
+
box.addEventListener("close", () => box.remove());
|
|
1716
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY irqoj5 -->
|
|
1717
|
+
const read = (name) => box.querySelector(`#f-${name}`)?.value ?? "";
|
|
1718
|
+
const composed = () => payloadFor(act, uid, read, { pairs, entry, rewords });
|
|
1719
|
+
<!-- @req- irqoj5 -->
|
|
1720
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM tboibr -->
|
|
1721
|
+
box.querySelector("#hold").addEventListener("click", (event) => {
|
|
1722
|
+
event.preventDefault();
|
|
1723
|
+
let change;
|
|
1724
|
+
try {
|
|
1725
|
+
change = composed();
|
|
1726
|
+
} catch (broken) {
|
|
1727
|
+
return state(broken);
|
|
1728
|
+
}
|
|
1729
|
+
<!-- @req- tboibr -->
|
|
1730
|
+
<!-- @req+ REQ-77248647@YMbLJ7it09qa 76jwll -->
|
|
1731
|
+
batch.push({ uid, stamp: uid ? byUid[uid]?.stamp : null,
|
|
1732
|
+
title: read("title") || defaultTitle(act, uid, making, entry),
|
|
1733
|
+
steps: change.steps });
|
|
1734
|
+
<!-- @req- 76jwll -->
|
|
1735
|
+
<!-- @req> REQ-37379566@bnI8OsUtcA0S s4rzud -->
|
|
1736
|
+
notice = keepBatch();
|
|
1737
|
+
box.close();
|
|
1738
|
+
draw();
|
|
1739
|
+
});
|
|
1740
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY eyvnty -->
|
|
1741
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM 7emiwr -->
|
|
1742
|
+
const send = box.querySelector("#send");
|
|
1743
|
+
send.addEventListener("click", async (event) => {
|
|
1744
|
+
event.preventDefault();
|
|
1745
|
+
let change;
|
|
1746
|
+
try {
|
|
1747
|
+
change = composed();
|
|
1748
|
+
} catch (broken) {
|
|
1749
|
+
return state(broken);
|
|
1750
|
+
}
|
|
1751
|
+
<!-- @req- 7emiwr -->
|
|
1752
|
+
<!-- @req- eyvnty -->
|
|
1753
|
+
<!-- @req> REQ-88874370@H8CRzlKTmNrt o2w2jj -->
|
|
1754
|
+
send.disabled = true;
|
|
1755
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY shighj -->
|
|
1756
|
+
<!-- @req+ REQ-77248647@YMbLJ7it09qa sriqw5 -->
|
|
1757
|
+
try {
|
|
1758
|
+
const url = await dispatch(
|
|
1759
|
+
change, read("title") || defaultTitle(act, uid, making, entry), say);
|
|
1760
|
+
<!-- @req- sriqw5 -->
|
|
1761
|
+
<!-- @req- shighj -->
|
|
1762
|
+
<!-- @req> REQ-11095894@-kE72Zroi1iE ggou7r -->
|
|
1763
|
+
note.innerHTML = `Opened: <a href="${esc(url)}" target="_blank" rel="noreferrer">${esc(url)}</a>`;
|
|
1764
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM fp5r6w -->
|
|
1765
|
+
} catch (broken) {
|
|
1766
|
+
say(broken.message, true);
|
|
1767
|
+
<!-- @req- fp5r6w -->
|
|
1768
|
+
<!-- @req> REQ-79353053@6FhI0Lfvazmg 5cnbia -->
|
|
1769
|
+
send.disabled = false;
|
|
1770
|
+
<!-- @req+ REQ-64610303@tFQ0fV9lxwXY b4vzei -->
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
<!-- @req- b4vzei -->
|
|
1774
|
+
<!-- @req+ REQ-71989308@UpVZOmFxqw4a atmwzs -->
|
|
1775
|
+
box.showModal();
|
|
1776
|
+
}
|
|
1777
|
+
<!-- @req- atmwzs -->
|
|
1778
|
+
function queued() {
|
|
1779
|
+
if (batch.length) return batch.length;
|
|
1780
|
+
try {
|
|
1781
|
+
const waiting = JSON.parse(localStorage.getItem(BATCH) || "[]");
|
|
1782
|
+
return Array.isArray(waiting) ? waiting.length : 0;
|
|
1783
|
+
} catch {
|
|
1784
|
+
return 0;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function changeRepo(onRefusal) {
|
|
1789
|
+
const waiting = queued();
|
|
1790
|
+
<!-- @req- onxtnl -->
|
|
1791
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe i3og6d -->
|
|
1792
|
+
if (waiting) {
|
|
1793
|
+
return onRefusal(`${repo()} has ${waiting} queued change${waiting > 1 ? "s" : ""}: `
|
|
1794
|
+
+ "open the pull request or discard them before changing the repository");
|
|
1795
|
+
}
|
|
1796
|
+
<!-- @req- szdcfo -->
|
|
1797
|
+
<!-- @req- i3og6d -->
|
|
1798
|
+
<!-- @req+ REQ-12062416@zyf6QGAMGROf gm2ond -->
|
|
1799
|
+
<!-- @req> REQ-17728624@TD_EImR1TLzY 7mbb2p -->
|
|
1800
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe b2p7cb -->
|
|
1801
|
+
localStorage.removeItem(PLACE);
|
|
1802
|
+
setupScreen("");
|
|
1803
|
+
<!-- @req- gm2ond -->
|
|
1804
|
+
<!-- @req> REQ-12062416@zyf6QGAMGROf atauwb -->
|
|
1805
|
+
}
|
|
1806
|
+
<!-- @req- b2p7cb -->
|
|
1807
|
+
<!-- @req- u3w4cp -->
|
|
1808
|
+
<!-- @req- ixf5l5 -->
|
|
1809
|
+
<!-- @req- nj3n23 -->
|
|
1810
|
+
<!-- @req- gp5s5q -->
|
|
1811
|
+
<!-- @req- tmvsti -->
|
|
1812
|
+
<!-- @req- kjszsz -->
|
|
1813
|
+
<!-- @req- pcdkio -->
|
|
1814
|
+
<!-- @req- b5ynwh -->
|
|
1815
|
+
<!-- @req- ybcc2f -->
|
|
1816
|
+
<!-- @req- qdillp -->
|
|
1817
|
+
<!-- @req- wbec45 -->
|
|
1818
|
+
<!-- @req- 6lt63c -->
|
|
1819
|
+
<!-- @req- 54yzc7 -->
|
|
1820
|
+
<!-- @req- h3nmbz -->
|
|
1821
|
+
<!-- @req- stdpm2 -->
|
|
1822
|
+
<!-- @req- ni5i2y -->
|
|
1823
|
+
<!-- @req- 3k7dmz -->
|
|
1824
|
+
<!-- @req- 62vyej -->
|
|
1825
|
+
<!-- @req- 75zy5s -->
|
|
1826
|
+
<!-- @req- o6bnhc -->
|
|
1827
|
+
<!-- @req- x4yeo7 -->
|
|
1828
|
+
|
|
1829
|
+
function handed() {
|
|
1830
|
+
<!-- @req- ei5gqg -->
|
|
1831
|
+
<!-- @req+ REQ-58466257@Mw_PWSEOU-gb 4bpkbk -->
|
|
1832
|
+
const given = new URLSearchParams(location.search).get("repo");
|
|
1833
|
+
if (given === null) return "";
|
|
1834
|
+
history.replaceState(null, "", location.pathname);
|
|
1835
|
+
if (!REPO_FORM.test(given) || given === repo()) return "";
|
|
1836
|
+
<!-- @req- 4bpkbk -->
|
|
1837
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe xyjt53 -->
|
|
1838
|
+
<!-- @req+ REQ-58466257@Mw_PWSEOU-gb 2den5p -->
|
|
1839
|
+
const waiting = queued();
|
|
1840
|
+
if (waiting) {
|
|
1841
|
+
return `${repo()} has ${waiting} queued change${waiting > 1 ? "s" : ""}: `
|
|
1842
|
+
+ `open the pull request or discard them before opening ${given}`;
|
|
1843
|
+
}
|
|
1844
|
+
<!-- @req- xyjt53 -->
|
|
1845
|
+
<!-- @req> REQ-53764133@s638VvRMfEgi vuiash -->
|
|
1846
|
+
try { localStorage.setItem(PLACE, given); } catch (broken) {
|
|
1847
|
+
<!-- @req+ REQ-53764133@s638VvRMfEgi y7dq7n -->
|
|
1848
|
+
return `the browser would not remember ${given}: ${broken.message}`;
|
|
1849
|
+
}
|
|
1850
|
+
return "";
|
|
1851
|
+
}
|
|
1852
|
+
<!-- @req- y7dq7n -->
|
|
1853
|
+
|
|
1854
|
+
<!-- @req+ REQ-13671144@gYp6g1_8U5dA vnfdvv -->
|
|
1855
|
+
<!-- @req+ REQ-42771008@O9ZIKWRPqITt oy7fi6 -->
|
|
1856
|
+
<!-- @req+ REQ-51994852@TKuebjB9MeY2 36u2ht -->
|
|
1857
|
+
<!-- @req+ REQ-58928387@e-BYRqijg-8x jdk4fj -->
|
|
1858
|
+
<!-- @req+ REQ-11203679@LKGrEB6ZNVCv hezq7z -->
|
|
1859
|
+
<!-- @req+ REQ-12062416@zyf6QGAMGROf lnqwwh -->
|
|
1860
|
+
<!-- @req+ REQ-15307279@nS5Wtd_cnICT rvfhlf -->
|
|
1861
|
+
<!-- @req+ REQ-16910419@bcXxnTl6FuGy 26bdnm -->
|
|
1862
|
+
<!-- @req+ REQ-17919170@qG4QtddUE5fI gr2vuc -->
|
|
1863
|
+
<!-- @req+ REQ-20643668@eOsy33VzidrR mvwfuj -->
|
|
1864
|
+
<!-- @req+ REQ-23350269@Wy4vkFUwo_eF glcrpy -->
|
|
1865
|
+
<!-- @req+ REQ-35173659@6-uptMskrE4L kt4h7x -->
|
|
1866
|
+
<!-- @req+ REQ-45207510@UCnNHap8pwx6 gukinx -->
|
|
1867
|
+
<!-- @req+ REQ-59774469@2L7AroyhuVFM tf2r7a -->
|
|
1868
|
+
<!-- @req+ REQ-62729430@uBypjG0c4OWO 5u3aky -->
|
|
1869
|
+
<!-- @req+ REQ-83033927@K3q-IpHctnCp khn3ql -->
|
|
1870
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe isgkfm -->
|
|
1871
|
+
<!-- @req+ REQ-90392012@aPwZvbqtP1TA 6ba7r4 -->
|
|
1872
|
+
<!-- @req+ REQ-94065353@13TDTQioAe4K pmfj4m -->
|
|
1873
|
+
<!-- @req+ REQ-97091321@hE76bTks4vSq 2gnf3m -->
|
|
1874
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho bhuckz -->
|
|
1875
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 z2x2ye -->
|
|
1876
|
+
<!-- @req> REQ-45005873@aGI8YILcA3YW rze4ij -->
|
|
1877
|
+
function forgetToken() {
|
|
1878
|
+
<!-- @req> REQ-45005873@aGI8YILcA3YW poosiz -->
|
|
1879
|
+
localStorage.removeItem(KEY);
|
|
1880
|
+
<!-- @req+ REQ-45005873@aGI8YILcA3YW gc3t5j -->
|
|
1881
|
+
setupScreen("The token was forgotten.");
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
<!-- @req> REQ-84459416@ZlrZpYNBUh8Q uzvzat -->
|
|
1885
|
+
const PERMISSIONS = JSON.parse(document.getElementById("token-permissions").textContent);
|
|
1886
|
+
|
|
1887
|
+
function setupScreen(problem) {
|
|
1888
|
+
const place = repo();
|
|
1889
|
+
<!-- @req- z2x2ye -->
|
|
1890
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 rhi3cu -->
|
|
1891
|
+
<!-- @req+ REQ-84459416@ZlrZpYNBUh8Q uiu4t5 -->
|
|
1892
|
+
document.getElementById("app").innerHTML = `
|
|
1893
|
+
<div class="setup">
|
|
1894
|
+
<h1 style="font-size:18px">requirements</h1>
|
|
1895
|
+
<p style="color:var(--mut)">This page reads the corpus and proposes changes to it.
|
|
1896
|
+
Every change is written by <span class="mono">reqctl</span> in CI and arrives as a
|
|
1897
|
+
pull request you review. Nothing here edits a requirement file.</p>
|
|
1898
|
+
${problem ? `<div class="note" id="problem">${esc(problem)}</div>` : ""}
|
|
1899
|
+
${place ? `<p>Repository: <span class="mono">${esc(place)}</span>
|
|
1900
|
+
<button class="thin" id="change">Change repository</button></p>` : `
|
|
1901
|
+
<input id="place" placeholder="owner/repo" autocomplete="off" spellcheck="false">`}
|
|
1902
|
+
${token() ? `<p>A token is saved in this browser.
|
|
1903
|
+
<button class="thin" id="forget">Forget token</button></p>` : `<ol>
|
|
1904
|
+
<li>On any account that can reach ${place ? `<span class="mono">${esc(place)}</span>`
|
|
1905
|
+
: "the repository"}, create a fine-grained personal access token scoped to it.</li>
|
|
1906
|
+
<li>Give it exactly these repository permissions:
|
|
1907
|
+
${Object.entries(PERMISSIONS).map(([name, {access}]) => `<b>${esc(name[0].toUpperCase()
|
|
1908
|
+
+ name.slice(1).replaceAll("_", " "))}: ${esc(access)}</b>`).join(", ")}.</li>
|
|
1909
|
+
<li>Paste it below. It is kept in this browser only and never leaves it
|
|
1910
|
+
except as an <span class="mono">Authorization</span> header to GitHub.</li>
|
|
1911
|
+
</ol>
|
|
1912
|
+
<input id="tok" type="password" placeholder="github_pat_…" autocomplete="off">`}
|
|
1913
|
+
<button class="go" id="save">Save and load the corpus</button>
|
|
1914
|
+
<div class="note">Anyone with this device can use this token to dispatch a change.
|
|
1915
|
+
What that opens is a pull request authored by the app, not by you, and it still
|
|
1916
|
+
needs a review before anything merges.</div>
|
|
1917
|
+
</div>`;
|
|
1918
|
+
<!-- @req- uiu4t5 -->
|
|
1919
|
+
<!-- @req- rhi3cu -->
|
|
1920
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 hdxmdh -->
|
|
1921
|
+
<!-- @req+ REQ-38789214@OMx9lVswwRCe wskye2 -->
|
|
1922
|
+
const tok = document.getElementById("tok");
|
|
1923
|
+
const entry = document.getElementById("place");
|
|
1924
|
+
const save = () => {
|
|
1925
|
+
const named = entry ? entry.value.trim() : place;
|
|
1926
|
+
const held = tok ? tok.value.trim() : token();
|
|
1927
|
+
<!-- @req- wskye2 -->
|
|
1928
|
+
<!-- @req- bhuckz -->
|
|
1929
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho teqgec -->
|
|
1930
|
+
if (!named) {
|
|
1931
|
+
return setupScreen("Enter the repository as owner/repo to continue");
|
|
1932
|
+
}
|
|
1933
|
+
<!-- @req- teqgec -->
|
|
1934
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho vcqk3a -->
|
|
1935
|
+
if (!REPO_FORM.test(named)) {
|
|
1936
|
+
return setupScreen(`${named} is not a repository: write it as owner/repo`);
|
|
1937
|
+
}
|
|
1938
|
+
<!-- @req- vcqk3a -->
|
|
1939
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho fa2pil -->
|
|
1940
|
+
if (!held) {
|
|
1941
|
+
return setupScreen("Enter the token to continue");
|
|
1942
|
+
}
|
|
1943
|
+
<!-- @req+ REQ-38789214@OMx9lVswwRCe xqb2qr -->
|
|
1944
|
+
localStorage.setItem(PLACE, named);
|
|
1945
|
+
localStorage.setItem(KEY, held);
|
|
1946
|
+
<!-- @req- xqb2qr -->
|
|
1947
|
+
start();
|
|
1948
|
+
};
|
|
1949
|
+
document.getElementById("save").addEventListener("click", save);
|
|
1950
|
+
document.getElementById("change")?.addEventListener("click", () => changeRepo(setupScreen));
|
|
1951
|
+
document.getElementById("forget")?.addEventListener("click", forgetToken);
|
|
1952
|
+
for (const field of [tok, entry]) {
|
|
1953
|
+
field?.addEventListener("keydown", (e) => {
|
|
1954
|
+
if (e.key === "Enter") save();
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S lduzym -->
|
|
1960
|
+
<!-- @req+ REQ-49900441@vzcVI-Jl7ucP cyfys3 -->
|
|
1961
|
+
<!-- @req+ REQ-76958744@G6gCTCrSsHet thvjby -->
|
|
1962
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM khjstk -->
|
|
1963
|
+
<!-- @req+ REQ-90181246@mRCzSBaTKCgT cxcywq -->
|
|
1964
|
+
function drawBatch() {
|
|
1965
|
+
if (!batch.length) return "";
|
|
1966
|
+
return `<div class="note" id="tray" style="margin:10px 14px 0">
|
|
1967
|
+
<b>${batch.length} change${batch.length > 1 ? "s" : ""} waiting</b>
|
|
1968
|
+
<ol style="margin:6px 0 8px;padding-left:20px">${
|
|
1969
|
+
batch.map((held) => `<li>${esc(held.title)}</li>`).join("")}</ol>
|
|
1970
|
+
<div style="display:flex;gap:8px">
|
|
1971
|
+
<button class="go" id="send-batch">Open one pull request</button>
|
|
1972
|
+
<button class="thin" id="drop-batch">Discard</button>
|
|
1973
|
+
</div>
|
|
1974
|
+
<p class="status" id="tray-say" style="padding:8px 0 0"></p>
|
|
1975
|
+
</div>`;
|
|
1976
|
+
}
|
|
1977
|
+
<!-- @req- cxcywq -->
|
|
1978
|
+
<!-- @req- khjstk -->
|
|
1979
|
+
<!-- @req- thvjby -->
|
|
1980
|
+
<!-- @req- cyfys3 -->
|
|
1981
|
+
<!-- @req- lduzym -->
|
|
1982
|
+
|
|
1983
|
+
function draw() {
|
|
1984
|
+
<!-- @req+ REQ-49900441@vzcVI-Jl7ucP ejho4e -->
|
|
1985
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S 4uts4c -->
|
|
1986
|
+
<!-- @req+ REQ-90181246@mRCzSBaTKCgT uqrqco -->
|
|
1987
|
+
document.getElementById("app").innerHTML = `
|
|
1988
|
+
<header>
|
|
1989
|
+
<h1>requirements</h1>
|
|
1990
|
+
<div class="grow"><input type="search" id="q" value="${esc(query)}"
|
|
1991
|
+
placeholder="search statements, uids, handles"></div>
|
|
1992
|
+
<button id="new">New</button>
|
|
1993
|
+
<button id="reload">Reload</button>
|
|
1994
|
+
<button id="change">Change repository</button>
|
|
1995
|
+
<button id="forget">Forget token</button>
|
|
1996
|
+
</header>
|
|
1997
|
+
${notice ? `<div class="note" id="notice" style="margin:10px 14px 0">${esc(notice)}</div>` : ""}
|
|
1998
|
+
${drawBatch()}
|
|
1999
|
+
${drawFacets()}
|
|
2000
|
+
<main>
|
|
2001
|
+
<div class="rows" id="rows">${drawRows()}</div>
|
|
2002
|
+
<aside id="detail">${drawDetail()}</aside>
|
|
2003
|
+
</main>`;
|
|
2004
|
+
<!-- @req- uqrqco -->
|
|
2005
|
+
<!-- @req- 4uts4c -->
|
|
2006
|
+
|
|
2007
|
+
const q = document.getElementById("q");
|
|
2008
|
+
q.addEventListener("input", () => {
|
|
2009
|
+
query = q.value;
|
|
2010
|
+
document.getElementById("rows").innerHTML = drawRows();
|
|
2011
|
+
});
|
|
2012
|
+
<!-- @req- ejho4e -->
|
|
2013
|
+
document.getElementById("reload").addEventListener("click", () => start());
|
|
2014
|
+
document.getElementById("new").addEventListener("click", () => openForm("new"));
|
|
2015
|
+
document.getElementById("change").addEventListener("click", () => changeRepo((said) => {
|
|
2016
|
+
notice = said;
|
|
2017
|
+
draw();
|
|
2018
|
+
}));
|
|
2019
|
+
document.getElementById("forget").addEventListener("click", forgetToken);
|
|
2020
|
+
document.getElementById("drop-batch")?.addEventListener("click", () => {
|
|
2021
|
+
batch = [];
|
|
2022
|
+
notice = keepBatch();
|
|
2023
|
+
draw();
|
|
2024
|
+
});
|
|
2025
|
+
<!-- @req+ REQ-64005208@VjZ0V0VbMEZR rg7wqa -->
|
|
2026
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM xr7aha -->
|
|
2027
|
+
const send = document.getElementById("send-batch");
|
|
2028
|
+
send?.addEventListener("click", async () => {
|
|
2029
|
+
const note = document.getElementById("tray-say");
|
|
2030
|
+
const say = (text, bad = false) => {
|
|
2031
|
+
note.textContent = text;
|
|
2032
|
+
note.classList.toggle("bad", bad);
|
|
2033
|
+
};
|
|
2034
|
+
<!-- @req- xr7aha -->
|
|
2035
|
+
<!-- @req- rg7wqa -->
|
|
2036
|
+
<!-- @req+ REQ-54337106@fv9NfknIlFJO 3dvxho -->
|
|
2037
|
+
const stale = batch.filter((held) =>
|
|
2038
|
+
held.uid && held.stamp && byUid[held.uid]?.stamp !== held.stamp);
|
|
2039
|
+
if (stale.length) {
|
|
2040
|
+
return say(`${stale.map((held) => held.uid).join(", ")} moved since `
|
|
2041
|
+
+ `${stale.length > 1 ? "these were" : "this was"} held; reload `
|
|
2042
|
+
+ `and compose against what the corpus says now`, true);
|
|
2043
|
+
}
|
|
2044
|
+
<!-- @req- 3dvxho -->
|
|
2045
|
+
<!-- @req> REQ-88874370@H8CRzlKTmNrt 5uimbj -->
|
|
2046
|
+
send.disabled = true;
|
|
2047
|
+
<!-- @req+ REQ-64005208@VjZ0V0VbMEZR cat6ir -->
|
|
2048
|
+
<!-- @req+ REQ-71407357@EUXcMYpw10y- clcnoo -->
|
|
2049
|
+
const title = batch.length === 1 ? batch[0].title
|
|
2050
|
+
: `feat(corpus): ${batch.length} changes from the portal`;
|
|
2051
|
+
try {
|
|
2052
|
+
const url = await dispatch({ steps: batched() }, title, say);
|
|
2053
|
+
<!-- @req- clcnoo -->
|
|
2054
|
+
<!-- @req- cat6ir -->
|
|
2055
|
+
<!-- @req+ REQ-67241140@WfIP6H42c1yv btpmsw -->
|
|
2056
|
+
batch = [];
|
|
2057
|
+
const kept = keepBatch();
|
|
2058
|
+
<!-- @req- btpmsw -->
|
|
2059
|
+
<!-- @req+ REQ-11095894@-kE72Zroi1iE hqgvdb -->
|
|
2060
|
+
<!-- @req+ REQ-37379566@bnI8OsUtcA0S wrattp -->
|
|
2061
|
+
document.getElementById("tray").innerHTML =
|
|
2062
|
+
`Opened: <a href="${esc(url)}" target="_blank" rel="noreferrer">${esc(url)}</a>`
|
|
2063
|
+
+ (kept ? `<p class="status bad">${esc(kept)}</p>` : "");
|
|
2064
|
+
<!-- @req- wrattp -->
|
|
2065
|
+
<!-- @req- hqgvdb -->
|
|
2066
|
+
<!-- @req+ REQ-84641100@hkDOGJ16SKpM xishzb -->
|
|
2067
|
+
} catch (broken) {
|
|
2068
|
+
say(broken.message, true);
|
|
2069
|
+
<!-- @req- xishzb -->
|
|
2070
|
+
<!-- @req> REQ-79353053@6FhI0Lfvazmg p55jfo -->
|
|
2071
|
+
send.disabled = false;
|
|
2072
|
+
<!-- @req+ REQ-64005208@VjZ0V0VbMEZR np2pp3 -->
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
<!-- @req- np2pp3 -->
|
|
2077
|
+
<!-- @req- fa2pil -->
|
|
2078
|
+
<!-- @req- hdxmdh -->
|
|
2079
|
+
<!-- @req- gc3t5j -->
|
|
2080
|
+
<!-- @req- 2gnf3m -->
|
|
2081
|
+
<!-- @req- pmfj4m -->
|
|
2082
|
+
<!-- @req- 6ba7r4 -->
|
|
2083
|
+
<!-- @req- isgkfm -->
|
|
2084
|
+
<!-- @req- khn3ql -->
|
|
2085
|
+
<!-- @req- 5u3aky -->
|
|
2086
|
+
<!-- @req- tf2r7a -->
|
|
2087
|
+
<!-- @req- gukinx -->
|
|
2088
|
+
<!-- @req- kt4h7x -->
|
|
2089
|
+
<!-- @req- glcrpy -->
|
|
2090
|
+
<!-- @req- mvwfuj -->
|
|
2091
|
+
<!-- @req- gr2vuc -->
|
|
2092
|
+
<!-- @req- 26bdnm -->
|
|
2093
|
+
<!-- @req- rvfhlf -->
|
|
2094
|
+
<!-- @req- lnqwwh -->
|
|
2095
|
+
<!-- @req- hezq7z -->
|
|
2096
|
+
<!-- @req- 2den5p -->
|
|
2097
|
+
|
|
2098
|
+
function onClick(event) {
|
|
2099
|
+
const facet = event.target.closest("[data-kind]");
|
|
2100
|
+
if (facet) {
|
|
2101
|
+
kind = facet.dataset.kind;
|
|
2102
|
+
return draw();
|
|
2103
|
+
}
|
|
2104
|
+
<!-- @req+ REQ-94065353@13TDTQioAe4K y7i2zf -->
|
|
2105
|
+
const act = event.target.closest("[data-act]");
|
|
2106
|
+
<!-- @req> REQ-62729430@uBypjG0c4OWO fce4yu -->
|
|
2107
|
+
if (act?.dataset.act === "family") return openFamily();
|
|
2108
|
+
<!-- @req- y7i2zf -->
|
|
2109
|
+
if (act) return openForm(act.dataset.act,
|
|
2110
|
+
act.dataset.entry ?? act.dataset.ack ?? act.dataset.target);
|
|
2111
|
+
const holder = event.target.closest("[data-uid]");
|
|
2112
|
+
const at = holder ? holder.dataset.uid.split(".")[0] : "";
|
|
2113
|
+
if (byUid[at]) {
|
|
2114
|
+
chosen = at;
|
|
2115
|
+
if (byUid[chosen].kind !== kind) kind = byUid[chosen].kind;
|
|
2116
|
+
draw();
|
|
2117
|
+
document.getElementById("detail").scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
<!-- @req+ REQ-11203679@LKGrEB6ZNVCv vhk72v -->
|
|
2121
|
+
<!-- @req+ REQ-12062416@zyf6QGAMGROf kxjoof -->
|
|
2122
|
+
<!-- @req+ REQ-15307279@nS5Wtd_cnICT zktztf -->
|
|
2123
|
+
<!-- @req+ REQ-17919170@qG4QtddUE5fI o6ygz7 -->
|
|
2124
|
+
<!-- @req+ REQ-20643668@eOsy33VzidrR p7ztzv -->
|
|
2125
|
+
<!-- @req+ REQ-23350269@Wy4vkFUwo_eF y6oiyo -->
|
|
2126
|
+
<!-- @req+ REQ-35173659@6-uptMskrE4L ue6267 -->
|
|
2127
|
+
<!-- @req+ REQ-45005873@aGI8YILcA3YW 3zy5fv -->
|
|
2128
|
+
<!-- @req+ REQ-45207510@UCnNHap8pwx6 7j6coj -->
|
|
2129
|
+
<!-- @req+ REQ-58466257@Mw_PWSEOU-gb kajsiv -->
|
|
2130
|
+
<!-- @req+ REQ-59774469@2L7AroyhuVFM ociwwz -->
|
|
2131
|
+
<!-- @req+ REQ-62729430@uBypjG0c4OWO w2fvhl -->
|
|
2132
|
+
<!-- @req+ REQ-80115998@5Q31K4gVM_b7 4qnf5k -->
|
|
2133
|
+
<!-- @req+ REQ-83033927@K3q-IpHctnCp n5gjsf -->
|
|
2134
|
+
<!-- @req+ REQ-83258052@QganBESCAGJe t7vx4u -->
|
|
2135
|
+
<!-- @req+ REQ-89638829@K13aY_gVspho mlnihd -->
|
|
2136
|
+
<!-- @req+ REQ-90392012@aPwZvbqtP1TA qvbxr5 -->
|
|
2137
|
+
<!-- @req+ REQ-94065353@13TDTQioAe4K mr35kn -->
|
|
2138
|
+
<!-- @req+ REQ-97091321@hE76bTks4vSq ggpkbh -->
|
|
2139
|
+
<!-- @req> REQ-16910419@bcXxnTl6FuGy o62avc -->
|
|
2140
|
+
async function start(said = "") {
|
|
2141
|
+
<!-- @req> REQ-16910419@bcXxnTl6FuGy 2luwj6 -->
|
|
2142
|
+
if (!token() || !repo()) return setupScreen(said);
|
|
2143
|
+
<!-- @req+ REQ-16910419@bcXxnTl6FuGy 26gvrn -->
|
|
2144
|
+
notice = said;
|
|
2145
|
+
<!-- @req+ REQ-29974318@Su0fkwC9yGmq eaes3o -->
|
|
2146
|
+
document.getElementById("app").innerHTML =
|
|
2147
|
+
`<p class="status">Loading the corpus…</p>`;
|
|
2148
|
+
try {
|
|
2149
|
+
<!-- @req- eaes3o -->
|
|
2150
|
+
<!-- @req- 26gvrn -->
|
|
2151
|
+
<!-- @req> REQ-16910419@bcXxnTl6FuGy lobaq4 -->
|
|
2152
|
+
const raw = await api(`/repos/${repo()}/contents/corpus.json?ref=${VIEW}`, { raw: true });
|
|
2153
|
+
<!-- @req+ REQ-16910419@bcXxnTl6FuGy tfa6za -->
|
|
2154
|
+
corpus = JSON.parse(raw);
|
|
2155
|
+
} catch (broken) {
|
|
2156
|
+
return setupScreen(`${broken.message} — if the ${VIEW} branch does not exist yet, merge to main once to publish it.`);
|
|
2157
|
+
}
|
|
2158
|
+
<!-- @req+ REQ-40692737@dc9rXL3Rwd-E 3snyba -->
|
|
2159
|
+
corpus.sort((a, b) => a.uid.localeCompare(b.uid));
|
|
2160
|
+
byUid = Object.fromEntries(corpus.map((i) => [i.uid, i]));
|
|
2161
|
+
<!-- @req- 3snyba -->
|
|
2162
|
+
<!-- @req> REQ-74219001@x7rQkCaRklIp xfyl4a -->
|
|
2163
|
+
if (!byUid[chosen]) chosen = null;
|
|
2164
|
+
<!-- @req+ REQ-38893754@LAv4UgvQ-csC g4b5r5 -->
|
|
2165
|
+
try {
|
|
2166
|
+
const waiting = JSON.parse(localStorage.getItem(BATCH) || "[]");
|
|
2167
|
+
if (!Array.isArray(waiting)) throw new SyntaxError("it held no list");
|
|
2168
|
+
batch = waiting;
|
|
2169
|
+
} catch (broken) {
|
|
2170
|
+
batch = [];
|
|
2171
|
+
notice = broken instanceof SyntaxError
|
|
2172
|
+
? `The waiting batch was unreadable, so it was discarded: ${broken.message}`
|
|
2173
|
+
: `The browser would not open its storage, so nothing was waiting or was `
|
|
2174
|
+
+ `discarded: ${broken.message}`;
|
|
2175
|
+
try {
|
|
2176
|
+
localStorage.removeItem(BATCH);
|
|
2177
|
+
} catch (worse) {
|
|
2178
|
+
notice += ` It could not be cleared either: ${worse.message}`;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
<!-- @req- g4b5r5 -->
|
|
2182
|
+
draw();
|
|
2183
|
+
}
|
|
2184
|
+
<!-- @req- tfa6za -->
|
|
2185
|
+
<!-- @req- ggpkbh -->
|
|
2186
|
+
<!-- @req- mr35kn -->
|
|
2187
|
+
<!-- @req- qvbxr5 -->
|
|
2188
|
+
<!-- @req- mlnihd -->
|
|
2189
|
+
<!-- @req- t7vx4u -->
|
|
2190
|
+
<!-- @req- n5gjsf -->
|
|
2191
|
+
<!-- @req- 4qnf5k -->
|
|
2192
|
+
<!-- @req- w2fvhl -->
|
|
2193
|
+
<!-- @req- ociwwz -->
|
|
2194
|
+
<!-- @req- kajsiv -->
|
|
2195
|
+
<!-- @req- 7j6coj -->
|
|
2196
|
+
<!-- @req- 3zy5fv -->
|
|
2197
|
+
<!-- @req- ue6267 -->
|
|
2198
|
+
<!-- @req- y6oiyo -->
|
|
2199
|
+
<!-- @req- p7ztzv -->
|
|
2200
|
+
<!-- @req- o6ygz7 -->
|
|
2201
|
+
<!-- @req- zktztf -->
|
|
2202
|
+
<!-- @req- kxjoof -->
|
|
2203
|
+
<!-- @req- vhk72v -->
|
|
2204
|
+
|
|
2205
|
+
document.body.addEventListener("click", onClick);
|
|
2206
|
+
<!-- @req- jdk4fj -->
|
|
2207
|
+
<!-- @req- 36u2ht -->
|
|
2208
|
+
<!-- @req- oy7fi6 -->
|
|
2209
|
+
<!-- @req- vnfdvv -->
|
|
2210
|
+
<!-- @req> REQ-58466257@Mw_PWSEOU-gb clfbsa -->
|
|
2211
|
+
start(handed());
|
|
2212
|
+
<!-- @req> REQ-49576265@pFihs7WdIwGS 6up6rq -->
|
|
2213
|
+
</script>
|