@jabworks/condux 0.12.0 → 0.13.0
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.0",
|
|
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": {
|
|
@@ -81,12 +81,16 @@
|
|
|
81
81
|
{
|
|
82
82
|
"query": "build the settings page from the approved design",
|
|
83
83
|
"should_trigger": false,
|
|
84
|
-
"expected_skill": null
|
|
84
|
+
"expected_skill": null,
|
|
85
|
+
"accept": ["workflow"],
|
|
86
|
+
"note": "Building from an approved design is an implementation request; workflow's any-implementation-request claim covers it. The case still guards blueprint against the \"design\" bait."
|
|
85
87
|
},
|
|
86
88
|
{
|
|
87
89
|
"query": "implement this Figma design as a React component",
|
|
88
90
|
"should_trigger": false,
|
|
89
|
-
"expected_skill": null
|
|
91
|
+
"expected_skill": null,
|
|
92
|
+
"accept": ["workflow"],
|
|
93
|
+
"note": "Implementing a design in code is an implementation request; workflow covers it. The case still guards blueprint against the \"Figma design\" bait."
|
|
90
94
|
},
|
|
91
95
|
{
|
|
92
96
|
"query": "pick a color palette and typography for the marketing site",
|
|
@@ -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,341 @@
|
|
|
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
|
+
.kit-copied {
|
|
345
|
+
color: var(--success);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
@media (prefers-reduced-motion: reduce) {
|
|
349
|
+
*,
|
|
350
|
+
*::before,
|
|
351
|
+
*::after {
|
|
352
|
+
animation-duration: 0.01ms !important;
|
|
353
|
+
animation-iteration-count: 1 !important;
|
|
354
|
+
transition-duration: 0.01ms !important;
|
|
355
|
+
scroll-behavior: auto !important;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* Browsers do not print background colours by default, so a dark-themed page
|
|
360
|
+
prints light text onto white and reads as blank. Repointing the tokens is
|
|
361
|
+
what makes Ctrl-P work at all; hiding chrome is the surface's opt-in. */
|
|
362
|
+
@media print {
|
|
363
|
+
:root {
|
|
364
|
+
--background: #ffffff;
|
|
365
|
+
--card: #ffffff;
|
|
366
|
+
--popover: #ffffff;
|
|
367
|
+
--muted: #f4f4f2;
|
|
368
|
+
--accent: #f4f4f2;
|
|
369
|
+
--foreground: #000000;
|
|
370
|
+
--card-foreground: #000000;
|
|
371
|
+
--muted-foreground: #444444;
|
|
372
|
+
--subtle: #666666;
|
|
373
|
+
--border: #999999;
|
|
374
|
+
--input: #999999;
|
|
375
|
+
--shadow-sm: none;
|
|
376
|
+
--shadow-md: none;
|
|
377
|
+
--shadow-lg: none;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
body {
|
|
381
|
+
background: #ffffff;
|
|
382
|
+
color: #000000;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.kit-skip,
|
|
386
|
+
.kit-help,
|
|
387
|
+
.kit-theme,
|
|
388
|
+
[data-kit-chrome] {
|
|
389
|
+
display: none !important;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
a[href^="http"]::after {
|
|
393
|
+
content: " (" attr(href) ")";
|
|
394
|
+
font-size: 90%;
|
|
395
|
+
color: #555555;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.kit-skel {
|
|
399
|
+
animation: none;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/* kit:css:end */
|
|
403
|
+
|
|
404
|
+
/* plan-review extensions — the annotation highlight. Outside the markers
|
|
405
|
+
on purpose: anything inside is replaced.
|
|
406
|
+
--radius-md was defined here at 0.25rem, shadowing the core's 0.5rem
|
|
407
|
+
by source order. The core value is adopted as-is; spots that wanted
|
|
408
|
+
the smaller 0.25rem step now say so explicitly via --radius, which
|
|
409
|
+
already carries that value and is already this file's convention
|
|
410
|
+
(.nav .tabs, .popover, .chip, code, pre, …). */
|
|
85
411
|
:root {
|
|
86
412
|
--hl: rgba(255, 202, 22, 0.30); --hl-active: rgba(255, 202, 22, 0.55);
|
|
87
|
-
--radius-md: 0.25rem;
|
|
88
413
|
}
|
|
89
414
|
@media (prefers-color-scheme: light) {
|
|
90
415
|
:root { --hl: rgba(234, 179, 8, 0.42); --hl-active: rgba(202, 138, 4, 0.62); }
|
|
91
416
|
}
|
|
92
417
|
* { box-sizing: border-box; }
|
|
93
418
|
html, body { height: 100%; }
|
|
94
|
-
|
|
95
|
-
|
|
419
|
+
/* .kit-error sets display:flex unconditionally; an author rule beats the
|
|
420
|
+
UA [hidden] default regardless of specificity, so the hidden attribute
|
|
421
|
+
needs its own author rule to still win. */
|
|
422
|
+
.kit-error[hidden] { display: none; }
|
|
423
|
+
/* Page shell: content row fills, the decision bar pins to the bottom.
|
|
424
|
+
.app below is the second axis (3 columns) — together they are the
|
|
425
|
+
one legitimately 2-D layout in this file; everything else here is a
|
|
426
|
+
one-dimensional row or column and stays flex. */
|
|
427
|
+
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; }
|
|
428
|
+
.app { display: grid; grid-template-columns: 248px minmax(0, 1fr) 372px; min-height: 0; }
|
|
429
|
+
@media (max-width: 1100px) {
|
|
430
|
+
.app { grid-template-columns: 208px minmax(0, 1fr) 320px; }
|
|
431
|
+
}
|
|
432
|
+
@media (max-width: 860px) {
|
|
433
|
+
.app { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto; max-height: none; }
|
|
434
|
+
.nav { max-height: 38vh; border-right: none; border-bottom: 1px solid var(--border); }
|
|
435
|
+
.chat { max-height: 34vh; border-left: none; border-top: 1px solid var(--border); }
|
|
436
|
+
}
|
|
437
|
+
@media (max-width: 640px) {
|
|
438
|
+
.main { padding: var(--space-5) var(--space-4) 100px; }
|
|
439
|
+
.popover { width: calc(100vw - 24px); }
|
|
440
|
+
}
|
|
96
441
|
::-webkit-scrollbar { width: 9px; height: 9px; }
|
|
97
442
|
::-webkit-scrollbar-thumb { background: var(--primary); border-radius: 9999px; border: 2px solid transparent; background-clip: padding-box; }
|
|
98
443
|
::-webkit-scrollbar-track { background: transparent; }
|
|
99
444
|
|
|
100
445
|
/* ---- Terminus: Button ---- */
|
|
101
446
|
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap;
|
|
102
|
-
font: 500
|
|
447
|
+
font: 500 var(--text-sm) var(--sans); border-radius: var(--radius); padding: 8px 14px; border: 1px solid transparent;
|
|
103
448
|
background: transparent; color: var(--foreground); cursor: pointer; transition: background-color .12s, border-color .12s, color .12s; }
|
|
104
449
|
.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
450
|
.btn:disabled { pointer-events: none; opacity: .4; }
|
|
106
|
-
.btn-sm { padding: 6px 10px; font-size:
|
|
451
|
+
.btn-sm { padding: 6px 10px; font-size: var(--text-xs); }
|
|
107
452
|
.btn-block { width: 100%; }
|
|
108
453
|
.btn-solid { background: var(--primary); color: var(--primary-foreground); border-color: var(--primary); }
|
|
109
454
|
.btn-solid:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
|
|
@@ -119,40 +464,41 @@
|
|
|
119
464
|
.btn-danger:hover { background: color-mix(in oklch, var(--destructive-muted) 70%, var(--destructive)); }
|
|
120
465
|
|
|
121
466
|
/* ---- Terminus: Badge ---- */
|
|
122
|
-
.badge { display: inline-flex; align-items: center; gap: 4px; border-radius: var(--radius
|
|
123
|
-
font: 500
|
|
467
|
+
.badge { display: inline-flex; align-items: center; gap: 4px; border-radius: var(--radius); border: 1px solid transparent;
|
|
468
|
+
font: 500 var(--text-2xs) var(--mono); padding: 1px 6px; min-width: 18px; justify-content: center; }
|
|
124
469
|
.badge-warning { background: var(--warning-muted); color: var(--warning); border-color: var(--warning-border); }
|
|
125
470
|
.badge-soft { background: var(--primary-muted); color: var(--primary-text); }
|
|
126
471
|
|
|
127
472
|
/* Left: navigation — Contents (docs + headings) | Files (paths touched) */
|
|
128
473
|
.nav { border-right: 1px solid var(--border); background: var(--card); overflow-y: auto; padding: 18px 14px 40px; }
|
|
474
|
+
.nav .kit-theme { margin: 0 0 12px; }
|
|
129
475
|
.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
|
|
476
|
+
.nav .tab { flex: 1; font: 500 var(--text-2xs) var(--sans); letter-spacing: .08em; text-transform: uppercase; padding: 6px 4px;
|
|
131
477
|
background: transparent; border: none; border-right: 1px solid var(--border); color: var(--subtle); cursor: pointer; }
|
|
132
478
|
.nav .tab:last-child { border-right: none; }
|
|
133
479
|
.nav .tab:hover { color: var(--foreground); }
|
|
134
480
|
.nav .tab.active { background: var(--accent); color: var(--foreground); }
|
|
135
481
|
.nav a.doc { font-weight: 600; color: var(--foreground); }
|
|
136
482
|
.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:
|
|
483
|
+
.nav a.file .t { font-family: var(--mono); font-size: var(--text-xs); }
|
|
484
|
+
.nav .none { color: var(--subtle); font-size: var(--text-xs); padding: 4px 6px; }
|
|
139
485
|
code.mention-flash { outline: 2px solid var(--warning); outline-offset: 1px; }
|
|
140
|
-
.nav .label, .chat .label { font-size:
|
|
486
|
+
.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
487
|
.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
488
|
.nav a span.t { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }
|
|
143
489
|
.nav a:hover { background: var(--accent); color: var(--foreground); }
|
|
144
490
|
.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:
|
|
491
|
+
.nav a.lvl2 { padding-left: 16px; } .nav a.lvl3 { padding-left: 28px; font-size: var(--text-xs); color: var(--subtle); }
|
|
146
492
|
|
|
147
493
|
/* Directory mode: doc-site tree — folder rows fold, filter narrows, the
|
|
148
494
|
active doc alone carries its headings. All of it DIRMODE-gated. */
|
|
149
495
|
.nav .tree-filter { display: none; margin: 0 0 10px; }
|
|
150
|
-
.nav .tree-filter input { width: 100%; font: inherit; font-size:
|
|
496
|
+
.nav .tree-filter input { width: 100%; font: inherit; font-size: var(--text-sm); color: var(--foreground); background: var(--background);
|
|
151
497
|
border: 1px solid var(--border); border-radius: var(--radius); padding: 6px 8px; outline: none; }
|
|
152
498
|
.nav .tree-filter input:focus { border-color: var(--ring); }
|
|
153
499
|
.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:
|
|
500
|
+
.nav a.folder .arrow { flex: none; width: 12px; color: var(--subtle); font-size: var(--text-2xs); }
|
|
501
|
+
.crumbs { display: none; max-width: 760px; margin: 0 auto 18px; font: var(--text-xs) var(--mono); color: var(--subtle);
|
|
156
502
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
157
503
|
.crumbs .seg { color: var(--muted-foreground); }
|
|
158
504
|
.crumbs a.seg { color: var(--muted-foreground); text-decoration: none; cursor: pointer; }
|
|
@@ -162,7 +508,7 @@
|
|
|
162
508
|
.pnav { display: none; max-width: 760px; margin: 32px auto 0; padding-top: 14px; border-top: 1px solid var(--border);
|
|
163
509
|
justify-content: space-between; gap: 12px; }
|
|
164
510
|
.pnav a { display: inline-flex; align-items: center; gap: 6px; max-width: 48%; color: var(--muted-foreground);
|
|
165
|
-
text-decoration: none; font-size:
|
|
511
|
+
text-decoration: none; font-size: var(--text-sm); padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius);
|
|
166
512
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
167
513
|
.pnav a:hover { background: var(--accent); color: var(--foreground); }
|
|
168
514
|
.pnav .spacer { flex: 1; }
|
|
@@ -173,20 +519,20 @@
|
|
|
173
519
|
linear-gradient(to right, color-mix(in oklab, var(--foreground) 3%, transparent) 1px, transparent 1px),
|
|
174
520
|
linear-gradient(to bottom, color-mix(in oklab, var(--foreground) 3%, transparent) 1px, transparent 1px);
|
|
175
521
|
background-size: 32px 32px; }
|
|
176
|
-
.main .hint { color: var(--subtle); margin: 0 auto 24px; font-size:
|
|
177
|
-
.doc { max-width: 760px; margin: 0 auto; }
|
|
522
|
+
.main .hint { color: var(--subtle); margin: 0 auto 24px; font-size: var(--text-xs); max-width: 760px; }
|
|
523
|
+
.doc { max-width: 760px; margin: 0 auto; line-height: var(--leading-relaxed); }
|
|
178
524
|
|
|
179
525
|
/* Revision diff — on a revised plan, show what changed instead of a silent swap. */
|
|
180
526
|
.revbar { display: none; align-items: center; gap: 10px; max-width: 760px; margin: 0 auto 20px;
|
|
181
527
|
padding: 9px 12px; border: 1px solid var(--warning-border); background: var(--warning-muted); border-radius: var(--radius); }
|
|
182
528
|
.revbar.show { display: flex; }
|
|
183
|
-
.revbar .rb-text { color: var(--warning); font: 600
|
|
184
|
-
.revbar .rb-stat { color: var(--muted-foreground); font-size:
|
|
529
|
+
.revbar .rb-text { color: var(--warning); font: 600 var(--text-xs) var(--sans); }
|
|
530
|
+
.revbar .rb-stat { color: var(--muted-foreground); font-size: var(--text-xs); }
|
|
185
531
|
.revbar .rb-actions { margin-left: auto; display: flex; gap: 6px; }
|
|
186
532
|
.diffwrap { display: none; max-width: 760px; margin: 0 auto; }
|
|
187
533
|
.diffwrap.show { display: block; }
|
|
188
534
|
.diffview { background: var(--muted); border: 1px solid var(--border); border-radius: var(--radius);
|
|
189
|
-
padding: 8px 0; overflow-x: auto; font:
|
|
535
|
+
padding: 8px 0; overflow-x: auto; font: var(--text-xs)/var(--leading-normal) var(--mono); }
|
|
190
536
|
.diffline { display: flex; white-space: pre; padding: 0 12px; }
|
|
191
537
|
.diffline .g { width: 14px; flex: none; color: var(--subtle); user-select: none; }
|
|
192
538
|
.diffline.ctx { color: var(--muted-foreground); }
|
|
@@ -195,20 +541,20 @@
|
|
|
195
541
|
.diffline.del { background: var(--destructive-muted); color: var(--destructive); }
|
|
196
542
|
.diffline.del .g { color: var(--destructive); }
|
|
197
543
|
.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:
|
|
544
|
+
h1, h2, h3, h4, h5, h6 { line-height: var(--leading-snug); margin: 1.5em 0 0.6em; scroll-margin-top: 24px; }
|
|
545
|
+
h1 { font-size: var(--text-xl); } h2 { font-size: var(--text-md); } h3 { font-size: var(--text-base); color: var(--muted-foreground); }
|
|
546
|
+
h4 { font-size: var(--text-sm); } h5, h6 { font-size: var(--text-xs); color: var(--muted-foreground); }
|
|
201
547
|
p { margin: 0.6em 0; }
|
|
202
548
|
ul, ol { margin: 0.6em 0; padding-left: 22px; } li { margin: 0.25em 0; }
|
|
203
549
|
li.task { list-style: none; margin-left: -18px; }
|
|
204
550
|
del { color: var(--subtle); }
|
|
205
551
|
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; }
|
|
552
|
+
code { background: var(--muted); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; font-family: var(--mono); font-size: var(--text-xs); }
|
|
553
|
+
pre { background: var(--muted); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; overflow-x: auto; font-family: var(--mono); }
|
|
208
554
|
pre code { background: none; border: none; padding: 0; }
|
|
209
555
|
blockquote { margin: 0.6em 0; padding: 2px 14px; color: var(--muted-foreground); border-left: 3px solid var(--primary); }
|
|
210
556
|
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:
|
|
557
|
+
table { border-collapse: collapse; margin: 0.9em 0; width: 100%; display: block; overflow-x: auto; font-size: var(--text-sm); }
|
|
212
558
|
th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; vertical-align: top; }
|
|
213
559
|
th { background: var(--muted); font-weight: 600; }
|
|
214
560
|
tbody tr:nth-child(even) { background: color-mix(in oklch, var(--muted) 40%, transparent); }
|
|
@@ -224,18 +570,18 @@
|
|
|
224
570
|
.popover:not(.expanded) .chip:last-child { border-bottom-right-radius: var(--radius); }
|
|
225
571
|
/* Floating annotation toolbar — a menubar of category items over an input. */
|
|
226
572
|
.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:
|
|
573
|
+
border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 0; }
|
|
574
|
+
.popover .q { color: var(--muted-foreground); font-size: var(--text-2xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
229
575
|
padding: 6px 10px; margin: 0; background: var(--muted); border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; }
|
|
230
576
|
/* Menubar strip: category items, evenly split, divider-separated */
|
|
231
577
|
.popover .cats { display: flex; margin: 0; border-bottom: 1px solid var(--border); }
|
|
232
|
-
.chip { flex: 1; font: 500
|
|
578
|
+
.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
579
|
background: transparent; color: var(--muted-foreground); cursor: pointer; transition: background-color .12s, color .12s; }
|
|
234
580
|
.chip:last-child { border-right: none; }
|
|
235
581
|
.chip:hover { background: var(--accent); color: var(--foreground); }
|
|
236
582
|
.chip.active { background: var(--primary-muted); color: var(--primary-text); }
|
|
237
583
|
.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:
|
|
584
|
+
.popover .hintk { color: var(--subtle); font-size: var(--text-2xs); margin-right: auto; }
|
|
239
585
|
/* Caret pointing at the selection (down when the toolbar sits above it) */
|
|
240
586
|
.popover::after { content: ''; position: absolute; left: 18px; width: 9px; height: 9px; background: var(--popover);
|
|
241
587
|
border: 1px solid var(--border); transform: rotate(45deg); display: none; }
|
|
@@ -243,8 +589,8 @@
|
|
|
243
589
|
.popover.below::after { display: block; top: -5px; background: var(--muted); border-bottom: none; border-right: none; }
|
|
244
590
|
|
|
245
591
|
/* ---- Terminus: Textarea ---- */
|
|
246
|
-
.ta { width: 100%; resize: vertical; font:
|
|
247
|
-
border: 1px solid var(--input); border-radius: var(--radius
|
|
592
|
+
.ta { width: 100%; resize: vertical; font: var(--text-sm)/var(--leading-normal) var(--sans); color: var(--foreground); background: transparent;
|
|
593
|
+
border: 1px solid var(--input); border-radius: var(--radius); padding: 8px 10px; }
|
|
248
594
|
.ta::placeholder { color: var(--subtle); }
|
|
249
595
|
.ta:focus-visible { outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 28%, transparent); }
|
|
250
596
|
.popover .ta { min-height: 132px; border: none; border-radius: 0; padding: 8px 10px; } .popover .ta:focus-visible { outline: none; box-shadow: none; }
|
|
@@ -256,26 +602,26 @@
|
|
|
256
602
|
.thread { flex: 1; overflow-y: auto; padding: 6px 14px; display: flex; flex-direction: column; gap: 8px; }
|
|
257
603
|
.thread .empty { color: var(--subtle); padding: 8px 4px; }
|
|
258
604
|
/* Terminus: Card */
|
|
259
|
-
.msg { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; background: var(--card); box-shadow:
|
|
605
|
+
.msg { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; background: var(--card); box-shadow: var(--shadow-sm); cursor: default; }
|
|
260
606
|
.msg.note { border-color: var(--warning-border); cursor: pointer; }
|
|
261
607
|
.msg.note.active { box-shadow: 0 0 0 2px var(--warning); }
|
|
262
608
|
.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:
|
|
609
|
+
.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); }
|
|
610
|
+
.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); }
|
|
611
|
+
.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
612
|
.msg .body { white-space: pre-wrap; word-break: break-word; color: var(--card-foreground); }
|
|
267
613
|
|
|
268
614
|
/* Terminus: Separator + composer */
|
|
269
615
|
.composer { border-top: 1px solid var(--border); padding: 12px 14px; }
|
|
270
616
|
.actions { padding: 0 14px 14px; }
|
|
271
|
-
.count { color: var(--subtle); font-size:
|
|
617
|
+
.count { color: var(--subtle); font-family: var(--mono); font-size: var(--text-2xs); }
|
|
272
618
|
|
|
273
619
|
/* Bottom decision bar — decisions read as the end of the review. Reject sits
|
|
274
620
|
isolated on the left; the two "continue" actions group on the right. The
|
|
275
621
|
summary in the middle says what rides along with the decision. */
|
|
276
622
|
.dbar { flex: none; display: flex; align-items: center; border-top: 1px solid var(--border); background: var(--card); padding: 10px 16px; min-height: 58px; }
|
|
277
623
|
.dbar-live { display: flex; flex: 1; align-items: center; gap: 12px; }
|
|
278
|
-
.dbar-summary { flex: 1; text-align: center; color: var(--subtle); font-size:
|
|
624
|
+
.dbar-summary { flex: 1; text-align: center; color: var(--subtle); font-size: var(--text-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
279
625
|
.dbar-group { display: flex; gap: 8px; }
|
|
280
626
|
|
|
281
627
|
/* Post-submit confirmation — replaces the decision bar content once a decision
|
|
@@ -285,12 +631,12 @@
|
|
|
285
631
|
.sent.approve { border-color: var(--success-border); background: var(--success-muted); }
|
|
286
632
|
.sent.revise { border-color: var(--warning-border); background: var(--warning-muted); }
|
|
287
633
|
.sent.deny { border-color: var(--destructive-border); background: var(--destructive-muted); }
|
|
288
|
-
.sent .h { display: flex; align-items: center; gap: 8px; font: 600
|
|
634
|
+
.sent .h { display: flex; align-items: center; gap: 8px; font: 600 var(--text-sm) var(--sans); }
|
|
289
635
|
.sent.approve .h { color: var(--success); }
|
|
290
636
|
.sent.revise .h { color: var(--warning); }
|
|
291
637
|
.sent.deny .h { color: var(--destructive); }
|
|
292
638
|
.sent .h { flex: none; }
|
|
293
|
-
.sent .sub { color: var(--muted-foreground); font-size:
|
|
639
|
+
.sent .sub { color: var(--muted-foreground); font-size: var(--text-xs); line-height: var(--leading-normal); }
|
|
294
640
|
.sent .ico { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; }
|
|
295
641
|
.sent .again { margin-top: 10px; }
|
|
296
642
|
.dot { width: 7px; height: 7px; border-radius: 9999px; background: currentColor; animation: pulse 1.3s ease-in-out infinite; }
|
|
@@ -299,21 +645,32 @@
|
|
|
299
645
|
</head>
|
|
300
646
|
<body>
|
|
301
647
|
<div class="app">
|
|
302
|
-
<aside class="nav">
|
|
648
|
+
<aside class="nav" data-kit-chrome>
|
|
649
|
+
<div class="kit-theme">
|
|
650
|
+
<button type="button" data-kit-theme="light">Light</button>
|
|
651
|
+
<button type="button" data-kit-theme="dark">Dark</button>
|
|
652
|
+
<button type="button" data-kit-theme="system">Auto</button>
|
|
653
|
+
</div>
|
|
303
654
|
<div class="tabs">
|
|
304
655
|
<button type="button" class="tab active" id="tab-contents">Contents</button>
|
|
305
656
|
<button type="button" class="tab" id="tab-files">Files</button>
|
|
306
657
|
</div>
|
|
307
658
|
<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"
|
|
659
|
+
<div class="tree-filter" id="tree-filter"><input type="search" id="tree-q" data-kit-filter placeholder="Filter docs…" aria-label="Filter documents"></div>
|
|
660
|
+
<nav id="toc">
|
|
661
|
+
<div class="kit-skel" style="width:70%;height:0.85rem;margin:0 6px 10px"></div>
|
|
662
|
+
<div class="kit-skel" style="width:55%;height:0.85rem;margin:0 6px 10px"></div>
|
|
663
|
+
<div class="kit-skel" style="width:80%;height:0.85rem;margin:0 6px 10px"></div>
|
|
664
|
+
<div class="kit-skel" style="width:45%;height:0.85rem;margin:0 6px 10px"></div>
|
|
665
|
+
</nav>
|
|
310
666
|
</div>
|
|
311
667
|
<div id="pane-files" style="display:none"><nav id="filelist"></nav></div>
|
|
312
668
|
</aside>
|
|
313
669
|
|
|
314
670
|
<main class="main">
|
|
315
671
|
<p class="hint">Select any text to comment on it. Or chat with the agent on the right. Then decide.</p>
|
|
316
|
-
<div class="
|
|
672
|
+
<div class="kit-error" id="api-error" hidden><span class="msg-text"></span></div>
|
|
673
|
+
<div class="revbar" id="revbar" data-kit-chrome>
|
|
317
674
|
<span class="rb-text">Plan revised</span>
|
|
318
675
|
<span class="rb-stat" id="rb-stat"></span>
|
|
319
676
|
<span class="rb-actions">
|
|
@@ -322,10 +679,15 @@
|
|
|
322
679
|
</span>
|
|
323
680
|
</div>
|
|
324
681
|
<div class="crumbs" id="crumbs"></div>
|
|
325
|
-
<div class="doc" id="plan">
|
|
682
|
+
<div class="doc" id="plan">
|
|
683
|
+
<div class="kit-skel" style="width:60%;height:1.4rem;margin-bottom:1em"></div>
|
|
684
|
+
<div class="kit-skel" style="width:100%;margin-bottom:0.6em"></div>
|
|
685
|
+
<div class="kit-skel" style="width:96%;margin-bottom:0.6em"></div>
|
|
686
|
+
<div class="kit-skel" style="width:88%;margin-bottom:0.6em"></div>
|
|
687
|
+
</div>
|
|
326
688
|
<div class="pnav" id="pnav"></div>
|
|
327
689
|
<div class="diffwrap" id="diffwrap"><div class="diffview" id="diffview"></div></div>
|
|
328
|
-
<div class="popover" id="popover">
|
|
690
|
+
<div class="popover" id="popover" data-kit-chrome>
|
|
329
691
|
<div class="q" id="pop-quote"></div>
|
|
330
692
|
<div class="cats" id="pop-cats">
|
|
331
693
|
<button type="button" class="chip active" data-cat="Comment">Comment</button>
|
|
@@ -341,7 +703,7 @@
|
|
|
341
703
|
</div>
|
|
342
704
|
</main>
|
|
343
705
|
|
|
344
|
-
<aside class="chat">
|
|
706
|
+
<aside class="chat" data-kit-chrome>
|
|
345
707
|
<p class="label">Review <span class="count" id="count"></span></p>
|
|
346
708
|
<div class="thread" id="thread"><div class="empty">No notes yet. Select text to comment, or send a message.</div></div>
|
|
347
709
|
<div class="composer">
|
|
@@ -355,7 +717,7 @@
|
|
|
355
717
|
|
|
356
718
|
<!-- Decisions live in a fixed bottom bar — the rail above is purely
|
|
357
719
|
conversational (notes + messages). Reject is isolated on the left. -->
|
|
358
|
-
<footer class="dbar">
|
|
720
|
+
<footer class="dbar" data-kit-chrome>
|
|
359
721
|
<div class="dbar-live" id="dbar-live">
|
|
360
722
|
<button class="btn btn-danger" id="btn-reject" data-decision="Reject">Reject</button>
|
|
361
723
|
<span class="dbar-summary" id="dbar-summary"></span>
|
|
@@ -370,6 +732,342 @@
|
|
|
370
732
|
</div>
|
|
371
733
|
</footer>
|
|
372
734
|
|
|
735
|
+
<script>
|
|
736
|
+
/* kit:js:start */
|
|
737
|
+
/* Shared behaviour layer — do not hand-edit; edit scripts/tokens/kit.js. */
|
|
738
|
+
|
|
739
|
+
/* Deliberately free of backticks and interpolation openers. No target is a JS
|
|
740
|
+
template literal since the board-shell extraction, so this is insurance
|
|
741
|
+
rather than a requirement — but it is the difference between this file being
|
|
742
|
+
safe to inline anywhere and being safe only where someone remembered to
|
|
743
|
+
check. Regexes and escapes are avoided for the same reason. */
|
|
744
|
+
|
|
745
|
+
(function () {
|
|
746
|
+
'use strict';
|
|
747
|
+
|
|
748
|
+
var THEME_KEY = 'jabworks-theme';
|
|
749
|
+
var THEME_MODES = ['light', 'dark', 'system'];
|
|
750
|
+
var root = document.documentElement;
|
|
751
|
+
|
|
752
|
+
/* localStorage throws on an opaque origin, which is what a file:// artifact
|
|
753
|
+
often gets. Degrade to session-only theming; never break the page. */
|
|
754
|
+
function readStored(key) {
|
|
755
|
+
try {
|
|
756
|
+
return window.localStorage.getItem(key);
|
|
757
|
+
} catch (e) {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function writeStored(key, value) {
|
|
763
|
+
try {
|
|
764
|
+
window.localStorage.setItem(key, value);
|
|
765
|
+
} catch (e) {
|
|
766
|
+
/* no-op */
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function applyTheme(mode) {
|
|
771
|
+
if (mode === 'light' || mode === 'dark') root.setAttribute('data-theme', mode);
|
|
772
|
+
else root.removeAttribute('data-theme');
|
|
773
|
+
|
|
774
|
+
writeStored(THEME_KEY, mode);
|
|
775
|
+
|
|
776
|
+
var buttons = document.querySelectorAll('[data-kit-theme]');
|
|
777
|
+
|
|
778
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
779
|
+
buttons[i].setAttribute('aria-pressed', String(buttons[i].getAttribute('data-kit-theme') === mode));
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function currentTheme() {
|
|
784
|
+
return root.getAttribute('data-theme') || 'system';
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function cycleTheme() {
|
|
788
|
+
applyTheme(THEME_MODES[(THEME_MODES.indexOf(currentTheme()) + 1) % THEME_MODES.length]);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function items() {
|
|
792
|
+
return Array.prototype.slice.call(document.querySelectorAll('[data-kit-item]'));
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/* document.activeElement is null in a detached or not-yet-painted document,
|
|
796
|
+
and Element.closest is absent on the document node itself. */
|
|
797
|
+
function activeWithin(selector) {
|
|
798
|
+
var el = document.activeElement;
|
|
799
|
+
|
|
800
|
+
return el && el.closest ? el.closest(selector) : null;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function moveItem(step) {
|
|
804
|
+
var all = items();
|
|
805
|
+
|
|
806
|
+
if (all.length === 0) return;
|
|
807
|
+
|
|
808
|
+
var index = all.indexOf(activeWithin('[data-kit-item]'));
|
|
809
|
+
var next = all[Math.min(Math.max(index + step, 0), all.length - 1)] || all[0];
|
|
810
|
+
|
|
811
|
+
next.setAttribute('tabindex', '-1');
|
|
812
|
+
next.focus();
|
|
813
|
+
next.scrollIntoView({ block: 'nearest' });
|
|
814
|
+
|
|
815
|
+
/* Reflect the focused item in the URL so a board or a review can be linked
|
|
816
|
+
to a specific row. replaceState, not a hash assignment: assigning to
|
|
817
|
+
location.hash pushes a history entry per keypress. */
|
|
818
|
+
if (next.id && window.history && window.history.replaceState) {
|
|
819
|
+
window.history.replaceState(null, '', '#' + next.id);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function reveal(el) {
|
|
824
|
+
if (el && el.focus) {
|
|
825
|
+
el.focus();
|
|
826
|
+
if (el.select) el.select();
|
|
827
|
+
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/* A surface whose filter field ships hidden cannot hang the hook on the field
|
|
835
|
+
itself — a hidden element does not take focus, so the binding would be a
|
|
836
|
+
silent no-op. Those surfaces put the hook on the control that REVEALS the
|
|
837
|
+
field instead; activate it, then focus what it revealed via aria-controls.
|
|
838
|
+
Without this the ? overlay would advertise "Focus filter" for a key that
|
|
839
|
+
only moves focus to a button, and a generated overlay that misdescribes its
|
|
840
|
+
own bindings is worse than no overlay. */
|
|
841
|
+
function focusFilter() {
|
|
842
|
+
var hook = document.querySelector('[data-kit-filter]');
|
|
843
|
+
|
|
844
|
+
if (!hook) return false;
|
|
845
|
+
|
|
846
|
+
if (hook.tagName === 'BUTTON') {
|
|
847
|
+
if (hook.getAttribute('aria-expanded') === 'false' && hook.click) hook.click();
|
|
848
|
+
|
|
849
|
+
if (reveal(document.getElementById(hook.getAttribute('aria-controls') || ''))) return true;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return reveal(hook);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function flashCopied(el) {
|
|
856
|
+
el.classList.add('kit-copied');
|
|
857
|
+
window.setTimeout(function () {
|
|
858
|
+
el.classList.remove('kit-copied');
|
|
859
|
+
}, 900);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function copyFrom(el) {
|
|
863
|
+
if (!el) return;
|
|
864
|
+
|
|
865
|
+
var text = el.getAttribute('data-kit-copy') || el.textContent || '';
|
|
866
|
+
|
|
867
|
+
if (!text || !navigator.clipboard || !navigator.clipboard.writeText) return;
|
|
868
|
+
|
|
869
|
+
navigator.clipboard.writeText(text.trim()).then(
|
|
870
|
+
function () {
|
|
871
|
+
flashCopied(el);
|
|
872
|
+
},
|
|
873
|
+
function () {
|
|
874
|
+
/* Clipboard is permission-gated; a refusal is not an error worth
|
|
875
|
+
interrupting the page for. */
|
|
876
|
+
},
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function sections() {
|
|
881
|
+
return Array.prototype.slice.call(document.querySelectorAll('[data-kit-section]'));
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function jumpToSection(n) {
|
|
885
|
+
var target = sections()[n - 1];
|
|
886
|
+
|
|
887
|
+
if (target) target.scrollIntoView({ block: 'start' });
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/* One registry drives both the key handler and the help overlay, so the
|
|
891
|
+
documented shortcuts cannot drift from the implemented ones. */
|
|
892
|
+
var BINDINGS = [
|
|
893
|
+
{ keys: ['j'], label: 'Next item', when: function () { return items().length > 0; }, run: function () { moveItem(1); } },
|
|
894
|
+
{ keys: ['k'], label: 'Previous item', when: function () { return items().length > 0; }, run: function () { moveItem(-1); } },
|
|
895
|
+
{
|
|
896
|
+
keys: ['/'],
|
|
897
|
+
label: 'Focus filter',
|
|
898
|
+
when: function () { return Boolean(document.querySelector('[data-kit-filter]')); },
|
|
899
|
+
run: focusFilter,
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
keys: ['y'],
|
|
903
|
+
label: 'Copy id or path',
|
|
904
|
+
when: function () { return Boolean(document.querySelector('[data-kit-copy]')); },
|
|
905
|
+
run: function () {
|
|
906
|
+
copyFrom(activeWithin('[data-kit-copy]'));
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
{ keys: ['t'], label: 'Cycle theme', when: function () { return true; }, run: cycleTheme },
|
|
910
|
+
{
|
|
911
|
+
keys: ['g', '1-9'],
|
|
912
|
+
label: 'Jump to section',
|
|
913
|
+
when: function () { return sections().length > 0; },
|
|
914
|
+
run: null,
|
|
915
|
+
},
|
|
916
|
+
{ keys: ['?'], label: 'Show shortcuts', when: function () { return true; }, run: null },
|
|
917
|
+
{ keys: ['Esc'], label: 'Dismiss', when: function () { return true; }, run: null },
|
|
918
|
+
];
|
|
919
|
+
|
|
920
|
+
var overlay = null;
|
|
921
|
+
|
|
922
|
+
function buildOverlay() {
|
|
923
|
+
var host = document.createElement('div');
|
|
924
|
+
|
|
925
|
+
host.className = 'kit-help';
|
|
926
|
+
host.hidden = true;
|
|
927
|
+
host.setAttribute('role', 'dialog');
|
|
928
|
+
host.setAttribute('aria-modal', 'true');
|
|
929
|
+
host.setAttribute('aria-label', 'Keyboard shortcuts');
|
|
930
|
+
|
|
931
|
+
var panel = document.createElement('div');
|
|
932
|
+
|
|
933
|
+
panel.className = 'kit-help__panel';
|
|
934
|
+
|
|
935
|
+
var title = document.createElement('p');
|
|
936
|
+
|
|
937
|
+
title.className = 'kit-help__title';
|
|
938
|
+
title.textContent = 'Keyboard shortcuts';
|
|
939
|
+
panel.appendChild(title);
|
|
940
|
+
|
|
941
|
+
for (var i = 0; i < BINDINGS.length; i++) {
|
|
942
|
+
if (!BINDINGS[i].when()) continue;
|
|
943
|
+
|
|
944
|
+
var row = document.createElement('div');
|
|
945
|
+
|
|
946
|
+
row.className = 'kit-help__row';
|
|
947
|
+
|
|
948
|
+
var label = document.createElement('span');
|
|
949
|
+
|
|
950
|
+
label.textContent = BINDINGS[i].label;
|
|
951
|
+
row.appendChild(label);
|
|
952
|
+
|
|
953
|
+
var keys = document.createElement('span');
|
|
954
|
+
|
|
955
|
+
keys.className = 'kit-help__keys';
|
|
956
|
+
|
|
957
|
+
for (var k = 0; k < BINDINGS[i].keys.length; k++) {
|
|
958
|
+
var kbd = document.createElement('kbd');
|
|
959
|
+
|
|
960
|
+
kbd.className = 'kit-kbd';
|
|
961
|
+
kbd.textContent = BINDINGS[i].keys[k];
|
|
962
|
+
keys.appendChild(kbd);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
row.appendChild(keys);
|
|
966
|
+
panel.appendChild(row);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
host.appendChild(panel);
|
|
970
|
+
host.addEventListener('click', function (event) {
|
|
971
|
+
if (event.target === host) toggleOverlay(false);
|
|
972
|
+
});
|
|
973
|
+
document.body.appendChild(host);
|
|
974
|
+
|
|
975
|
+
return host;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function toggleOverlay(show) {
|
|
979
|
+
if (!overlay) overlay = buildOverlay();
|
|
980
|
+
|
|
981
|
+
overlay.hidden = !show;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function isTypingTarget(el) {
|
|
985
|
+
if (!el) return false;
|
|
986
|
+
|
|
987
|
+
var tag = el.tagName;
|
|
988
|
+
|
|
989
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable === true;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
var pendingJump = false;
|
|
993
|
+
|
|
994
|
+
function onKeydown(event) {
|
|
995
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return;
|
|
996
|
+
|
|
997
|
+
if (event.key === 'Escape') {
|
|
998
|
+
toggleOverlay(false);
|
|
999
|
+
if (isTypingTarget(event.target) && event.target.blur) event.target.blur();
|
|
1000
|
+
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/* A filter box must be typeable: every other binding is inert while the
|
|
1005
|
+
caret is in a field. Escape above is the deliberate exception. */
|
|
1006
|
+
if (isTypingTarget(event.target)) return;
|
|
1007
|
+
|
|
1008
|
+
if (pendingJump) {
|
|
1009
|
+
pendingJump = false;
|
|
1010
|
+
|
|
1011
|
+
if (event.key >= '1' && event.key <= '9') {
|
|
1012
|
+
jumpToSection(Number(event.key));
|
|
1013
|
+
event.preventDefault();
|
|
1014
|
+
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if (event.key === 'g' && sections().length > 0) {
|
|
1020
|
+
pendingJump = true;
|
|
1021
|
+
window.setTimeout(function () {
|
|
1022
|
+
pendingJump = false;
|
|
1023
|
+
}, 1200);
|
|
1024
|
+
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (event.key === '?') {
|
|
1029
|
+
toggleOverlay(overlay ? overlay.hidden : true);
|
|
1030
|
+
event.preventDefault();
|
|
1031
|
+
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
for (var i = 0; i < BINDINGS.length; i++) {
|
|
1036
|
+
if (BINDINGS[i].run && BINDINGS[i].keys.indexOf(event.key) !== -1 && BINDINGS[i].when()) {
|
|
1037
|
+
BINDINGS[i].run();
|
|
1038
|
+
event.preventDefault();
|
|
1039
|
+
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function init() {
|
|
1046
|
+
applyTheme(readStored(THEME_KEY) || 'system');
|
|
1047
|
+
|
|
1048
|
+
var buttons = document.querySelectorAll('[data-kit-theme]');
|
|
1049
|
+
|
|
1050
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
1051
|
+
buttons[i].addEventListener('click', function (event) {
|
|
1052
|
+
applyTheme(event.currentTarget.getAttribute('data-kit-theme'));
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
document.addEventListener('click', function (event) {
|
|
1057
|
+
var target = event.target.closest ? event.target.closest('[data-kit-copy]') : null;
|
|
1058
|
+
|
|
1059
|
+
if (target) copyFrom(target);
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
document.addEventListener('keydown', onKeydown);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
1066
|
+
else init();
|
|
1067
|
+
})();
|
|
1068
|
+
/* kit:js:end */
|
|
1069
|
+
</script>
|
|
1070
|
+
|
|
373
1071
|
<script>
|
|
374
1072
|
// ---- Self-contained Markdown renderer (no external deps) ----
|
|
375
1073
|
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');}
|
|
@@ -395,13 +1093,26 @@
|
|
|
395
1093
|
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
1094
|
if(/^---+\s*$/.test(line)){out.push({html:'<hr>',heading:null});i++;continue;}
|
|
397
1095
|
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
|
-
|
|
1096
|
+
// Lazy continuation: a soft-wrapped bullet leaves its follow-on lines with no
|
|
1097
|
+
// marker. Advancing only on marker lines ended the list there and rendered the
|
|
1098
|
+
// remainder as a left-margin paragraph — every wrapped bullet in every spec.
|
|
1099
|
+
// The exclusion set is deliberately the paragraph loop's below, not a new one.
|
|
1100
|
+
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
1101
|
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
1102
|
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
1103
|
out.push({html:'<p>'+inline(buf.join(' '))+'</p>',heading:null});}
|
|
402
1104
|
return out;
|
|
403
1105
|
}
|
|
404
1106
|
|
|
1107
|
+
// ---- API error banner (kit-error) ----
|
|
1108
|
+
function showApiError(msg){
|
|
1109
|
+
const el=document.getElementById('api-error'); if(!el) return;
|
|
1110
|
+
el.querySelector('.msg-text').textContent=msg; el.hidden=false;
|
|
1111
|
+
}
|
|
1112
|
+
function hideApiError(){
|
|
1113
|
+
const el=document.getElementById('api-error'); if(el) el.hidden=true;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
405
1116
|
// ---- State ----
|
|
406
1117
|
const annotations = []; let nextId = 1;
|
|
407
1118
|
const planEl=document.getElementById('plan');
|
|
@@ -482,6 +1193,7 @@
|
|
|
482
1193
|
node.docs.forEach(function(doc){
|
|
483
1194
|
const m=matches(doc); if(!m.show) return;
|
|
484
1195
|
const d=document.createElement('a'); d.href='#'; d.className='doc'+(doc===activeDoc?' active':''); d.dataset.doc=doc; d.title=doc;
|
|
1196
|
+
d.setAttribute('data-kit-item','');
|
|
485
1197
|
indent(d,depth);
|
|
486
1198
|
const t=document.createElement('span'); t.className='t'; t.textContent=doc.slice(pathStr?pathStr.length+1:0); d.appendChild(t);
|
|
487
1199
|
const bd=document.createElement('span'); bd.className='badge badge-warning'; bd.style.display='none'; bd.dataset.fordoc=doc; d.appendChild(bd);
|
|
@@ -718,7 +1430,7 @@
|
|
|
718
1430
|
'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
1431
|
'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
1432
|
};
|
|
721
|
-
let submitting=false;
|
|
1433
|
+
let submitting=false, decisionSent=false;
|
|
722
1434
|
function clearAllNotes(){
|
|
723
1435
|
annotations.slice().forEach(function(a){ if(a.kind==='note') unhighlight(a.id); });
|
|
724
1436
|
annotations.length=0; nextId=1; paint();
|
|
@@ -740,10 +1452,18 @@
|
|
|
740
1452
|
async function submit(decision){
|
|
741
1453
|
if(submitting) return; submitting=true;
|
|
742
1454
|
document.querySelectorAll('.dbar button').forEach(function(b){b.disabled=true;});
|
|
743
|
-
try{
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1455
|
+
try{
|
|
1456
|
+
const r=await fetch('/api/feedback',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({decision:decision,thread:annotations})});
|
|
1457
|
+
if(!r.ok) throw new Error('status '+r.status);
|
|
1458
|
+
hideApiError();
|
|
1459
|
+
decisionSent=true; // a terminal decision makes the server exit —
|
|
1460
|
+
// the SSE drop that follows is expected, not a fault
|
|
1461
|
+
clearAllNotes(); // notes clear the moment the decision is sent
|
|
1462
|
+
showSent(decision);
|
|
1463
|
+
}catch(e){
|
|
1464
|
+
showApiError('Could not send your decision — the review server may be unreachable. Nothing was sent; try again.');
|
|
1465
|
+
document.querySelectorAll('.dbar button').forEach(function(b){b.disabled=false;});
|
|
1466
|
+
}
|
|
747
1467
|
submitting=false;
|
|
748
1468
|
}
|
|
749
1469
|
document.getElementById('send').onclick=sendChat;
|
|
@@ -850,10 +1570,18 @@
|
|
|
850
1570
|
tabC.onclick=function(){setTab(false);}; tabF.onclick=function(){setTab(true);};
|
|
851
1571
|
|
|
852
1572
|
// ---- Load + live reload ----
|
|
853
|
-
|
|
1573
|
+
// null means "fetch failed" (error banner already shown) and is kept
|
|
1574
|
+
// distinct from '' so a transient failure can never masquerade as a
|
|
1575
|
+
// real empty document and trip the revised-doc diff below.
|
|
1576
|
+
function fetchDoc(doc){
|
|
1577
|
+
return fetch(DIRMODE?'/api/plan?doc='+encodeURIComponent(doc):'/api/plan')
|
|
1578
|
+
.then(function(r){ if(!r.ok) throw new Error('status '+r.status); hideApiError(); return r.text(); })
|
|
1579
|
+
.catch(function(e){ showApiError('Could not load "'+doc+'" — the review server may be unreachable.'); return null; });
|
|
1580
|
+
}
|
|
854
1581
|
async function reloadDoc(doc){
|
|
855
1582
|
if(!docState[doc]) return;
|
|
856
1583
|
const md=await fetchDoc(doc);
|
|
1584
|
+
if(md===null) return; // load failed; state untouched, banner already shown
|
|
857
1585
|
const st=docState[doc];
|
|
858
1586
|
if(md===st.md) return; // spurious watch event, no content change
|
|
859
1587
|
const prev=st.md, revised=prev!==null;
|
|
@@ -869,8 +1597,22 @@
|
|
|
869
1597
|
}
|
|
870
1598
|
async function boot(){
|
|
871
1599
|
let meta={dir:false,docs:['plan']};
|
|
872
|
-
try{
|
|
1600
|
+
try{
|
|
1601
|
+
const r=await fetch('/api/docs');
|
|
1602
|
+
if(!r.ok) throw new Error('status '+r.status);
|
|
1603
|
+
meta=await r.json();
|
|
1604
|
+
hideApiError();
|
|
1605
|
+
}catch(e){ showApiError('Could not reach the review server. Retry once it is back up.'); }
|
|
873
1606
|
DIRMODE=!!meta.dir; docs=meta.docs||[];
|
|
1607
|
+
// A directory with no markdown at all — nothing to render, nothing to
|
|
1608
|
+
// load per-doc. Show the empty state and stop before the doc loop.
|
|
1609
|
+
if(DIRMODE&&!docs.length){
|
|
1610
|
+
tocEl.innerHTML='';
|
|
1611
|
+
planEl.innerHTML='<div class="kit-empty"><p class="kit-empty__title">No documents to review</p>'
|
|
1612
|
+
+'<p class="kit-empty__body">This directory has no markdown files.</p></div>';
|
|
1613
|
+
paint();
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
874
1616
|
// Accept-or-fix reviews hide the plan-only "Reject / don't build it"
|
|
875
1617
|
// verdict: directory (spec) review always — a spec documents work already
|
|
876
1618
|
// decided — and single-file review when the server was started with
|
|
@@ -889,22 +1631,31 @@
|
|
|
889
1631
|
};
|
|
890
1632
|
qEl.onkeydown=function(e){ if(e.key==='Escape'){ qEl.value=''; qEl.oninput(); qEl.blur(); } };
|
|
891
1633
|
}
|
|
892
|
-
|
|
1634
|
+
// Panes are built off-DOM and only swapped in once every doc has
|
|
1635
|
+
// resolved, so the .kit-skel placeholders already in #plan stay
|
|
1636
|
+
// visible for the whole fetch instead of being wiped to blank first.
|
|
1637
|
+
const frag=document.createDocumentFragment();
|
|
893
1638
|
docs.forEach(function(d,i){
|
|
894
1639
|
const pane=document.createElement('div'); pane.className='docpane'; pane.style.display='none';
|
|
895
|
-
|
|
1640
|
+
frag.appendChild(pane);
|
|
896
1641
|
docState[d]={md:null,pane:pane,scroll:0,key:'d'+i,headings:[]};
|
|
897
1642
|
});
|
|
898
|
-
for(let i=0;i<docs.length;i++){ const d=docs[i];
|
|
1643
|
+
for(let i=0;i<docs.length;i++){ const d=docs[i]; const md=await fetchDoc(d); if(md!==null) renderDocPane(d,md); }
|
|
1644
|
+
planEl.innerHTML=''; planEl.appendChild(frag);
|
|
899
1645
|
if(!DIRMODE||!docs.length) buildToc(); // DIRMODE: switchDoc below builds the tree once, seeded
|
|
900
1646
|
paint();
|
|
901
1647
|
if(docs.length) switchDoc(docs[0]);
|
|
902
1648
|
buildFilesIndex();
|
|
903
1649
|
}
|
|
904
|
-
try{
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
1650
|
+
try{
|
|
1651
|
+
const es=new EventSource('/api/events');
|
|
1652
|
+
es.onmessage=function(ev){
|
|
1653
|
+
hideApiError();
|
|
1654
|
+
const m=String((ev&&ev.data)||''); const doc=m.indexOf('change:')===0?m.slice(7):docs[0];
|
|
1655
|
+
reloadDoc(doc);
|
|
1656
|
+
};
|
|
1657
|
+
es.onerror=function(){ if(!decisionSent) showApiError('Live connection to the review server was lost — revised docs will not auto-reload. Your notes are safe.'); };
|
|
1658
|
+
}catch(e){ showApiError('Live reload is unavailable in this browser.'); }
|
|
908
1659
|
boot();
|
|
909
1660
|
</script>
|
|
910
1661
|
</body>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
[
|
|
2
|
-
{ "query": "write tests first for this", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
3
|
-
{ "query": "tdd this feature", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
4
|
-
{ "query": "test-first for the parser module", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
5
|
-
{ "query": "write failing tests before implementing", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
6
|
-
{ "query": "red green refactor cycle please", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
7
|
-
{ "query": "can you do this test driven", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
8
|
-
{ "query": "write unit tests for the date helpers before you code them", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
9
|
-
{ "query": "update the failing test to match the new behavior", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
10
|
-
{ "query": "the test is failing so just fix the test", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
11
|
-
{ "query": "should I tdd ui components", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
2
|
+
{ "query": "write tests first for this", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow"], "note": "Implementation request with a test-first modifier. routing.md lists test-first-development among the skills that execute within workflow, never instead of it, so the router is a correct first answer." },
|
|
3
|
+
{ "query": "tdd this feature", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow"] },
|
|
4
|
+
{ "query": "test-first for the parser module", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow"] },
|
|
5
|
+
{ "query": "write failing tests before implementing", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow"] },
|
|
6
|
+
{ "query": "red green refactor cycle please", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow"] },
|
|
7
|
+
{ "query": "can you do this test driven", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow"] },
|
|
8
|
+
{ "query": "write unit tests for the date helpers before you code them", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow"] },
|
|
9
|
+
{ "query": "update the failing test to match the new behavior", "should_trigger": true, "expected_skill": "test-first-development", "note": "Ownership of an existing test, not an implementation request — test-first-development is the sole right answer and this case must stay strict to keep its discriminating power." },
|
|
10
|
+
{ "query": "the test is failing so just fix the test", "should_trigger": true, "expected_skill": "test-first-development", "note": "Ownership of an existing test — strict on purpose, see above." },
|
|
11
|
+
{ "query": "should I tdd ui components", "should_trigger": true, "expected_skill": "test-first-development", "note": "Question about the practice, not a request to build anything — workflow would be wrong here, so no accept alternate." },
|
|
12
12
|
{ "query": "you already asked once, dont re-ask each cycle right?", "should_trigger": true, "expected_skill": "test-first-development", "kind": "in-context" },
|
|
13
|
-
{ "query": "bug: prove it with a failing test then fix it", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
14
|
-
{ "query": "tets first plz", "should_trigger": true, "expected_skill": "test-first-development" },
|
|
13
|
+
{ "query": "bug: prove it with a failing test then fix it", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow", "root-cause-analysis"], "note": "Bug-shaped and test-first at once. Doctrine routes a bug through workflow, which loads root-cause-analysis — both are defensible first answers." },
|
|
14
|
+
{ "query": "tets first plz", "should_trigger": true, "expected_skill": "test-first-development", "accept": ["workflow"] },
|
|
15
15
|
{ "query": "add tests for the existing legacy code", "should_trigger": true, "expected_skill": "workflow" },
|
|
16
16
|
{ "query": "implement the currency formatter utility", "should_trigger": true, "expected_skill": "workflow" },
|
|
17
17
|
{ "query": "write e2e tests with playwright for the flow", "should_trigger": true, "expected_skill": "workflow" },
|