@try-works/dsh-recursive-mode 0.2.3 → 0.2.4
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/doc-viewer.d.ts +40 -0
- package/lib/client/host-api.d.ts +13 -0
- package/lib/client/index.d.ts +1 -0
- package/lib/client.js +813 -58
- package/lib/index.js +142 -59
- package/lib/live-route.d.ts +1 -1
- package/package.json +1 -1
- package/src/client/doc-viewer.tsx +306 -0
- package/src/client/host-api.ts +28 -0
- package/src/client/index.ts +1 -0
- package/src/client/inspector.tsx +13 -2
- package/src/client/styles.ts +310 -0
- package/src/live-route.ts +69 -2
package/lib/client.js
CHANGED
|
@@ -405,64 +405,6 @@ window.__ModuleLoader__.load({
|
|
|
405
405
|
})));
|
|
406
406
|
}
|
|
407
407
|
//#endregion
|
|
408
|
-
//#region src/client/inspector.tsx
|
|
409
|
-
/**
|
|
410
|
-
* Drill-down inspector (SP2 R1 + run 15/16): centered modal-style detail panel
|
|
411
|
-
* (Paper .detail shape) with back + close, data-theme (default light), theme
|
|
412
|
-
* toggle, and solid status pills. All from the live host-route snapshot.
|
|
413
|
-
* useBoardTheme is hoisted ABOVE the not-found early return.
|
|
414
|
-
*/
|
|
415
|
-
function closeButton(onClose) {
|
|
416
|
-
if (onClose === void 0) return null;
|
|
417
|
-
return (0, react.createElement)("button", {
|
|
418
|
-
type: "button",
|
|
419
|
-
className: "rec-close",
|
|
420
|
-
onClick: onClose,
|
|
421
|
-
title: "Close",
|
|
422
|
-
"aria-label": "Close"
|
|
423
|
-
}, "×");
|
|
424
|
-
}
|
|
425
|
-
function solidPill(kind) {
|
|
426
|
-
return (0, react.createElement)("span", {
|
|
427
|
-
className: "rec-pill",
|
|
428
|
-
"data-pill": kind
|
|
429
|
-
}, PILL_LABELS[kind]);
|
|
430
|
-
}
|
|
431
|
-
function detailShell(runId, onBackToToolDetails, onClose, headerExtra, body, theme, toggle) {
|
|
432
|
-
return (0, react.createElement)("div", {
|
|
433
|
-
className: "rec-inspector",
|
|
434
|
-
"data-theme": theme
|
|
435
|
-
}, (0, react.createElement)("div", { className: "rec-detail" }, (0, react.createElement)("div", { className: "rec-detail-header" }, (0, react.createElement)("button", {
|
|
436
|
-
type: "button",
|
|
437
|
-
className: "rec-back",
|
|
438
|
-
onClick: onBackToToolDetails
|
|
439
|
-
}, "← Back"), (0, react.createElement)("h2", { className: "rec-detail-title" }, runId), headerExtra, (0, react.createElement)(ThemeToggle, {
|
|
440
|
-
theme,
|
|
441
|
-
toggle
|
|
442
|
-
}), closeButton(onClose)), (0, react.createElement)("div", { className: "rec-detail-body" }, body)));
|
|
443
|
-
}
|
|
444
|
-
function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails, onClose }) {
|
|
445
|
-
const { theme, toggle } = useBoardTheme();
|
|
446
|
-
const card = snapshot?.projection?.[worktreeRoot]?.[runId];
|
|
447
|
-
if (card === void 0) return detailShell(runId, onBackToToolDetails, onClose, null, (0, react.createElement)("p", { className: "rec-detail-text" }, "Run not found: " + runId), theme, toggle);
|
|
448
|
-
const facts = cardFacts(card);
|
|
449
|
-
const rows = expandPhaseRows(card);
|
|
450
|
-
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", {
|
|
451
|
-
key: row.phase,
|
|
452
|
-
className: "rec-phase-row"
|
|
453
|
-
}, (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);
|
|
454
|
-
}
|
|
455
|
-
//#endregion
|
|
456
|
-
//#region src/client/settings.tsx
|
|
457
|
-
/**
|
|
458
|
-
* Recursive settings page (Phase D R8, §11.8): a settings.section list entry
|
|
459
|
-
* surfacing the host-owned enforcement config. The client is read-mostly; all
|
|
460
|
-
* writes ride the host config path (never run files).
|
|
461
|
-
*/
|
|
462
|
-
function RecursiveSettings({ close }) {
|
|
463
|
-
return (0, react.createElement)("div", { className: "rec-settings" }, (0, react.createElement)("h2", {}, "Recursive"), (0, react.createElement)("p", {}, "Enforcement policy, scratch format, provider defaults, and preset install path are configured on the host. The client is read-only (§11.9)."), (0, react.createElement)("button", { onClick: close }, "Close"));
|
|
464
|
-
}
|
|
465
|
-
//#endregion
|
|
466
408
|
//#region src/client/host-api.ts
|
|
467
409
|
/**
|
|
468
410
|
* Live recursive board/strip feed (SP2 R1): the browser half of the host
|
|
@@ -503,6 +445,27 @@ window.__ModuleLoader__.load({
|
|
|
503
445
|
}
|
|
504
446
|
}
|
|
505
447
|
/**
|
|
448
|
+
* Fetch one per-phase run doc (0.2.4) via the lazy GET /.recursive/api/doc
|
|
449
|
+
* route. Raw markdown text; the host validates the root (known workspace) and
|
|
450
|
+
* guards the file path (phase-doc basename only, containment under the run
|
|
451
|
+
* dir). Same 15s AbortController timeout as fetchLiveState.
|
|
452
|
+
*/
|
|
453
|
+
async function fetchPhaseDoc(req) {
|
|
454
|
+
const controller = new AbortController();
|
|
455
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
456
|
+
try {
|
|
457
|
+
const q = "root=" + encodeURIComponent(req.root) + "&runId=" + encodeURIComponent(req.runId) + "&file=" + encodeURIComponent(req.file);
|
|
458
|
+
const res = await fetch("/.recursive/api/doc?" + q, {
|
|
459
|
+
signal: controller.signal,
|
|
460
|
+
cache: "no-store"
|
|
461
|
+
});
|
|
462
|
+
if (!res.ok) throw new Error("recursive phase doc: HTTP " + res.status);
|
|
463
|
+
return await res.text();
|
|
464
|
+
} finally {
|
|
465
|
+
clearTimeout(timer);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
506
469
|
* Subscribe to the SSE events feed. Returns a disposer. The host pushes a full
|
|
507
470
|
* {revision, root, projection} frame on every fs change + a 15s heartbeat.
|
|
508
471
|
*
|
|
@@ -536,6 +499,485 @@ window.__ModuleLoader__.load({
|
|
|
536
499
|
};
|
|
537
500
|
}
|
|
538
501
|
//#endregion
|
|
502
|
+
//#region src/client/doc-viewer.tsx
|
|
503
|
+
/**
|
|
504
|
+
* Per-phase run-doc viewer (0.2.4): renders one .recursive/run/<runId>/<file>
|
|
505
|
+
* markdown doc read LAZILY via the host GET /.recursive/api/doc route, inside
|
|
506
|
+
* the Inspector run-detail panel. Read-only: NO approve / changes / comment /
|
|
507
|
+
* quit actions, NO session-answer writes, NO recursive/* session events.
|
|
508
|
+
*
|
|
509
|
+
* ATTRIBUTION — ported from @guillaumemeyer/dsh-plan-approval (MIT),
|
|
510
|
+
* https://github.com/guillaumemeyer/dsh-plan-approval:
|
|
511
|
+
* - the markdown line parser (parseDoc, based on parsePlan), and
|
|
512
|
+
* - the vim-nav + / search key handling (onKey/move/goTop/goBottom/
|
|
513
|
+
* computeMatches/searchNext/searchPrev/openSearch/closeSearch).
|
|
514
|
+
* Reused under MIT. NO planReviewOf / overlay / session-question logic is
|
|
515
|
+
* carried over — this viewer reads run artifacts.
|
|
516
|
+
*/
|
|
517
|
+
/**
|
|
518
|
+
* Markdown -> line tokens. Base is parsePlan (blank/h1-h4/li/plain, MIT),
|
|
519
|
+
* extended for fenced code blocks (one code line per block) and pipe tables
|
|
520
|
+
* (one table line per block, header separator row dropped).
|
|
521
|
+
*/
|
|
522
|
+
function parseDoc(plan) {
|
|
523
|
+
const raw = String(plan == null ? "" : plan).split("\n");
|
|
524
|
+
const out = [];
|
|
525
|
+
let i = 0;
|
|
526
|
+
while (i < raw.length) {
|
|
527
|
+
const line = raw[i];
|
|
528
|
+
if (/^\s*$/.test(line)) {
|
|
529
|
+
out.push({
|
|
530
|
+
kind: "blank",
|
|
531
|
+
text: ""
|
|
532
|
+
});
|
|
533
|
+
i += 1;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (/^```\s*([\w+-]*)\s*$/.exec(line)) {
|
|
537
|
+
const code = [];
|
|
538
|
+
i += 1;
|
|
539
|
+
while (i < raw.length && !/^```\s*$/.test(raw[i])) {
|
|
540
|
+
code.push(raw[i]);
|
|
541
|
+
i += 1;
|
|
542
|
+
}
|
|
543
|
+
i += 1;
|
|
544
|
+
out.push({
|
|
545
|
+
kind: "code",
|
|
546
|
+
text: code.join("\n")
|
|
547
|
+
});
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (/^\s*\|/.test(line)) {
|
|
551
|
+
const rows = [];
|
|
552
|
+
while (i < raw.length && /^\s*\|/.test(raw[i])) {
|
|
553
|
+
const parts = raw[i].split("|").slice(1, -1).map((c) => c.trim());
|
|
554
|
+
rows.push(parts);
|
|
555
|
+
i += 1;
|
|
556
|
+
}
|
|
557
|
+
const data = rows.filter((r) => !r.every((c) => /^:?-{3,}:?$/.test(c)));
|
|
558
|
+
out.push({
|
|
559
|
+
kind: "table",
|
|
560
|
+
text: rows.map((r) => r.join(" | ")).join("\n"),
|
|
561
|
+
cells: data
|
|
562
|
+
});
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const h = /^(#{1,4})\s+(.+?)\s*$/.exec(line);
|
|
566
|
+
if (h) {
|
|
567
|
+
let kind;
|
|
568
|
+
switch (h[1].length) {
|
|
569
|
+
case 1:
|
|
570
|
+
kind = "h1";
|
|
571
|
+
break;
|
|
572
|
+
case 2:
|
|
573
|
+
kind = "h2";
|
|
574
|
+
break;
|
|
575
|
+
case 3:
|
|
576
|
+
kind = "h3";
|
|
577
|
+
break;
|
|
578
|
+
default: kind = "h4";
|
|
579
|
+
}
|
|
580
|
+
out.push({
|
|
581
|
+
kind,
|
|
582
|
+
text: h[2]
|
|
583
|
+
});
|
|
584
|
+
i += 1;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const li = /^\s*[-*]\s+(.+)$/.exec(line);
|
|
588
|
+
if (li) {
|
|
589
|
+
out.push({
|
|
590
|
+
kind: "li",
|
|
591
|
+
text: li[1]
|
|
592
|
+
});
|
|
593
|
+
i += 1;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
out.push({
|
|
597
|
+
kind: "plain",
|
|
598
|
+
text: line
|
|
599
|
+
});
|
|
600
|
+
i += 1;
|
|
601
|
+
}
|
|
602
|
+
return out;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Inline scanner: **bold**, `code`, [text](url); unmatched markers stay plain
|
|
606
|
+
* text. Used to build the React children of a line text.
|
|
607
|
+
*/
|
|
608
|
+
function parseInline(text) {
|
|
609
|
+
const out = [];
|
|
610
|
+
const re = /(\*\*(.+?)\*\*)|(`([^`]+)`)|(\[([^\]]+)\]\(([^)]+)\))/g;
|
|
611
|
+
let last = 0;
|
|
612
|
+
let m;
|
|
613
|
+
while ((m = re.exec(text)) !== null) {
|
|
614
|
+
if (m.index > last) out.push({
|
|
615
|
+
type: "text",
|
|
616
|
+
text: text.slice(last, m.index)
|
|
617
|
+
});
|
|
618
|
+
if (m[1] !== void 0) out.push({
|
|
619
|
+
type: "bold",
|
|
620
|
+
text: m[2]
|
|
621
|
+
});
|
|
622
|
+
if (m[3] !== void 0) out.push({
|
|
623
|
+
type: "code",
|
|
624
|
+
text: m[4]
|
|
625
|
+
});
|
|
626
|
+
if (m[5] !== void 0) out.push({
|
|
627
|
+
type: "link",
|
|
628
|
+
text: m[6],
|
|
629
|
+
href: m[7]
|
|
630
|
+
});
|
|
631
|
+
last = re.lastIndex;
|
|
632
|
+
}
|
|
633
|
+
if (last < text.length) out.push({
|
|
634
|
+
type: "text",
|
|
635
|
+
text: text.slice(last)
|
|
636
|
+
});
|
|
637
|
+
return out;
|
|
638
|
+
}
|
|
639
|
+
const SEARCH_HINTS = [
|
|
640
|
+
["j/k", "move"],
|
|
641
|
+
["gg/G", "ends"],
|
|
642
|
+
["/", "search"],
|
|
643
|
+
["n/N", "next/prev"],
|
|
644
|
+
["y", "copy"],
|
|
645
|
+
["Esc", "close"]
|
|
646
|
+
];
|
|
647
|
+
/** Inline segments -> ReactNodes (bold/code/link markup). */
|
|
648
|
+
function inlineNodes(segments, baseKey) {
|
|
649
|
+
return segments.map((seg, n) => {
|
|
650
|
+
const key = baseKey + "-seg-" + String(n);
|
|
651
|
+
if (seg.type === "bold") return (0, react.createElement)("strong", { key }, seg.text);
|
|
652
|
+
if (seg.type === "code") return (0, react.createElement)("code", {
|
|
653
|
+
key,
|
|
654
|
+
className: "rec-doc-inline-code"
|
|
655
|
+
}, seg.text);
|
|
656
|
+
if (seg.type === "link") return (0, react.createElement)("a", {
|
|
657
|
+
key,
|
|
658
|
+
href: seg.href,
|
|
659
|
+
target: "_blank",
|
|
660
|
+
rel: "noreferrer",
|
|
661
|
+
className: "rec-doc-inline-link"
|
|
662
|
+
}, seg.text);
|
|
663
|
+
return (0, react.createElement)("span", { key }, seg.text);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Render one parsed line as a React element. Headings/bullets get parsePlan
|
|
668
|
+
* sizing; code/table get block layout; inline markup applies to plain-ish text.
|
|
669
|
+
*/
|
|
670
|
+
function lineElement(line, i, isCurrent) {
|
|
671
|
+
const cls = "rec-doc-line rec-doc-" + line.kind + (isCurrent ? " rec-doc-line-current" : "");
|
|
672
|
+
if (line.kind === "blank") return (0, react.createElement)("div", {
|
|
673
|
+
key: i,
|
|
674
|
+
"data-line": String(i),
|
|
675
|
+
className: cls
|
|
676
|
+
}, null);
|
|
677
|
+
if (line.kind === "code") return (0, react.createElement)("pre", {
|
|
678
|
+
key: i,
|
|
679
|
+
"data-line": String(i),
|
|
680
|
+
className: cls + " rec-doc-pre"
|
|
681
|
+
}, (0, react.createElement)("code", { className: "rec-doc-code" }, line.text));
|
|
682
|
+
if (line.kind === "table") {
|
|
683
|
+
const all = line.cells ?? [];
|
|
684
|
+
const header = all[0] ?? [];
|
|
685
|
+
const body = all.slice(1);
|
|
686
|
+
return (0, react.createElement)("div", {
|
|
687
|
+
key: i,
|
|
688
|
+
"data-line": String(i),
|
|
689
|
+
className: cls
|
|
690
|
+
}, (0, react.createElement)("table", { className: "rec-doc-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, header.map((c, n) => (0, react.createElement)("th", { key: "th-" + String(n) }, c)))), (0, react.createElement)("tbody", null, body.map((r, n) => (0, react.createElement)("tr", { key: "tr-" + String(n) }, r.map((c, m) => (0, react.createElement)("td", { key: "td-" + String(m) }, c)))))));
|
|
691
|
+
}
|
|
692
|
+
const nodes = inlineNodes(parseInline(line.text), String(i));
|
|
693
|
+
if (line.kind === "li") return (0, react.createElement)("div", {
|
|
694
|
+
key: i,
|
|
695
|
+
"data-line": String(i),
|
|
696
|
+
className: cls
|
|
697
|
+
}, (0, react.createElement)("span", { className: "rec-doc-bullet" }, "•"), (0, react.createElement)("span", { className: "rec-doc-li-text" }, nodes));
|
|
698
|
+
return (0, react.createElement)("div", {
|
|
699
|
+
key: i,
|
|
700
|
+
"data-line": String(i),
|
|
701
|
+
className: cls
|
|
702
|
+
}, nodes);
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* The per-phase doc viewer. Fetches the route on mount / fileName change.
|
|
706
|
+
* Vim nav + / search + n/N + Esc; y copies the doc. Esc closes search first,
|
|
707
|
+
* else the viewer. data-theme is passed down by the hoisting Inspector
|
|
708
|
+
* (0.1.10 invariant: useBoardTheme lives in the panel).
|
|
709
|
+
*/
|
|
710
|
+
function DocViewer({ runId, worktreeRoot, fileName, theme, onClose }) {
|
|
711
|
+
const [text, setText] = (0, react.useState)(null);
|
|
712
|
+
const [error, setError] = (0, react.useState)(null);
|
|
713
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
714
|
+
const [cursor, setCursor] = (0, react.useState)(0);
|
|
715
|
+
const [searchOpen, setSearchOpen] = (0, react.useState)(false);
|
|
716
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
717
|
+
const [matches, setMatches] = (0, react.useState)([]);
|
|
718
|
+
const [activeMatch, setActiveMatch] = (0, react.useState)(0);
|
|
719
|
+
const ggArmed = (0, react.useRef)(false);
|
|
720
|
+
const rootRef = (0, react.useRef)(null);
|
|
721
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
722
|
+
(0, react.useEffect)(() => {
|
|
723
|
+
let disposed = false;
|
|
724
|
+
setText(null);
|
|
725
|
+
setError(null);
|
|
726
|
+
setNotice(null);
|
|
727
|
+
setCopied(false);
|
|
728
|
+
setCursor(0);
|
|
729
|
+
fetchPhaseDoc({
|
|
730
|
+
root: worktreeRoot,
|
|
731
|
+
runId,
|
|
732
|
+
file: fileName
|
|
733
|
+
}).then((t) => {
|
|
734
|
+
if (!disposed) {
|
|
735
|
+
setText(t);
|
|
736
|
+
setCopied(false);
|
|
737
|
+
}
|
|
738
|
+
}).catch(() => {
|
|
739
|
+
if (!disposed) setError("Failed to load doc");
|
|
740
|
+
});
|
|
741
|
+
return () => {
|
|
742
|
+
disposed = true;
|
|
743
|
+
};
|
|
744
|
+
}, [
|
|
745
|
+
worktreeRoot,
|
|
746
|
+
runId,
|
|
747
|
+
fileName
|
|
748
|
+
]);
|
|
749
|
+
const docLines = (0, react.useMemo)(() => text === null ? [] : parseDoc(text), [text]);
|
|
750
|
+
const move = (delta) => {
|
|
751
|
+
if (docLines.length === 0) return;
|
|
752
|
+
setCursor((c) => Math.max(0, Math.min(docLines.length - 1, c + delta)));
|
|
753
|
+
};
|
|
754
|
+
const goTop = () => setCursor(0);
|
|
755
|
+
const goBottom = () => setCursor(Math.max(0, docLines.length - 1));
|
|
756
|
+
const computeMatches = (q) => {
|
|
757
|
+
if (!q) return [];
|
|
758
|
+
const needle = q.toLowerCase();
|
|
759
|
+
const found = [];
|
|
760
|
+
for (let i = 0; i < docLines.length; i++) if (docLines[i].text.toLowerCase().indexOf(needle) >= 0) found.push(i);
|
|
761
|
+
return found;
|
|
762
|
+
};
|
|
763
|
+
const openSearch = () => {
|
|
764
|
+
setSearchOpen(true);
|
|
765
|
+
setError(null);
|
|
766
|
+
setNotice(null);
|
|
767
|
+
};
|
|
768
|
+
const closeSearch = () => {
|
|
769
|
+
setSearchOpen(false);
|
|
770
|
+
setQuery("");
|
|
771
|
+
setMatches([]);
|
|
772
|
+
setActiveMatch(0);
|
|
773
|
+
if (rootRef.current) rootRef.current.focus();
|
|
774
|
+
};
|
|
775
|
+
const onSearchChange = (e) => {
|
|
776
|
+
const q = e.target.value;
|
|
777
|
+
setQuery(q);
|
|
778
|
+
setMatches(computeMatches(q));
|
|
779
|
+
setActiveMatch(0);
|
|
780
|
+
};
|
|
781
|
+
const searchNext = () => {
|
|
782
|
+
if (matches.length === 0) return;
|
|
783
|
+
const next = (activeMatch + 1) % matches.length;
|
|
784
|
+
setActiveMatch(next);
|
|
785
|
+
setCursor(matches[next]);
|
|
786
|
+
};
|
|
787
|
+
const searchPrev = () => {
|
|
788
|
+
if (matches.length === 0) return;
|
|
789
|
+
const prev = (activeMatch - 1 + matches.length) % matches.length;
|
|
790
|
+
setActiveMatch(prev);
|
|
791
|
+
setCursor(matches[prev]);
|
|
792
|
+
};
|
|
793
|
+
const onSearchKey = (e) => {
|
|
794
|
+
if (e.key === "Enter" && e.shiftKey) {
|
|
795
|
+
e.preventDefault();
|
|
796
|
+
searchPrev();
|
|
797
|
+
} else if (e.key === "Enter") {
|
|
798
|
+
e.preventDefault();
|
|
799
|
+
searchNext();
|
|
800
|
+
} else if (e.key === "Escape") {
|
|
801
|
+
e.preventDefault();
|
|
802
|
+
closeSearch();
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
const copyDoc = () => {
|
|
806
|
+
const clip = globalThis.navigator?.clipboard;
|
|
807
|
+
if (clip === void 0 || clip.writeText === void 0) {
|
|
808
|
+
setError("Copy unavailable");
|
|
809
|
+
setNotice(null);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (clip && clip.writeText) {
|
|
813
|
+
const data = text ?? "";
|
|
814
|
+
clip.writeText(data).then(() => {
|
|
815
|
+
setNotice("Doc copied");
|
|
816
|
+
setError(null);
|
|
817
|
+
setCopied(true);
|
|
818
|
+
}).catch(() => {
|
|
819
|
+
setError("Copy failed");
|
|
820
|
+
setNotice(null);
|
|
821
|
+
});
|
|
822
|
+
} else {
|
|
823
|
+
setError("Copy unavailable");
|
|
824
|
+
setNotice(null);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
const onKey = (e) => {
|
|
828
|
+
const tag = e.target && e.target.tagName || "";
|
|
829
|
+
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
830
|
+
const rawKey = e.key || "";
|
|
831
|
+
const key = rawKey.toLowerCase();
|
|
832
|
+
if (rawKey === "G") {
|
|
833
|
+
e.preventDefault();
|
|
834
|
+
ggArmed.current = false;
|
|
835
|
+
goBottom();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (rawKey === "N") {
|
|
839
|
+
e.preventDefault();
|
|
840
|
+
ggArmed.current = false;
|
|
841
|
+
searchPrev();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (key === "g") {
|
|
845
|
+
e.preventDefault();
|
|
846
|
+
if (ggArmed.current) {
|
|
847
|
+
ggArmed.current = false;
|
|
848
|
+
goTop();
|
|
849
|
+
} else ggArmed.current = true;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
ggArmed.current = false;
|
|
853
|
+
if (key === "j") {
|
|
854
|
+
e.preventDefault();
|
|
855
|
+
move(1);
|
|
856
|
+
} else if (key === "k") {
|
|
857
|
+
e.preventDefault();
|
|
858
|
+
move(-1);
|
|
859
|
+
} else if (key === "/") {
|
|
860
|
+
e.preventDefault();
|
|
861
|
+
openSearch();
|
|
862
|
+
} else if (key === "n") {
|
|
863
|
+
e.preventDefault();
|
|
864
|
+
searchNext();
|
|
865
|
+
} else if (key === "y") {
|
|
866
|
+
e.preventDefault();
|
|
867
|
+
copyDoc();
|
|
868
|
+
} else if (key === "escape") {
|
|
869
|
+
e.preventDefault();
|
|
870
|
+
if (searchOpen) closeSearch();
|
|
871
|
+
else onClose();
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
new Set(matches);
|
|
875
|
+
matches.length > 0 && matches[activeMatch];
|
|
876
|
+
const lineEls = docLines.map((line, i) => lineElement(line, i, i === cursor));
|
|
877
|
+
const searchBar = searchOpen ? (0, react.createElement)("div", { className: "rec-doc-search" }, (0, react.createElement)("input", {
|
|
878
|
+
className: "rec-doc-search-input",
|
|
879
|
+
value: query,
|
|
880
|
+
placeholder: "/ search doc…",
|
|
881
|
+
onChange: onSearchChange,
|
|
882
|
+
onKeyDown: onSearchKey
|
|
883
|
+
}), (0, react.createElement)("span", { className: "rec-doc-search-count" }, matches.length > 0 ? activeMatch + 1 + "/" + matches.length : query ? "0" : "")) : null;
|
|
884
|
+
const statusText = error || notice || docLines.length + " lines";
|
|
885
|
+
const statusCls = "rec-doc-status" + (error ? " rec-doc-status-error" : notice ? " rec-doc-status-ok" : "");
|
|
886
|
+
return (0, react.createElement)("div", {
|
|
887
|
+
className: "rec-doc",
|
|
888
|
+
"data-theme": theme,
|
|
889
|
+
onKeyDown: onKey,
|
|
890
|
+
ref: rootRef,
|
|
891
|
+
tabIndex: -1
|
|
892
|
+
}, (0, react.createElement)("header", { className: "rec-doc-header" }, (0, react.createElement)("div", { className: "rec-doc-heading" }, (0, react.createElement)("span", { className: "rec-doc-badge" }, "Phase doc"), (0, react.createElement)("h2", { className: "rec-doc-title" }, fileName), (0, react.createElement)("span", { className: "rec-doc-run" }, runId)), (0, react.createElement)("button", {
|
|
893
|
+
type: "button",
|
|
894
|
+
className: "rec-doc-btn rec-doc-copy",
|
|
895
|
+
onClick: copyDoc,
|
|
896
|
+
title: "Copy doc",
|
|
897
|
+
"aria-label": "Copy doc"
|
|
898
|
+
}, "Copy doc"), (0, react.createElement)("button", {
|
|
899
|
+
type: "button",
|
|
900
|
+
className: "rec-doc-btn rec-doc-close",
|
|
901
|
+
onClick: onClose,
|
|
902
|
+
title: "Close",
|
|
903
|
+
"aria-label": "Close"
|
|
904
|
+
}, "Close")), searchBar, (0, react.createElement)("div", { className: "rec-doc-body" }, text === null && error === null ? (0, react.createElement)("p", { className: "rec-doc-text" }, "Loading doc…") : null, error !== null ? (0, react.createElement)("p", { className: "rec-doc-text rec-doc-error" }, error) : null, text !== null ? lineEls : null), (0, react.createElement)("footer", { className: "rec-doc-footer" }, (0, react.createElement)("div", {
|
|
905
|
+
className: statusCls,
|
|
906
|
+
role: "status"
|
|
907
|
+
}, statusText), (0, react.createElement)("div", { className: "rec-doc-hints" }, SEARCH_HINTS.map((h, n) => (0, react.createElement)("span", { key: "hint-" + String(n) }, (0, react.createElement)("kbd", null, h[0]), " " + h[1] + (n < SEARCH_HINTS.length - 1 ? " |" : ""))))));
|
|
908
|
+
}
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/client/inspector.tsx
|
|
911
|
+
/**
|
|
912
|
+
* Drill-down inspector (SP2 R1 + run 15/16): centered modal-style detail panel
|
|
913
|
+
* (Paper .detail shape) with back + close, data-theme (default light), theme
|
|
914
|
+
* toggle, and solid status pills. All from the live host-route snapshot.
|
|
915
|
+
* useBoardTheme is hoisted ABOVE the not-found early return.
|
|
916
|
+
*/
|
|
917
|
+
function closeButton(onClose) {
|
|
918
|
+
if (onClose === void 0) return null;
|
|
919
|
+
return (0, react.createElement)("button", {
|
|
920
|
+
type: "button",
|
|
921
|
+
className: "rec-close",
|
|
922
|
+
onClick: onClose,
|
|
923
|
+
title: "Close",
|
|
924
|
+
"aria-label": "Close"
|
|
925
|
+
}, "×");
|
|
926
|
+
}
|
|
927
|
+
function solidPill(kind) {
|
|
928
|
+
return (0, react.createElement)("span", {
|
|
929
|
+
className: "rec-pill",
|
|
930
|
+
"data-pill": kind
|
|
931
|
+
}, PILL_LABELS[kind]);
|
|
932
|
+
}
|
|
933
|
+
function detailShell(runId, onBackToToolDetails, onClose, headerExtra, body, theme, toggle) {
|
|
934
|
+
return (0, react.createElement)("div", {
|
|
935
|
+
className: "rec-inspector",
|
|
936
|
+
"data-theme": theme
|
|
937
|
+
}, (0, react.createElement)("div", { className: "rec-detail" }, (0, react.createElement)("div", { className: "rec-detail-header" }, (0, react.createElement)("button", {
|
|
938
|
+
type: "button",
|
|
939
|
+
className: "rec-back",
|
|
940
|
+
onClick: onBackToToolDetails
|
|
941
|
+
}, "← Back"), (0, react.createElement)("h2", { className: "rec-detail-title" }, runId), headerExtra, (0, react.createElement)(ThemeToggle, {
|
|
942
|
+
theme,
|
|
943
|
+
toggle
|
|
944
|
+
}), closeButton(onClose)), (0, react.createElement)("div", { className: "rec-detail-body" }, body)));
|
|
945
|
+
}
|
|
946
|
+
function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails, onClose }) {
|
|
947
|
+
const { theme, toggle } = useBoardTheme();
|
|
948
|
+
const [openFileName, setOpenFileName] = (0, react.useState)(null);
|
|
949
|
+
const card = snapshot?.projection?.[worktreeRoot]?.[runId];
|
|
950
|
+
if (card === void 0) return detailShell(runId, onBackToToolDetails, onClose, null, (0, react.createElement)("p", { className: "rec-detail-text" }, "Run not found: " + runId), theme, toggle);
|
|
951
|
+
const facts = cardFacts(card);
|
|
952
|
+
const rows = expandPhaseRows(card);
|
|
953
|
+
return detailShell(runId, onBackToToolDetails, onClose, solidPill(cardPill(card)), openFileName !== null ? (0, react.createElement)(DocViewer, {
|
|
954
|
+
runId,
|
|
955
|
+
worktreeRoot,
|
|
956
|
+
fileName: openFileName,
|
|
957
|
+
theme,
|
|
958
|
+
onClose: () => setOpenFileName(null)
|
|
959
|
+
}) : (0, react.createElement)("section", { className: "rec-detail-section" }, (0, react.createElement)("h3", {}, "Phases"), rows.map((row) => (0, react.createElement)("div", {
|
|
960
|
+
key: row.phase,
|
|
961
|
+
className: "rec-phase-row"
|
|
962
|
+
}, (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)), row.present && row.fileName !== null && (0, react.createElement)("button", {
|
|
963
|
+
type: "button",
|
|
964
|
+
className: "rec-phase-view",
|
|
965
|
+
onClick: () => setOpenFileName(row.fileName),
|
|
966
|
+
title: "View phase doc",
|
|
967
|
+
"aria-label": "View phase doc"
|
|
968
|
+
}, "View phase"))), 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);
|
|
969
|
+
}
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region src/client/settings.tsx
|
|
972
|
+
/**
|
|
973
|
+
* Recursive settings page (Phase D R8, §11.8): a settings.section list entry
|
|
974
|
+
* surfacing the host-owned enforcement config. The client is read-mostly; all
|
|
975
|
+
* writes ride the host config path (never run files).
|
|
976
|
+
*/
|
|
977
|
+
function RecursiveSettings({ close }) {
|
|
978
|
+
return (0, react.createElement)("div", { className: "rec-settings" }, (0, react.createElement)("h2", {}, "Recursive"), (0, react.createElement)("p", {}, "Enforcement policy, scratch format, provider defaults, and preset install path are configured on the host. The client is read-only (§11.9)."), (0, react.createElement)("button", { onClick: close }, "Close"));
|
|
979
|
+
}
|
|
980
|
+
//#endregion
|
|
539
981
|
//#region src/client/use-live.ts
|
|
540
982
|
/**
|
|
541
983
|
* useLiveProjection(scope): the client bridge to the live host route (SP2 R1).
|
|
@@ -1178,6 +1620,316 @@ window.__ModuleLoader__.load({
|
|
|
1178
1620
|
color: var(--board-muted-fg);
|
|
1179
1621
|
}
|
|
1180
1622
|
|
|
1623
|
+
/* ===== Per-phase doc viewer (0.2.4, inside the inspector detail body) ===== */
|
|
1624
|
+
|
|
1625
|
+
/* View phase ghost button on each present phase row. */
|
|
1626
|
+
.rec-phase-view {
|
|
1627
|
+
flex: none;
|
|
1628
|
+
padding: 3px 10px;
|
|
1629
|
+
font-size: var(--board-text-xs);
|
|
1630
|
+
color: var(--board-info);
|
|
1631
|
+
background: transparent;
|
|
1632
|
+
border: 1px solid var(--board-border);
|
|
1633
|
+
border-radius: 999px;
|
|
1634
|
+
cursor: pointer;
|
|
1635
|
+
white-space: nowrap;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.rec-phase-view:hover {
|
|
1639
|
+
background: var(--board-accent);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/* Viewer shell: fills the inspector detail body (the panel owns data-theme). */
|
|
1643
|
+
.rec-doc {
|
|
1644
|
+
display: flex;
|
|
1645
|
+
flex-direction: column;
|
|
1646
|
+
min-height: 0;
|
|
1647
|
+
gap: var(--board-space-12);
|
|
1648
|
+
color: var(--board-fg);
|
|
1649
|
+
font-family: var(--board-font-sans);
|
|
1650
|
+
background: var(--board-bg);
|
|
1651
|
+
border: 1px solid var(--board-border);
|
|
1652
|
+
border-radius: var(--board-radius-xl);
|
|
1653
|
+
overflow: hidden;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
.rec-doc-header {
|
|
1657
|
+
display: flex;
|
|
1658
|
+
align-items: center;
|
|
1659
|
+
gap: var(--board-space-12);
|
|
1660
|
+
padding: var(--board-space-12) var(--board-space-16);
|
|
1661
|
+
border-bottom: 1px solid var(--board-border);
|
|
1662
|
+
flex: none;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
.rec-doc-heading {
|
|
1666
|
+
display: flex;
|
|
1667
|
+
align-items: baseline;
|
|
1668
|
+
gap: var(--board-space-12);
|
|
1669
|
+
flex: 1;
|
|
1670
|
+
min-width: 0;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
.rec-doc-badge {
|
|
1674
|
+
flex: none;
|
|
1675
|
+
padding: 2px 10px;
|
|
1676
|
+
font-size: 11px;
|
|
1677
|
+
font-weight: var(--board-fw-semibold);
|
|
1678
|
+
letter-spacing: 0.05em;
|
|
1679
|
+
text-transform: uppercase;
|
|
1680
|
+
border-radius: 999px;
|
|
1681
|
+
background: var(--board-accent);
|
|
1682
|
+
color: var(--board-muted-fg);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
.rec-doc-title {
|
|
1686
|
+
margin: 0;
|
|
1687
|
+
font-size: var(--board-text-sm);
|
|
1688
|
+
font-weight: var(--board-fw-semibold);
|
|
1689
|
+
letter-spacing: var(--board-tracking-tight);
|
|
1690
|
+
overflow: hidden;
|
|
1691
|
+
text-overflow: ellipsis;
|
|
1692
|
+
white-space: nowrap;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
.rec-doc-run {
|
|
1696
|
+
flex: none;
|
|
1697
|
+
font-family: var(--board-font-mono);
|
|
1698
|
+
font-size: var(--board-text-xs);
|
|
1699
|
+
color: var(--board-muted-fg);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
.rec-doc-btn {
|
|
1703
|
+
flex: none;
|
|
1704
|
+
padding: 5px 12px;
|
|
1705
|
+
font-size: var(--board-text-xs);
|
|
1706
|
+
color: var(--board-fg);
|
|
1707
|
+
background: transparent;
|
|
1708
|
+
border: 1px solid var(--board-border);
|
|
1709
|
+
border-radius: 999px;
|
|
1710
|
+
cursor: pointer;
|
|
1711
|
+
white-space: nowrap;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
.rec-doc-btn:hover {
|
|
1715
|
+
background: var(--board-accent);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
/* Search bar (/ to open). */
|
|
1719
|
+
.rec-doc-search {
|
|
1720
|
+
display: flex;
|
|
1721
|
+
align-items: center;
|
|
1722
|
+
gap: var(--board-space-12);
|
|
1723
|
+
padding: var(--board-space-8) var(--board-space-16);
|
|
1724
|
+
border-bottom: 1px solid var(--board-border);
|
|
1725
|
+
flex: none;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
.rec-doc-search-input {
|
|
1729
|
+
flex: 1 1 auto;
|
|
1730
|
+
background: var(--board-muted);
|
|
1731
|
+
color: var(--board-fg);
|
|
1732
|
+
border: 1px solid var(--board-border);
|
|
1733
|
+
border-radius: var(--board-radius-md);
|
|
1734
|
+
padding: 5px 10px;
|
|
1735
|
+
font-size: var(--board-text-xs);
|
|
1736
|
+
font-family: inherit;
|
|
1737
|
+
min-width: 0;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
.rec-doc-search-input::placeholder {
|
|
1741
|
+
color: var(--board-muted-fg);
|
|
1742
|
+
opacity: 1;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
.rec-doc-search-count {
|
|
1746
|
+
font-size: var(--board-text-xs);
|
|
1747
|
+
color: var(--board-muted-fg);
|
|
1748
|
+
white-space: nowrap;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
/* Body: scrollable line list. */
|
|
1752
|
+
.rec-doc-body {
|
|
1753
|
+
flex: 1 1 auto;
|
|
1754
|
+
min-height: 0;
|
|
1755
|
+
overflow-y: auto;
|
|
1756
|
+
padding: var(--board-space-12) var(--board-space-16) var(--board-space-16);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
.rec-doc-line {
|
|
1760
|
+
font-size: var(--board-text-sm);
|
|
1761
|
+
line-height: 1.7;
|
|
1762
|
+
padding: 0 6px;
|
|
1763
|
+
border-left: 2px solid transparent;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
.rec-doc-line-current {
|
|
1767
|
+
background: var(--board-muted);
|
|
1768
|
+
border-left-color: var(--board-info);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
.rec-doc-blank {
|
|
1772
|
+
height: 10px;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
.rec-doc-plain {
|
|
1776
|
+
white-space: pre-wrap;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
.rec-doc-h1 {
|
|
1780
|
+
font-size: 22px;
|
|
1781
|
+
font-weight: 700;
|
|
1782
|
+
margin: 8px 0 6px;
|
|
1783
|
+
line-height: 1.3;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
.rec-doc-h2 {
|
|
1787
|
+
font-size: 18px;
|
|
1788
|
+
font-weight: 600;
|
|
1789
|
+
margin: 12px 0 4px;
|
|
1790
|
+
line-height: 1.3;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
.rec-doc-h3 {
|
|
1794
|
+
font-size: 15px;
|
|
1795
|
+
font-weight: 600;
|
|
1796
|
+
margin: 10px 0 4px;
|
|
1797
|
+
line-height: 1.3;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
.rec-doc-h4 {
|
|
1801
|
+
font-size: 14px;
|
|
1802
|
+
font-weight: 600;
|
|
1803
|
+
margin: 8px 0 4px;
|
|
1804
|
+
line-height: 1.3;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
.rec-doc-li {
|
|
1808
|
+
display: flex;
|
|
1809
|
+
align-items: baseline;
|
|
1810
|
+
gap: 8px;
|
|
1811
|
+
font-size: var(--board-text-sm);
|
|
1812
|
+
line-height: 1.65;
|
|
1813
|
+
margin: 0 0 4px;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
.rec-doc-bullet {
|
|
1817
|
+
flex: none;
|
|
1818
|
+
color: var(--board-muted-fg);
|
|
1819
|
+
width: 14px;
|
|
1820
|
+
text-align: center;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
.rec-doc-li-text {
|
|
1824
|
+
flex: 1;
|
|
1825
|
+
min-width: 0;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
.rec-doc-pre {
|
|
1829
|
+
margin: 6px 0;
|
|
1830
|
+
padding: var(--board-space-12);
|
|
1831
|
+
background: var(--board-muted);
|
|
1832
|
+
border: 1px solid var(--board-border);
|
|
1833
|
+
border-radius: var(--board-radius-md);
|
|
1834
|
+
overflow-x: auto;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
.rec-doc-code,
|
|
1838
|
+
.rec-doc-inline-code {
|
|
1839
|
+
font-family: var(--board-font-mono);
|
|
1840
|
+
font-size: 12.5px;
|
|
1841
|
+
letter-spacing: var(--board-tracking-mono);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
.rec-doc-inline-code {
|
|
1845
|
+
background: var(--board-muted);
|
|
1846
|
+
border: 1px solid var(--board-border);
|
|
1847
|
+
border-radius: 4px;
|
|
1848
|
+
padding: 0 4px;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
.rec-doc-inline-link {
|
|
1852
|
+
color: var(--board-info);
|
|
1853
|
+
text-decoration: underline;
|
|
1854
|
+
cursor: pointer;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
.rec-doc-table {
|
|
1858
|
+
width: 100%;
|
|
1859
|
+
border-collapse: collapse;
|
|
1860
|
+
margin: 6px 0;
|
|
1861
|
+
font-size: var(--board-text-xs);
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
.rec-doc-table th,
|
|
1865
|
+
.rec-doc-table td {
|
|
1866
|
+
border: 1px solid var(--board-border);
|
|
1867
|
+
padding: 4px 8px;
|
|
1868
|
+
text-align: left;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
.rec-doc-table th {
|
|
1872
|
+
background: var(--board-muted);
|
|
1873
|
+
color: var(--board-muted-fg);
|
|
1874
|
+
font-weight: var(--board-fw-semibold);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/* Footer: status + key hints. */
|
|
1878
|
+
.rec-doc-footer {
|
|
1879
|
+
flex: none;
|
|
1880
|
+
display: flex;
|
|
1881
|
+
align-items: center;
|
|
1882
|
+
gap: var(--board-space-16);
|
|
1883
|
+
padding: var(--board-space-8) var(--board-space-16);
|
|
1884
|
+
border-top: 1px solid var(--board-border);
|
|
1885
|
+
flex-wrap: wrap;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
.rec-doc-status {
|
|
1889
|
+
font-size: var(--board-text-xs);
|
|
1890
|
+
color: var(--board-muted-fg);
|
|
1891
|
+
min-width: 120px;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
.rec-doc-status-error {
|
|
1895
|
+
color: var(--board-error);
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
.rec-doc-status-ok {
|
|
1899
|
+
color: var(--board-success);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
.rec-doc-hints {
|
|
1903
|
+
display: flex;
|
|
1904
|
+
align-items: center;
|
|
1905
|
+
gap: 2px;
|
|
1906
|
+
flex-wrap: wrap;
|
|
1907
|
+
font-size: var(--board-text-xs);
|
|
1908
|
+
color: var(--board-muted-fg);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
.rec-doc-hints kbd {
|
|
1912
|
+
background: var(--board-muted);
|
|
1913
|
+
border: 1px solid var(--board-border);
|
|
1914
|
+
border-radius: 4px;
|
|
1915
|
+
padding: 1px 6px;
|
|
1916
|
+
font-family: inherit;
|
|
1917
|
+
font-size: 11px;
|
|
1918
|
+
font-weight: 600;
|
|
1919
|
+
color: var(--board-fg);
|
|
1920
|
+
margin: 0 3px 0 8px;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
.rec-doc-text {
|
|
1924
|
+
margin: 0;
|
|
1925
|
+
font-size: var(--board-text-sm);
|
|
1926
|
+
color: var(--board-muted-fg);
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
.rec-doc-error {
|
|
1930
|
+
color: var(--board-error);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1181
1933
|
/* ===== Strip (session dock) — keeps the shell --dsw-* theme ===== */
|
|
1182
1934
|
.rec-badge {
|
|
1183
1935
|
flex: none;
|
|
@@ -1407,6 +2159,7 @@ window.__ModuleLoader__.load({
|
|
|
1407
2159
|
}
|
|
1408
2160
|
//#endregion
|
|
1409
2161
|
exports.Board = Board;
|
|
2162
|
+
exports.DocViewer = DocViewer;
|
|
1410
2163
|
exports.Inspector = Inspector;
|
|
1411
2164
|
exports.RecursiveSettings = RecursiveSettings;
|
|
1412
2165
|
exports.RecursiveView = RecursiveView;
|
|
@@ -1417,6 +2170,8 @@ window.__ModuleLoader__.load({
|
|
|
1417
2170
|
exports.inject = inject;
|
|
1418
2171
|
exports.isRecursivePreset = isRecursivePreset;
|
|
1419
2172
|
exports.listRuns = listRuns;
|
|
2173
|
+
exports.parseDoc = parseDoc;
|
|
2174
|
+
exports.parseInline = parseInline;
|
|
1420
2175
|
exports.subscribeLiveEvents = subscribeLiveEvents;
|
|
1421
2176
|
exports.useLiveProjection = useLiveProjection;
|
|
1422
2177
|
return module.exports;
|