@try-works/dsh-recursive-mode 0.1.11 → 0.1.13
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/lib/client/board.d.ts +1 -26
- package/lib/client/derive.d.ts +11 -0
- package/lib/client/inspector.d.ts +2 -14
- package/lib/client/styles.d.ts +17 -0
- package/lib/client/theme.d.ts +20 -0
- package/lib/client.js +724 -278
- package/package.json +1 -1
- package/src/client/board.tsx +67 -97
- package/src/client/derive.ts +34 -0
- package/src/client/inspector.tsx +50 -50
- package/src/client/slots.ts +3 -0
- package/src/client/styles.ts +566 -0
- package/src/client/theme.ts +53 -0
package/lib/client.js
CHANGED
|
@@ -206,6 +206,34 @@ window.__ModuleLoader__.load({
|
|
|
206
206
|
subagents
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
|
+
/** Human pill label per kind (neutral renders an empty dash). */
|
|
210
|
+
const PILL_LABELS = {
|
|
211
|
+
locked: "locked",
|
|
212
|
+
"in-progress": "in progress",
|
|
213
|
+
paused: "paused",
|
|
214
|
+
blocked: "blocked",
|
|
215
|
+
tampered: "tampered",
|
|
216
|
+
advisory: "advisory",
|
|
217
|
+
neutral: "—"
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Solid-pill kind for a whole run card. Priority: tamper > fully-locked >
|
|
221
|
+
* run-state blocked > run-state paused > still in-progress.
|
|
222
|
+
*/
|
|
223
|
+
function cardPill(card) {
|
|
224
|
+
const facts = cardFacts(card);
|
|
225
|
+
if (facts.tampered) return "tampered";
|
|
226
|
+
if (facts.lockValidity === "locked") return "locked";
|
|
227
|
+
if (facts.state === "blocked") return "blocked";
|
|
228
|
+
if (facts.state === "paused") return "paused";
|
|
229
|
+
return "in-progress";
|
|
230
|
+
}
|
|
231
|
+
/** Solid-pill kind for a phase row status string (LOCKED / DRAFT / '—'). */
|
|
232
|
+
function phaseStatusPill(status) {
|
|
233
|
+
if (status === "LOCKED") return "locked";
|
|
234
|
+
if (status === "" || status === "—") return "neutral";
|
|
235
|
+
return "in-progress";
|
|
236
|
+
}
|
|
209
237
|
/**
|
|
210
238
|
* Expand a card's phase chain into full inspector rows: the always-shown
|
|
211
239
|
* '03.5' slot (§11.10 mandatory deviation) plus 01.5 when present, and
|
|
@@ -242,104 +270,60 @@ window.__ModuleLoader__.load({
|
|
|
242
270
|
return rows;
|
|
243
271
|
}
|
|
244
272
|
//#endregion
|
|
273
|
+
//#region src/client/theme.ts
|
|
274
|
+
/**
|
|
275
|
+
* Board/Inspector theme state (run 16 / Paper). Default LIGHT, dark via a
|
|
276
|
+
* header toggle that flips data-theme on the root .rec-board / .rec-inspector.
|
|
277
|
+
* Persisted to localStorage under 'dsh-recursive-theme' (first visit = light).
|
|
278
|
+
*/
|
|
279
|
+
const BOARD_THEME_STORAGE_KEY = "dsh-recursive-theme";
|
|
280
|
+
function readInitialTheme() {
|
|
281
|
+
if (typeof localStorage === "undefined") return "light";
|
|
282
|
+
try {
|
|
283
|
+
return localStorage.getItem("dsh-recursive-theme") === "dark" ? "dark" : "light";
|
|
284
|
+
} catch {
|
|
285
|
+
return "light";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Hoisted at the TOP of Board and Inspector (never after an early return — run
|
|
290
|
+
* 0.1.10 invariant). Defaults light, reads a persisted dark on init, and writes
|
|
291
|
+
* the chosen theme back to localStorage.
|
|
292
|
+
*/
|
|
293
|
+
function useBoardTheme() {
|
|
294
|
+
const [theme, setTheme] = (0, react.useState)(readInitialTheme);
|
|
295
|
+
const toggle = (0, react.useCallback)(() => setTheme((t) => t === "light" ? "dark" : "light"), []);
|
|
296
|
+
(0, react.useEffect)(() => {
|
|
297
|
+
try {
|
|
298
|
+
localStorage.setItem(BOARD_THEME_STORAGE_KEY, theme);
|
|
299
|
+
} catch {}
|
|
300
|
+
}, [theme]);
|
|
301
|
+
return {
|
|
302
|
+
theme,
|
|
303
|
+
toggle
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/** The shared header toggle button (moon in light, sun in dark). */
|
|
307
|
+
function ThemeToggle({ theme, toggle }) {
|
|
308
|
+
return (0, react.createElement)("button", {
|
|
309
|
+
type: "button",
|
|
310
|
+
className: "rec-theme-toggle",
|
|
311
|
+
onClick: toggle,
|
|
312
|
+
title: theme === "light" ? "Switch to dark theme" : "Switch to light theme",
|
|
313
|
+
"aria-label": "Toggle theme"
|
|
314
|
+
}, theme === "light" ? "☾" : "☀");
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
245
317
|
//#region src/client/board.tsx
|
|
246
318
|
/**
|
|
247
|
-
* Board overlay (SP2 R1 + run 08 R3 + run
|
|
248
|
-
* host-route projection
|
|
249
|
-
*
|
|
250
|
-
* mapped to their current-phase lane via derive.columnForRun. Renders NOTHING
|
|
251
|
-
* when the snapshot is null (no recursive root / not loaded) or the current
|
|
252
|
-
* session is not on the recursive preset (the gate lives in slots.ts — here an
|
|
253
|
-
* empty projection with a null root is a no-op, not an "empty board").
|
|
254
|
-
*
|
|
255
|
-
* Run 08 R3: a card click opens the drill-down inspector via onOpenInspector
|
|
256
|
-
* (the overlay swaps board <-> inspector in slots.ts). Read-only still: the
|
|
257
|
-
* callback only selects a run for display; no mutation.
|
|
319
|
+
* Board overlay (SP2 R1 + run 08 R3 + run 15/16): kanban over the live
|
|
320
|
+
* host-route projection, class-based grid board with the Paper theme.
|
|
321
|
+
* Pure read-only renderer — no filesystem, no run-file scraping.
|
|
258
322
|
*
|
|
259
|
-
* Run
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* (no CSS pipeline in the CJS client bundle): full-cover fixed overlay, opaque
|
|
263
|
-
* backdrop, pointerEvents auto, and a visible close button.
|
|
323
|
+
* Run 16 (Paper): data-theme (default light), header theme toggle, workspace
|
|
324
|
+
* path + count chip, solid status pills. useBoardTheme is hoisted ABOVE the
|
|
325
|
+
* early returns (0.1.10 hook invariant).
|
|
264
326
|
*/
|
|
265
|
-
/** Full-cover overlay style — the shell.overlay seat is click-through + unstyled by default (run 12). */
|
|
266
|
-
const overlayStyle = {
|
|
267
|
-
position: "fixed",
|
|
268
|
-
inset: 0,
|
|
269
|
-
background: "rgba(15, 17, 21, 0.92)",
|
|
270
|
-
color: "#e6e6e6",
|
|
271
|
-
zIndex: 9999,
|
|
272
|
-
overflowY: "auto",
|
|
273
|
-
padding: "24px 32px",
|
|
274
|
-
pointerEvents: "auto",
|
|
275
|
-
fontFamily: "system-ui, sans-serif"
|
|
276
|
-
};
|
|
277
|
-
const laneStyle = { marginBottom: 24 };
|
|
278
|
-
const laneTitleStyle = {
|
|
279
|
-
fontSize: 14,
|
|
280
|
-
textTransform: "uppercase",
|
|
281
|
-
letterSpacing: "0.08em",
|
|
282
|
-
marginBottom: 8,
|
|
283
|
-
color: "#aaa"
|
|
284
|
-
};
|
|
285
|
-
const cardStyle = {
|
|
286
|
-
display: "inline-block",
|
|
287
|
-
width: 280,
|
|
288
|
-
margin: "0 12px 12px 0",
|
|
289
|
-
padding: 12,
|
|
290
|
-
background: "#1f2329",
|
|
291
|
-
borderRadius: 8,
|
|
292
|
-
border: "1px solid #333",
|
|
293
|
-
cursor: "pointer",
|
|
294
|
-
verticalAlign: "top"
|
|
295
|
-
};
|
|
296
|
-
const cardHeadStyle = {
|
|
297
|
-
display: "flex",
|
|
298
|
-
justifyContent: "space-between",
|
|
299
|
-
marginBottom: 8
|
|
300
|
-
};
|
|
301
|
-
const badgeStyle$1 = {
|
|
302
|
-
fontSize: 11,
|
|
303
|
-
padding: "2px 6px",
|
|
304
|
-
borderRadius: 4,
|
|
305
|
-
background: "#333"
|
|
306
|
-
};
|
|
307
|
-
const badgeTamperStyle = { background: "#7a1f1f" };
|
|
308
|
-
const progressStyle = {
|
|
309
|
-
height: 6,
|
|
310
|
-
background: "#333",
|
|
311
|
-
borderRadius: 3,
|
|
312
|
-
overflow: "hidden"
|
|
313
|
-
};
|
|
314
|
-
const progressFillStyle = {
|
|
315
|
-
background: "#4c9aff",
|
|
316
|
-
height: "100%"
|
|
317
|
-
};
|
|
318
|
-
const metaStyle = {
|
|
319
|
-
marginTop: 8,
|
|
320
|
-
fontSize: 12,
|
|
321
|
-
color: "#aaa",
|
|
322
|
-
display: "flex",
|
|
323
|
-
justifyContent: "space-between"
|
|
324
|
-
};
|
|
325
|
-
const emptyStyle = {
|
|
326
|
-
padding: 40,
|
|
327
|
-
textAlign: "center",
|
|
328
|
-
color: "#888"
|
|
329
|
-
};
|
|
330
|
-
const closeStyle$1 = {
|
|
331
|
-
position: "absolute",
|
|
332
|
-
top: 12,
|
|
333
|
-
right: 16,
|
|
334
|
-
background: "#1f2329",
|
|
335
|
-
color: "#e6e6e6",
|
|
336
|
-
border: "1px solid #444",
|
|
337
|
-
borderRadius: 6,
|
|
338
|
-
padding: "4px 12px",
|
|
339
|
-
cursor: "pointer",
|
|
340
|
-
fontSize: 16,
|
|
341
|
-
zIndex: 1
|
|
342
|
-
};
|
|
343
327
|
/** Flatten the worktree-grouped projection into a stable run list. */
|
|
344
328
|
function listRuns(projection) {
|
|
345
329
|
if (projection === void 0) return [];
|
|
@@ -347,220 +331,116 @@ window.__ModuleLoader__.load({
|
|
|
347
331
|
for (const worktreeRoot of Object.keys(projection)) for (const runId of Object.keys(projection[worktreeRoot])) runs.push(projection[worktreeRoot][runId]);
|
|
348
332
|
return runs;
|
|
349
333
|
}
|
|
334
|
+
function closeButton$1(onClose) {
|
|
335
|
+
if (onClose === void 0) return null;
|
|
336
|
+
return (0, react.createElement)("button", {
|
|
337
|
+
type: "button",
|
|
338
|
+
className: "rec-close",
|
|
339
|
+
onClick: onClose,
|
|
340
|
+
title: "Close",
|
|
341
|
+
"aria-label": "Close"
|
|
342
|
+
}, "×");
|
|
343
|
+
}
|
|
344
|
+
function solidPill$1(kind) {
|
|
345
|
+
return (0, react.createElement)("span", {
|
|
346
|
+
className: "rec-pill",
|
|
347
|
+
"data-pill": kind
|
|
348
|
+
}, PILL_LABELS[kind]);
|
|
349
|
+
}
|
|
350
350
|
function Board({ snapshot, onOpenInspector, onClose }) {
|
|
351
|
+
const { theme, toggle } = useBoardTheme();
|
|
351
352
|
if (snapshot === null || snapshot.root === null) return null;
|
|
352
353
|
const runs = listRuns(snapshot.projection);
|
|
353
|
-
const
|
|
354
|
-
className: "rec-close",
|
|
355
|
-
style: closeStyle$1,
|
|
356
|
-
onClick: onClose,
|
|
357
|
-
title: "Close"
|
|
358
|
-
}, "×") : null;
|
|
359
|
-
if (runs.length === 0) return (0, react.createElement)("div", {
|
|
360
|
-
className: "rec-board",
|
|
361
|
-
"data-empty": true,
|
|
362
|
-
style: overlayStyle
|
|
363
|
-
}, close, (0, react.createElement)("p", {
|
|
364
|
-
className: "rec-board-empty",
|
|
365
|
-
style: emptyStyle
|
|
366
|
-
}, "No recursive runs in this workspace yet."));
|
|
354
|
+
const workspacePath = snapshot.root;
|
|
367
355
|
const open = (run) => () => onOpenInspector?.({
|
|
368
356
|
worktreeRoot: run.worktreeRoot,
|
|
369
357
|
runId: run.runId
|
|
370
358
|
});
|
|
359
|
+
if (runs.length === 0) return (0, react.createElement)("div", {
|
|
360
|
+
className: "rec-board",
|
|
361
|
+
"data-theme": theme,
|
|
362
|
+
"data-empty": true
|
|
363
|
+
}, (0, react.createElement)("header", { className: "rec-board-header" }, (0, react.createElement)("h2", { className: "rec-board-title" }, "Recursive runs"), (0, react.createElement)("span", { className: "rec-board-path" }, workspacePath), (0, react.createElement)("span", { className: "rec-board-count" }, "0 runs"), (0, react.createElement)(ThemeToggle, {
|
|
364
|
+
theme,
|
|
365
|
+
toggle
|
|
366
|
+
}), closeButton$1(onClose)), (0, react.createElement)("p", { className: "rec-board-empty" }, "No recursive runs in this workspace yet."));
|
|
371
367
|
return (0, react.createElement)("div", {
|
|
372
368
|
className: "rec-board",
|
|
373
|
-
|
|
374
|
-
},
|
|
369
|
+
"data-theme": theme
|
|
370
|
+
}, (0, react.createElement)("header", { className: "rec-board-header" }, (0, react.createElement)("h2", { className: "rec-board-title" }, "Recursive runs"), (0, react.createElement)("span", { className: "rec-board-path" }, workspacePath), (0, react.createElement)("span", { className: "rec-board-count" }, runs.length + " runs"), (0, react.createElement)(ThemeToggle, {
|
|
371
|
+
theme,
|
|
372
|
+
toggle
|
|
373
|
+
}), closeButton$1(onClose)), (0, react.createElement)("div", { className: "rec-columns" }, KANBAN_LANES.map((lane) => {
|
|
375
374
|
const laneRuns = runs.filter((r) => columnForRun(r) === lane.id);
|
|
376
375
|
return (0, react.createElement)("section", {
|
|
377
376
|
key: lane.id,
|
|
378
|
-
className: "rec-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}, lane.label), laneRuns.map((run) => {
|
|
377
|
+
className: "rec-column"
|
|
378
|
+
}, (0, react.createElement)("header", { className: "rec-column-header" }, (0, react.createElement)("span", {
|
|
379
|
+
className: "rec-status-dot",
|
|
380
|
+
"data-status": laneRuns.length > 0 ? laneRuns[laneRuns.length - 1].state : "new",
|
|
381
|
+
"aria-hidden": true
|
|
382
|
+
}), (0, react.createElement)("h3", { className: "rec-column-title" }, lane.label), (0, react.createElement)("span", { className: "rec-column-count" }, String(laneRuns.length))), (0, react.createElement)("div", { className: "rec-cards" }, laneRuns.map((run) => {
|
|
384
383
|
const facts = cardFacts(run);
|
|
384
|
+
const pill = cardPill(run);
|
|
385
385
|
return (0, react.createElement)("article", {
|
|
386
386
|
key: run.worktreeRoot + "\0" + run.runId,
|
|
387
387
|
className: "rec-card",
|
|
388
|
-
|
|
388
|
+
"data-state": run.state,
|
|
389
389
|
onClick: open(run)
|
|
390
|
-
}, (0, react.createElement)("div", {
|
|
391
|
-
className: "rec-card-head",
|
|
392
|
-
style: cardHeadStyle
|
|
393
|
-
}, (0, react.createElement)("strong", {}, run.runId), facts.tampered && (0, react.createElement)("span", {
|
|
394
|
-
className: "rec-badge rec-badge-tamper",
|
|
395
|
-
style: {
|
|
396
|
-
...badgeStyle$1,
|
|
397
|
-
...badgeTamperStyle
|
|
398
|
-
},
|
|
399
|
-
title: "tampered"
|
|
400
|
-
}, "!")), (0, react.createElement)("div", {
|
|
401
|
-
className: "rec-card-progress",
|
|
402
|
-
style: progressStyle
|
|
403
|
-
}, (0, react.createElement)("div", {
|
|
390
|
+
}, (0, react.createElement)("div", { className: "rec-card-title" }, run.runId), (0, react.createElement)("div", { className: "rec-card-progress" }, (0, react.createElement)("div", {
|
|
404
391
|
className: "rec-card-progress-fill",
|
|
405
|
-
style: {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
})), (0, react.createElement)("div", {
|
|
410
|
-
className: "rec-card-meta",
|
|
411
|
-
style: metaStyle
|
|
412
|
-
}, (0, react.createElement)("span", {}, facts.currentPhase ?? "no phases"), (0, react.createElement)("span", {
|
|
413
|
-
className: "rec-badge",
|
|
414
|
-
style: badgeStyle$1
|
|
415
|
-
}, facts.state)));
|
|
416
|
-
}));
|
|
417
|
-
}));
|
|
392
|
+
style: { width: Math.round(facts.progress * 100) + "%" }
|
|
393
|
+
})), (0, react.createElement)("div", { className: "rec-card-meta" }, (0, react.createElement)("span", { className: "rec-card-phase" }, facts.currentPhase ?? "no phases"), solidPill$1(pill)));
|
|
394
|
+
}), laneRuns.length === 0 && (0, react.createElement)("div", { className: "rec-column-empty" }, "—")));
|
|
395
|
+
})));
|
|
418
396
|
}
|
|
419
397
|
//#endregion
|
|
420
398
|
//#region src/client/inspector.tsx
|
|
421
399
|
/**
|
|
422
|
-
* Drill-down inspector (SP2 R1 + run
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
* Run 12 (LIVE BUG): same as the board — shell.overlay is click-through and
|
|
428
|
-
* unstyled, so the inspector mounts INVISIBLE + CLICK-THROUGH without inline
|
|
429
|
-
* styles. Full-cover fixed overlay + pointerEvents auto + a back button and a
|
|
430
|
-
* close button.
|
|
400
|
+
* Drill-down inspector (SP2 R1 + run 15/16): centered modal-style detail panel
|
|
401
|
+
* (Paper .detail shape) with back + close, data-theme (default light), theme
|
|
402
|
+
* toggle, and solid status pills. All from the live host-route snapshot.
|
|
403
|
+
* useBoardTheme is hoisted ABOVE the not-found early return.
|
|
431
404
|
*/
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
marginBottom: 16,
|
|
437
|
-
borderBottom: "1px solid #333",
|
|
438
|
-
paddingBottom: 12
|
|
439
|
-
};
|
|
440
|
-
const backStyle = {
|
|
441
|
-
background: "#1f2329",
|
|
442
|
-
color: "#e6e6e6",
|
|
443
|
-
border: "1px solid #444",
|
|
444
|
-
borderRadius: 6,
|
|
445
|
-
padding: "6px 12px",
|
|
446
|
-
cursor: "pointer"
|
|
447
|
-
};
|
|
448
|
-
const closeStyle = {
|
|
449
|
-
background: "#1f2329",
|
|
450
|
-
color: "#e6e6e6",
|
|
451
|
-
border: "1px solid #444",
|
|
452
|
-
borderRadius: 6,
|
|
453
|
-
padding: "6px 12px",
|
|
454
|
-
cursor: "pointer",
|
|
455
|
-
marginLeft: "auto"
|
|
456
|
-
};
|
|
457
|
-
const sectionStyle = { marginBottom: 16 };
|
|
458
|
-
const rowStyle = {
|
|
459
|
-
display: "flex",
|
|
460
|
-
gap: 12,
|
|
461
|
-
alignItems: "center",
|
|
462
|
-
padding: "6px 0",
|
|
463
|
-
borderBottom: "1px solid #222",
|
|
464
|
-
fontSize: 13
|
|
465
|
-
};
|
|
466
|
-
const phaseIdStyle = {
|
|
467
|
-
minWidth: 220,
|
|
468
|
-
fontFamily: "monospace",
|
|
469
|
-
color: "#4c9aff"
|
|
470
|
-
};
|
|
471
|
-
const phaseNameStyle = {
|
|
472
|
-
flex: 1,
|
|
473
|
-
color: "#ccc"
|
|
474
|
-
};
|
|
475
|
-
const badgeStyle = {
|
|
476
|
-
fontSize: 11,
|
|
477
|
-
padding: "2px 6px",
|
|
478
|
-
borderRadius: 4,
|
|
479
|
-
background: "#333"
|
|
480
|
-
};
|
|
481
|
-
const lockhashStyle = {
|
|
482
|
-
fontFamily: "monospace",
|
|
483
|
-
fontSize: 11,
|
|
484
|
-
color: "#888"
|
|
485
|
-
};
|
|
486
|
-
function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails, onClose }) {
|
|
487
|
-
const close = onClose !== void 0 ? (0, react.createElement)("button", {
|
|
405
|
+
function closeButton(onClose) {
|
|
406
|
+
if (onClose === void 0) return null;
|
|
407
|
+
return (0, react.createElement)("button", {
|
|
408
|
+
type: "button",
|
|
488
409
|
className: "rec-close",
|
|
489
|
-
style: closeStyle,
|
|
490
410
|
onClick: onClose,
|
|
491
|
-
title: "Close"
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
411
|
+
title: "Close",
|
|
412
|
+
"aria-label": "Close"
|
|
413
|
+
}, "×");
|
|
414
|
+
}
|
|
415
|
+
function solidPill(kind) {
|
|
416
|
+
return (0, react.createElement)("span", {
|
|
417
|
+
className: "rec-pill",
|
|
418
|
+
"data-pill": kind
|
|
419
|
+
}, PILL_LABELS[kind]);
|
|
420
|
+
}
|
|
421
|
+
function detailShell(runId, onBackToToolDetails, onClose, headerExtra, body, theme, toggle) {
|
|
422
|
+
return (0, react.createElement)("div", {
|
|
495
423
|
className: "rec-inspector",
|
|
496
|
-
|
|
497
|
-
}, (0, react.createElement)("div", {
|
|
498
|
-
|
|
499
|
-
style: headStyle
|
|
500
|
-
}, (0, react.createElement)("button", {
|
|
501
|
-
onClick: onBackToToolDetails,
|
|
424
|
+
"data-theme": theme
|
|
425
|
+
}, (0, react.createElement)("div", { className: "rec-detail" }, (0, react.createElement)("div", { className: "rec-detail-header" }, (0, react.createElement)("button", {
|
|
426
|
+
type: "button",
|
|
502
427
|
className: "rec-back",
|
|
503
|
-
|
|
504
|
-
}, "←
|
|
428
|
+
onClick: onBackToToolDetails
|
|
429
|
+
}, "← Back"), (0, react.createElement)("h2", { className: "rec-detail-title" }, runId), headerExtra, (0, react.createElement)(ThemeToggle, {
|
|
430
|
+
theme,
|
|
431
|
+
toggle
|
|
432
|
+
}), closeButton(onClose)), (0, react.createElement)("div", { className: "rec-detail-body" }, body)));
|
|
433
|
+
}
|
|
434
|
+
function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails, onClose }) {
|
|
435
|
+
const { theme, toggle } = useBoardTheme();
|
|
436
|
+
const card = snapshot?.projection?.[worktreeRoot]?.[runId];
|
|
437
|
+
if (card === void 0) return detailShell(runId, onBackToToolDetails, onClose, null, (0, react.createElement)("p", { className: "rec-detail-text" }, "Run not found: " + runId), theme, toggle);
|
|
505
438
|
const facts = cardFacts(card);
|
|
506
439
|
const rows = expandPhaseRows(card);
|
|
507
|
-
return (0, react.createElement)("div", {
|
|
508
|
-
className: "rec-inspector",
|
|
509
|
-
style: overlayStyle
|
|
510
|
-
}, (0, react.createElement)("div", {
|
|
511
|
-
className: "rec-inspector-head",
|
|
512
|
-
style: headStyle
|
|
513
|
-
}, (0, react.createElement)("button", {
|
|
514
|
-
onClick: onBackToToolDetails,
|
|
515
|
-
className: "rec-back",
|
|
516
|
-
style: backStyle
|
|
517
|
-
}, "← back to tool details"), (0, react.createElement)("h2", { style: {
|
|
518
|
-
margin: 0,
|
|
519
|
-
fontSize: 18
|
|
520
|
-
} }, runId), (0, react.createElement)("span", {
|
|
521
|
-
className: "rec-badge",
|
|
522
|
-
style: badgeStyle
|
|
523
|
-
}, facts.state), facts.tampered && (0, react.createElement)("span", {
|
|
524
|
-
className: "rec-badge rec-badge-tamper",
|
|
525
|
-
style: {
|
|
526
|
-
...badgeStyle,
|
|
527
|
-
background: "#7a1f1f"
|
|
528
|
-
}
|
|
529
|
-
}, "tampered"), close), (0, react.createElement)("section", {
|
|
530
|
-
className: "rec-inspector-section",
|
|
531
|
-
style: sectionStyle
|
|
532
|
-
}, (0, react.createElement)("h3", { style: {
|
|
533
|
-
fontSize: 13,
|
|
534
|
-
textTransform: "uppercase",
|
|
535
|
-
letterSpacing: "0.08em",
|
|
536
|
-
color: "#aaa",
|
|
537
|
-
marginBottom: 8
|
|
538
|
-
} }, "Phases"), rows.map((row) => (0, react.createElement)("div", {
|
|
440
|
+
return detailShell(runId, onBackToToolDetails, onClose, solidPill(cardPill(card)), (0, react.createElement)("section", { className: "rec-detail-section" }, (0, react.createElement)("h3", {}, "Phases"), rows.map((row) => (0, react.createElement)("div", {
|
|
539
441
|
key: row.phase,
|
|
540
|
-
className: "rec-phase-row"
|
|
541
|
-
|
|
542
|
-
}, (0, react.createElement)("span", {
|
|
543
|
-
className: "rec-phase-id",
|
|
544
|
-
style: phaseIdStyle
|
|
545
|
-
}, row.phase), (0, react.createElement)("span", {
|
|
546
|
-
className: "rec-phase-name",
|
|
547
|
-
style: phaseNameStyle
|
|
548
|
-
}, row.fileName ?? "—"), (0, react.createElement)("span", {
|
|
549
|
-
className: "rec-badge",
|
|
550
|
-
style: badgeStyle
|
|
551
|
-
}, row.status), row.lockHash !== void 0 && (0, react.createElement)("code", {
|
|
552
|
-
className: "rec-lockhash",
|
|
553
|
-
style: lockhashStyle
|
|
554
|
-
}, "#" + row.lockHash.slice(0, 8))))), facts.gateBlocked && (0, react.createElement)("section", {
|
|
555
|
-
className: "rec-inspector-section",
|
|
556
|
-
style: sectionStyle
|
|
557
|
-
}, (0, react.createElement)("h3", { style: {
|
|
558
|
-
fontSize: 13,
|
|
559
|
-
textTransform: "uppercase",
|
|
560
|
-
letterSpacing: "0.08em",
|
|
561
|
-
color: "#aaa",
|
|
562
|
-
marginBottom: 8
|
|
563
|
-
} }, "Gate"), (0, react.createElement)("p", {}, "Blocked (" + (facts.gateKind ?? "") + ")")));
|
|
442
|
+
className: "rec-phase-row"
|
|
443
|
+
}, (0, react.createElement)("span", { className: "rec-phase-id" }, row.phase), (0, react.createElement)("span", { className: "rec-phase-name" }, row.fileName ?? "—"), solidPill(phaseStatusPill(row.status)), row.lockHash !== void 0 && (0, react.createElement)("code", { className: "rec-lockhash" }, "#" + row.lockHash.slice(0, 8)))), facts.gateBlocked && (0, react.createElement)("div", { className: "rec-detail-section" }, (0, react.createElement)("h3", {}, "Gate"), (0, react.createElement)("p", { className: "rec-detail-text" }, "Blocked (" + (facts.gateKind ?? "") + ")"))), theme, toggle);
|
|
564
444
|
}
|
|
565
445
|
//#endregion
|
|
566
446
|
//#region src/client/strip.tsx
|
|
@@ -766,6 +646,571 @@ window.__ModuleLoader__.load({
|
|
|
766
646
|
return (0, react.useSyncExternalStore)(store.subscribe, store.get, store.get);
|
|
767
647
|
}
|
|
768
648
|
//#endregion
|
|
649
|
+
//#region src/client/styles.ts
|
|
650
|
+
/**
|
|
651
|
+
* One-shot board/inspector stylesheet injection (run 15 + run 16 / Paper).
|
|
652
|
+
* The client bundle is a plain CJS closure with NO CSS pipeline (tsdown), so we
|
|
653
|
+
* inject a single <style data-dsh-recursive> element into document.head.
|
|
654
|
+
*
|
|
655
|
+
* Run 16 (Paper): the design is ported 1:1 — DEFAULT LIGHT theme, dark via a
|
|
656
|
+
* data-theme attribute on the .rec-board / .rec-inspector root. Raw --rm3-*
|
|
657
|
+
* tokens live on the scoped roots; semantic --board-* aliases remap under
|
|
658
|
+
* [data-theme='dark']. EVERY component rule consumes ONLY --board-* aliases
|
|
659
|
+
* (never --rm3-* directly). Idempotent injection; disposer removes the element.
|
|
660
|
+
*/
|
|
661
|
+
const BOARD_CSS = `
|
|
662
|
+
/* ===== dsh-recursive Paper theme (run 16) ===== */
|
|
663
|
+
|
|
664
|
+
/* Raw tokens: light (default) + dark + shared, scoped to the board/inspector. */
|
|
665
|
+
.rec-board, .rec-inspector {
|
|
666
|
+
/* light */
|
|
667
|
+
--rm3-light-background: #FFFFFF;
|
|
668
|
+
--rm3-light-foreground: #111111;
|
|
669
|
+
--rm3-light-card: #FFFFFF;
|
|
670
|
+
--rm3-light-muted: #FAFAFA;
|
|
671
|
+
--rm3-light-muted-foreground: #666666;
|
|
672
|
+
--rm3-light-accent: #F5F5F5;
|
|
673
|
+
--rm3-light-border: #EAEAEA;
|
|
674
|
+
--rm3-light-success: #27A644;
|
|
675
|
+
--rm3-light-warning: #B67A11;
|
|
676
|
+
--rm3-light-error: #D84F6A;
|
|
677
|
+
--rm3-light-advisory: #9664E8;
|
|
678
|
+
--rm3-light-info: #3F87F5;
|
|
679
|
+
--rm3-light-destructive: #B4261A;
|
|
680
|
+
/* dark */
|
|
681
|
+
--rm3-background: #0A0A0A;
|
|
682
|
+
--rm3-foreground: #EDEDED;
|
|
683
|
+
--rm3-card: #0F0F0F;
|
|
684
|
+
--rm3-muted: #141414;
|
|
685
|
+
--rm3-muted-foreground: #9A9A9A;
|
|
686
|
+
--rm3-accent: #1A1A1A;
|
|
687
|
+
--rm3-border: #1F1F1F;
|
|
688
|
+
--rm3-success: #27A644;
|
|
689
|
+
--rm3-warning: #D9A441;
|
|
690
|
+
--rm3-error: #E06C89;
|
|
691
|
+
--rm3-advisory: #B479FF;
|
|
692
|
+
--rm3-info: #6EA8FF;
|
|
693
|
+
/* shared */
|
|
694
|
+
--rm3-font-sans: 'Geist', ui-sans-serif, system-ui, sans-serif;
|
|
695
|
+
--rm3-font-mono: 'Geist Mono', ui-monospace, Menlo, monospace;
|
|
696
|
+
--rm3-text-xs: 12px;
|
|
697
|
+
--rm3-text-sm: 14px;
|
|
698
|
+
--rm3-text-md: 16px;
|
|
699
|
+
--rm3-text-lg: 20px;
|
|
700
|
+
--rm3-text-xl: 28px;
|
|
701
|
+
--rm3-font-weight-regular: 400;
|
|
702
|
+
--rm3-font-weight-medium: 500;
|
|
703
|
+
--rm3-font-weight-semibold: 600;
|
|
704
|
+
--rm3-radius-sm: 5px;
|
|
705
|
+
--rm3-radius-md: 6px;
|
|
706
|
+
--rm3-radius-lg: 8px;
|
|
707
|
+
--rm3-radius-xl: 11px;
|
|
708
|
+
--rm3-space-8: 8px;
|
|
709
|
+
--rm3-space-12: 12px;
|
|
710
|
+
--rm3-space-16: 16px;
|
|
711
|
+
--rm3-space-24: 24px;
|
|
712
|
+
--rm3-space-32: 32px;
|
|
713
|
+
--rm3-tracking-tight: -0.02em;
|
|
714
|
+
--rm3-tracking-mono: 0.02em;
|
|
715
|
+
|
|
716
|
+
/* Semantic aliases (LIGHT default) */
|
|
717
|
+
--board-bg: var(--rm3-light-background);
|
|
718
|
+
--board-fg: var(--rm3-light-foreground);
|
|
719
|
+
--board-card: var(--rm3-light-card);
|
|
720
|
+
--board-muted: var(--rm3-light-muted);
|
|
721
|
+
--board-muted-fg: var(--rm3-light-muted-foreground);
|
|
722
|
+
--board-accent: var(--rm3-light-accent);
|
|
723
|
+
--board-border: var(--rm3-light-border);
|
|
724
|
+
--board-success: var(--rm3-light-success);
|
|
725
|
+
--board-warning: var(--rm3-light-warning);
|
|
726
|
+
--board-error: var(--rm3-light-error);
|
|
727
|
+
--board-advisory: var(--rm3-light-advisory);
|
|
728
|
+
--board-info: var(--rm3-light-info);
|
|
729
|
+
--board-destructive: var(--rm3-light-destructive);
|
|
730
|
+
--board-font-sans: var(--rm3-font-sans);
|
|
731
|
+
--board-font-mono: var(--rm3-font-mono);
|
|
732
|
+
--board-text-xs: var(--rm3-text-xs);
|
|
733
|
+
--board-text-sm: var(--rm3-text-sm);
|
|
734
|
+
--board-text-md: var(--rm3-text-md);
|
|
735
|
+
--board-text-lg: var(--rm3-text-lg);
|
|
736
|
+
--board-text-xl: var(--rm3-text-xl);
|
|
737
|
+
--board-fw-regular: var(--rm3-font-weight-regular);
|
|
738
|
+
--board-fw-medium: var(--rm3-font-weight-medium);
|
|
739
|
+
--board-fw-semibold: var(--rm3-font-weight-semibold);
|
|
740
|
+
--board-radius-sm: var(--rm3-radius-sm);
|
|
741
|
+
--board-radius-md: var(--rm3-radius-md);
|
|
742
|
+
--board-radius-lg: var(--rm3-radius-lg);
|
|
743
|
+
--board-radius-xl: var(--rm3-radius-xl);
|
|
744
|
+
--board-space-8: var(--rm3-space-8);
|
|
745
|
+
--board-space-12: var(--rm3-space-12);
|
|
746
|
+
--board-space-16: var(--rm3-space-16);
|
|
747
|
+
--board-space-24: var(--rm3-space-24);
|
|
748
|
+
--board-space-32: var(--rm3-space-32);
|
|
749
|
+
--board-tracking-tight: var(--rm3-tracking-tight);
|
|
750
|
+
--board-tracking-mono: var(--rm3-tracking-mono);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/* Semantic aliases remapped for DARK. */
|
|
754
|
+
.rec-board[data-theme='dark'], .rec-inspector[data-theme='dark'] {
|
|
755
|
+
--board-bg: var(--rm3-background);
|
|
756
|
+
--board-fg: var(--rm3-foreground);
|
|
757
|
+
--board-card: var(--rm3-card);
|
|
758
|
+
--board-muted: var(--rm3-muted);
|
|
759
|
+
--board-muted-fg: var(--rm3-muted-foreground);
|
|
760
|
+
--board-accent: var(--rm3-accent);
|
|
761
|
+
--board-border: var(--rm3-border);
|
|
762
|
+
--board-success: var(--rm3-success);
|
|
763
|
+
--board-warning: var(--rm3-warning);
|
|
764
|
+
--board-error: var(--rm3-error);
|
|
765
|
+
--board-advisory: var(--rm3-advisory);
|
|
766
|
+
--board-info: var(--rm3-info);
|
|
767
|
+
--board-destructive: var(--rm3-error);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* ===== Board ===== */
|
|
771
|
+
.rec-board {
|
|
772
|
+
position: fixed;
|
|
773
|
+
inset: 0;
|
|
774
|
+
z-index: 9999;
|
|
775
|
+
display: flex;
|
|
776
|
+
flex-direction: column;
|
|
777
|
+
box-sizing: border-box;
|
|
778
|
+
padding: var(--board-space-16);
|
|
779
|
+
gap: var(--board-space-12);
|
|
780
|
+
background: var(--board-bg);
|
|
781
|
+
color: var(--board-fg);
|
|
782
|
+
font-family: var(--board-font-sans);
|
|
783
|
+
pointer-events: auto;
|
|
784
|
+
overflow: hidden;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.rec-board-header {
|
|
788
|
+
display: flex;
|
|
789
|
+
align-items: center;
|
|
790
|
+
gap: var(--board-space-12);
|
|
791
|
+
flex: none;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.rec-board-title {
|
|
795
|
+
margin: 0;
|
|
796
|
+
font-size: 22px;
|
|
797
|
+
font-weight: var(--board-fw-semibold);
|
|
798
|
+
letter-spacing: var(--board-tracking-tight);
|
|
799
|
+
color: var(--board-fg);
|
|
800
|
+
white-space: nowrap;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.rec-board-path {
|
|
804
|
+
flex: none;
|
|
805
|
+
font-family: var(--board-font-mono);
|
|
806
|
+
font-size: var(--board-text-xs);
|
|
807
|
+
letter-spacing: var(--board-tracking-mono);
|
|
808
|
+
color: var(--board-muted-fg);
|
|
809
|
+
overflow: hidden;
|
|
810
|
+
text-overflow: ellipsis;
|
|
811
|
+
white-space: nowrap;
|
|
812
|
+
max-width: 40%;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.rec-board-count {
|
|
816
|
+
flex: none;
|
|
817
|
+
font-size: var(--board-text-xs);
|
|
818
|
+
font-weight: var(--board-fw-medium);
|
|
819
|
+
color: var(--board-muted-fg);
|
|
820
|
+
background: var(--board-muted);
|
|
821
|
+
border-radius: 9999px;
|
|
822
|
+
padding: 4px 10px;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.rec-board-empty {
|
|
826
|
+
margin: 0;
|
|
827
|
+
padding: var(--board-space-32);
|
|
828
|
+
text-align: center;
|
|
829
|
+
color: var(--board-muted-fg);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.rec-close {
|
|
833
|
+
display: inline-flex;
|
|
834
|
+
align-items: center;
|
|
835
|
+
justify-content: center;
|
|
836
|
+
width: 28px;
|
|
837
|
+
height: 28px;
|
|
838
|
+
padding: 0;
|
|
839
|
+
margin-left: auto;
|
|
840
|
+
flex: none;
|
|
841
|
+
background: transparent;
|
|
842
|
+
border: 1px solid var(--board-border);
|
|
843
|
+
border-radius: var(--board-radius-md);
|
|
844
|
+
color: var(--board-muted-fg);
|
|
845
|
+
cursor: pointer;
|
|
846
|
+
font-size: var(--board-text-md);
|
|
847
|
+
line-height: 1;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.rec-close:hover {
|
|
851
|
+
background: var(--board-accent);
|
|
852
|
+
color: var(--board-fg);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.rec-theme-toggle {
|
|
856
|
+
display: inline-flex;
|
|
857
|
+
align-items: center;
|
|
858
|
+
justify-content: center;
|
|
859
|
+
width: 28px;
|
|
860
|
+
height: 28px;
|
|
861
|
+
padding: 0;
|
|
862
|
+
flex: none;
|
|
863
|
+
background: transparent;
|
|
864
|
+
border: 1px solid var(--board-border);
|
|
865
|
+
border-radius: var(--board-radius-md);
|
|
866
|
+
color: var(--board-muted-fg);
|
|
867
|
+
cursor: pointer;
|
|
868
|
+
font-size: var(--board-text-md);
|
|
869
|
+
line-height: 1;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.rec-theme-toggle:hover {
|
|
873
|
+
background: var(--board-accent);
|
|
874
|
+
color: var(--board-fg);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.rec-columns {
|
|
878
|
+
display: grid;
|
|
879
|
+
grid-auto-flow: column;
|
|
880
|
+
grid-auto-columns: minmax(220px, 1fr);
|
|
881
|
+
gap: var(--board-space-12);
|
|
882
|
+
flex: 1;
|
|
883
|
+
min-height: 0;
|
|
884
|
+
overflow-x: auto;
|
|
885
|
+
overflow-y: hidden;
|
|
886
|
+
overscroll-behavior-inline: contain;
|
|
887
|
+
padding-bottom: 6px;
|
|
888
|
+
scrollbar-color: var(--board-border) var(--board-accent);
|
|
889
|
+
scrollbar-width: thin;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.rec-columns::-webkit-scrollbar { height: 10px; }
|
|
893
|
+
.rec-columns::-webkit-scrollbar-track { background: var(--board-accent); border-radius: 999px; }
|
|
894
|
+
.rec-columns::-webkit-scrollbar-thumb { background: var(--board-border); border-radius: 999px; }
|
|
895
|
+
|
|
896
|
+
.rec-column {
|
|
897
|
+
display: flex;
|
|
898
|
+
flex-direction: column;
|
|
899
|
+
min-height: 0;
|
|
900
|
+
background: var(--board-muted);
|
|
901
|
+
border: 1px solid var(--board-border);
|
|
902
|
+
border-radius: var(--board-radius-xl);
|
|
903
|
+
overflow: hidden;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.rec-column-header {
|
|
907
|
+
display: flex;
|
|
908
|
+
align-items: center;
|
|
909
|
+
gap: 6px;
|
|
910
|
+
padding: var(--board-space-12);
|
|
911
|
+
flex: none;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.rec-column-title {
|
|
915
|
+
margin: 0;
|
|
916
|
+
flex: 1;
|
|
917
|
+
font-size: 13px;
|
|
918
|
+
font-weight: var(--board-fw-semibold);
|
|
919
|
+
color: var(--board-fg);
|
|
920
|
+
overflow: hidden;
|
|
921
|
+
text-overflow: ellipsis;
|
|
922
|
+
white-space: nowrap;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.rec-column-count {
|
|
926
|
+
flex: none;
|
|
927
|
+
font-size: var(--board-text-xs);
|
|
928
|
+
font-weight: var(--board-fw-medium);
|
|
929
|
+
color: var(--board-muted-fg);
|
|
930
|
+
background: var(--board-accent);
|
|
931
|
+
border-radius: 9999px;
|
|
932
|
+
padding: 1px 8px;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
.rec-status-dot {
|
|
936
|
+
width: 6px;
|
|
937
|
+
height: 6px;
|
|
938
|
+
border-radius: 50%;
|
|
939
|
+
flex: none;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.rec-status-dot[data-status='new'] { background: var(--board-muted-fg); }
|
|
943
|
+
.rec-status-dot[data-status='active'] { background: var(--board-info); }
|
|
944
|
+
.rec-status-dot[data-status='paused'] { background: var(--board-warning); }
|
|
945
|
+
.rec-status-dot[data-status='blocked'] { background: var(--board-error); }
|
|
946
|
+
.rec-status-dot[data-status='complete'] { background: var(--board-success); }
|
|
947
|
+
|
|
948
|
+
.rec-cards {
|
|
949
|
+
display: flex;
|
|
950
|
+
flex-direction: column;
|
|
951
|
+
gap: var(--board-space-8);
|
|
952
|
+
padding: 10px;
|
|
953
|
+
overflow-y: auto;
|
|
954
|
+
flex: 1;
|
|
955
|
+
min-height: 0;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.rec-column-empty {
|
|
959
|
+
padding: var(--board-space-24) var(--board-space-8);
|
|
960
|
+
text-align: center;
|
|
961
|
+
font-size: var(--board-text-xs);
|
|
962
|
+
color: var(--board-muted-fg);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.rec-card {
|
|
966
|
+
display: flex;
|
|
967
|
+
flex-direction: column;
|
|
968
|
+
gap: var(--board-space-8);
|
|
969
|
+
padding: var(--board-space-12) 14px;
|
|
970
|
+
text-align: left;
|
|
971
|
+
background: var(--board-card);
|
|
972
|
+
border: 1px solid var(--board-border);
|
|
973
|
+
border-radius: var(--board-radius-lg);
|
|
974
|
+
cursor: pointer;
|
|
975
|
+
color: var(--board-fg);
|
|
976
|
+
font-family: inherit;
|
|
977
|
+
transition: box-shadow 120ms ease, border-color 120ms ease, transform 120ms ease;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.rec-card:hover {
|
|
981
|
+
border-color: var(--board-info);
|
|
982
|
+
transform: translateY(-1px);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
.rec-card:active {
|
|
986
|
+
transform: translateY(0);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.rec-card-title {
|
|
990
|
+
font-size: 13px;
|
|
991
|
+
font-weight: var(--board-fw-semibold);
|
|
992
|
+
line-height: 1.35;
|
|
993
|
+
overflow: hidden;
|
|
994
|
+
display: -webkit-box;
|
|
995
|
+
-webkit-line-clamp: 2;
|
|
996
|
+
-webkit-box-orient: vertical;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.rec-card-progress {
|
|
1000
|
+
height: 6px;
|
|
1001
|
+
background: var(--board-accent);
|
|
1002
|
+
border-radius: 3px;
|
|
1003
|
+
overflow: hidden;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
.rec-card-progress-fill {
|
|
1007
|
+
height: 100%;
|
|
1008
|
+
background: var(--board-info);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.rec-card-meta {
|
|
1012
|
+
display: flex;
|
|
1013
|
+
align-items: center;
|
|
1014
|
+
gap: var(--board-space-8);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.rec-card-phase {
|
|
1018
|
+
flex: 1;
|
|
1019
|
+
font-family: var(--board-font-mono);
|
|
1020
|
+
font-size: var(--board-text-xs);
|
|
1021
|
+
letter-spacing: var(--board-tracking-mono);
|
|
1022
|
+
color: var(--board-muted-fg);
|
|
1023
|
+
overflow: hidden;
|
|
1024
|
+
text-overflow: ellipsis;
|
|
1025
|
+
white-space: nowrap;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/* ===== Solid status pill (Paper StatusPill) ===== */
|
|
1029
|
+
.rec-pill {
|
|
1030
|
+
flex: none;
|
|
1031
|
+
border-radius: 9999px;
|
|
1032
|
+
padding: 4px 10px;
|
|
1033
|
+
font-size: var(--board-text-xs);
|
|
1034
|
+
font-weight: var(--board-fw-medium);
|
|
1035
|
+
line-height: 1;
|
|
1036
|
+
border: none;
|
|
1037
|
+
background: var(--board-muted-fg);
|
|
1038
|
+
color: var(--board-bg);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
.rec-pill[data-pill='locked'] { background: var(--board-success); color: var(--board-bg); }
|
|
1042
|
+
.rec-pill[data-pill='in-progress'] { background: var(--board-info); color: var(--board-bg); }
|
|
1043
|
+
.rec-pill[data-pill='paused'] { background: var(--board-warning); color: var(--board-bg); }
|
|
1044
|
+
.rec-pill[data-pill='blocked'] { background: var(--board-error); color: var(--board-bg); }
|
|
1045
|
+
.rec-pill[data-pill='tampered'] { background: var(--board-error); color: var(--board-bg); }
|
|
1046
|
+
.rec-pill[data-pill='advisory'] { background: var(--board-advisory); color: var(--board-bg); }
|
|
1047
|
+
.rec-pill[data-pill='neutral'] { background: var(--board-muted-fg); color: var(--board-bg); }
|
|
1048
|
+
|
|
1049
|
+
/* ===== Inspector: centered modal detail ===== */
|
|
1050
|
+
.rec-inspector {
|
|
1051
|
+
position: fixed;
|
|
1052
|
+
inset: 0;
|
|
1053
|
+
z-index: 9999;
|
|
1054
|
+
display: flex;
|
|
1055
|
+
align-items: center;
|
|
1056
|
+
justify-content: center;
|
|
1057
|
+
background: rgba(0, 0, 0, 0.4);
|
|
1058
|
+
pointer-events: auto;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.rec-detail {
|
|
1062
|
+
display: flex;
|
|
1063
|
+
flex-direction: column;
|
|
1064
|
+
width: min(640px, calc(100vw - 48px));
|
|
1065
|
+
max-height: calc(100vh - 80px);
|
|
1066
|
+
background: var(--board-bg);
|
|
1067
|
+
border: 1px solid var(--board-border);
|
|
1068
|
+
border-radius: 14px;
|
|
1069
|
+
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.24);
|
|
1070
|
+
color: var(--board-fg);
|
|
1071
|
+
font-family: var(--board-font-sans);
|
|
1072
|
+
overflow: hidden;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.rec-detail-header {
|
|
1076
|
+
display: flex;
|
|
1077
|
+
align-items: center;
|
|
1078
|
+
gap: var(--board-space-12);
|
|
1079
|
+
padding: var(--board-space-16) var(--board-space-16);
|
|
1080
|
+
border-bottom: 1px solid var(--board-border);
|
|
1081
|
+
flex: none;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.rec-detail-title {
|
|
1085
|
+
margin: 0;
|
|
1086
|
+
flex: 1;
|
|
1087
|
+
font-size: 15px;
|
|
1088
|
+
font-weight: var(--board-fw-semibold);
|
|
1089
|
+
letter-spacing: var(--board-tracking-tight);
|
|
1090
|
+
overflow-wrap: anywhere;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.rec-back {
|
|
1094
|
+
display: inline-flex;
|
|
1095
|
+
align-items: center;
|
|
1096
|
+
gap: 4px;
|
|
1097
|
+
padding: 5px var(--board-space-12);
|
|
1098
|
+
font-size: var(--board-text-xs);
|
|
1099
|
+
color: var(--board-fg);
|
|
1100
|
+
background: transparent;
|
|
1101
|
+
border: 1px solid var(--board-border);
|
|
1102
|
+
border-radius: var(--board-radius-lg);
|
|
1103
|
+
cursor: pointer;
|
|
1104
|
+
white-space: nowrap;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.rec-back:hover {
|
|
1108
|
+
background: var(--board-accent);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
.rec-detail-body {
|
|
1112
|
+
padding: var(--board-space-16);
|
|
1113
|
+
overflow-y: auto;
|
|
1114
|
+
display: flex;
|
|
1115
|
+
flex-direction: column;
|
|
1116
|
+
gap: var(--board-space-16);
|
|
1117
|
+
flex: 1;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
.rec-detail-section {
|
|
1121
|
+
display: flex;
|
|
1122
|
+
flex-direction: column;
|
|
1123
|
+
gap: 6px;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.rec-detail-section h3 {
|
|
1127
|
+
margin: 0;
|
|
1128
|
+
font-size: var(--board-text-xs);
|
|
1129
|
+
font-weight: var(--board-fw-semibold);
|
|
1130
|
+
color: var(--board-muted-fg);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.rec-detail-text {
|
|
1134
|
+
margin: 0;
|
|
1135
|
+
font-size: var(--board-text-sm);
|
|
1136
|
+
color: var(--board-muted-fg);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.rec-phase-row {
|
|
1140
|
+
display: flex;
|
|
1141
|
+
align-items: center;
|
|
1142
|
+
gap: var(--board-space-12);
|
|
1143
|
+
padding: var(--board-space-8) var(--board-space-12);
|
|
1144
|
+
border: 1px solid var(--board-border);
|
|
1145
|
+
border-radius: var(--board-radius-lg);
|
|
1146
|
+
font-size: 13px;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.rec-phase-id {
|
|
1150
|
+
min-width: 48px;
|
|
1151
|
+
font-family: var(--board-font-mono);
|
|
1152
|
+
font-size: var(--board-text-xs);
|
|
1153
|
+
letter-spacing: var(--board-tracking-mono);
|
|
1154
|
+
color: var(--board-info);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.rec-phase-name {
|
|
1158
|
+
flex: 1;
|
|
1159
|
+
color: var(--board-fg);
|
|
1160
|
+
overflow: hidden;
|
|
1161
|
+
text-overflow: ellipsis;
|
|
1162
|
+
white-space: nowrap;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.rec-lockhash {
|
|
1166
|
+
font-family: var(--board-font-mono);
|
|
1167
|
+
font-size: 11px;
|
|
1168
|
+
letter-spacing: var(--board-tracking-mono);
|
|
1169
|
+
color: var(--board-muted-fg);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/* ===== Strip (session dock) — keeps the shell --dsw-* theme ===== */
|
|
1173
|
+
.rec-badge {
|
|
1174
|
+
flex: none;
|
|
1175
|
+
padding: 2px 10px;
|
|
1176
|
+
font-size: 12px;
|
|
1177
|
+
border-radius: 999px;
|
|
1178
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
1179
|
+
color: var(--dsw-alias-label-secondary);
|
|
1180
|
+
}
|
|
1181
|
+
.rec-badge-tamper {
|
|
1182
|
+
color: var(--dsw-alias-state-error-primary);
|
|
1183
|
+
border-color: var(--dsw-alias-state-error-primary);
|
|
1184
|
+
}
|
|
1185
|
+
.rec-badge-gate {
|
|
1186
|
+
color: var(--dsw-alias-state-warn-primary);
|
|
1187
|
+
border-color: var(--dsw-alias-state-warn-primary);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1191
|
+
.rec-card, .rec-back, .rec-close, .rec-theme-toggle { transition: none; }
|
|
1192
|
+
}
|
|
1193
|
+
`;
|
|
1194
|
+
/**
|
|
1195
|
+
* Inject the board/inspector stylesheet once. Idempotent: if a <style
|
|
1196
|
+
* data-dsh-recursive> already exists, returns a disposer that does nothing
|
|
1197
|
+
* (the first injector owns the element). Returns a disposer that removes it.
|
|
1198
|
+
*/
|
|
1199
|
+
function injectBoardStyles() {
|
|
1200
|
+
if (typeof document === "undefined") return () => {};
|
|
1201
|
+
if (document.querySelector("style[data-dsh-recursive]") !== null) return () => {};
|
|
1202
|
+
const style = document.createElement("style");
|
|
1203
|
+
style.setAttribute("data-dsh-recursive", "");
|
|
1204
|
+
style.textContent = BOARD_CSS;
|
|
1205
|
+
document.head.appendChild(style);
|
|
1206
|
+
let disposed = false;
|
|
1207
|
+
return () => {
|
|
1208
|
+
if (disposed) return;
|
|
1209
|
+
disposed = true;
|
|
1210
|
+
style.remove();
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
//#endregion
|
|
769
1214
|
//#region src/client/slots.ts
|
|
770
1215
|
/**
|
|
771
1216
|
* Slot registrations (SP2 R1 + run 08 R1/R3/R4): the sidebar launcher, the
|
|
@@ -817,6 +1262,7 @@ window.__ModuleLoader__.load({
|
|
|
817
1262
|
}
|
|
818
1263
|
function registerSlots(ctx) {
|
|
819
1264
|
const disposers = [];
|
|
1265
|
+
disposers.push(injectBoardStyles());
|
|
820
1266
|
disposers.push(ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
821
1267
|
name: "sidebar.footer.action",
|
|
822
1268
|
id: "recursive",
|