@try-works/dsh-recursive-mode 0.1.8 → 0.1.9
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 +26 -1
- package/lib/client/inspector.d.ts +16 -1
- package/lib/client.js +278 -29
- package/package.json +1 -1
- package/src/client/board.tsx +79 -21
- package/src/client/inspector.tsx +46 -21
- package/src/client/slots.ts +2 -1
package/lib/client/board.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Board overlay (SP2 R1 + run 08 R3 + run 12): the kanban view over the live
|
|
3
|
+
* host-route projection. Pure read-only renderer — no filesystem, no run-file
|
|
4
|
+
* scraping. Cards are grouped by worktree root (the projection's outer key) and
|
|
5
|
+
* mapped to their current-phase lane via derive.columnForRun. Renders NOTHING
|
|
6
|
+
* when the snapshot is null (no recursive root / not loaded) or the current
|
|
7
|
+
* session is not on the recursive preset (the gate lives in slots.ts — here an
|
|
8
|
+
* empty projection with a null root is a no-op, not an "empty board").
|
|
9
|
+
*
|
|
10
|
+
* Run 08 R3: a card click opens the drill-down inspector via onOpenInspector
|
|
11
|
+
* (the overlay swaps board <-> inspector in slots.ts). Read-only still: the
|
|
12
|
+
* callback only selects a run for display; no mutation.
|
|
13
|
+
*
|
|
14
|
+
* Run 12 (LIVE BUG): shell.overlay is a click-through, unstyled frame layer —
|
|
15
|
+
* without inline styles the mounted board was INVISIBLE (no position/size/z)
|
|
16
|
+
* and CLICK-THROUGH (no pointer-events opt-in). Everything is styled inline
|
|
17
|
+
* (no CSS pipeline in the CJS client bundle): full-cover fixed overlay, opaque
|
|
18
|
+
* backdrop, pointerEvents auto, and a visible close button.
|
|
19
|
+
*/
|
|
20
|
+
import { type CSSProperties } from 'react';
|
|
1
21
|
import type { RecursiveProjection, RecursiveRunCard } from '../types.ts';
|
|
2
22
|
import type { LiveProjectionValue } from './contract.ts';
|
|
3
23
|
import type { BoardSelection } from './open-state.ts';
|
|
24
|
+
/** Full-cover overlay style — the shell.overlay seat is click-through + unstyled by default (run 12). */
|
|
25
|
+
export declare const overlayStyle: CSSProperties;
|
|
4
26
|
/** Flatten the worktree-grouped projection into a stable run list. */
|
|
5
27
|
export declare function listRuns(projection: RecursiveProjection | undefined): RecursiveRunCard[];
|
|
6
28
|
export interface BoardProps {
|
|
@@ -8,7 +30,10 @@ export interface BoardProps {
|
|
|
8
30
|
snapshot: LiveProjectionValue | null;
|
|
9
31
|
/** Opens the drill-down inspector for a run (R3). */
|
|
10
32
|
onOpenInspector?: (selection: BoardSelection) => void;
|
|
33
|
+
/** Closes the overlay (run 12). */
|
|
34
|
+
onClose?: () => void;
|
|
11
35
|
}
|
|
12
|
-
export declare function Board({ snapshot, onOpenInspector }: BoardProps): import("react").DetailedReactHTMLElement<{
|
|
36
|
+
export declare function Board({ snapshot, onOpenInspector, onClose }: BoardProps): import("react").DetailedReactHTMLElement<{
|
|
13
37
|
className: string;
|
|
38
|
+
style: CSSProperties;
|
|
14
39
|
}, HTMLElement> | null;
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drill-down inspector (SP2 R1 + run 12): the details-seat swap rendering one
|
|
3
|
+
* run's phase chain (with the always-shown 3.5 row + full 6/7/8 rows), lock
|
|
4
|
+
* badges, and tamper/gate facts — all from the live host-route snapshot, never
|
|
5
|
+
* disk.
|
|
6
|
+
*
|
|
7
|
+
* Run 12 (LIVE BUG): same as the board — shell.overlay is click-through and
|
|
8
|
+
* unstyled, so the inspector mounts INVISIBLE + CLICK-THROUGH without inline
|
|
9
|
+
* styles. Full-cover fixed overlay + pointerEvents auto + a back button and a
|
|
10
|
+
* close button.
|
|
11
|
+
*/
|
|
12
|
+
import { type CSSProperties } from 'react';
|
|
1
13
|
import type { LiveProjectionValue } from './contract.ts';
|
|
2
14
|
export interface InspectorProps {
|
|
3
15
|
runId: string;
|
|
4
16
|
worktreeRoot: string;
|
|
5
17
|
snapshot: LiveProjectionValue | null;
|
|
6
18
|
onBackToToolDetails: () => void;
|
|
19
|
+
/** Closes the overlay (run 12). */
|
|
20
|
+
onClose?: () => void;
|
|
7
21
|
}
|
|
8
|
-
export declare function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails }: InspectorProps): import("react").DetailedReactHTMLElement<{
|
|
22
|
+
export declare function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails, onClose }: InspectorProps): import("react").DetailedReactHTMLElement<{
|
|
9
23
|
className: string;
|
|
24
|
+
style: CSSProperties;
|
|
10
25
|
}, HTMLElement>;
|
package/lib/client.js
CHANGED
|
@@ -219,18 +219,102 @@ window.__ModuleLoader__.load({
|
|
|
219
219
|
//#endregion
|
|
220
220
|
//#region src/client/board.tsx
|
|
221
221
|
/**
|
|
222
|
-
* Board overlay (SP2 R1 + run 08 R3): the kanban view over the live
|
|
223
|
-
* projection. Pure read-only renderer — no filesystem, no run-file
|
|
224
|
-
* Cards are grouped by worktree root (the projection's outer key) and
|
|
225
|
-
* their current-phase lane via derive.columnForRun. Renders NOTHING
|
|
226
|
-
* snapshot is null (no recursive root / not loaded) or the current
|
|
227
|
-
* not on the recursive preset (the gate lives in slots.ts — here an
|
|
228
|
-
* projection with a null root is a no-op, not an "empty board").
|
|
222
|
+
* Board overlay (SP2 R1 + run 08 R3 + run 12): the kanban view over the live
|
|
223
|
+
* host-route projection. Pure read-only renderer — no filesystem, no run-file
|
|
224
|
+
* scraping. Cards are grouped by worktree root (the projection's outer key) and
|
|
225
|
+
* mapped to their current-phase lane via derive.columnForRun. Renders NOTHING
|
|
226
|
+
* when the snapshot is null (no recursive root / not loaded) or the current
|
|
227
|
+
* session is not on the recursive preset (the gate lives in slots.ts — here an
|
|
228
|
+
* empty projection with a null root is a no-op, not an "empty board").
|
|
229
229
|
*
|
|
230
230
|
* Run 08 R3: a card click opens the drill-down inspector via onOpenInspector
|
|
231
231
|
* (the overlay swaps board <-> inspector in slots.ts). Read-only still: the
|
|
232
232
|
* callback only selects a run for display; no mutation.
|
|
233
|
+
*
|
|
234
|
+
* Run 12 (LIVE BUG): shell.overlay is a click-through, unstyled frame layer —
|
|
235
|
+
* without inline styles the mounted board was INVISIBLE (no position/size/z)
|
|
236
|
+
* and CLICK-THROUGH (no pointer-events opt-in). Everything is styled inline
|
|
237
|
+
* (no CSS pipeline in the CJS client bundle): full-cover fixed overlay, opaque
|
|
238
|
+
* backdrop, pointerEvents auto, and a visible close button.
|
|
233
239
|
*/
|
|
240
|
+
/** Full-cover overlay style — the shell.overlay seat is click-through + unstyled by default (run 12). */
|
|
241
|
+
const overlayStyle = {
|
|
242
|
+
position: "fixed",
|
|
243
|
+
inset: 0,
|
|
244
|
+
background: "rgba(15, 17, 21, 0.92)",
|
|
245
|
+
color: "#e6e6e6",
|
|
246
|
+
zIndex: 9999,
|
|
247
|
+
overflowY: "auto",
|
|
248
|
+
padding: "24px 32px",
|
|
249
|
+
pointerEvents: "auto",
|
|
250
|
+
fontFamily: "system-ui, sans-serif"
|
|
251
|
+
};
|
|
252
|
+
const laneStyle = { marginBottom: 24 };
|
|
253
|
+
const laneTitleStyle = {
|
|
254
|
+
fontSize: 14,
|
|
255
|
+
textTransform: "uppercase",
|
|
256
|
+
letterSpacing: "0.08em",
|
|
257
|
+
marginBottom: 8,
|
|
258
|
+
color: "#aaa"
|
|
259
|
+
};
|
|
260
|
+
const cardStyle = {
|
|
261
|
+
display: "inline-block",
|
|
262
|
+
width: 280,
|
|
263
|
+
margin: "0 12px 12px 0",
|
|
264
|
+
padding: 12,
|
|
265
|
+
background: "#1f2329",
|
|
266
|
+
borderRadius: 8,
|
|
267
|
+
border: "1px solid #333",
|
|
268
|
+
cursor: "pointer",
|
|
269
|
+
verticalAlign: "top"
|
|
270
|
+
};
|
|
271
|
+
const cardHeadStyle = {
|
|
272
|
+
display: "flex",
|
|
273
|
+
justifyContent: "space-between",
|
|
274
|
+
marginBottom: 8
|
|
275
|
+
};
|
|
276
|
+
const badgeStyle$1 = {
|
|
277
|
+
fontSize: 11,
|
|
278
|
+
padding: "2px 6px",
|
|
279
|
+
borderRadius: 4,
|
|
280
|
+
background: "#333"
|
|
281
|
+
};
|
|
282
|
+
const badgeTamperStyle = { background: "#7a1f1f" };
|
|
283
|
+
const progressStyle = {
|
|
284
|
+
height: 6,
|
|
285
|
+
background: "#333",
|
|
286
|
+
borderRadius: 3,
|
|
287
|
+
overflow: "hidden"
|
|
288
|
+
};
|
|
289
|
+
const progressFillStyle = {
|
|
290
|
+
background: "#4c9aff",
|
|
291
|
+
height: "100%"
|
|
292
|
+
};
|
|
293
|
+
const metaStyle = {
|
|
294
|
+
marginTop: 8,
|
|
295
|
+
fontSize: 12,
|
|
296
|
+
color: "#aaa",
|
|
297
|
+
display: "flex",
|
|
298
|
+
justifyContent: "space-between"
|
|
299
|
+
};
|
|
300
|
+
const emptyStyle = {
|
|
301
|
+
padding: 40,
|
|
302
|
+
textAlign: "center",
|
|
303
|
+
color: "#888"
|
|
304
|
+
};
|
|
305
|
+
const closeStyle$1 = {
|
|
306
|
+
position: "absolute",
|
|
307
|
+
top: 12,
|
|
308
|
+
right: 16,
|
|
309
|
+
background: "#1f2329",
|
|
310
|
+
color: "#e6e6e6",
|
|
311
|
+
border: "1px solid #444",
|
|
312
|
+
borderRadius: 6,
|
|
313
|
+
padding: "4px 12px",
|
|
314
|
+
cursor: "pointer",
|
|
315
|
+
fontSize: 16,
|
|
316
|
+
zIndex: 1
|
|
317
|
+
};
|
|
234
318
|
/** Flatten the worktree-grouped projection into a stable run list. */
|
|
235
319
|
function listRuns(projection) {
|
|
236
320
|
if (projection === void 0) return [];
|
|
@@ -238,57 +322,220 @@ window.__ModuleLoader__.load({
|
|
|
238
322
|
for (const worktreeRoot of Object.keys(projection)) for (const runId of Object.keys(projection[worktreeRoot])) runs.push(projection[worktreeRoot][runId]);
|
|
239
323
|
return runs;
|
|
240
324
|
}
|
|
241
|
-
function Board({ snapshot, onOpenInspector }) {
|
|
325
|
+
function Board({ snapshot, onOpenInspector, onClose }) {
|
|
242
326
|
if (snapshot === null || snapshot.root === null) return null;
|
|
243
327
|
const runs = listRuns(snapshot.projection);
|
|
328
|
+
const close = onClose !== void 0 ? (0, react.createElement)("button", {
|
|
329
|
+
className: "rec-close",
|
|
330
|
+
style: closeStyle$1,
|
|
331
|
+
onClick: onClose,
|
|
332
|
+
title: "Close"
|
|
333
|
+
}, "×") : null;
|
|
244
334
|
if (runs.length === 0) return (0, react.createElement)("div", {
|
|
245
335
|
className: "rec-board",
|
|
246
|
-
"data-empty": true
|
|
247
|
-
|
|
336
|
+
"data-empty": true,
|
|
337
|
+
style: overlayStyle
|
|
338
|
+
}, close, (0, react.createElement)("p", {
|
|
339
|
+
className: "rec-board-empty",
|
|
340
|
+
style: emptyStyle
|
|
341
|
+
}, "No recursive runs in this workspace yet."));
|
|
248
342
|
const open = (run) => () => onOpenInspector?.({
|
|
249
343
|
worktreeRoot: run.worktreeRoot,
|
|
250
344
|
runId: run.runId
|
|
251
345
|
});
|
|
252
|
-
return (0, react.createElement)("div", {
|
|
346
|
+
return (0, react.createElement)("div", {
|
|
347
|
+
className: "rec-board",
|
|
348
|
+
style: overlayStyle
|
|
349
|
+
}, close, KANBAN_LANES.map((lane) => {
|
|
253
350
|
const laneRuns = runs.filter((r) => columnForRun(r) === lane.id);
|
|
254
351
|
return (0, react.createElement)("section", {
|
|
255
352
|
key: lane.id,
|
|
256
|
-
className: "rec-lane"
|
|
257
|
-
|
|
353
|
+
className: "rec-lane",
|
|
354
|
+
style: laneStyle
|
|
355
|
+
}, (0, react.createElement)("h2", {
|
|
356
|
+
className: "rec-lane-title",
|
|
357
|
+
style: laneTitleStyle
|
|
358
|
+
}, lane.label), laneRuns.map((run) => {
|
|
258
359
|
const facts = cardFacts(run);
|
|
259
360
|
return (0, react.createElement)("article", {
|
|
260
361
|
key: run.worktreeRoot + "\0" + run.runId,
|
|
261
362
|
className: "rec-card",
|
|
363
|
+
style: cardStyle,
|
|
262
364
|
onClick: open(run)
|
|
263
|
-
}, (0, react.createElement)("div", {
|
|
365
|
+
}, (0, react.createElement)("div", {
|
|
366
|
+
className: "rec-card-head",
|
|
367
|
+
style: cardHeadStyle
|
|
368
|
+
}, (0, react.createElement)("strong", {}, run.runId), facts.tampered && (0, react.createElement)("span", {
|
|
264
369
|
className: "rec-badge rec-badge-tamper",
|
|
370
|
+
style: {
|
|
371
|
+
...badgeStyle$1,
|
|
372
|
+
...badgeTamperStyle
|
|
373
|
+
},
|
|
265
374
|
title: "tampered"
|
|
266
|
-
}, "!")), (0, react.createElement)("div", {
|
|
375
|
+
}, "!")), (0, react.createElement)("div", {
|
|
376
|
+
className: "rec-card-progress",
|
|
377
|
+
style: progressStyle
|
|
378
|
+
}, (0, react.createElement)("div", {
|
|
267
379
|
className: "rec-card-progress-fill",
|
|
268
|
-
style: {
|
|
269
|
-
|
|
380
|
+
style: {
|
|
381
|
+
...progressFillStyle,
|
|
382
|
+
width: Math.round(facts.progress * 100) + "%"
|
|
383
|
+
}
|
|
384
|
+
})), (0, react.createElement)("div", {
|
|
385
|
+
className: "rec-card-meta",
|
|
386
|
+
style: metaStyle
|
|
387
|
+
}, (0, react.createElement)("span", {}, facts.currentPhase ?? "no phases"), (0, react.createElement)("span", {
|
|
388
|
+
className: "rec-badge",
|
|
389
|
+
style: badgeStyle$1
|
|
390
|
+
}, facts.state)));
|
|
270
391
|
}));
|
|
271
392
|
}));
|
|
272
393
|
}
|
|
273
394
|
//#endregion
|
|
274
395
|
//#region src/client/inspector.tsx
|
|
275
396
|
/**
|
|
276
|
-
* Drill-down inspector (SP2 R1): the details-seat swap rendering one
|
|
277
|
-
* phase chain (with the always-shown 3.5 row + full 6/7/8 rows), lock
|
|
278
|
-
* and tamper/gate facts — all from the live host-route snapshot, never
|
|
397
|
+
* Drill-down inspector (SP2 R1 + run 12): the details-seat swap rendering one
|
|
398
|
+
* run's phase chain (with the always-shown 3.5 row + full 6/7/8 rows), lock
|
|
399
|
+
* badges, and tamper/gate facts — all from the live host-route snapshot, never
|
|
400
|
+
* disk.
|
|
401
|
+
*
|
|
402
|
+
* Run 12 (LIVE BUG): same as the board — shell.overlay is click-through and
|
|
403
|
+
* unstyled, so the inspector mounts INVISIBLE + CLICK-THROUGH without inline
|
|
404
|
+
* styles. Full-cover fixed overlay + pointerEvents auto + a back button and a
|
|
405
|
+
* close button.
|
|
279
406
|
*/
|
|
280
|
-
|
|
407
|
+
const headStyle = {
|
|
408
|
+
display: "flex",
|
|
409
|
+
alignItems: "center",
|
|
410
|
+
gap: 12,
|
|
411
|
+
marginBottom: 16,
|
|
412
|
+
borderBottom: "1px solid #333",
|
|
413
|
+
paddingBottom: 12
|
|
414
|
+
};
|
|
415
|
+
const backStyle = {
|
|
416
|
+
background: "#1f2329",
|
|
417
|
+
color: "#e6e6e6",
|
|
418
|
+
border: "1px solid #444",
|
|
419
|
+
borderRadius: 6,
|
|
420
|
+
padding: "6px 12px",
|
|
421
|
+
cursor: "pointer"
|
|
422
|
+
};
|
|
423
|
+
const closeStyle = {
|
|
424
|
+
background: "#1f2329",
|
|
425
|
+
color: "#e6e6e6",
|
|
426
|
+
border: "1px solid #444",
|
|
427
|
+
borderRadius: 6,
|
|
428
|
+
padding: "6px 12px",
|
|
429
|
+
cursor: "pointer",
|
|
430
|
+
marginLeft: "auto"
|
|
431
|
+
};
|
|
432
|
+
const sectionStyle = { marginBottom: 16 };
|
|
433
|
+
const rowStyle = {
|
|
434
|
+
display: "flex",
|
|
435
|
+
gap: 12,
|
|
436
|
+
alignItems: "center",
|
|
437
|
+
padding: "6px 0",
|
|
438
|
+
borderBottom: "1px solid #222",
|
|
439
|
+
fontSize: 13
|
|
440
|
+
};
|
|
441
|
+
const phaseIdStyle = {
|
|
442
|
+
minWidth: 220,
|
|
443
|
+
fontFamily: "monospace",
|
|
444
|
+
color: "#4c9aff"
|
|
445
|
+
};
|
|
446
|
+
const phaseNameStyle = {
|
|
447
|
+
flex: 1,
|
|
448
|
+
color: "#ccc"
|
|
449
|
+
};
|
|
450
|
+
const badgeStyle = {
|
|
451
|
+
fontSize: 11,
|
|
452
|
+
padding: "2px 6px",
|
|
453
|
+
borderRadius: 4,
|
|
454
|
+
background: "#333"
|
|
455
|
+
};
|
|
456
|
+
const lockhashStyle = {
|
|
457
|
+
fontFamily: "monospace",
|
|
458
|
+
fontSize: 11,
|
|
459
|
+
color: "#888"
|
|
460
|
+
};
|
|
461
|
+
function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails, onClose }) {
|
|
462
|
+
const close = onClose !== void 0 ? (0, react.createElement)("button", {
|
|
463
|
+
className: "rec-close",
|
|
464
|
+
style: closeStyle,
|
|
465
|
+
onClick: onClose,
|
|
466
|
+
title: "Close"
|
|
467
|
+
}, "×") : null;
|
|
281
468
|
const card = snapshot?.projection?.[worktreeRoot]?.[runId];
|
|
282
|
-
if (card === void 0) return (0, react.createElement)("div", {
|
|
469
|
+
if (card === void 0) return (0, react.createElement)("div", {
|
|
470
|
+
className: "rec-inspector",
|
|
471
|
+
style: overlayStyle
|
|
472
|
+
}, (0, react.createElement)("div", {
|
|
473
|
+
className: "rec-inspector-head",
|
|
474
|
+
style: headStyle
|
|
475
|
+
}, (0, react.createElement)("button", {
|
|
476
|
+
onClick: onBackToToolDetails,
|
|
477
|
+
className: "rec-back",
|
|
478
|
+
style: backStyle
|
|
479
|
+
}, "← back to tool details"), close), (0, react.createElement)("p", {}, "Run not found: " + runId));
|
|
283
480
|
const facts = cardFacts(card);
|
|
284
481
|
const rows = expandPhaseRows(card);
|
|
285
|
-
return (0, react.createElement)("div", {
|
|
482
|
+
return (0, react.createElement)("div", {
|
|
483
|
+
className: "rec-inspector",
|
|
484
|
+
style: overlayStyle
|
|
485
|
+
}, (0, react.createElement)("div", {
|
|
486
|
+
className: "rec-inspector-head",
|
|
487
|
+
style: headStyle
|
|
488
|
+
}, (0, react.createElement)("button", {
|
|
286
489
|
onClick: onBackToToolDetails,
|
|
287
|
-
className: "rec-back"
|
|
288
|
-
|
|
490
|
+
className: "rec-back",
|
|
491
|
+
style: backStyle
|
|
492
|
+
}, "← back to tool details"), (0, react.createElement)("h2", { style: {
|
|
493
|
+
margin: 0,
|
|
494
|
+
fontSize: 18
|
|
495
|
+
} }, runId), (0, react.createElement)("span", {
|
|
496
|
+
className: "rec-badge",
|
|
497
|
+
style: badgeStyle
|
|
498
|
+
}, facts.state), facts.tampered && (0, react.createElement)("span", {
|
|
499
|
+
className: "rec-badge rec-badge-tamper",
|
|
500
|
+
style: {
|
|
501
|
+
...badgeStyle,
|
|
502
|
+
background: "#7a1f1f"
|
|
503
|
+
}
|
|
504
|
+
}, "tampered"), close), (0, react.createElement)("section", {
|
|
505
|
+
className: "rec-inspector-section",
|
|
506
|
+
style: sectionStyle
|
|
507
|
+
}, (0, react.createElement)("h3", { style: {
|
|
508
|
+
fontSize: 13,
|
|
509
|
+
textTransform: "uppercase",
|
|
510
|
+
letterSpacing: "0.08em",
|
|
511
|
+
color: "#aaa",
|
|
512
|
+
marginBottom: 8
|
|
513
|
+
} }, "Phases"), rows.map((row) => (0, react.createElement)("div", {
|
|
289
514
|
key: row.phase,
|
|
290
|
-
className: "rec-phase-row"
|
|
291
|
-
|
|
515
|
+
className: "rec-phase-row",
|
|
516
|
+
style: rowStyle
|
|
517
|
+
}, (0, react.createElement)("span", {
|
|
518
|
+
className: "rec-phase-id",
|
|
519
|
+
style: phaseIdStyle
|
|
520
|
+
}, row.phase), (0, react.createElement)("span", {
|
|
521
|
+
className: "rec-phase-name",
|
|
522
|
+
style: phaseNameStyle
|
|
523
|
+
}, row.fileName ?? "—"), (0, react.createElement)("span", {
|
|
524
|
+
className: "rec-badge",
|
|
525
|
+
style: badgeStyle
|
|
526
|
+
}, row.status), row.lockHash !== void 0 && (0, react.createElement)("code", {
|
|
527
|
+
className: "rec-lockhash",
|
|
528
|
+
style: lockhashStyle
|
|
529
|
+
}, "#" + row.lockHash.slice(0, 8))))), facts.gateBlocked && (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
|
+
} }, "Gate"), (0, react.createElement)("p", {}, "Blocked (" + (facts.gateKind ?? "") + ")")));
|
|
292
539
|
}
|
|
293
540
|
//#endregion
|
|
294
541
|
//#region src/client/strip.tsx
|
|
@@ -607,11 +854,13 @@ window.__ModuleLoader__.load({
|
|
|
607
854
|
runId: board.selection.runId,
|
|
608
855
|
worktreeRoot: board.selection.worktreeRoot,
|
|
609
856
|
snapshot,
|
|
610
|
-
onBackToToolDetails: () => boardState.backToBoard()
|
|
857
|
+
onBackToToolDetails: () => boardState.backToBoard(),
|
|
858
|
+
onClose: () => boardState.close()
|
|
611
859
|
});
|
|
612
860
|
return (0, react.createElement)(Board, {
|
|
613
861
|
snapshot,
|
|
614
|
-
onOpenInspector: (sel) => boardState.openInspector(sel)
|
|
862
|
+
onOpenInspector: (sel) => boardState.openInspector(sel),
|
|
863
|
+
onClose: () => boardState.close()
|
|
615
864
|
});
|
|
616
865
|
}
|
|
617
866
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-recursive-mode",
|
|
3
3
|
"description": "recursive-mode workflow as a DeepSeek Harness bundle: RecursiveRuntime service + recursive_status tool",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.9",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
package/src/client/board.tsx
CHANGED
|
@@ -1,22 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Board overlay (SP2 R1 + run 08 R3): the kanban view over the live
|
|
3
|
-
* projection. Pure read-only renderer — no filesystem, no run-file
|
|
4
|
-
* Cards are grouped by worktree root (the projection's outer key) and
|
|
5
|
-
* their current-phase lane via derive.columnForRun. Renders NOTHING
|
|
6
|
-
* snapshot is null (no recursive root / not loaded) or the current
|
|
7
|
-
* not on the recursive preset (the gate lives in slots.ts — here an
|
|
8
|
-
* projection with a null root is a no-op, not an "empty board").
|
|
2
|
+
* Board overlay (SP2 R1 + run 08 R3 + run 12): the kanban view over the live
|
|
3
|
+
* host-route projection. Pure read-only renderer — no filesystem, no run-file
|
|
4
|
+
* scraping. Cards are grouped by worktree root (the projection's outer key) and
|
|
5
|
+
* mapped to their current-phase lane via derive.columnForRun. Renders NOTHING
|
|
6
|
+
* when the snapshot is null (no recursive root / not loaded) or the current
|
|
7
|
+
* session is not on the recursive preset (the gate lives in slots.ts — here an
|
|
8
|
+
* empty projection with a null root is a no-op, not an "empty board").
|
|
9
9
|
*
|
|
10
10
|
* Run 08 R3: a card click opens the drill-down inspector via onOpenInspector
|
|
11
11
|
* (the overlay swaps board <-> inspector in slots.ts). Read-only still: the
|
|
12
12
|
* callback only selects a run for display; no mutation.
|
|
13
|
+
*
|
|
14
|
+
* Run 12 (LIVE BUG): shell.overlay is a click-through, unstyled frame layer —
|
|
15
|
+
* without inline styles the mounted board was INVISIBLE (no position/size/z)
|
|
16
|
+
* and CLICK-THROUGH (no pointer-events opt-in). Everything is styled inline
|
|
17
|
+
* (no CSS pipeline in the CJS client bundle): full-cover fixed overlay, opaque
|
|
18
|
+
* backdrop, pointerEvents auto, and a visible close button.
|
|
13
19
|
*/
|
|
14
|
-
import { createElement } from 'react'
|
|
20
|
+
import { createElement, type CSSProperties } from 'react'
|
|
15
21
|
import type { RecursiveProjection, RecursiveRunCard } from '../types.ts'
|
|
16
22
|
import type { LiveProjectionValue } from './contract.ts'
|
|
17
23
|
import { cardFacts, columnForRun, KANBAN_LANES } from './derive.ts'
|
|
18
24
|
import type { BoardSelection } from './open-state.ts'
|
|
19
25
|
|
|
26
|
+
/** Full-cover overlay style — the shell.overlay seat is click-through + unstyled by default (run 12). */
|
|
27
|
+
export const overlayStyle: CSSProperties = {
|
|
28
|
+
position: 'fixed',
|
|
29
|
+
inset: 0,
|
|
30
|
+
background: 'rgba(15, 17, 21, 0.92)',
|
|
31
|
+
color: '#e6e6e6',
|
|
32
|
+
zIndex: 9999,
|
|
33
|
+
overflowY: 'auto',
|
|
34
|
+
padding: '24px 32px',
|
|
35
|
+
pointerEvents: 'auto',
|
|
36
|
+
fontFamily: 'system-ui, sans-serif',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const laneStyle: CSSProperties = { marginBottom: 24 }
|
|
40
|
+
const laneTitleStyle: CSSProperties = { fontSize: 14, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8, color: '#aaa' }
|
|
41
|
+
const cardStyle: CSSProperties = {
|
|
42
|
+
display: 'inline-block',
|
|
43
|
+
width: 280,
|
|
44
|
+
margin: '0 12px 12px 0',
|
|
45
|
+
padding: 12,
|
|
46
|
+
background: '#1f2329',
|
|
47
|
+
borderRadius: 8,
|
|
48
|
+
border: '1px solid #333',
|
|
49
|
+
cursor: 'pointer',
|
|
50
|
+
verticalAlign: 'top',
|
|
51
|
+
}
|
|
52
|
+
const cardHeadStyle: CSSProperties = { display: 'flex', justifyContent: 'space-between', marginBottom: 8 }
|
|
53
|
+
const badgeStyle: CSSProperties = { fontSize: 11, padding: '2px 6px', borderRadius: 4, background: '#333' }
|
|
54
|
+
const badgeTamperStyle: CSSProperties = { background: '#7a1f1f' }
|
|
55
|
+
const progressStyle: CSSProperties = { height: 6, background: '#333', borderRadius: 3, overflow: 'hidden' }
|
|
56
|
+
const progressFillStyle: CSSProperties = { background: '#4c9aff', height: '100%' }
|
|
57
|
+
const metaStyle: CSSProperties = { marginTop: 8, fontSize: 12, color: '#aaa', display: 'flex', justifyContent: 'space-between' }
|
|
58
|
+
const emptyStyle: CSSProperties = { padding: 40, textAlign: 'center', color: '#888' }
|
|
59
|
+
const closeStyle: CSSProperties = {
|
|
60
|
+
position: 'absolute',
|
|
61
|
+
top: 12,
|
|
62
|
+
right: 16,
|
|
63
|
+
background: '#1f2329',
|
|
64
|
+
color: '#e6e6e6',
|
|
65
|
+
border: '1px solid #444',
|
|
66
|
+
borderRadius: 6,
|
|
67
|
+
padding: '4px 12px',
|
|
68
|
+
cursor: 'pointer',
|
|
69
|
+
fontSize: 16,
|
|
70
|
+
zIndex: 1,
|
|
71
|
+
}
|
|
72
|
+
|
|
20
73
|
/** Flatten the worktree-grouped projection into a stable run list. */
|
|
21
74
|
export function listRuns(projection: RecursiveProjection | undefined): RecursiveRunCard[] {
|
|
22
75
|
if (projection === undefined) return []
|
|
@@ -34,35 +87,40 @@ export interface BoardProps {
|
|
|
34
87
|
snapshot: LiveProjectionValue | null
|
|
35
88
|
/** Opens the drill-down inspector for a run (R3). */
|
|
36
89
|
onOpenInspector?: (selection: BoardSelection) => void
|
|
90
|
+
/** Closes the overlay (run 12). */
|
|
91
|
+
onClose?: () => void
|
|
37
92
|
}
|
|
38
93
|
|
|
39
|
-
export function Board({ snapshot, onOpenInspector }: BoardProps) {
|
|
94
|
+
export function Board({ snapshot, onOpenInspector, onClose }: BoardProps) {
|
|
40
95
|
if (snapshot === null || snapshot.root === null) return null
|
|
41
96
|
const runs = listRuns(snapshot.projection)
|
|
97
|
+
const close = onClose !== undefined ? createElement('button', { className: 'rec-close', style: closeStyle, onClick: onClose, title: 'Close' }, '×') : null
|
|
42
98
|
if (runs.length === 0) {
|
|
43
|
-
return createElement('div', { className: 'rec-board', 'data-empty': true },
|
|
44
|
-
|
|
99
|
+
return createElement('div', { className: 'rec-board', 'data-empty': true, style: overlayStyle },
|
|
100
|
+
close,
|
|
101
|
+
createElement('p', { className: 'rec-board-empty', style: emptyStyle }, 'No recursive runs in this workspace yet.'),
|
|
45
102
|
)
|
|
46
103
|
}
|
|
47
104
|
const open = (run: RecursiveRunCard) => () => onOpenInspector?.({ worktreeRoot: run.worktreeRoot, runId: run.runId })
|
|
48
|
-
return createElement('div', { className: 'rec-board' },
|
|
105
|
+
return createElement('div', { className: 'rec-board', style: overlayStyle },
|
|
106
|
+
close,
|
|
49
107
|
KANBAN_LANES.map((lane) => {
|
|
50
108
|
const laneRuns = runs.filter((r) => columnForRun(r) === lane.id)
|
|
51
|
-
return createElement('section', { key: lane.id, className: 'rec-lane' },
|
|
52
|
-
createElement('h2', { className: 'rec-lane-title' }, lane.label),
|
|
109
|
+
return createElement('section', { key: lane.id, className: 'rec-lane', style: laneStyle },
|
|
110
|
+
createElement('h2', { className: 'rec-lane-title', style: laneTitleStyle }, lane.label),
|
|
53
111
|
laneRuns.map((run) => {
|
|
54
112
|
const facts = cardFacts(run)
|
|
55
|
-
return createElement('article', { key: run.worktreeRoot + '\u0000' + run.runId, className: 'rec-card', onClick: open(run) },
|
|
56
|
-
createElement('div', { className: 'rec-card-head' },
|
|
113
|
+
return createElement('article', { key: run.worktreeRoot + '\u0000' + run.runId, className: 'rec-card', style: cardStyle, onClick: open(run) },
|
|
114
|
+
createElement('div', { className: 'rec-card-head', style: cardHeadStyle },
|
|
57
115
|
createElement('strong', {}, run.runId),
|
|
58
|
-
facts.tampered && createElement('span', { className: 'rec-badge rec-badge-tamper', title: 'tampered' }, '!'),
|
|
116
|
+
facts.tampered && createElement('span', { className: 'rec-badge rec-badge-tamper', style: { ...badgeStyle, ...badgeTamperStyle }, title: 'tampered' }, '!'),
|
|
59
117
|
),
|
|
60
|
-
createElement('div', { className: 'rec-card-progress' },
|
|
61
|
-
createElement('div', { className: 'rec-card-progress-fill', style: { width: Math.round(facts.progress * 100) + '%' } }),
|
|
118
|
+
createElement('div', { className: 'rec-card-progress', style: progressStyle },
|
|
119
|
+
createElement('div', { className: 'rec-card-progress-fill', style: { ...progressFillStyle, width: Math.round(facts.progress * 100) + '%' } }),
|
|
62
120
|
),
|
|
63
|
-
createElement('div', { className: 'rec-card-meta' },
|
|
121
|
+
createElement('div', { className: 'rec-card-meta', style: metaStyle },
|
|
64
122
|
createElement('span', {}, facts.currentPhase ?? 'no phases'),
|
|
65
|
-
createElement('span', { className: 'rec-badge' }, facts.state),
|
|
123
|
+
createElement('span', { className: 'rec-badge', style: badgeStyle }, facts.state),
|
|
66
124
|
),
|
|
67
125
|
)
|
|
68
126
|
}),
|
package/src/client/inspector.tsx
CHANGED
|
@@ -1,46 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Drill-down inspector (SP2 R1): the details-seat swap rendering one
|
|
3
|
-
* phase chain (with the always-shown 3.5 row + full 6/7/8 rows), lock
|
|
4
|
-
* and tamper/gate facts — all from the live host-route snapshot, never
|
|
2
|
+
* Drill-down inspector (SP2 R1 + run 12): the details-seat swap rendering one
|
|
3
|
+
* run's phase chain (with the always-shown 3.5 row + full 6/7/8 rows), lock
|
|
4
|
+
* badges, and tamper/gate facts — all from the live host-route snapshot, never
|
|
5
|
+
* disk.
|
|
6
|
+
*
|
|
7
|
+
* Run 12 (LIVE BUG): same as the board — shell.overlay is click-through and
|
|
8
|
+
* unstyled, so the inspector mounts INVISIBLE + CLICK-THROUGH without inline
|
|
9
|
+
* styles. Full-cover fixed overlay + pointerEvents auto + a back button and a
|
|
10
|
+
* close button.
|
|
5
11
|
*/
|
|
6
|
-
import { createElement } from 'react'
|
|
12
|
+
import { createElement, type CSSProperties } from 'react'
|
|
7
13
|
import type { LiveProjectionValue } from './contract.ts'
|
|
8
14
|
import { cardFacts, expandPhaseRows } from './derive.ts'
|
|
15
|
+
import { overlayStyle } from './board.tsx'
|
|
16
|
+
|
|
17
|
+
const headStyle: CSSProperties = { display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16, borderBottom: '1px solid #333', paddingBottom: 12 }
|
|
18
|
+
const backStyle: CSSProperties = { background: '#1f2329', color: '#e6e6e6', border: '1px solid #444', borderRadius: 6, padding: '6px 12px', cursor: 'pointer' }
|
|
19
|
+
const closeStyle: CSSProperties = { background: '#1f2329', color: '#e6e6e6', border: '1px solid #444', borderRadius: 6, padding: '6px 12px', cursor: 'pointer', marginLeft: 'auto' }
|
|
20
|
+
const sectionStyle: CSSProperties = { marginBottom: 16 }
|
|
21
|
+
const rowStyle: CSSProperties = { display: 'flex', gap: 12, alignItems: 'center', padding: '6px 0', borderBottom: '1px solid #222', fontSize: 13 }
|
|
22
|
+
const phaseIdStyle: CSSProperties = { minWidth: 220, fontFamily: 'monospace', color: '#4c9aff' }
|
|
23
|
+
const phaseNameStyle: CSSProperties = { flex: 1, color: '#ccc' }
|
|
24
|
+
const badgeStyle: CSSProperties = { fontSize: 11, padding: '2px 6px', borderRadius: 4, background: '#333' }
|
|
25
|
+
const lockhashStyle: CSSProperties = { fontFamily: 'monospace', fontSize: 11, color: '#888' }
|
|
9
26
|
|
|
10
27
|
export interface InspectorProps {
|
|
11
28
|
runId: string
|
|
12
29
|
worktreeRoot: string
|
|
13
30
|
snapshot: LiveProjectionValue | null
|
|
14
31
|
onBackToToolDetails: () => void
|
|
32
|
+
/** Closes the overlay (run 12). */
|
|
33
|
+
onClose?: () => void
|
|
15
34
|
}
|
|
16
35
|
|
|
17
|
-
export function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails }: InspectorProps) {
|
|
36
|
+
export function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails, onClose }: InspectorProps) {
|
|
37
|
+
const close = onClose !== undefined ? createElement('button', { className: 'rec-close', style: closeStyle, onClick: onClose, title: 'Close' }, '×') : null
|
|
18
38
|
const card = snapshot?.projection?.[worktreeRoot]?.[runId]
|
|
19
39
|
if (card === undefined) {
|
|
20
|
-
return createElement('div', { className: 'rec-inspector' },
|
|
40
|
+
return createElement('div', { className: 'rec-inspector', style: overlayStyle },
|
|
41
|
+
createElement('div', { className: 'rec-inspector-head', style: headStyle },
|
|
42
|
+
createElement('button', { onClick: onBackToToolDetails, className: 'rec-back', style: backStyle }, '← back to tool details'),
|
|
43
|
+
close,
|
|
44
|
+
),
|
|
21
45
|
createElement('p', {}, 'Run not found: ' + runId),
|
|
22
46
|
)
|
|
23
47
|
}
|
|
24
48
|
const facts = cardFacts(card)
|
|
25
49
|
const rows = expandPhaseRows(card)
|
|
26
|
-
return createElement('div', { className: 'rec-inspector' },
|
|
27
|
-
createElement('div', { className: 'rec-inspector-head' },
|
|
28
|
-
createElement('button', { onClick: onBackToToolDetails, className: 'rec-back' }, '← back to tool details'),
|
|
29
|
-
createElement('h2', {}, runId),
|
|
30
|
-
createElement('span', { className: 'rec-badge' }, facts.state),
|
|
31
|
-
facts.tampered && createElement('span', { className: 'rec-badge rec-badge-tamper' }, 'tampered'),
|
|
50
|
+
return createElement('div', { className: 'rec-inspector', style: overlayStyle },
|
|
51
|
+
createElement('div', { className: 'rec-inspector-head', style: headStyle },
|
|
52
|
+
createElement('button', { onClick: onBackToToolDetails, className: 'rec-back', style: backStyle }, '← back to tool details'),
|
|
53
|
+
createElement('h2', { style: { margin: 0, fontSize: 18 } }, runId),
|
|
54
|
+
createElement('span', { className: 'rec-badge', style: badgeStyle }, facts.state),
|
|
55
|
+
facts.tampered && createElement('span', { className: 'rec-badge rec-badge-tamper', style: { ...badgeStyle, background: '#7a1f1f' } }, 'tampered'),
|
|
56
|
+
close,
|
|
32
57
|
),
|
|
33
|
-
createElement('section', { className: 'rec-inspector-section' },
|
|
34
|
-
createElement('h3', {}, 'Phases'),
|
|
35
|
-
rows.map((row) => createElement('div', { key: row.phase, className: 'rec-phase-row' },
|
|
36
|
-
createElement('span', { className: 'rec-phase-id' }, row.phase),
|
|
37
|
-
createElement('span', { className: 'rec-phase-name' }, row.fileName ?? '—'),
|
|
38
|
-
createElement('span', { className: 'rec-badge' }, row.status),
|
|
39
|
-
row.lockHash !== undefined && createElement('code', { className: 'rec-lockhash' }, '#' + row.lockHash.slice(0, 8)),
|
|
58
|
+
createElement('section', { className: 'rec-inspector-section', style: sectionStyle },
|
|
59
|
+
createElement('h3', { style: { fontSize: 13, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#aaa', marginBottom: 8 } }, 'Phases'),
|
|
60
|
+
rows.map((row) => createElement('div', { key: row.phase, className: 'rec-phase-row', style: rowStyle },
|
|
61
|
+
createElement('span', { className: 'rec-phase-id', style: phaseIdStyle }, row.phase),
|
|
62
|
+
createElement('span', { className: 'rec-phase-name', style: phaseNameStyle }, row.fileName ?? '—'),
|
|
63
|
+
createElement('span', { className: 'rec-badge', style: badgeStyle }, row.status),
|
|
64
|
+
row.lockHash !== undefined && createElement('code', { className: 'rec-lockhash', style: lockhashStyle }, '#' + row.lockHash.slice(0, 8)),
|
|
40
65
|
)),
|
|
41
66
|
),
|
|
42
|
-
facts.gateBlocked && createElement('section', { className: 'rec-inspector-section' },
|
|
43
|
-
createElement('h3', {}, 'Gate'),
|
|
67
|
+
facts.gateBlocked && createElement('section', { className: 'rec-inspector-section', style: sectionStyle },
|
|
68
|
+
createElement('h3', { style: { fontSize: 13, textTransform: 'uppercase', letterSpacing: '0.08em', color: '#aaa', marginBottom: 8 } }, 'Gate'),
|
|
44
69
|
createElement('p', {}, 'Blocked (' + (facts.gateKind ?? '') + ')'),
|
|
45
70
|
),
|
|
46
71
|
)
|
package/src/client/slots.ts
CHANGED
|
@@ -143,9 +143,10 @@ function RecursiveBoardOverlay({ useSessions }: { useSessions: SnapshotSelectorH
|
|
|
143
143
|
worktreeRoot: board.selection.worktreeRoot,
|
|
144
144
|
snapshot,
|
|
145
145
|
onBackToToolDetails: () => boardState.backToBoard(),
|
|
146
|
+
onClose: () => boardState.close(),
|
|
146
147
|
})
|
|
147
148
|
}
|
|
148
|
-
return createElement(Board, { snapshot, onOpenInspector: (sel) => boardState.openInspector(sel) })
|
|
149
|
+
return createElement(Board, { snapshot, onOpenInspector: (sel) => boardState.openInspector(sel), onClose: () => boardState.close() })
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
/**
|