@try-works/dsh-recursive-mode 0.1.11 → 0.1.12
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 +0 -26
- package/lib/client/inspector.d.ts +0 -14
- package/lib/client/styles.d.ts +15 -0
- package/lib/client.js +468 -274
- package/package.json +1 -1
- package/src/client/board.tsx +53 -95
- package/src/client/inspector.tsx +40 -49
- package/src/client/slots.ts +3 -0
- package/src/client/styles.ts +397 -0
package/lib/client.js
CHANGED
|
@@ -244,102 +244,14 @@ window.__ModuleLoader__.load({
|
|
|
244
244
|
//#endregion
|
|
245
245
|
//#region src/client/board.tsx
|
|
246
246
|
/**
|
|
247
|
-
* Board overlay (SP2 R1 + run 08 R3 + run
|
|
247
|
+
* Board overlay (SP2 R1 + run 08 R3 + run 15): the kanban view over the live
|
|
248
248
|
* host-route projection. Pure read-only renderer — no filesystem, no run-file
|
|
249
|
-
* scraping. Cards are grouped by worktree root
|
|
250
|
-
*
|
|
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").
|
|
249
|
+
* scraping. Cards are grouped by worktree root and mapped to their current-phase
|
|
250
|
+
* lane via derive.columnForRun.
|
|
254
251
|
*
|
|
255
|
-
* Run
|
|
256
|
-
*
|
|
257
|
-
* callback only selects a run for display; no mutation.
|
|
258
|
-
*
|
|
259
|
-
* Run 12 (LIVE BUG): shell.overlay is a click-through, unstyled frame layer —
|
|
260
|
-
* without inline styles the mounted board was INVISIBLE (no position/size/z)
|
|
261
|
-
* and CLICK-THROUGH (no pointer-events opt-in). Everything is styled inline
|
|
262
|
-
* (no CSS pipeline in the CJS client bundle): full-cover fixed overlay, opaque
|
|
263
|
-
* backdrop, pointerEvents auto, and a visible close button.
|
|
252
|
+
* Run 15 (task-board redesign): class-based structure + theme-token stylesheet
|
|
253
|
+
* injected once via styles.ts (no inline styles — unlocks hover/grid/theme).
|
|
264
254
|
*/
|
|
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
255
|
/** Flatten the worktree-grouped projection into a stable run list. */
|
|
344
256
|
function listRuns(projection) {
|
|
345
257
|
if (projection === void 0) return [];
|
|
@@ -347,220 +259,105 @@ window.__ModuleLoader__.load({
|
|
|
347
259
|
for (const worktreeRoot of Object.keys(projection)) for (const runId of Object.keys(projection[worktreeRoot])) runs.push(projection[worktreeRoot][runId]);
|
|
348
260
|
return runs;
|
|
349
261
|
}
|
|
262
|
+
function closeButton$1(onClose) {
|
|
263
|
+
if (onClose === void 0) return null;
|
|
264
|
+
return (0, react.createElement)("button", {
|
|
265
|
+
type: "button",
|
|
266
|
+
className: "rec-close",
|
|
267
|
+
onClick: onClose,
|
|
268
|
+
title: "Close",
|
|
269
|
+
"aria-label": "Close"
|
|
270
|
+
}, "×");
|
|
271
|
+
}
|
|
350
272
|
function Board({ snapshot, onOpenInspector, onClose }) {
|
|
351
273
|
if (snapshot === null || snapshot.root === null) return null;
|
|
352
274
|
const runs = listRuns(snapshot.projection);
|
|
353
|
-
const close = onClose !== void 0 ? (0, react.createElement)("button", {
|
|
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."));
|
|
367
275
|
const open = (run) => () => onOpenInspector?.({
|
|
368
276
|
worktreeRoot: run.worktreeRoot,
|
|
369
277
|
runId: run.runId
|
|
370
278
|
});
|
|
371
|
-
return (0, react.createElement)("div", {
|
|
279
|
+
if (runs.length === 0) return (0, react.createElement)("div", {
|
|
372
280
|
className: "rec-board",
|
|
373
|
-
|
|
374
|
-
},
|
|
281
|
+
"data-empty": true
|
|
282
|
+
}, (0, react.createElement)("header", { className: "rec-board-header" }, (0, react.createElement)("h2", { className: "rec-board-title" }, "Recursive runs"), closeButton$1(onClose)), (0, react.createElement)("p", { className: "rec-board-empty" }, "No recursive runs in this workspace yet."));
|
|
283
|
+
return (0, react.createElement)("div", { className: "rec-board" }, (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-count" }, String(runs.length)), closeButton$1(onClose)), (0, react.createElement)("div", { className: "rec-columns" }, KANBAN_LANES.map((lane) => {
|
|
375
284
|
const laneRuns = runs.filter((r) => columnForRun(r) === lane.id);
|
|
376
285
|
return (0, react.createElement)("section", {
|
|
377
286
|
key: lane.id,
|
|
378
|
-
className: "rec-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}, lane.label), laneRuns.map((run) => {
|
|
287
|
+
className: "rec-column"
|
|
288
|
+
}, (0, react.createElement)("header", { className: "rec-column-header" }, (0, react.createElement)("span", {
|
|
289
|
+
className: "rec-status-dot",
|
|
290
|
+
"data-status": laneRuns.length > 0 ? laneRuns[laneRuns.length - 1].state : "new",
|
|
291
|
+
"aria-hidden": true
|
|
292
|
+
}), (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
293
|
const facts = cardFacts(run);
|
|
385
294
|
return (0, react.createElement)("article", {
|
|
386
295
|
key: run.worktreeRoot + "\0" + run.runId,
|
|
387
296
|
className: "rec-card",
|
|
388
|
-
|
|
297
|
+
"data-state": run.state,
|
|
389
298
|
onClick: open(run)
|
|
390
|
-
}, (0, react.createElement)("div", {
|
|
391
|
-
className: "rec-card-
|
|
392
|
-
style:
|
|
393
|
-
}, (0, react.createElement)("
|
|
299
|
+
}, (0, react.createElement)("div", { className: "rec-card-title" }, run.runId), (0, react.createElement)("div", { className: "rec-card-progress" }, (0, react.createElement)("div", {
|
|
300
|
+
className: "rec-card-progress-fill",
|
|
301
|
+
style: { width: Math.round(facts.progress * 100) + "%" }
|
|
302
|
+
})), (0, react.createElement)("div", { className: "rec-card-meta" }, (0, react.createElement)("span", { className: "rec-card-phase" }, facts.currentPhase ?? "no phases"), facts.tampered && (0, react.createElement)("span", {
|
|
394
303
|
className: "rec-badge rec-badge-tamper",
|
|
395
|
-
|
|
396
|
-
...badgeStyle$1,
|
|
397
|
-
...badgeTamperStyle
|
|
398
|
-
},
|
|
304
|
+
"data-status": run.state,
|
|
399
305
|
title: "tampered"
|
|
400
|
-
}, "!")
|
|
401
|
-
className: "rec-card-progress",
|
|
402
|
-
style: progressStyle
|
|
403
|
-
}, (0, react.createElement)("div", {
|
|
404
|
-
className: "rec-card-progress-fill",
|
|
405
|
-
style: {
|
|
406
|
-
...progressFillStyle,
|
|
407
|
-
width: Math.round(facts.progress * 100) + "%"
|
|
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", {
|
|
306
|
+
}, "!"), (0, react.createElement)("span", {
|
|
413
307
|
className: "rec-badge",
|
|
414
|
-
|
|
415
|
-
},
|
|
416
|
-
}));
|
|
417
|
-
}));
|
|
308
|
+
"data-status": run.state
|
|
309
|
+
}, run.state)));
|
|
310
|
+
}), laneRuns.length === 0 && (0, react.createElement)("div", { className: "rec-column-empty" }, "—")));
|
|
311
|
+
})));
|
|
418
312
|
}
|
|
419
313
|
//#endregion
|
|
420
314
|
//#region src/client/inspector.tsx
|
|
421
315
|
/**
|
|
422
|
-
* Drill-down inspector (SP2 R1 + run
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* disk.
|
|
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.
|
|
316
|
+
* Drill-down inspector (SP2 R1 + run 15): centered modal-style detail panel
|
|
317
|
+
* (task-board .detail shape) rendering one run's phase chain, lock badges, and
|
|
318
|
+
* tamper/gate facts — all from the live host-route snapshot, never disk.
|
|
431
319
|
*/
|
|
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", {
|
|
320
|
+
function closeButton(onClose) {
|
|
321
|
+
if (onClose === void 0) return null;
|
|
322
|
+
return (0, react.createElement)("button", {
|
|
323
|
+
type: "button",
|
|
488
324
|
className: "rec-close",
|
|
489
|
-
style: closeStyle,
|
|
490
325
|
onClick: onClose,
|
|
491
|
-
title: "Close"
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
className: "rec-inspector-head",
|
|
499
|
-
style: headStyle
|
|
500
|
-
}, (0, react.createElement)("button", {
|
|
501
|
-
onClick: onBackToToolDetails,
|
|
326
|
+
title: "Close",
|
|
327
|
+
"aria-label": "Close"
|
|
328
|
+
}, "×");
|
|
329
|
+
}
|
|
330
|
+
function detailShell(runId, onBackToToolDetails, onClose, headerExtra, body) {
|
|
331
|
+
return (0, react.createElement)("div", { className: "rec-inspector" }, (0, react.createElement)("div", { className: "rec-detail" }, (0, react.createElement)("div", { className: "rec-detail-header" }, (0, react.createElement)("button", {
|
|
332
|
+
type: "button",
|
|
502
333
|
className: "rec-back",
|
|
503
|
-
|
|
504
|
-
}, "←
|
|
334
|
+
onClick: onBackToToolDetails
|
|
335
|
+
}, "← Back"), (0, react.createElement)("h2", { className: "rec-detail-title" }, runId), headerExtra, closeButton(onClose)), (0, react.createElement)("div", { className: "rec-detail-body" }, body)));
|
|
336
|
+
}
|
|
337
|
+
function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails, onClose }) {
|
|
338
|
+
const card = snapshot?.projection?.[worktreeRoot]?.[runId];
|
|
339
|
+
if (card === void 0) return detailShell(runId, onBackToToolDetails, onClose, null, (0, react.createElement)("p", { className: "rec-detail-text" }, "Run not found: " + runId));
|
|
505
340
|
const facts = cardFacts(card);
|
|
506
341
|
const rows = expandPhaseRows(card);
|
|
507
|
-
|
|
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", {
|
|
342
|
+
const badge = (0, react.createElement)("span", {
|
|
521
343
|
className: "rec-badge",
|
|
522
|
-
|
|
523
|
-
}, facts.state)
|
|
344
|
+
"data-status": card.state
|
|
345
|
+
}, facts.state);
|
|
346
|
+
const tamper = facts.tampered ? (0, react.createElement)("span", {
|
|
524
347
|
className: "rec-badge rec-badge-tamper",
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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", {
|
|
348
|
+
"data-status": card.state
|
|
349
|
+
}, "tampered") : null;
|
|
350
|
+
return detailShell(runId, onBackToToolDetails, onClose, (0, react.createElement)("span", { style: {
|
|
351
|
+
display: "flex",
|
|
352
|
+
alignItems: "center",
|
|
353
|
+
gap: 6
|
|
354
|
+
} }, badge, tamper), (0, react.createElement)("section", { className: "rec-detail-section" }, (0, react.createElement)("h3", {}, "Phases"), rows.map((row) => (0, react.createElement)("div", {
|
|
539
355
|
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", {
|
|
356
|
+
className: "rec-phase-row"
|
|
357
|
+
}, (0, react.createElement)("span", { className: "rec-phase-id" }, row.phase), (0, react.createElement)("span", { className: "rec-phase-name" }, row.fileName ?? "—"), (0, react.createElement)("span", {
|
|
549
358
|
className: "rec-badge",
|
|
550
|
-
|
|
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 ?? "") + ")")));
|
|
359
|
+
"data-status": row.status === "LOCKED" ? "complete" : "active"
|
|
360
|
+
}, 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 ?? "") + ")"))));
|
|
564
361
|
}
|
|
565
362
|
//#endregion
|
|
566
363
|
//#region src/client/strip.tsx
|
|
@@ -766,6 +563,402 @@ window.__ModuleLoader__.load({
|
|
|
766
563
|
return (0, react.useSyncExternalStore)(store.subscribe, store.get, store.get);
|
|
767
564
|
}
|
|
768
565
|
//#endregion
|
|
566
|
+
//#region src/client/styles.ts
|
|
567
|
+
/**
|
|
568
|
+
* One-shot board/inspector stylesheet injection (run 15). The client bundle is a
|
|
569
|
+
* plain CJS closure with NO CSS pipeline (tsdown has no CSS handling), so we
|
|
570
|
+
* inject a single <style data-dsh-recursive> element into document.head and
|
|
571
|
+
* style via class names + the shell's --dsw-* theme tokens. That unlocks hover,
|
|
572
|
+
* grid layout, and light/dark/skin theme following — impossible with inline
|
|
573
|
+
* styles. Idempotent: guarded by document.querySelector, so every registration
|
|
574
|
+
* reuses the one element; the returned disposer removes it.
|
|
575
|
+
*/
|
|
576
|
+
const BOARD_CSS = `
|
|
577
|
+
/* dsh-recursive board + inspector (theme-token driven, .rec-* scoped) */
|
|
578
|
+
.rec-board {
|
|
579
|
+
position: fixed;
|
|
580
|
+
inset: 0;
|
|
581
|
+
z-index: 9999;
|
|
582
|
+
background: var(--dsw-alias-bg-base);
|
|
583
|
+
color: var(--dsw-alias-label-primary);
|
|
584
|
+
font-family: var(--dsw-font-family);
|
|
585
|
+
display: flex;
|
|
586
|
+
flex-direction: column;
|
|
587
|
+
box-sizing: border-box;
|
|
588
|
+
padding: 14px 16px 16px;
|
|
589
|
+
gap: 12px;
|
|
590
|
+
pointer-events: auto;
|
|
591
|
+
overflow: hidden;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.rec-board-header {
|
|
595
|
+
display: flex;
|
|
596
|
+
align-items: center;
|
|
597
|
+
gap: 10px;
|
|
598
|
+
flex: none;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.rec-board-title {
|
|
602
|
+
margin: 0;
|
|
603
|
+
font-size: 16px;
|
|
604
|
+
font-weight: 700;
|
|
605
|
+
color: var(--dsw-alias-label-primary);
|
|
606
|
+
white-space: nowrap;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.rec-board-count {
|
|
610
|
+
flex: none;
|
|
611
|
+
font-size: 12px;
|
|
612
|
+
color: var(--dsw-alias-label-tertiary);
|
|
613
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
614
|
+
border-radius: 999px;
|
|
615
|
+
padding: 1px 8px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.rec-board-empty {
|
|
619
|
+
margin: 0;
|
|
620
|
+
padding: 40px;
|
|
621
|
+
text-align: center;
|
|
622
|
+
color: var(--dsw-alias-label-tertiary);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.rec-close {
|
|
626
|
+
position: absolute;
|
|
627
|
+
top: 14px;
|
|
628
|
+
right: 16px;
|
|
629
|
+
z-index: 1;
|
|
630
|
+
display: inline-flex;
|
|
631
|
+
align-items: center;
|
|
632
|
+
justify-content: center;
|
|
633
|
+
width: 26px;
|
|
634
|
+
height: 26px;
|
|
635
|
+
padding: 0;
|
|
636
|
+
background: transparent;
|
|
637
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
638
|
+
border-radius: 6px;
|
|
639
|
+
color: var(--dsw-alias-label-secondary);
|
|
640
|
+
cursor: pointer;
|
|
641
|
+
font-size: 15px;
|
|
642
|
+
line-height: 1;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.rec-close:hover {
|
|
646
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
647
|
+
color: var(--dsw-alias-label-primary);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.rec-columns {
|
|
651
|
+
display: grid;
|
|
652
|
+
grid-auto-flow: column;
|
|
653
|
+
grid-auto-columns: minmax(240px, 1fr);
|
|
654
|
+
gap: 12px;
|
|
655
|
+
flex: 1;
|
|
656
|
+
min-height: 0;
|
|
657
|
+
overflow-x: auto;
|
|
658
|
+
overflow-y: hidden;
|
|
659
|
+
overscroll-behavior-inline: contain;
|
|
660
|
+
padding-bottom: 6px;
|
|
661
|
+
scrollbar-color: var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);
|
|
662
|
+
scrollbar-width: thin;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.rec-columns::-webkit-scrollbar { height: 10px; }
|
|
666
|
+
.rec-columns::-webkit-scrollbar-track { background: var(--dsw-alias-interactive-bg-hover); border-radius: 999px; }
|
|
667
|
+
.rec-columns::-webkit-scrollbar-thumb { background: var(--dsw-alias-border-l3); background-clip: content-box; border: 2px solid transparent; border-radius: 999px; }
|
|
668
|
+
.rec-columns::-webkit-scrollbar-thumb:hover { background: var(--dsw-alias-border-l3); background-clip: content-box; }
|
|
669
|
+
|
|
670
|
+
.rec-column {
|
|
671
|
+
display: flex;
|
|
672
|
+
flex-direction: column;
|
|
673
|
+
min-height: 0;
|
|
674
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
675
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
676
|
+
border-radius: 12px;
|
|
677
|
+
overflow: hidden;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.rec-column-header {
|
|
681
|
+
display: flex;
|
|
682
|
+
align-items: center;
|
|
683
|
+
gap: 6px;
|
|
684
|
+
padding: 10px 12px;
|
|
685
|
+
flex: none;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.rec-column-title {
|
|
689
|
+
margin: 0;
|
|
690
|
+
flex: 1;
|
|
691
|
+
font-size: 13px;
|
|
692
|
+
font-weight: 700;
|
|
693
|
+
color: var(--dsw-alias-label-primary);
|
|
694
|
+
overflow: hidden;
|
|
695
|
+
text-overflow: ellipsis;
|
|
696
|
+
white-space: nowrap;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.rec-column-count {
|
|
700
|
+
flex: none;
|
|
701
|
+
font-size: 12px;
|
|
702
|
+
color: var(--dsw-alias-label-tertiary);
|
|
703
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
704
|
+
border-radius: 999px;
|
|
705
|
+
padding: 1px 8px;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.rec-status-dot {
|
|
709
|
+
width: 8px;
|
|
710
|
+
height: 8px;
|
|
711
|
+
border-radius: 50%;
|
|
712
|
+
flex: none;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.rec-status-dot[data-status='new'] { background: var(--dsw-alias-label-tertiary); }
|
|
716
|
+
.rec-status-dot[data-status='active'] { background: var(--dsw-alias-state-business-primary); }
|
|
717
|
+
.rec-status-dot[data-status='paused'] { background: var(--dsw-alias-state-warn-primary); }
|
|
718
|
+
.rec-status-dot[data-status='blocked'] { background: var(--dsw-alias-state-error-primary); }
|
|
719
|
+
.rec-status-dot[data-status='complete'] { background: var(--dsw-alias-state-success-primary); }
|
|
720
|
+
|
|
721
|
+
.rec-cards {
|
|
722
|
+
display: flex;
|
|
723
|
+
flex-direction: column;
|
|
724
|
+
gap: 8px;
|
|
725
|
+
padding: 2px 8px 10px;
|
|
726
|
+
overflow-y: auto;
|
|
727
|
+
flex: 1;
|
|
728
|
+
min-height: 0;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.rec-column-empty {
|
|
732
|
+
padding: 24px 8px;
|
|
733
|
+
text-align: center;
|
|
734
|
+
font-size: 12px;
|
|
735
|
+
color: var(--dsw-alias-label-tertiary);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.rec-card {
|
|
739
|
+
display: flex;
|
|
740
|
+
flex-direction: column;
|
|
741
|
+
gap: 6px;
|
|
742
|
+
padding: 10px 12px;
|
|
743
|
+
text-align: left;
|
|
744
|
+
background: var(--dsw-alias-bg-base);
|
|
745
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
746
|
+
border-radius: 10px;
|
|
747
|
+
cursor: pointer;
|
|
748
|
+
color: var(--dsw-alias-label-primary);
|
|
749
|
+
font-family: inherit;
|
|
750
|
+
transition: box-shadow 120ms ease, border-color 120ms ease, transform 120ms ease;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.rec-card:hover {
|
|
754
|
+
box-shadow: var(--dsw-shadow-lv2);
|
|
755
|
+
border-color: var(--dsw-alias-border-l3);
|
|
756
|
+
transform: translateY(-1px);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.rec-card:active {
|
|
760
|
+
box-shadow: var(--dsw-shadow-lv1);
|
|
761
|
+
transform: translateY(0);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.rec-card[data-state='active'] { border-color: var(--dsw-alias-state-business-primary); }
|
|
765
|
+
.rec-card[data-state='paused'] { border-color: var(--dsw-alias-state-warn-primary); }
|
|
766
|
+
.rec-card[data-state='blocked'] { border-color: var(--dsw-alias-state-error-primary); }
|
|
767
|
+
.rec-card[data-state='complete'] { border-color: var(--dsw-alias-state-success-primary); }
|
|
768
|
+
|
|
769
|
+
.rec-card-title {
|
|
770
|
+
font-size: 13px;
|
|
771
|
+
font-weight: 600;
|
|
772
|
+
line-height: 1.35;
|
|
773
|
+
overflow: hidden;
|
|
774
|
+
display: -webkit-box;
|
|
775
|
+
-webkit-line-clamp: 2;
|
|
776
|
+
-webkit-box-orient: vertical;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.rec-card-progress {
|
|
780
|
+
height: 6px;
|
|
781
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
782
|
+
border-radius: 3px;
|
|
783
|
+
overflow: hidden;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.rec-card-progress-fill {
|
|
787
|
+
height: 100%;
|
|
788
|
+
background: var(--dsw-alias-state-business-primary);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.rec-card-meta {
|
|
792
|
+
display: flex;
|
|
793
|
+
align-items: center;
|
|
794
|
+
gap: 8px;
|
|
795
|
+
font-size: 11px;
|
|
796
|
+
color: var(--dsw-alias-label-tertiary);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.rec-card-phase {
|
|
800
|
+
flex: 1;
|
|
801
|
+
overflow: hidden;
|
|
802
|
+
text-overflow: ellipsis;
|
|
803
|
+
white-space: nowrap;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.rec-badge {
|
|
807
|
+
flex: none;
|
|
808
|
+
padding: 2px 10px;
|
|
809
|
+
font-size: 12px;
|
|
810
|
+
border-radius: 999px;
|
|
811
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
812
|
+
color: var(--dsw-alias-label-secondary);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.rec-badge[data-status='active'] { color: var(--dsw-alias-state-business-primary); border-color: var(--dsw-alias-state-business-primary); }
|
|
816
|
+
.rec-badge[data-status='paused'] { color: var(--dsw-alias-state-warn-primary); border-color: var(--dsw-alias-state-warn-primary); }
|
|
817
|
+
.rec-badge[data-status='blocked'] { color: var(--dsw-alias-state-error-primary); border-color: var(--dsw-alias-state-error-primary); }
|
|
818
|
+
.rec-badge[data-status='complete'] { color: var(--dsw-alias-state-success-primary); border-color: var(--dsw-alias-state-success-primary); }
|
|
819
|
+
.rec-badge[data-status='new'] { color: var(--dsw-alias-label-tertiary); }
|
|
820
|
+
|
|
821
|
+
.rec-badge-tamper {
|
|
822
|
+
color: var(--dsw-alias-state-error-primary);
|
|
823
|
+
border-color: var(--dsw-alias-state-error-primary);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/* inspector: centered modal-style detail */
|
|
827
|
+
.rec-inspector {
|
|
828
|
+
position: fixed;
|
|
829
|
+
inset: 0;
|
|
830
|
+
z-index: 9999;
|
|
831
|
+
display: flex;
|
|
832
|
+
align-items: center;
|
|
833
|
+
justify-content: center;
|
|
834
|
+
background: var(--dsw-alias-bg-mask-1);
|
|
835
|
+
pointer-events: auto;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.rec-detail {
|
|
839
|
+
display: flex;
|
|
840
|
+
flex-direction: column;
|
|
841
|
+
width: min(640px, calc(100vw - 48px));
|
|
842
|
+
max-height: calc(100vh - 80px);
|
|
843
|
+
background: var(--dsw-alias-bg-base);
|
|
844
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
845
|
+
border-radius: 14px;
|
|
846
|
+
box-shadow: var(--dsw-shadow-lv3);
|
|
847
|
+
color: var(--dsw-alias-label-primary);
|
|
848
|
+
overflow: hidden;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
.rec-detail-header {
|
|
852
|
+
display: flex;
|
|
853
|
+
align-items: center;
|
|
854
|
+
gap: 10px;
|
|
855
|
+
padding: 14px 18px;
|
|
856
|
+
border-bottom: 1px solid var(--dsw-alias-separator-primary);
|
|
857
|
+
flex: none;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.rec-detail-title {
|
|
861
|
+
margin: 0;
|
|
862
|
+
flex: 1;
|
|
863
|
+
font-size: 15px;
|
|
864
|
+
font-weight: 700;
|
|
865
|
+
overflow-wrap: anywhere;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
.rec-back {
|
|
869
|
+
display: inline-flex;
|
|
870
|
+
align-items: center;
|
|
871
|
+
gap: 4px;
|
|
872
|
+
padding: 5px 12px;
|
|
873
|
+
font-size: 12px;
|
|
874
|
+
color: var(--dsw-alias-label-primary);
|
|
875
|
+
background: transparent;
|
|
876
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
877
|
+
border-radius: 8px;
|
|
878
|
+
cursor: pointer;
|
|
879
|
+
white-space: nowrap;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.rec-back:hover {
|
|
883
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.rec-detail-body {
|
|
887
|
+
padding: 14px 18px;
|
|
888
|
+
overflow-y: auto;
|
|
889
|
+
display: flex;
|
|
890
|
+
flex-direction: column;
|
|
891
|
+
gap: 16px;
|
|
892
|
+
flex: 1;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.rec-detail-section {
|
|
896
|
+
display: flex;
|
|
897
|
+
flex-direction: column;
|
|
898
|
+
gap: 6px;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.rec-detail-section h3 {
|
|
902
|
+
margin: 0;
|
|
903
|
+
font-size: 12px;
|
|
904
|
+
font-weight: 700;
|
|
905
|
+
color: var(--dsw-alias-label-tertiary);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.rec-phase-row {
|
|
909
|
+
display: flex;
|
|
910
|
+
align-items: center;
|
|
911
|
+
gap: 10px;
|
|
912
|
+
padding: 8px 10px;
|
|
913
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
914
|
+
border-radius: 8px;
|
|
915
|
+
font-size: 13px;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.rec-phase-id {
|
|
919
|
+
min-width: 48px;
|
|
920
|
+
font-family: var(--dsw-font-markdown-code-block-small);
|
|
921
|
+
color: var(--dsw-alias-state-business-primary);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
.rec-phase-name {
|
|
925
|
+
flex: 1;
|
|
926
|
+
color: var(--dsw-alias-label-primary);
|
|
927
|
+
overflow: hidden;
|
|
928
|
+
text-overflow: ellipsis;
|
|
929
|
+
white-space: nowrap;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.rec-lockhash {
|
|
933
|
+
font-family: var(--dsw-font-markdown-code-block-small);
|
|
934
|
+
font-size: 11px;
|
|
935
|
+
color: var(--dsw-alias-label-tertiary);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
@media (prefers-reduced-motion: reduce) {
|
|
939
|
+
.rec-card, .rec-back, .rec-close { transition: none; }
|
|
940
|
+
}
|
|
941
|
+
`;
|
|
942
|
+
/**
|
|
943
|
+
* Inject the board/inspector stylesheet once. Idempotent: if a <style
|
|
944
|
+
* data-dsh-recursive> already exists, returns a disposer that does nothing
|
|
945
|
+
* (the first injector owns the element). Returns a disposer that removes it.
|
|
946
|
+
*/
|
|
947
|
+
function injectBoardStyles() {
|
|
948
|
+
if (typeof document === "undefined") return () => {};
|
|
949
|
+
if (document.querySelector("style[data-dsh-recursive]") !== null) return () => {};
|
|
950
|
+
const style = document.createElement("style");
|
|
951
|
+
style.setAttribute("data-dsh-recursive", "");
|
|
952
|
+
style.textContent = BOARD_CSS;
|
|
953
|
+
document.head.appendChild(style);
|
|
954
|
+
let disposed = false;
|
|
955
|
+
return () => {
|
|
956
|
+
if (disposed) return;
|
|
957
|
+
disposed = true;
|
|
958
|
+
style.remove();
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
//#endregion
|
|
769
962
|
//#region src/client/slots.ts
|
|
770
963
|
/**
|
|
771
964
|
* Slot registrations (SP2 R1 + run 08 R1/R3/R4): the sidebar launcher, the
|
|
@@ -817,6 +1010,7 @@ window.__ModuleLoader__.load({
|
|
|
817
1010
|
}
|
|
818
1011
|
function registerSlots(ctx) {
|
|
819
1012
|
const disposers = [];
|
|
1013
|
+
disposers.push(injectBoardStyles());
|
|
820
1014
|
disposers.push(ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
821
1015
|
name: "sidebar.footer.action",
|
|
822
1016
|
id: "recursive",
|