@jabworks/condux 0.12.1 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jabworks/condux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "OpenCode plugin for the condux workflow toolkit — bundles the condux skills and injects the specialist agents (coder, explorer, planner, researcher) plus an opt-in plan-review listener.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
Card, Textarea, Popover, Separator) — adapted as vanilla CSS, no React. -->
|
|
10
10
|
<style>
|
|
11
11
|
/* tokens:core:start */
|
|
12
|
-
/* Shared
|
|
12
|
+
/* Shared design core — do not hand-edit; edit scripts/tokens/core.css. */
|
|
13
13
|
:root {
|
|
14
14
|
--background: #111110;
|
|
15
15
|
--card: #191918;
|
|
@@ -41,11 +41,49 @@
|
|
|
41
41
|
--info-border: #104d87;
|
|
42
42
|
--outline: rgba(237, 237, 236, 0.08);
|
|
43
43
|
--subtle: #686560;
|
|
44
|
-
--
|
|
44
|
+
--shadow-sm: 0 1px 2px rgba(6, 6, 5, 0.5);
|
|
45
|
+
--shadow-md: 0 4px 14px rgba(6, 6, 5, 0.55);
|
|
46
|
+
--shadow-lg: 0 20px 60px rgba(6, 6, 5, 0.62);
|
|
45
47
|
--mono: "Geist Mono", "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Monaco, "Cascadia Code", monospace;
|
|
48
|
+
--sans: "Geist", "Inter Tight", ui-sans-serif, system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
49
|
+
--text-2xs: 0.6875rem;
|
|
50
|
+
--text-xs: 0.75rem;
|
|
51
|
+
--text-sm: 0.8125rem;
|
|
52
|
+
--text-base: 0.875rem;
|
|
53
|
+
--text-md: 1rem;
|
|
54
|
+
--text-lg: 1.125rem;
|
|
55
|
+
--text-xl: 1.375rem;
|
|
56
|
+
--text-2xl: 1.75rem;
|
|
57
|
+
--text-3xl: 2.5rem;
|
|
58
|
+
--text-4xl: 3.5rem;
|
|
59
|
+
--leading-tight: 1.15;
|
|
60
|
+
--leading-snug: 1.35;
|
|
61
|
+
--leading-normal: 1.55;
|
|
62
|
+
--leading-relaxed: 1.7;
|
|
63
|
+
--tracking-tight: -0.022em;
|
|
64
|
+
--tracking-normal: 0em;
|
|
65
|
+
--tracking-wide: 0.06em;
|
|
66
|
+
--space-1: 0.25rem;
|
|
67
|
+
--space-2: 0.5rem;
|
|
68
|
+
--space-3: 0.75rem;
|
|
69
|
+
--space-4: 1rem;
|
|
70
|
+
--space-5: 1.5rem;
|
|
71
|
+
--space-6: 2rem;
|
|
72
|
+
--space-8: 3rem;
|
|
73
|
+
--space-12: 4rem;
|
|
74
|
+
--radius-sm: 0.1875rem;
|
|
75
|
+
--radius: 0.25rem;
|
|
76
|
+
--radius-md: 0.5rem;
|
|
77
|
+
--radius-lg: 0.75rem;
|
|
78
|
+
--radius-full: 9999px;
|
|
79
|
+
--dur-fast: 120ms;
|
|
80
|
+
--dur: 200ms;
|
|
81
|
+
--dur-slow: 320ms;
|
|
82
|
+
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
|
|
83
|
+
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
46
84
|
}
|
|
47
85
|
@media (prefers-color-scheme: light) {
|
|
48
|
-
:root {
|
|
86
|
+
:root:not([data-theme="dark"]) {
|
|
49
87
|
--background: #f6f5ef;
|
|
50
88
|
--card: #fcfbf6;
|
|
51
89
|
--card-foreground: #36302a;
|
|
@@ -76,34 +114,381 @@
|
|
|
76
114
|
--info-border: #bed7ef;
|
|
77
115
|
--outline: rgba(54, 48, 42, 0.08);
|
|
78
116
|
--subtle: #a8a294;
|
|
117
|
+
--shadow-sm: 0 1px 2px rgba(54, 48, 42, 0.1);
|
|
118
|
+
--shadow-md: 0 4px 14px rgba(54, 48, 42, 0.13);
|
|
119
|
+
--shadow-lg: 0 20px 60px rgba(54, 48, 42, 0.18);
|
|
79
120
|
}
|
|
80
121
|
}
|
|
122
|
+
:root[data-theme="light"] {
|
|
123
|
+
--background: #f6f5ef;
|
|
124
|
+
--card: #fcfbf6;
|
|
125
|
+
--card-foreground: #36302a;
|
|
126
|
+
--popover: #fcfbf6;
|
|
127
|
+
--foreground: #36302a;
|
|
128
|
+
--muted: #f0efe3;
|
|
129
|
+
--muted-foreground: #726c5e;
|
|
130
|
+
--border: #ddd6c6;
|
|
131
|
+
--input: #ddd6c6;
|
|
132
|
+
--primary: #967e60;
|
|
133
|
+
--primary-foreground: #ffffff;
|
|
134
|
+
--primary-hover: #897254;
|
|
135
|
+
--primary-muted: #efece0;
|
|
136
|
+
--primary-text: #6f5e44;
|
|
137
|
+
--ring: #967e60;
|
|
138
|
+
--accent: #eae7da;
|
|
139
|
+
--success: #3f7a34;
|
|
140
|
+
--success-muted: #e6f5e2;
|
|
141
|
+
--success-border: #c2e1b3;
|
|
142
|
+
--warning: #93761d;
|
|
143
|
+
--warning-muted: #fbf4c4;
|
|
144
|
+
--warning-border: #ebd87f;
|
|
145
|
+
--destructive: #bf4f3b;
|
|
146
|
+
--destructive-muted: #fbe8e2;
|
|
147
|
+
--destructive-border: #f0c9ba;
|
|
148
|
+
--info: #3f6fa6;
|
|
149
|
+
--info-muted: #e6f1fc;
|
|
150
|
+
--info-border: #bed7ef;
|
|
151
|
+
--outline: rgba(54, 48, 42, 0.08);
|
|
152
|
+
--subtle: #a8a294;
|
|
153
|
+
--shadow-sm: 0 1px 2px rgba(54, 48, 42, 0.1);
|
|
154
|
+
--shadow-md: 0 4px 14px rgba(54, 48, 42, 0.13);
|
|
155
|
+
--shadow-lg: 0 20px 60px rgba(54, 48, 42, 0.18);
|
|
156
|
+
}
|
|
81
157
|
/* tokens:core:end */
|
|
82
158
|
|
|
83
|
-
|
|
84
|
-
|
|
159
|
+
/* kit:css:start */
|
|
160
|
+
/* Shared state layer — do not hand-edit; edit scripts/tokens/kit.css. */
|
|
161
|
+
|
|
162
|
+
/* :where() keeps these at zero specificity, so a surface overrides them by
|
|
163
|
+
writing a plain rule rather than escalating into an !important contest. */
|
|
164
|
+
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
|
|
165
|
+
outline: 2px solid var(--ring);
|
|
166
|
+
outline-offset: 2px;
|
|
167
|
+
border-radius: var(--radius-sm);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Mouse users should not see the ring; keyboard users must. */
|
|
171
|
+
:where(a, button, input, select, textarea, summary, [tabindex]):focus:not(:focus-visible) {
|
|
172
|
+
outline: none;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.kit-skip {
|
|
176
|
+
position: absolute;
|
|
177
|
+
left: var(--space-2);
|
|
178
|
+
top: -4rem;
|
|
179
|
+
z-index: 999;
|
|
180
|
+
padding: var(--space-2) var(--space-3);
|
|
181
|
+
background: var(--primary);
|
|
182
|
+
color: var(--primary-foreground);
|
|
183
|
+
border-radius: var(--radius);
|
|
184
|
+
text-decoration: none;
|
|
185
|
+
transition: top var(--dur) var(--ease-out);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.kit-skip:focus {
|
|
189
|
+
top: var(--space-2);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Empty, loading and error states. Namespaced kit- because three of the four
|
|
193
|
+
surfaces already own an .empty or .error class of their own. */
|
|
194
|
+
.kit-empty {
|
|
195
|
+
border: 1px dashed var(--border);
|
|
196
|
+
border-radius: var(--radius-md);
|
|
197
|
+
padding: var(--space-8) var(--space-4);
|
|
198
|
+
text-align: center;
|
|
199
|
+
color: var(--muted-foreground);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.kit-empty__title {
|
|
203
|
+
margin: 0 0 var(--space-1);
|
|
204
|
+
font-size: var(--text-md);
|
|
205
|
+
color: var(--foreground);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.kit-empty__body {
|
|
209
|
+
margin: 0 auto;
|
|
210
|
+
max-width: 44ch;
|
|
211
|
+
font-size: var(--text-sm);
|
|
212
|
+
text-wrap: pretty;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.kit-error {
|
|
216
|
+
display: flex;
|
|
217
|
+
gap: var(--space-2);
|
|
218
|
+
align-items: baseline;
|
|
219
|
+
padding: var(--space-2) var(--space-3);
|
|
220
|
+
background: var(--destructive-muted);
|
|
221
|
+
border: 1px solid var(--destructive-border);
|
|
222
|
+
border-radius: var(--radius);
|
|
223
|
+
color: var(--destructive);
|
|
224
|
+
font-size: var(--text-sm);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Shaped like the content it stands in for — never a spinner. Width is the
|
|
228
|
+
caller's job, so one rule serves a title, a row and a paragraph. */
|
|
229
|
+
.kit-skel {
|
|
230
|
+
height: 0.75rem;
|
|
231
|
+
border-radius: var(--radius-sm);
|
|
232
|
+
background: linear-gradient(90deg, var(--muted) 0%, var(--accent) 50%, var(--muted) 100%);
|
|
233
|
+
background-size: 200% 100%;
|
|
234
|
+
animation: kit-shimmer 1.4s var(--ease-out) infinite;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@keyframes kit-shimmer {
|
|
238
|
+
100% {
|
|
239
|
+
background-position: -200% 0;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Keyboard help overlay, populated by kit.js from the live binding registry. */
|
|
244
|
+
.kit-help {
|
|
245
|
+
position: fixed;
|
|
246
|
+
inset: 0;
|
|
247
|
+
z-index: 998;
|
|
248
|
+
display: grid;
|
|
249
|
+
place-items: center;
|
|
250
|
+
padding: var(--space-4);
|
|
251
|
+
background: color-mix(in oklch, var(--background) 78%, transparent);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.kit-help[hidden] {
|
|
255
|
+
display: none;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.kit-help__panel {
|
|
259
|
+
width: min(34rem, 100%);
|
|
260
|
+
max-height: 80dvh;
|
|
261
|
+
overflow-y: auto;
|
|
262
|
+
padding: var(--space-5);
|
|
263
|
+
background: var(--card);
|
|
264
|
+
color: var(--card-foreground);
|
|
265
|
+
border: 1px solid var(--border);
|
|
266
|
+
border-radius: var(--radius-md);
|
|
267
|
+
box-shadow: var(--shadow-lg);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.kit-help__title {
|
|
271
|
+
margin: 0 0 var(--space-4);
|
|
272
|
+
font-family: var(--mono);
|
|
273
|
+
font-size: var(--text-2xs);
|
|
274
|
+
letter-spacing: var(--tracking-wide);
|
|
275
|
+
text-transform: uppercase;
|
|
276
|
+
color: var(--subtle);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.kit-help__row {
|
|
280
|
+
display: flex;
|
|
281
|
+
justify-content: space-between;
|
|
282
|
+
gap: var(--space-4);
|
|
283
|
+
padding: var(--space-1) 0;
|
|
284
|
+
font-size: var(--text-sm);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.kit-help__keys {
|
|
288
|
+
flex: none;
|
|
289
|
+
display: flex;
|
|
290
|
+
gap: var(--space-1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.kit-kbd {
|
|
294
|
+
min-width: 1.4rem;
|
|
295
|
+
padding: 0.1rem 0.4rem;
|
|
296
|
+
text-align: center;
|
|
297
|
+
font-family: var(--mono);
|
|
298
|
+
font-size: var(--text-2xs);
|
|
299
|
+
color: var(--foreground);
|
|
300
|
+
background: var(--popover);
|
|
301
|
+
border: 1px solid var(--border);
|
|
302
|
+
border-bottom-width: 2px;
|
|
303
|
+
border-radius: var(--radius-sm);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.kit-theme {
|
|
307
|
+
display: inline-flex;
|
|
308
|
+
gap: 1px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.kit-theme button {
|
|
312
|
+
font: inherit;
|
|
313
|
+
font-size: var(--text-2xs);
|
|
314
|
+
padding: var(--space-1) var(--space-2);
|
|
315
|
+
color: var(--muted-foreground);
|
|
316
|
+
background: var(--card);
|
|
317
|
+
border: 1px solid var(--border);
|
|
318
|
+
cursor: pointer;
|
|
319
|
+
transition:
|
|
320
|
+
color var(--dur-fast) var(--ease-out),
|
|
321
|
+
background-color var(--dur-fast) var(--ease-out),
|
|
322
|
+
border-color var(--dur-fast) var(--ease-out);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.kit-theme button:first-child {
|
|
326
|
+
border-radius: var(--radius) 0 0 var(--radius);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.kit-theme button:last-child {
|
|
330
|
+
border-radius: 0 var(--radius) var(--radius) 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.kit-theme button:hover {
|
|
334
|
+
color: var(--foreground);
|
|
335
|
+
border-color: var(--primary);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.kit-theme button[aria-pressed="true"] {
|
|
339
|
+
color: var(--primary-foreground);
|
|
340
|
+
background: var(--primary);
|
|
341
|
+
border-color: var(--primary);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* A keyboard layer nobody can discover is a keyboard layer nobody uses. kit.js
|
|
345
|
+
places this next to every theme group, so the ? overlay has a visible way in
|
|
346
|
+
for anyone who was never told the shortcut exists. */
|
|
347
|
+
/* kit.js wraps the theme group and the help button in this. The theme group
|
|
348
|
+
keeps growing where its host stretched it (the guide's rail), and stays
|
|
349
|
+
content-sized where the host did not (the board's action row). */
|
|
350
|
+
.kit-controls {
|
|
351
|
+
display: flex;
|
|
352
|
+
align-items: center;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.kit-controls > .kit-theme {
|
|
356
|
+
flex: 1 1 auto;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.kit-helpbtn {
|
|
360
|
+
margin-left: var(--space-2);
|
|
361
|
+
width: 1.65rem;
|
|
362
|
+
height: 1.65rem;
|
|
363
|
+
flex: none;
|
|
364
|
+
font: inherit;
|
|
365
|
+
font-family: var(--mono);
|
|
366
|
+
font-size: var(--text-2xs);
|
|
367
|
+
line-height: 1;
|
|
368
|
+
color: var(--muted-foreground);
|
|
369
|
+
background: var(--card);
|
|
370
|
+
border: 1px solid var(--border);
|
|
371
|
+
border-radius: var(--radius-full);
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
transition:
|
|
374
|
+
color var(--dur-fast) var(--ease-out),
|
|
375
|
+
border-color var(--dur-fast) var(--ease-out);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.kit-helpbtn:hover {
|
|
379
|
+
color: var(--foreground);
|
|
380
|
+
border-color: var(--primary);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.kit-copied {
|
|
384
|
+
color: var(--success);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@media (prefers-reduced-motion: reduce) {
|
|
388
|
+
*,
|
|
389
|
+
*::before,
|
|
390
|
+
*::after {
|
|
391
|
+
animation-duration: 0.01ms !important;
|
|
392
|
+
animation-iteration-count: 1 !important;
|
|
393
|
+
transition-duration: 0.01ms !important;
|
|
394
|
+
scroll-behavior: auto !important;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* Browsers do not print background colours by default, so a dark-themed page
|
|
399
|
+
prints light text onto white and reads as blank. Repointing the tokens is
|
|
400
|
+
what makes Ctrl-P work at all; hiding chrome is the surface's opt-in. */
|
|
401
|
+
@media print {
|
|
402
|
+
:root {
|
|
403
|
+
--background: #ffffff;
|
|
404
|
+
--card: #ffffff;
|
|
405
|
+
--popover: #ffffff;
|
|
406
|
+
--muted: #f4f4f2;
|
|
407
|
+
--accent: #f4f4f2;
|
|
408
|
+
--foreground: #000000;
|
|
409
|
+
--card-foreground: #000000;
|
|
410
|
+
--muted-foreground: #444444;
|
|
411
|
+
--subtle: #666666;
|
|
412
|
+
--border: #999999;
|
|
413
|
+
--input: #999999;
|
|
414
|
+
--shadow-sm: none;
|
|
415
|
+
--shadow-md: none;
|
|
416
|
+
--shadow-lg: none;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
body {
|
|
420
|
+
background: #ffffff;
|
|
421
|
+
color: #000000;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.kit-skip,
|
|
425
|
+
.kit-help,
|
|
426
|
+
.kit-helpbtn,
|
|
427
|
+
.kit-theme,
|
|
428
|
+
[data-kit-chrome] {
|
|
429
|
+
display: none !important;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
a[href^="http"]::after {
|
|
433
|
+
content: " (" attr(href) ")";
|
|
434
|
+
font-size: 90%;
|
|
435
|
+
color: #555555;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.kit-skel {
|
|
439
|
+
animation: none;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/* kit:css:end */
|
|
443
|
+
|
|
444
|
+
/* plan-review extensions — the annotation highlight. Outside the markers
|
|
445
|
+
on purpose: anything inside is replaced.
|
|
446
|
+
--radius-md was defined here at 0.25rem, shadowing the core's 0.5rem
|
|
447
|
+
by source order. The core value is adopted as-is; spots that wanted
|
|
448
|
+
the smaller 0.25rem step now say so explicitly via --radius, which
|
|
449
|
+
already carries that value and is already this file's convention
|
|
450
|
+
(.nav .tabs, .popover, .chip, code, pre, …). */
|
|
85
451
|
:root {
|
|
86
452
|
--hl: rgba(255, 202, 22, 0.30); --hl-active: rgba(255, 202, 22, 0.55);
|
|
87
|
-
--radius-md: 0.25rem;
|
|
88
453
|
}
|
|
89
454
|
@media (prefers-color-scheme: light) {
|
|
90
455
|
:root { --hl: rgba(234, 179, 8, 0.42); --hl-active: rgba(202, 138, 4, 0.62); }
|
|
91
456
|
}
|
|
92
457
|
* { box-sizing: border-box; }
|
|
93
458
|
html, body { height: 100%; }
|
|
94
|
-
|
|
95
|
-
|
|
459
|
+
/* .kit-error sets display:flex unconditionally; an author rule beats the
|
|
460
|
+
UA [hidden] default regardless of specificity, so the hidden attribute
|
|
461
|
+
needs its own author rule to still win. */
|
|
462
|
+
.kit-error[hidden] { display: none; }
|
|
463
|
+
/* Page shell: content row fills, the decision bar pins to the bottom.
|
|
464
|
+
.app below is the second axis (3 columns) — together they are the
|
|
465
|
+
one legitimately 2-D layout in this file; everything else here is a
|
|
466
|
+
one-dimensional row or column and stays flex. */
|
|
467
|
+
body { margin: 0; background: var(--background); color: var(--foreground); font-family: var(--sans); font-size: var(--text-sm); line-height: var(--leading-normal); font-variant-numeric: tabular-nums; display: grid; grid-template-rows: 1fr auto; height: 100dvh; }
|
|
468
|
+
.app { display: grid; grid-template-columns: 248px minmax(0, 1fr) 372px; min-height: 0; }
|
|
469
|
+
@media (max-width: 1100px) {
|
|
470
|
+
.app { grid-template-columns: 208px minmax(0, 1fr) 320px; }
|
|
471
|
+
}
|
|
472
|
+
@media (max-width: 860px) {
|
|
473
|
+
.app { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto; max-height: none; }
|
|
474
|
+
.nav { max-height: 38vh; border-right: none; border-bottom: 1px solid var(--border); }
|
|
475
|
+
.chat { max-height: 34vh; border-left: none; border-top: 1px solid var(--border); }
|
|
476
|
+
}
|
|
477
|
+
@media (max-width: 640px) {
|
|
478
|
+
.main { padding: var(--space-5) var(--space-4) 100px; }
|
|
479
|
+
.popover { width: calc(100vw - 24px); }
|
|
480
|
+
}
|
|
96
481
|
::-webkit-scrollbar { width: 9px; height: 9px; }
|
|
97
482
|
::-webkit-scrollbar-thumb { background: var(--primary); border-radius: 9999px; border: 2px solid transparent; background-clip: padding-box; }
|
|
98
483
|
::-webkit-scrollbar-track { background: transparent; }
|
|
99
484
|
|
|
100
485
|
/* ---- Terminus: Button ---- */
|
|
101
486
|
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap;
|
|
102
|
-
font: 500
|
|
487
|
+
font: 500 var(--text-sm) var(--sans); border-radius: var(--radius); padding: 8px 14px; border: 1px solid transparent;
|
|
103
488
|
background: transparent; color: var(--foreground); cursor: pointer; transition: background-color .12s, border-color .12s, color .12s; }
|
|
104
489
|
.btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--background), 0 0 0 4px color-mix(in oklch, var(--ring) 45%, transparent); }
|
|
105
490
|
.btn:disabled { pointer-events: none; opacity: .4; }
|
|
106
|
-
.btn-sm { padding: 6px 10px; font-size:
|
|
491
|
+
.btn-sm { padding: 6px 10px; font-size: var(--text-xs); }
|
|
107
492
|
.btn-block { width: 100%; }
|
|
108
493
|
.btn-solid { background: var(--primary); color: var(--primary-foreground); border-color: var(--primary); }
|
|
109
494
|
.btn-solid:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
|
|
@@ -119,40 +504,41 @@
|
|
|
119
504
|
.btn-danger:hover { background: color-mix(in oklch, var(--destructive-muted) 70%, var(--destructive)); }
|
|
120
505
|
|
|
121
506
|
/* ---- Terminus: Badge ---- */
|
|
122
|
-
.badge { display: inline-flex; align-items: center; gap: 4px; border-radius: var(--radius
|
|
123
|
-
font: 500
|
|
507
|
+
.badge { display: inline-flex; align-items: center; gap: 4px; border-radius: var(--radius); border: 1px solid transparent;
|
|
508
|
+
font: 500 var(--text-2xs) var(--mono); padding: 1px 6px; min-width: 18px; justify-content: center; }
|
|
124
509
|
.badge-warning { background: var(--warning-muted); color: var(--warning); border-color: var(--warning-border); }
|
|
125
510
|
.badge-soft { background: var(--primary-muted); color: var(--primary-text); }
|
|
126
511
|
|
|
127
512
|
/* Left: navigation — Contents (docs + headings) | Files (paths touched) */
|
|
128
513
|
.nav { border-right: 1px solid var(--border); background: var(--card); overflow-y: auto; padding: 18px 14px 40px; }
|
|
514
|
+
.nav .kit-theme { margin: 0 0 12px; }
|
|
129
515
|
.nav .tabs { display: flex; margin: 0 0 12px; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
|
130
|
-
.nav .tab { flex: 1; font: 500
|
|
516
|
+
.nav .tab { flex: 1; font: 500 var(--text-2xs) var(--sans); letter-spacing: .08em; text-transform: uppercase; padding: 6px 4px;
|
|
131
517
|
background: transparent; border: none; border-right: 1px solid var(--border); color: var(--subtle); cursor: pointer; }
|
|
132
518
|
.nav .tab:last-child { border-right: none; }
|
|
133
519
|
.nav .tab:hover { color: var(--foreground); }
|
|
134
520
|
.nav .tab.active { background: var(--accent); color: var(--foreground); }
|
|
135
521
|
.nav a.doc { font-weight: 600; color: var(--foreground); }
|
|
136
522
|
.nav a.doc.active { border-left-color: var(--primary); background: var(--accent); }
|
|
137
|
-
.nav a.file .t { font-size:
|
|
138
|
-
.nav .none { color: var(--subtle); font-size:
|
|
523
|
+
.nav a.file .t { font-family: var(--mono); font-size: var(--text-xs); }
|
|
524
|
+
.nav .none { color: var(--subtle); font-size: var(--text-xs); padding: 4px 6px; }
|
|
139
525
|
code.mention-flash { outline: 2px solid var(--warning); outline-offset: 1px; }
|
|
140
|
-
.nav .label, .chat .label { font-size:
|
|
526
|
+
.nav .label, .chat .label { font-size: var(--text-2xs); letter-spacing: 0.08em; text-transform: uppercase; color: var(--subtle); margin: 0 0 12px; padding: 0 6px; }
|
|
141
527
|
.nav a { display: flex; align-items: center; gap: 6px; color: var(--muted-foreground); text-decoration: none; padding: 4px 6px; border-radius: 4px; border-left: 2px solid transparent; }
|
|
142
528
|
.nav a span.t { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }
|
|
143
529
|
.nav a:hover { background: var(--accent); color: var(--foreground); }
|
|
144
530
|
.nav a.active { color: var(--foreground); border-left-color: var(--primary); background: var(--accent); }
|
|
145
|
-
.nav a.lvl2 { padding-left: 16px; } .nav a.lvl3 { padding-left: 28px; font-size:
|
|
531
|
+
.nav a.lvl2 { padding-left: 16px; } .nav a.lvl3 { padding-left: 28px; font-size: var(--text-xs); color: var(--subtle); }
|
|
146
532
|
|
|
147
533
|
/* Directory mode: doc-site tree — folder rows fold, filter narrows, the
|
|
148
534
|
active doc alone carries its headings. All of it DIRMODE-gated. */
|
|
149
535
|
.nav .tree-filter { display: none; margin: 0 0 10px; }
|
|
150
|
-
.nav .tree-filter input { width: 100%; font: inherit; font-size:
|
|
536
|
+
.nav .tree-filter input { width: 100%; font: inherit; font-size: var(--text-sm); color: var(--foreground); background: var(--background);
|
|
151
537
|
border: 1px solid var(--border); border-radius: var(--radius); padding: 6px 8px; outline: none; }
|
|
152
538
|
.nav .tree-filter input:focus { border-color: var(--ring); }
|
|
153
539
|
.nav a.folder { font-weight: 600; color: var(--muted-foreground); }
|
|
154
|
-
.nav a.folder .arrow { flex: none; width: 12px; color: var(--subtle); font-size:
|
|
155
|
-
.crumbs { display: none; max-width: 760px; margin: 0 auto 18px; font:
|
|
540
|
+
.nav a.folder .arrow { flex: none; width: 12px; color: var(--subtle); font-size: var(--text-2xs); }
|
|
541
|
+
.crumbs { display: none; max-width: 760px; margin: 0 auto 18px; font: var(--text-xs) var(--mono); color: var(--subtle);
|
|
156
542
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
157
543
|
.crumbs .seg { color: var(--muted-foreground); }
|
|
158
544
|
.crumbs a.seg { color: var(--muted-foreground); text-decoration: none; cursor: pointer; }
|
|
@@ -162,7 +548,7 @@
|
|
|
162
548
|
.pnav { display: none; max-width: 760px; margin: 32px auto 0; padding-top: 14px; border-top: 1px solid var(--border);
|
|
163
549
|
justify-content: space-between; gap: 12px; }
|
|
164
550
|
.pnav a { display: inline-flex; align-items: center; gap: 6px; max-width: 48%; color: var(--muted-foreground);
|
|
165
|
-
text-decoration: none; font-size:
|
|
551
|
+
text-decoration: none; font-size: var(--text-sm); padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius);
|
|
166
552
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
167
553
|
.pnav a:hover { background: var(--accent); color: var(--foreground); }
|
|
168
554
|
.pnav .spacer { flex: 1; }
|
|
@@ -173,20 +559,20 @@
|
|
|
173
559
|
linear-gradient(to right, color-mix(in oklab, var(--foreground) 3%, transparent) 1px, transparent 1px),
|
|
174
560
|
linear-gradient(to bottom, color-mix(in oklab, var(--foreground) 3%, transparent) 1px, transparent 1px);
|
|
175
561
|
background-size: 32px 32px; }
|
|
176
|
-
.main .hint { color: var(--subtle); margin: 0 auto 24px; font-size:
|
|
177
|
-
.doc { max-width: 760px; margin: 0 auto; }
|
|
562
|
+
.main .hint { color: var(--subtle); margin: 0 auto 24px; font-size: var(--text-xs); max-width: 760px; }
|
|
563
|
+
.doc { max-width: 760px; margin: 0 auto; line-height: var(--leading-relaxed); }
|
|
178
564
|
|
|
179
565
|
/* Revision diff — on a revised plan, show what changed instead of a silent swap. */
|
|
180
566
|
.revbar { display: none; align-items: center; gap: 10px; max-width: 760px; margin: 0 auto 20px;
|
|
181
567
|
padding: 9px 12px; border: 1px solid var(--warning-border); background: var(--warning-muted); border-radius: var(--radius); }
|
|
182
568
|
.revbar.show { display: flex; }
|
|
183
|
-
.revbar .rb-text { color: var(--warning); font: 600
|
|
184
|
-
.revbar .rb-stat { color: var(--muted-foreground); font-size:
|
|
569
|
+
.revbar .rb-text { color: var(--warning); font: 600 var(--text-xs) var(--sans); }
|
|
570
|
+
.revbar .rb-stat { color: var(--muted-foreground); font-size: var(--text-xs); }
|
|
185
571
|
.revbar .rb-actions { margin-left: auto; display: flex; gap: 6px; }
|
|
186
572
|
.diffwrap { display: none; max-width: 760px; margin: 0 auto; }
|
|
187
573
|
.diffwrap.show { display: block; }
|
|
188
574
|
.diffview { background: var(--muted); border: 1px solid var(--border); border-radius: var(--radius);
|
|
189
|
-
padding: 8px 0; overflow-x: auto; font:
|
|
575
|
+
padding: 8px 0; overflow-x: auto; font: var(--text-xs)/var(--leading-normal) var(--mono); }
|
|
190
576
|
.diffline { display: flex; white-space: pre; padding: 0 12px; }
|
|
191
577
|
.diffline .g { width: 14px; flex: none; color: var(--subtle); user-select: none; }
|
|
192
578
|
.diffline.ctx { color: var(--muted-foreground); }
|
|
@@ -195,20 +581,20 @@
|
|
|
195
581
|
.diffline.del { background: var(--destructive-muted); color: var(--destructive); }
|
|
196
582
|
.diffline.del .g { color: var(--destructive); }
|
|
197
583
|
.diffline.fold { justify-content: center; color: var(--subtle); font-style: italic; padding: 3px 12px; }
|
|
198
|
-
h1, h2, h3, h4, h5, h6 { line-height:
|
|
199
|
-
h1 { font-size:
|
|
200
|
-
h4 { font-size:
|
|
584
|
+
h1, h2, h3, h4, h5, h6 { line-height: var(--leading-snug); margin: 1.5em 0 0.6em; scroll-margin-top: 24px; }
|
|
585
|
+
h1 { font-size: var(--text-xl); } h2 { font-size: var(--text-md); } h3 { font-size: var(--text-base); color: var(--muted-foreground); }
|
|
586
|
+
h4 { font-size: var(--text-sm); } h5, h6 { font-size: var(--text-xs); color: var(--muted-foreground); }
|
|
201
587
|
p { margin: 0.6em 0; }
|
|
202
588
|
ul, ol { margin: 0.6em 0; padding-left: 22px; } li { margin: 0.25em 0; }
|
|
203
589
|
li.task { list-style: none; margin-left: -18px; }
|
|
204
590
|
del { color: var(--subtle); }
|
|
205
591
|
a { color: var(--info); }
|
|
206
|
-
code { background: var(--muted); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; font-size:
|
|
207
|
-
pre { background: var(--muted); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; overflow-x: auto; }
|
|
592
|
+
code { background: var(--muted); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; font-family: var(--mono); font-size: var(--text-xs); }
|
|
593
|
+
pre { background: var(--muted); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; overflow-x: auto; font-family: var(--mono); }
|
|
208
594
|
pre code { background: none; border: none; padding: 0; }
|
|
209
595
|
blockquote { margin: 0.6em 0; padding: 2px 14px; color: var(--muted-foreground); border-left: 3px solid var(--primary); }
|
|
210
596
|
hr { border: none; border-top: 1px solid var(--border); margin: 1.6em 0; }
|
|
211
|
-
table { border-collapse: collapse; margin: 0.9em 0; width: 100%; display: block; overflow-x: auto; font-size:
|
|
597
|
+
table { border-collapse: collapse; margin: 0.9em 0; width: 100%; display: block; overflow-x: auto; font-size: var(--text-sm); }
|
|
212
598
|
th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; vertical-align: top; }
|
|
213
599
|
th { background: var(--muted); font-weight: 600; }
|
|
214
600
|
tbody tr:nth-child(even) { background: color-mix(in oklch, var(--muted) 40%, transparent); }
|
|
@@ -224,18 +610,18 @@
|
|
|
224
610
|
.popover:not(.expanded) .chip:last-child { border-bottom-right-radius: var(--radius); }
|
|
225
611
|
/* Floating annotation toolbar — a menubar of category items over an input. */
|
|
226
612
|
.popover { position: absolute; z-index: 31; display: none; width: 340px; background: var(--popover); border: 1px solid var(--border);
|
|
227
|
-
border-radius: var(--radius); box-shadow:
|
|
228
|
-
.popover .q { color: var(--muted-foreground); font-size:
|
|
613
|
+
border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 0; }
|
|
614
|
+
.popover .q { color: var(--muted-foreground); font-size: var(--text-2xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
229
615
|
padding: 6px 10px; margin: 0; background: var(--muted); border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; }
|
|
230
616
|
/* Menubar strip: category items, evenly split, divider-separated */
|
|
231
617
|
.popover .cats { display: flex; margin: 0; border-bottom: 1px solid var(--border); }
|
|
232
|
-
.chip { flex: 1; font: 500
|
|
618
|
+
.chip { flex: 1; font: 500 var(--text-2xs) var(--sans); padding: 6px 8px; text-align: center; border: none; border-right: 1px solid var(--border);
|
|
233
619
|
background: transparent; color: var(--muted-foreground); cursor: pointer; transition: background-color .12s, color .12s; }
|
|
234
620
|
.chip:last-child { border-right: none; }
|
|
235
621
|
.chip:hover { background: var(--accent); color: var(--foreground); }
|
|
236
622
|
.chip.active { background: var(--primary-muted); color: var(--primary-text); }
|
|
237
623
|
.popover .row { display: flex; justify-content: flex-end; gap: 8px; align-items: center; padding: 6px 8px; border-top: 1px solid var(--border); }
|
|
238
|
-
.popover .hintk { color: var(--subtle); font-size:
|
|
624
|
+
.popover .hintk { color: var(--subtle); font-size: var(--text-2xs); margin-right: auto; }
|
|
239
625
|
/* Caret pointing at the selection (down when the toolbar sits above it) */
|
|
240
626
|
.popover::after { content: ''; position: absolute; left: 18px; width: 9px; height: 9px; background: var(--popover);
|
|
241
627
|
border: 1px solid var(--border); transform: rotate(45deg); display: none; }
|
|
@@ -243,8 +629,8 @@
|
|
|
243
629
|
.popover.below::after { display: block; top: -5px; background: var(--muted); border-bottom: none; border-right: none; }
|
|
244
630
|
|
|
245
631
|
/* ---- Terminus: Textarea ---- */
|
|
246
|
-
.ta { width: 100%; resize: vertical; font:
|
|
247
|
-
border: 1px solid var(--input); border-radius: var(--radius
|
|
632
|
+
.ta { width: 100%; resize: vertical; font: var(--text-sm)/var(--leading-normal) var(--sans); color: var(--foreground); background: transparent;
|
|
633
|
+
border: 1px solid var(--input); border-radius: var(--radius); padding: 8px 10px; }
|
|
248
634
|
.ta::placeholder { color: var(--subtle); }
|
|
249
635
|
.ta:focus-visible { outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 28%, transparent); }
|
|
250
636
|
.popover .ta { min-height: 132px; border: none; border-radius: 0; padding: 8px 10px; } .popover .ta:focus-visible { outline: none; box-shadow: none; }
|
|
@@ -256,26 +642,26 @@
|
|
|
256
642
|
.thread { flex: 1; overflow-y: auto; padding: 6px 14px; display: flex; flex-direction: column; gap: 8px; }
|
|
257
643
|
.thread .empty { color: var(--subtle); padding: 8px 4px; }
|
|
258
644
|
/* Terminus: Card */
|
|
259
|
-
.msg { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; background: var(--card); box-shadow:
|
|
645
|
+
.msg { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; background: var(--card); box-shadow: var(--shadow-sm); cursor: default; }
|
|
260
646
|
.msg.note { border-color: var(--warning-border); cursor: pointer; }
|
|
261
647
|
.msg.note.active { box-shadow: 0 0 0 2px var(--warning); }
|
|
262
648
|
.msg .meta { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 4px; align-items: center; }
|
|
263
|
-
.msg .who { color: var(--muted-foreground); font-size:
|
|
264
|
-
.msg .del { cursor: pointer; border: none; background: none; color: var(--subtle); font-size:
|
|
265
|
-
.msg .quote { color: var(--muted-foreground); font-size:
|
|
649
|
+
.msg .who { color: var(--muted-foreground); font-size: var(--text-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: .07em; } .msg.note .who { color: var(--warning); }
|
|
650
|
+
.msg .del { cursor: pointer; border: none; background: none; color: var(--subtle); font-size: var(--text-2xs); padding: 0 2px; } .msg .del:hover { color: var(--destructive); }
|
|
651
|
+
.msg .quote { color: var(--muted-foreground); font-size: var(--text-2xs); border-left: 2px solid var(--warning); padding: 1px 8px; margin-bottom: 5px; max-height: 42px; overflow: hidden; }
|
|
266
652
|
.msg .body { white-space: pre-wrap; word-break: break-word; color: var(--card-foreground); }
|
|
267
653
|
|
|
268
654
|
/* Terminus: Separator + composer */
|
|
269
655
|
.composer { border-top: 1px solid var(--border); padding: 12px 14px; }
|
|
270
656
|
.actions { padding: 0 14px 14px; }
|
|
271
|
-
.count { color: var(--subtle); font-size:
|
|
657
|
+
.count { color: var(--subtle); font-family: var(--mono); font-size: var(--text-2xs); }
|
|
272
658
|
|
|
273
659
|
/* Bottom decision bar — decisions read as the end of the review. Reject sits
|
|
274
660
|
isolated on the left; the two "continue" actions group on the right. The
|
|
275
661
|
summary in the middle says what rides along with the decision. */
|
|
276
662
|
.dbar { flex: none; display: flex; align-items: center; border-top: 1px solid var(--border); background: var(--card); padding: 10px 16px; min-height: 58px; }
|
|
277
663
|
.dbar-live { display: flex; flex: 1; align-items: center; gap: 12px; }
|
|
278
|
-
.dbar-summary { flex: 1; text-align: center; color: var(--subtle); font-size:
|
|
664
|
+
.dbar-summary { flex: 1; text-align: center; color: var(--subtle); font-size: var(--text-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
279
665
|
.dbar-group { display: flex; gap: 8px; }
|
|
280
666
|
|
|
281
667
|
/* Post-submit confirmation — replaces the decision bar content once a decision
|
|
@@ -285,12 +671,12 @@
|
|
|
285
671
|
.sent.approve { border-color: var(--success-border); background: var(--success-muted); }
|
|
286
672
|
.sent.revise { border-color: var(--warning-border); background: var(--warning-muted); }
|
|
287
673
|
.sent.deny { border-color: var(--destructive-border); background: var(--destructive-muted); }
|
|
288
|
-
.sent .h { display: flex; align-items: center; gap: 8px; font: 600
|
|
674
|
+
.sent .h { display: flex; align-items: center; gap: 8px; font: 600 var(--text-sm) var(--sans); }
|
|
289
675
|
.sent.approve .h { color: var(--success); }
|
|
290
676
|
.sent.revise .h { color: var(--warning); }
|
|
291
677
|
.sent.deny .h { color: var(--destructive); }
|
|
292
678
|
.sent .h { flex: none; }
|
|
293
|
-
.sent .sub { color: var(--muted-foreground); font-size:
|
|
679
|
+
.sent .sub { color: var(--muted-foreground); font-size: var(--text-xs); line-height: var(--leading-normal); }
|
|
294
680
|
.sent .ico { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; }
|
|
295
681
|
.sent .again { margin-top: 10px; }
|
|
296
682
|
.dot { width: 7px; height: 7px; border-radius: 9999px; background: currentColor; animation: pulse 1.3s ease-in-out infinite; }
|
|
@@ -299,21 +685,32 @@
|
|
|
299
685
|
</head>
|
|
300
686
|
<body>
|
|
301
687
|
<div class="app">
|
|
302
|
-
<aside class="nav">
|
|
688
|
+
<aside class="nav" data-kit-chrome>
|
|
689
|
+
<div class="kit-theme">
|
|
690
|
+
<button type="button" data-kit-theme="light">Light</button>
|
|
691
|
+
<button type="button" data-kit-theme="dark">Dark</button>
|
|
692
|
+
<button type="button" data-kit-theme="system">Auto</button>
|
|
693
|
+
</div>
|
|
303
694
|
<div class="tabs">
|
|
304
695
|
<button type="button" class="tab active" id="tab-contents">Contents</button>
|
|
305
696
|
<button type="button" class="tab" id="tab-files">Files</button>
|
|
306
697
|
</div>
|
|
307
698
|
<div id="pane-contents">
|
|
308
|
-
<div class="tree-filter" id="tree-filter"><input type="search" id="tree-q" placeholder="Filter docs…" aria-label="Filter documents"></div>
|
|
309
|
-
<nav id="toc"
|
|
699
|
+
<div class="tree-filter" id="tree-filter"><input type="search" id="tree-q" data-kit-filter placeholder="Filter docs…" aria-label="Filter documents"></div>
|
|
700
|
+
<nav id="toc">
|
|
701
|
+
<div class="kit-skel" style="width:70%;height:0.85rem;margin:0 6px 10px"></div>
|
|
702
|
+
<div class="kit-skel" style="width:55%;height:0.85rem;margin:0 6px 10px"></div>
|
|
703
|
+
<div class="kit-skel" style="width:80%;height:0.85rem;margin:0 6px 10px"></div>
|
|
704
|
+
<div class="kit-skel" style="width:45%;height:0.85rem;margin:0 6px 10px"></div>
|
|
705
|
+
</nav>
|
|
310
706
|
</div>
|
|
311
707
|
<div id="pane-files" style="display:none"><nav id="filelist"></nav></div>
|
|
312
708
|
</aside>
|
|
313
709
|
|
|
314
710
|
<main class="main">
|
|
315
711
|
<p class="hint">Select any text to comment on it. Or chat with the agent on the right. Then decide.</p>
|
|
316
|
-
<div class="
|
|
712
|
+
<div class="kit-error" id="api-error" hidden><span class="msg-text"></span></div>
|
|
713
|
+
<div class="revbar" id="revbar" data-kit-chrome>
|
|
317
714
|
<span class="rb-text">Plan revised</span>
|
|
318
715
|
<span class="rb-stat" id="rb-stat"></span>
|
|
319
716
|
<span class="rb-actions">
|
|
@@ -322,10 +719,15 @@
|
|
|
322
719
|
</span>
|
|
323
720
|
</div>
|
|
324
721
|
<div class="crumbs" id="crumbs"></div>
|
|
325
|
-
<div class="doc" id="plan">
|
|
722
|
+
<div class="doc" id="plan">
|
|
723
|
+
<div class="kit-skel" style="width:60%;height:1.4rem;margin-bottom:1em"></div>
|
|
724
|
+
<div class="kit-skel" style="width:100%;margin-bottom:0.6em"></div>
|
|
725
|
+
<div class="kit-skel" style="width:96%;margin-bottom:0.6em"></div>
|
|
726
|
+
<div class="kit-skel" style="width:88%;margin-bottom:0.6em"></div>
|
|
727
|
+
</div>
|
|
326
728
|
<div class="pnav" id="pnav"></div>
|
|
327
729
|
<div class="diffwrap" id="diffwrap"><div class="diffview" id="diffview"></div></div>
|
|
328
|
-
<div class="popover" id="popover">
|
|
730
|
+
<div class="popover" id="popover" data-kit-chrome>
|
|
329
731
|
<div class="q" id="pop-quote"></div>
|
|
330
732
|
<div class="cats" id="pop-cats">
|
|
331
733
|
<button type="button" class="chip active" data-cat="Comment">Comment</button>
|
|
@@ -341,7 +743,7 @@
|
|
|
341
743
|
</div>
|
|
342
744
|
</main>
|
|
343
745
|
|
|
344
|
-
<aside class="chat">
|
|
746
|
+
<aside class="chat" data-kit-chrome>
|
|
345
747
|
<p class="label">Review <span class="count" id="count"></span></p>
|
|
346
748
|
<div class="thread" id="thread"><div class="empty">No notes yet. Select text to comment, or send a message.</div></div>
|
|
347
749
|
<div class="composer">
|
|
@@ -355,7 +757,7 @@
|
|
|
355
757
|
|
|
356
758
|
<!-- Decisions live in a fixed bottom bar — the rail above is purely
|
|
357
759
|
conversational (notes + messages). Reject is isolated on the left. -->
|
|
358
|
-
<footer class="dbar">
|
|
760
|
+
<footer class="dbar" data-kit-chrome>
|
|
359
761
|
<div class="dbar-live" id="dbar-live">
|
|
360
762
|
<button class="btn btn-danger" id="btn-reject" data-decision="Reject">Reject</button>
|
|
361
763
|
<span class="dbar-summary" id="dbar-summary"></span>
|
|
@@ -370,6 +772,373 @@
|
|
|
370
772
|
</div>
|
|
371
773
|
</footer>
|
|
372
774
|
|
|
775
|
+
<script>
|
|
776
|
+
/* kit:js:start */
|
|
777
|
+
/* Shared behaviour layer — do not hand-edit; edit scripts/tokens/kit.js. */
|
|
778
|
+
|
|
779
|
+
/* Deliberately free of backticks and interpolation openers. No target is a JS
|
|
780
|
+
template literal since the board-shell extraction, so this is insurance
|
|
781
|
+
rather than a requirement — but it is the difference between this file being
|
|
782
|
+
safe to inline anywhere and being safe only where someone remembered to
|
|
783
|
+
check. Regexes and escapes are avoided for the same reason. */
|
|
784
|
+
|
|
785
|
+
(function () {
|
|
786
|
+
'use strict';
|
|
787
|
+
|
|
788
|
+
var THEME_KEY = 'jabworks-theme';
|
|
789
|
+
var THEME_MODES = ['light', 'dark', 'system'];
|
|
790
|
+
var root = document.documentElement;
|
|
791
|
+
|
|
792
|
+
/* localStorage throws on an opaque origin, which is what a file:// artifact
|
|
793
|
+
often gets. Degrade to session-only theming; never break the page. */
|
|
794
|
+
function readStored(key) {
|
|
795
|
+
try {
|
|
796
|
+
return window.localStorage.getItem(key);
|
|
797
|
+
} catch (e) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function writeStored(key, value) {
|
|
803
|
+
try {
|
|
804
|
+
window.localStorage.setItem(key, value);
|
|
805
|
+
} catch (e) {
|
|
806
|
+
/* no-op */
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function applyTheme(mode) {
|
|
811
|
+
if (mode === 'light' || mode === 'dark') root.setAttribute('data-theme', mode);
|
|
812
|
+
else root.removeAttribute('data-theme');
|
|
813
|
+
|
|
814
|
+
writeStored(THEME_KEY, mode);
|
|
815
|
+
|
|
816
|
+
var buttons = document.querySelectorAll('[data-kit-theme]');
|
|
817
|
+
|
|
818
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
819
|
+
buttons[i].setAttribute('aria-pressed', String(buttons[i].getAttribute('data-kit-theme') === mode));
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function currentTheme() {
|
|
824
|
+
return root.getAttribute('data-theme') || 'system';
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function cycleTheme() {
|
|
828
|
+
applyTheme(THEME_MODES[(THEME_MODES.indexOf(currentTheme()) + 1) % THEME_MODES.length]);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function items() {
|
|
832
|
+
return Array.prototype.slice.call(document.querySelectorAll('[data-kit-item]'));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/* document.activeElement is null in a detached or not-yet-painted document,
|
|
836
|
+
and Element.closest is absent on the document node itself. */
|
|
837
|
+
function activeWithin(selector) {
|
|
838
|
+
var el = document.activeElement;
|
|
839
|
+
|
|
840
|
+
return el && el.closest ? el.closest(selector) : null;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function moveItem(step) {
|
|
844
|
+
var all = items();
|
|
845
|
+
|
|
846
|
+
if (all.length === 0) return;
|
|
847
|
+
|
|
848
|
+
var index = all.indexOf(activeWithin('[data-kit-item]'));
|
|
849
|
+
var next = all[Math.min(Math.max(index + step, 0), all.length - 1)] || all[0];
|
|
850
|
+
|
|
851
|
+
next.setAttribute('tabindex', '-1');
|
|
852
|
+
next.focus();
|
|
853
|
+
next.scrollIntoView({ block: 'nearest' });
|
|
854
|
+
|
|
855
|
+
/* Reflect the focused item in the URL so a board or a review can be linked
|
|
856
|
+
to a specific row. replaceState, not a hash assignment: assigning to
|
|
857
|
+
location.hash pushes a history entry per keypress. */
|
|
858
|
+
if (next.id && window.history && window.history.replaceState) {
|
|
859
|
+
window.history.replaceState(null, '', '#' + next.id);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function reveal(el) {
|
|
864
|
+
if (el && el.focus) {
|
|
865
|
+
el.focus();
|
|
866
|
+
if (el.select) el.select();
|
|
867
|
+
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/* A surface whose filter field ships hidden cannot hang the hook on the field
|
|
875
|
+
itself — a hidden element does not take focus, so the binding would be a
|
|
876
|
+
silent no-op. Those surfaces put the hook on the control that REVEALS the
|
|
877
|
+
field instead; activate it, then focus what it revealed via aria-controls.
|
|
878
|
+
Without this the ? overlay would advertise "Focus filter" for a key that
|
|
879
|
+
only moves focus to a button, and a generated overlay that misdescribes its
|
|
880
|
+
own bindings is worse than no overlay. */
|
|
881
|
+
function focusFilter() {
|
|
882
|
+
var hook = document.querySelector('[data-kit-filter]');
|
|
883
|
+
|
|
884
|
+
if (!hook) return false;
|
|
885
|
+
|
|
886
|
+
if (hook.tagName === 'BUTTON') {
|
|
887
|
+
if (hook.getAttribute('aria-expanded') === 'false' && hook.click) hook.click();
|
|
888
|
+
|
|
889
|
+
if (reveal(document.getElementById(hook.getAttribute('aria-controls') || ''))) return true;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return reveal(hook);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function flashCopied(el) {
|
|
896
|
+
el.classList.add('kit-copied');
|
|
897
|
+
window.setTimeout(function () {
|
|
898
|
+
el.classList.remove('kit-copied');
|
|
899
|
+
}, 900);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function copyFrom(el) {
|
|
903
|
+
if (!el) return;
|
|
904
|
+
|
|
905
|
+
var text = el.getAttribute('data-kit-copy') || el.textContent || '';
|
|
906
|
+
|
|
907
|
+
if (!text || !navigator.clipboard || !navigator.clipboard.writeText) return;
|
|
908
|
+
|
|
909
|
+
navigator.clipboard.writeText(text.trim()).then(
|
|
910
|
+
function () {
|
|
911
|
+
flashCopied(el);
|
|
912
|
+
},
|
|
913
|
+
function () {
|
|
914
|
+
/* Clipboard is permission-gated; a refusal is not an error worth
|
|
915
|
+
interrupting the page for. */
|
|
916
|
+
},
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function sections() {
|
|
921
|
+
return Array.prototype.slice.call(document.querySelectorAll('[data-kit-section]'));
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function jumpToSection(n) {
|
|
925
|
+
var target = sections()[n - 1];
|
|
926
|
+
|
|
927
|
+
if (target) target.scrollIntoView({ block: 'start' });
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/* One registry drives both the key handler and the help overlay, so the
|
|
931
|
+
documented shortcuts cannot drift from the implemented ones. */
|
|
932
|
+
var BINDINGS = [
|
|
933
|
+
{ keys: ['j'], label: 'Next item', when: function () { return items().length > 0; }, run: function () { moveItem(1); } },
|
|
934
|
+
{ keys: ['k'], label: 'Previous item', when: function () { return items().length > 0; }, run: function () { moveItem(-1); } },
|
|
935
|
+
{
|
|
936
|
+
keys: ['/'],
|
|
937
|
+
label: 'Focus filter',
|
|
938
|
+
when: function () { return Boolean(document.querySelector('[data-kit-filter]')); },
|
|
939
|
+
run: focusFilter,
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
keys: ['y'],
|
|
943
|
+
label: 'Copy id or path',
|
|
944
|
+
when: function () { return Boolean(document.querySelector('[data-kit-copy]')); },
|
|
945
|
+
run: function () {
|
|
946
|
+
copyFrom(activeWithin('[data-kit-copy]'));
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
{ keys: ['t'], label: 'Cycle theme', when: function () { return true; }, run: cycleTheme },
|
|
950
|
+
{
|
|
951
|
+
keys: ['g', '1-9'],
|
|
952
|
+
label: 'Jump to section',
|
|
953
|
+
when: function () { return sections().length > 0; },
|
|
954
|
+
run: null,
|
|
955
|
+
},
|
|
956
|
+
{ keys: ['?'], label: 'Show shortcuts', when: function () { return true; }, run: null },
|
|
957
|
+
{ keys: ['Esc'], label: 'Dismiss', when: function () { return true; }, run: null },
|
|
958
|
+
];
|
|
959
|
+
|
|
960
|
+
var overlay = null;
|
|
961
|
+
|
|
962
|
+
function buildOverlay() {
|
|
963
|
+
var host = document.createElement('div');
|
|
964
|
+
|
|
965
|
+
host.className = 'kit-help';
|
|
966
|
+
host.hidden = true;
|
|
967
|
+
host.setAttribute('role', 'dialog');
|
|
968
|
+
host.setAttribute('aria-modal', 'true');
|
|
969
|
+
host.setAttribute('aria-label', 'Keyboard shortcuts');
|
|
970
|
+
|
|
971
|
+
var panel = document.createElement('div');
|
|
972
|
+
|
|
973
|
+
panel.className = 'kit-help__panel';
|
|
974
|
+
|
|
975
|
+
var title = document.createElement('p');
|
|
976
|
+
|
|
977
|
+
title.className = 'kit-help__title';
|
|
978
|
+
title.textContent = 'Keyboard shortcuts';
|
|
979
|
+
panel.appendChild(title);
|
|
980
|
+
|
|
981
|
+
for (var i = 0; i < BINDINGS.length; i++) {
|
|
982
|
+
if (!BINDINGS[i].when()) continue;
|
|
983
|
+
|
|
984
|
+
var row = document.createElement('div');
|
|
985
|
+
|
|
986
|
+
row.className = 'kit-help__row';
|
|
987
|
+
|
|
988
|
+
var label = document.createElement('span');
|
|
989
|
+
|
|
990
|
+
label.textContent = BINDINGS[i].label;
|
|
991
|
+
row.appendChild(label);
|
|
992
|
+
|
|
993
|
+
var keys = document.createElement('span');
|
|
994
|
+
|
|
995
|
+
keys.className = 'kit-help__keys';
|
|
996
|
+
|
|
997
|
+
for (var k = 0; k < BINDINGS[i].keys.length; k++) {
|
|
998
|
+
var kbd = document.createElement('kbd');
|
|
999
|
+
|
|
1000
|
+
kbd.className = 'kit-kbd';
|
|
1001
|
+
kbd.textContent = BINDINGS[i].keys[k];
|
|
1002
|
+
keys.appendChild(kbd);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
row.appendChild(keys);
|
|
1006
|
+
panel.appendChild(row);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
host.appendChild(panel);
|
|
1010
|
+
host.addEventListener('click', function (event) {
|
|
1011
|
+
if (event.target === host) toggleOverlay(false);
|
|
1012
|
+
});
|
|
1013
|
+
document.body.appendChild(host);
|
|
1014
|
+
|
|
1015
|
+
return host;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function toggleOverlay(show) {
|
|
1019
|
+
if (!overlay) overlay = buildOverlay();
|
|
1020
|
+
|
|
1021
|
+
overlay.hidden = !show;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function isTypingTarget(el) {
|
|
1025
|
+
if (!el) return false;
|
|
1026
|
+
|
|
1027
|
+
var tag = el.tagName;
|
|
1028
|
+
|
|
1029
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable === true;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
var pendingJump = false;
|
|
1033
|
+
|
|
1034
|
+
function onKeydown(event) {
|
|
1035
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
1036
|
+
|
|
1037
|
+
if (event.key === 'Escape') {
|
|
1038
|
+
toggleOverlay(false);
|
|
1039
|
+
if (isTypingTarget(event.target) && event.target.blur) event.target.blur();
|
|
1040
|
+
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/* A filter box must be typeable: every other binding is inert while the
|
|
1045
|
+
caret is in a field. Escape above is the deliberate exception. */
|
|
1046
|
+
if (isTypingTarget(event.target)) return;
|
|
1047
|
+
|
|
1048
|
+
if (pendingJump) {
|
|
1049
|
+
pendingJump = false;
|
|
1050
|
+
|
|
1051
|
+
if (event.key >= '1' && event.key <= '9') {
|
|
1052
|
+
jumpToSection(Number(event.key));
|
|
1053
|
+
event.preventDefault();
|
|
1054
|
+
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (event.key === 'g' && sections().length > 0) {
|
|
1060
|
+
pendingJump = true;
|
|
1061
|
+
window.setTimeout(function () {
|
|
1062
|
+
pendingJump = false;
|
|
1063
|
+
}, 1200);
|
|
1064
|
+
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (event.key === '?') {
|
|
1069
|
+
toggleOverlay(overlay ? overlay.hidden : true);
|
|
1070
|
+
event.preventDefault();
|
|
1071
|
+
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
for (var i = 0; i < BINDINGS.length; i++) {
|
|
1076
|
+
if (BINDINGS[i].run && BINDINGS[i].keys.indexOf(event.key) !== -1 && BINDINGS[i].when()) {
|
|
1077
|
+
BINDINGS[i].run();
|
|
1078
|
+
event.preventDefault();
|
|
1079
|
+
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function init() {
|
|
1086
|
+
applyTheme(readStored(THEME_KEY) || 'system');
|
|
1087
|
+
|
|
1088
|
+
var buttons = document.querySelectorAll('[data-kit-theme]');
|
|
1089
|
+
|
|
1090
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
1091
|
+
buttons[i].addEventListener('click', function (event) {
|
|
1092
|
+
applyTheme(event.currentTarget.getAttribute('data-kit-theme'));
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/* Give the ? overlay a visible way in. It rides alongside the theme group
|
|
1097
|
+
because every surface already has one, and because a floating badge is
|
|
1098
|
+
chrome each surface would then have to design around. */
|
|
1099
|
+
var groups = document.querySelectorAll('.kit-theme');
|
|
1100
|
+
|
|
1101
|
+
for (var g = 0; g < groups.length; g++) {
|
|
1102
|
+
if (!groups[g].parentNode) continue;
|
|
1103
|
+
|
|
1104
|
+
var help = document.createElement('button');
|
|
1105
|
+
|
|
1106
|
+
help.type = 'button';
|
|
1107
|
+
help.className = 'kit-helpbtn';
|
|
1108
|
+
help.textContent = '?';
|
|
1109
|
+
help.title = 'Keyboard shortcuts (?)';
|
|
1110
|
+
help.setAttribute('aria-label', 'Keyboard shortcuts');
|
|
1111
|
+
help.addEventListener('click', function () {
|
|
1112
|
+
toggleOverlay(true);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
/* Wrap rather than insert as a sibling: a surface may host the theme group
|
|
1116
|
+
in a flex row (the board) or in block flow (the guide's rail), and in
|
|
1117
|
+
block flow a bare sibling drops onto its own line. The wrapper makes the
|
|
1118
|
+
pairing hold in either. */
|
|
1119
|
+
var controls = document.createElement('div');
|
|
1120
|
+
|
|
1121
|
+
controls.className = 'kit-controls';
|
|
1122
|
+
groups[g].parentNode.insertBefore(controls, groups[g]);
|
|
1123
|
+
controls.appendChild(groups[g]);
|
|
1124
|
+
controls.appendChild(help);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
document.addEventListener('click', function (event) {
|
|
1128
|
+
var target = event.target.closest ? event.target.closest('[data-kit-copy]') : null;
|
|
1129
|
+
|
|
1130
|
+
if (target) copyFrom(target);
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
document.addEventListener('keydown', onKeydown);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
1137
|
+
else init();
|
|
1138
|
+
})();
|
|
1139
|
+
/* kit:js:end */
|
|
1140
|
+
</script>
|
|
1141
|
+
|
|
373
1142
|
<script>
|
|
374
1143
|
// ---- Self-contained Markdown renderer (no external deps) ----
|
|
375
1144
|
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');}
|
|
@@ -395,13 +1164,26 @@
|
|
|
395
1164
|
if(/^#{1,6}\s/.test(line)){const lvl=line.match(/^#+/)[0].length;const text=line.replace(/^#+\s/,'');out.push({html:'<h'+lvl+'>'+inline(text)+'</h'+lvl+'>',heading:{level:lvl,text:text}});i++;continue;}
|
|
396
1165
|
if(/^---+\s*$/.test(line)){out.push({html:'<hr>',heading:null});i++;continue;}
|
|
397
1166
|
if(/^\s*>/.test(line)){const buf=[];while(i<lines.length&&/^\s*>/.test(lines[i]))buf.push(lines[i++].replace(/^\s*>\s?/,''));out.push({html:'<blockquote>'+inline(buf.join(' '))+'</blockquote>',heading:null});continue;}
|
|
398
|
-
|
|
1167
|
+
// Lazy continuation: a soft-wrapped bullet leaves its follow-on lines with no
|
|
1168
|
+
// marker. Advancing only on marker lines ended the list there and rendered the
|
|
1169
|
+
// remainder as a left-margin paragraph — every wrapped bullet in every spec.
|
|
1170
|
+
// The exclusion set is deliberately the paragraph loop's below, not a new one.
|
|
1171
|
+
if(/^\s*([-*+]|\d+\.)\s/.test(line)){const ordered=/^\s*\d+\./.test(line);const buf=[];while(i<lines.length&&/^\s*([-*+]|\d+\.)\s/.test(lines[i])){let it=lines[i].replace(/^\s*([-*+]|\d+\.)\s/,'');i++;while(i<lines.length&&!/^\s*$/.test(lines[i])&&!/^(#{1,6}\s|```|\s*>|\s*([-*+]|\d+\.)\s|---+\s*$)/.test(lines[i])&&!(lines[i].indexOf('|')>=0&&i+1<lines.length&&isDelim(lines[i+1]))){it+=' '+lines[i].trim();i++;}const cb=it.match(/^\[([ xX])\]\s+/);if(cb){it=it.slice(cb[0].length);buf.push('<li class="task">'+(/[xX]/.test(cb[1])?'☑':'☐')+' '+inline(it)+'</li>');}else{buf.push('<li>'+inline(it)+'</li>');}}out.push({html:'<'+(ordered?'ol':'ul')+'>'+buf.join('')+'</'+(ordered?'ol':'ul')+'>',heading:null});continue;}
|
|
399
1172
|
if(line.indexOf('|')>=0&&i+1<lines.length&&isDelim(lines[i+1])){const header=tcells(line);const aligns=tcells(lines[i+1]).map(function(c){const l=/^:/.test(c),r=/:$/.test(c);return l&&r?'center':r?'right':l?'left':'';});i+=2;const rows=[];while(i<lines.length&&lines[i].indexOf('|')>=0&&!/^\s*$/.test(lines[i])&&!/^(#{1,6}\s|```|---+\s*$)/.test(lines[i])){rows.push(tcells(lines[i]));i++;}let h='<table><thead><tr>';header.forEach(function(c,ci){h+='<th'+(aligns[ci]?' style="text-align:'+aligns[ci]+'"':'')+'>'+inline(c)+'</th>';});h+='</tr></thead><tbody>';rows.forEach(function(cs){h+='<tr>';header.forEach(function(_,ci){h+='<td'+(aligns[ci]?' style="text-align:'+aligns[ci]+'"':'')+'>'+inline(cs[ci]!==undefined?cs[ci]:'')+'</td>';});h+='</tr>';});h+='</tbody></table>';out.push({html:h,heading:null});continue;}
|
|
400
1173
|
const buf=[];while(i<lines.length&&!/^\s*$/.test(lines[i])&&!/^(#{1,6}\s|```|\s*>|\s*([-*+]|\d+\.)\s|---+\s*$)/.test(lines[i])&&!(lines[i].indexOf('|')>=0&&i+1<lines.length&&isDelim(lines[i+1])))buf.push(lines[i++]);
|
|
401
1174
|
out.push({html:'<p>'+inline(buf.join(' '))+'</p>',heading:null});}
|
|
402
1175
|
return out;
|
|
403
1176
|
}
|
|
404
1177
|
|
|
1178
|
+
// ---- API error banner (kit-error) ----
|
|
1179
|
+
function showApiError(msg){
|
|
1180
|
+
const el=document.getElementById('api-error'); if(!el) return;
|
|
1181
|
+
el.querySelector('.msg-text').textContent=msg; el.hidden=false;
|
|
1182
|
+
}
|
|
1183
|
+
function hideApiError(){
|
|
1184
|
+
const el=document.getElementById('api-error'); if(el) el.hidden=true;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
405
1187
|
// ---- State ----
|
|
406
1188
|
const annotations = []; let nextId = 1;
|
|
407
1189
|
const planEl=document.getElementById('plan');
|
|
@@ -482,6 +1264,7 @@
|
|
|
482
1264
|
node.docs.forEach(function(doc){
|
|
483
1265
|
const m=matches(doc); if(!m.show) return;
|
|
484
1266
|
const d=document.createElement('a'); d.href='#'; d.className='doc'+(doc===activeDoc?' active':''); d.dataset.doc=doc; d.title=doc;
|
|
1267
|
+
d.setAttribute('data-kit-item','');
|
|
485
1268
|
indent(d,depth);
|
|
486
1269
|
const t=document.createElement('span'); t.className='t'; t.textContent=doc.slice(pathStr?pathStr.length+1:0); d.appendChild(t);
|
|
487
1270
|
const bd=document.createElement('span'); bd.className='badge badge-warning'; bd.style.display='none'; bd.dataset.fordoc=doc; d.appendChild(bd);
|
|
@@ -718,7 +1501,7 @@
|
|
|
718
1501
|
'Request Revisions':{cls:'revise', ico:'',wait:true, title:'Revisions requested',sub:'Your notes are on their way. The agent will revise the plan; the updated version reloads here when it’s ready.'},
|
|
719
1502
|
'Reject': {cls:'deny', ico:'✕',wait:false,title:'Rejected',sub:'Sent to the agent — it will not implement this. It steps back to reconsider whether the feature should be built at all.'},
|
|
720
1503
|
};
|
|
721
|
-
let submitting=false;
|
|
1504
|
+
let submitting=false, decisionSent=false;
|
|
722
1505
|
function clearAllNotes(){
|
|
723
1506
|
annotations.slice().forEach(function(a){ if(a.kind==='note') unhighlight(a.id); });
|
|
724
1507
|
annotations.length=0; nextId=1; paint();
|
|
@@ -740,10 +1523,18 @@
|
|
|
740
1523
|
async function submit(decision){
|
|
741
1524
|
if(submitting) return; submitting=true;
|
|
742
1525
|
document.querySelectorAll('.dbar button').forEach(function(b){b.disabled=true;});
|
|
743
|
-
try{
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1526
|
+
try{
|
|
1527
|
+
const r=await fetch('/api/feedback',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({decision:decision,thread:annotations})});
|
|
1528
|
+
if(!r.ok) throw new Error('status '+r.status);
|
|
1529
|
+
hideApiError();
|
|
1530
|
+
decisionSent=true; // a terminal decision makes the server exit —
|
|
1531
|
+
// the SSE drop that follows is expected, not a fault
|
|
1532
|
+
clearAllNotes(); // notes clear the moment the decision is sent
|
|
1533
|
+
showSent(decision);
|
|
1534
|
+
}catch(e){
|
|
1535
|
+
showApiError('Could not send your decision — the review server may be unreachable. Nothing was sent; try again.');
|
|
1536
|
+
document.querySelectorAll('.dbar button').forEach(function(b){b.disabled=false;});
|
|
1537
|
+
}
|
|
747
1538
|
submitting=false;
|
|
748
1539
|
}
|
|
749
1540
|
document.getElementById('send').onclick=sendChat;
|
|
@@ -850,10 +1641,18 @@
|
|
|
850
1641
|
tabC.onclick=function(){setTab(false);}; tabF.onclick=function(){setTab(true);};
|
|
851
1642
|
|
|
852
1643
|
// ---- Load + live reload ----
|
|
853
|
-
|
|
1644
|
+
// null means "fetch failed" (error banner already shown) and is kept
|
|
1645
|
+
// distinct from '' so a transient failure can never masquerade as a
|
|
1646
|
+
// real empty document and trip the revised-doc diff below.
|
|
1647
|
+
function fetchDoc(doc){
|
|
1648
|
+
return fetch(DIRMODE?'/api/plan?doc='+encodeURIComponent(doc):'/api/plan')
|
|
1649
|
+
.then(function(r){ if(!r.ok) throw new Error('status '+r.status); hideApiError(); return r.text(); })
|
|
1650
|
+
.catch(function(e){ showApiError('Could not load "'+doc+'" — the review server may be unreachable.'); return null; });
|
|
1651
|
+
}
|
|
854
1652
|
async function reloadDoc(doc){
|
|
855
1653
|
if(!docState[doc]) return;
|
|
856
1654
|
const md=await fetchDoc(doc);
|
|
1655
|
+
if(md===null) return; // load failed; state untouched, banner already shown
|
|
857
1656
|
const st=docState[doc];
|
|
858
1657
|
if(md===st.md) return; // spurious watch event, no content change
|
|
859
1658
|
const prev=st.md, revised=prev!==null;
|
|
@@ -869,8 +1668,22 @@
|
|
|
869
1668
|
}
|
|
870
1669
|
async function boot(){
|
|
871
1670
|
let meta={dir:false,docs:['plan']};
|
|
872
|
-
try{
|
|
1671
|
+
try{
|
|
1672
|
+
const r=await fetch('/api/docs');
|
|
1673
|
+
if(!r.ok) throw new Error('status '+r.status);
|
|
1674
|
+
meta=await r.json();
|
|
1675
|
+
hideApiError();
|
|
1676
|
+
}catch(e){ showApiError('Could not reach the review server. Retry once it is back up.'); }
|
|
873
1677
|
DIRMODE=!!meta.dir; docs=meta.docs||[];
|
|
1678
|
+
// A directory with no markdown at all — nothing to render, nothing to
|
|
1679
|
+
// load per-doc. Show the empty state and stop before the doc loop.
|
|
1680
|
+
if(DIRMODE&&!docs.length){
|
|
1681
|
+
tocEl.innerHTML='';
|
|
1682
|
+
planEl.innerHTML='<div class="kit-empty"><p class="kit-empty__title">No documents to review</p>'
|
|
1683
|
+
+'<p class="kit-empty__body">This directory has no markdown files.</p></div>';
|
|
1684
|
+
paint();
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
874
1687
|
// Accept-or-fix reviews hide the plan-only "Reject / don't build it"
|
|
875
1688
|
// verdict: directory (spec) review always — a spec documents work already
|
|
876
1689
|
// decided — and single-file review when the server was started with
|
|
@@ -889,22 +1702,31 @@
|
|
|
889
1702
|
};
|
|
890
1703
|
qEl.onkeydown=function(e){ if(e.key==='Escape'){ qEl.value=''; qEl.oninput(); qEl.blur(); } };
|
|
891
1704
|
}
|
|
892
|
-
|
|
1705
|
+
// Panes are built off-DOM and only swapped in once every doc has
|
|
1706
|
+
// resolved, so the .kit-skel placeholders already in #plan stay
|
|
1707
|
+
// visible for the whole fetch instead of being wiped to blank first.
|
|
1708
|
+
const frag=document.createDocumentFragment();
|
|
893
1709
|
docs.forEach(function(d,i){
|
|
894
1710
|
const pane=document.createElement('div'); pane.className='docpane'; pane.style.display='none';
|
|
895
|
-
|
|
1711
|
+
frag.appendChild(pane);
|
|
896
1712
|
docState[d]={md:null,pane:pane,scroll:0,key:'d'+i,headings:[]};
|
|
897
1713
|
});
|
|
898
|
-
for(let i=0;i<docs.length;i++){ const d=docs[i];
|
|
1714
|
+
for(let i=0;i<docs.length;i++){ const d=docs[i]; const md=await fetchDoc(d); if(md!==null) renderDocPane(d,md); }
|
|
1715
|
+
planEl.innerHTML=''; planEl.appendChild(frag);
|
|
899
1716
|
if(!DIRMODE||!docs.length) buildToc(); // DIRMODE: switchDoc below builds the tree once, seeded
|
|
900
1717
|
paint();
|
|
901
1718
|
if(docs.length) switchDoc(docs[0]);
|
|
902
1719
|
buildFilesIndex();
|
|
903
1720
|
}
|
|
904
|
-
try{
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
1721
|
+
try{
|
|
1722
|
+
const es=new EventSource('/api/events');
|
|
1723
|
+
es.onmessage=function(ev){
|
|
1724
|
+
hideApiError();
|
|
1725
|
+
const m=String((ev&&ev.data)||''); const doc=m.indexOf('change:')===0?m.slice(7):docs[0];
|
|
1726
|
+
reloadDoc(doc);
|
|
1727
|
+
};
|
|
1728
|
+
es.onerror=function(){ if(!decisionSent) showApiError('Live connection to the review server was lost — revised docs will not auto-reload. Your notes are safe.'); };
|
|
1729
|
+
}catch(e){ showApiError('Live reload is unavailable in this browser.'); }
|
|
908
1730
|
boot();
|
|
909
1731
|
</script>
|
|
910
1732
|
</body>
|