dsh-diff-approval 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +363 -198
- package/lib/index.js +190 -7
- package/lib/types/client/PendingPanel.d.ts +2 -2
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/port.d.ts +3 -1
- package/lib/types/client/slots.d.ts +3 -1
- package/lib/types/client/store.d.ts +3 -0
- package/lib/types/index.d.ts +17 -6
- package/lib/types/open.d.ts +17 -0
- package/lib/types/types.d.ts +7 -0
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -11523,7 +11523,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11523
11523
|
}
|
|
11524
11524
|
//#endregion
|
|
11525
11525
|
//#region \0dsh-css:C:\PROJECTS\dsh-diff-approval\src\client\PendingPanel.module.css.mjs
|
|
11526
|
-
const css = ".Ay7oXG_layer{flex:none;align-items:center;width:100%;height:49px;margin:8px 0 0;display:flex;position:relative}.Ay7oXG_footerButtons{align-items:center;width:100%;display:flex}.Ay7oXG_badge{width:100%;height:49px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:0 8px 0 6px;font-family:inherit;font-size:14px;display:inline-flex;overflow:hidden}.Ay7oXG_badge:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.Ay7oXG_badge[data-active]{background:var(--dsw-alias-interactive-bg-hover)}.Ay7oXG_badgeLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Ay7oXG_badgeCount{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;margin-left:auto;font-size:12px;line-height:16px}.Ay7oXG_layer.Ay7oXG_rail{width:36px;height:36px;margin:0}.Ay7oXG_rail .Ay7oXG_badge{border-radius:50%;justify-content:center;gap:0;width:36px;height:36px;padding:0}.Ay7oXG_rail .Ay7oXG_badgeLabel{display:none}.Ay7oXG_rail .Ay7oXG_badgeCount{box-sizing:border-box;background:var(--dsw-alias-state-business-primary);min-width:18px;height:18px;color:var(--dsw-alias-label-primary-foreground);font-variant-numeric:tabular-nums;border-radius:9px;justify-content:center;align-items:center;padding:0 4px;font-size:11px;line-height:18px;display:flex;position:absolute;top:-3px;right:-7px}.Ay7oXG_panel{z-index:30;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:none;
|
|
11526
|
+
const css = ".Ay7oXG_layer{flex:none;align-items:center;width:100%;height:49px;margin:8px 0 0;display:flex;position:relative}.Ay7oXG_footerButtons{align-items:center;width:100%;display:flex}.Ay7oXG_badge{width:100%;height:49px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:0 8px 0 6px;font-family:inherit;font-size:14px;display:inline-flex;overflow:hidden}.Ay7oXG_badge:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.Ay7oXG_badge[data-active]{background:var(--dsw-alias-interactive-bg-hover)}.Ay7oXG_badgeLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Ay7oXG_badgeCount{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;margin-left:auto;font-size:12px;line-height:16px}.Ay7oXG_layer.Ay7oXG_rail{width:36px;height:36px;margin:0}.Ay7oXG_rail .Ay7oXG_badge{border-radius:50%;justify-content:center;gap:0;width:36px;height:36px;padding:0}.Ay7oXG_rail .Ay7oXG_badgeLabel{display:none}.Ay7oXG_rail .Ay7oXG_badgeCount{box-sizing:border-box;background:var(--dsw-alias-state-business-primary);min-width:18px;height:18px;color:var(--dsw-alias-label-primary-foreground);font-variant-numeric:tabular-nums;border-radius:9px;justify-content:center;align-items:center;padding:0 4px;font-size:11px;line-height:18px;display:flex;position:absolute;top:-3px;right:-7px}.Ay7oXG_panel{z-index:30;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:none;box-shadow:var(--dsw-shadow-lv2);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px;flex-direction:column;display:flex;position:fixed;inset:8px 8px 128px;overflow:hidden}.Ay7oXG_header{box-sizing:border-box;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex:none;justify-content:space-between;align-items:center;min-height:44px;padding:10px 12px;display:flex}.Ay7oXG_states{flex:1;min-height:0;padding:4px 12px 12px;overflow-y:auto}.Ay7oXG_split{flex:1;align-items:stretch;min-height:0;display:flex}.Ay7oXG_fileList{box-sizing:border-box;border-right:1px solid var(--dsw-alias-border-l2);flex:none;width:240px;min-height:0;padding:4px 8px 12px;overflow-y:auto}.Ay7oXG_resizeHandle{cursor:col-resize;background:0 0;flex:none;width:5px;margin:0 -2px}.Ay7oXG_resizeHandle:hover{background:var(--dsw-alias-border-l2)}.Ay7oXG_detail{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.Ay7oXG_detailEmpty{color:var(--dsw-alias-label-tertiary);text-align:center;flex:1;justify-content:center;align-items:center;margin:0;padding:24px;font-size:12px;line-height:18px;display:flex}.Ay7oXG_title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}.Ay7oXG_note,.Ay7oXG_readError,.Ay7oXG_hint{color:var(--dsw-alias-label-tertiary);margin:4px 0;font-size:12px;line-height:18px}.Ay7oXG_readError{color:var(--dsw-alias-state-error-primary)}.Ay7oXG_group{color:var(--dsw-alias-label-tertiary);margin:8px 0 4px;font-size:12px;font-weight:500;line-height:16px}.Ay7oXG_rows{margin:0;padding:0;list-style:none}.Ay7oXG_row{border:1px solid #0000;border-radius:10px;margin:2px 0}.Ay7oXG_rowHead{width:100%;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:baseline;gap:8px;padding:6px 8px;display:flex}.Ay7oXG_rowHead:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.Ay7oXG_rowHead[data-selected]{background:var(--dsw-alias-interactive-bg-hover)}.Ay7oXG_rowPath{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:var(--dsw-font-markdown-code-block);text-align:left;direction:rtl;overflow:hidden}.Ay7oXG_kindTag{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.Ay7oXG_kindHint{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:16px}.Ay7oXG_divergedHint,.Ay7oXG_missingHint{border-bottom:1px solid var(--dsw-alias-border-l2);margin:0;padding:6px 8px;font-size:12px;line-height:18px}.Ay7oXG_missing{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary));white-space:nowrap;border-radius:6px;flex:none;padding:1px 6px;font-size:11px;line-height:14px}.Ay7oXG_missingHint{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary))}.Ay7oXG_rowMeta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;gap:6px;margin-left:auto;font-size:12px;line-height:16px;display:inline-flex}.Ay7oXG_addCount{color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 60%, var(--dsw-alias-label-primary))}.Ay7oXG_delCount{color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 60%, var(--dsw-alias-label-primary))}.Ay7oXG_diff{background:var(--dsw-alias-markdown-code-block);flex-direction:column;flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.Ay7oXG_diffActions{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:6px 8px;display:flex}.Ay7oXG_diffStats{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px;line-height:16px}.Ay7oXG_flexSpacer{flex:1}.Ay7oXG_action{border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:3px 12px;font-family:inherit;font-size:12px;line-height:18px}.Ay7oXG_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.Ay7oXG_action:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Ay7oXG_iconAction{justify-content:center;align-items:center;padding:4px 6px;display:inline-flex}.Ay7oXG_diffBody{min-height:0;font:var(--dsw-font-markdown-code-block);flex:1;padding:8px 10px;overflow:auto}.Ay7oXG_lines{border-spacing:0;width:max-content;min-width:100%;display:table}.Ay7oXG_line{user-select:none;display:table-row}.Ay7oXG_gutter{box-sizing:border-box;text-align:right;width:44px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;user-select:none;padding-right:10px;display:table-cell}.Ay7oXG_code{white-space:pre;display:table-cell}.Ay7oXG_context{color:var(--dsw-alias-label-primary)}.Ay7oXG_del{background-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}.Ay7oXG_add{background-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent)}.Ay7oXG_toolbar{z-index:2;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);box-shadow:var(--dsw-shadow-lv2);border-radius:8px;gap:4px;padding:3px;display:flex;position:absolute;right:10px}.Ay7oXG_toolbarButton{color:var(--dsw-alias-label-primary);white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:6px;padding:3px 10px;font-family:inherit;font-size:12px;line-height:18px}.Ay7oXG_toolbarButton:hover{background:var(--dsw-alias-interactive-bg-hover)}";
|
|
11527
11527
|
const tagId = "dsh-diff-approval/PendingPanel.module.css";
|
|
11528
11528
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
11529
11529
|
const tag = document.createElement("style");
|
|
@@ -11533,49 +11533,57 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11533
11533
|
document.head.appendChild(tag);
|
|
11534
11534
|
}
|
|
11535
11535
|
var PendingPanel_module_css_default = {
|
|
11536
|
+
"panel": "Ay7oXG_panel",
|
|
11537
|
+
"note": "Ay7oXG_note",
|
|
11538
|
+
"code": "Ay7oXG_code",
|
|
11539
|
+
"rowHead": "Ay7oXG_rowHead",
|
|
11540
|
+
"line": "Ay7oXG_line",
|
|
11536
11541
|
"diffActions": "Ay7oXG_diffActions",
|
|
11537
|
-
"
|
|
11538
|
-
"
|
|
11542
|
+
"diff": "Ay7oXG_diff",
|
|
11543
|
+
"rowPath": "Ay7oXG_rowPath",
|
|
11544
|
+
"missing": "Ay7oXG_missing",
|
|
11545
|
+
"add": "Ay7oXG_add",
|
|
11546
|
+
"badge": "Ay7oXG_badge",
|
|
11539
11547
|
"badgeCount": "Ay7oXG_badgeCount",
|
|
11540
|
-
"rail": "Ay7oXG_rail",
|
|
11541
|
-
"kindHint": "Ay7oXG_kindHint",
|
|
11542
11548
|
"row": "Ay7oXG_row",
|
|
11543
|
-
"
|
|
11544
|
-
"
|
|
11545
|
-
"
|
|
11546
|
-
"
|
|
11547
|
-
"
|
|
11549
|
+
"kindHint": "Ay7oXG_kindHint",
|
|
11550
|
+
"badgeLabel": "Ay7oXG_badgeLabel",
|
|
11551
|
+
"resizeHandle": "Ay7oXG_resizeHandle",
|
|
11552
|
+
"iconAction": "Ay7oXG_iconAction",
|
|
11553
|
+
"gutter": "Ay7oXG_gutter",
|
|
11548
11554
|
"diffStats": "Ay7oXG_diffStats",
|
|
11549
|
-
"
|
|
11550
|
-
"
|
|
11555
|
+
"missingHint": "Ay7oXG_missingHint",
|
|
11556
|
+
"states": "Ay7oXG_states",
|
|
11557
|
+
"layer": "Ay7oXG_layer",
|
|
11558
|
+
"detail": "Ay7oXG_detail",
|
|
11559
|
+
"readError": "Ay7oXG_readError",
|
|
11560
|
+
"del": "Ay7oXG_del",
|
|
11561
|
+
"detailEmpty": "Ay7oXG_detailEmpty",
|
|
11562
|
+
"title": "Ay7oXG_title",
|
|
11551
11563
|
"hint": "Ay7oXG_hint",
|
|
11552
|
-
"panel": "Ay7oXG_panel",
|
|
11553
|
-
"rowHead": "Ay7oXG_rowHead",
|
|
11554
|
-
"badgeLabel": "Ay7oXG_badgeLabel",
|
|
11555
|
-
"badge": "Ay7oXG_badge",
|
|
11556
|
-
"header": "Ay7oXG_header",
|
|
11557
|
-
"rowPath": "Ay7oXG_rowPath",
|
|
11558
11564
|
"rowMeta": "Ay7oXG_rowMeta",
|
|
11559
|
-
"
|
|
11560
|
-
"
|
|
11561
|
-
"
|
|
11565
|
+
"rows": "Ay7oXG_rows",
|
|
11566
|
+
"divergedHint": "Ay7oXG_divergedHint",
|
|
11567
|
+
"fileList": "Ay7oXG_fileList",
|
|
11568
|
+
"addCount": "Ay7oXG_addCount",
|
|
11569
|
+
"split": "Ay7oXG_split",
|
|
11570
|
+
"rail": "Ay7oXG_rail",
|
|
11571
|
+
"delCount": "Ay7oXG_delCount",
|
|
11572
|
+
"kindTag": "Ay7oXG_kindTag",
|
|
11562
11573
|
"action": "Ay7oXG_action",
|
|
11563
|
-
"note": "Ay7oXG_note",
|
|
11564
11574
|
"diffBody": "Ay7oXG_diffBody",
|
|
11565
|
-
"gutter": "Ay7oXG_gutter",
|
|
11566
|
-
"kindTag": "Ay7oXG_kindTag",
|
|
11567
|
-
"line": "Ay7oXG_line",
|
|
11568
|
-
"context": "Ay7oXG_context",
|
|
11569
|
-
"lines": "Ay7oXG_lines",
|
|
11570
11575
|
"toolbar": "Ay7oXG_toolbar",
|
|
11571
|
-
"
|
|
11572
|
-
"
|
|
11573
|
-
"
|
|
11574
|
-
"
|
|
11576
|
+
"toolbarButton": "Ay7oXG_toolbarButton",
|
|
11577
|
+
"header": "Ay7oXG_header",
|
|
11578
|
+
"footerButtons": "Ay7oXG_footerButtons",
|
|
11579
|
+
"context": "Ay7oXG_context",
|
|
11580
|
+
"group": "Ay7oXG_group",
|
|
11581
|
+
"flexSpacer": "Ay7oXG_flexSpacer",
|
|
11582
|
+
"lines": "Ay7oXG_lines"
|
|
11575
11583
|
};
|
|
11576
11584
|
//#endregion
|
|
11577
11585
|
//#region lib/types/client/PendingPanel.js
|
|
11578
|
-
/** Sidebar-foot pending-edit review action and the
|
|
11586
|
+
/** Sidebar-foot pending-edit review action and the split review panel it opens. */
|
|
11579
11587
|
/**
|
|
11580
11588
|
* How often the panel re-reads the pending list. An external plugin cannot
|
|
11581
11589
|
* register on the host's forwarded-event allowlist, so polling is its change
|
|
@@ -11583,6 +11591,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11583
11591
|
* even with the panel closed. The read is one small RPC per second.
|
|
11584
11592
|
*/
|
|
11585
11593
|
const POLL_INTERVAL_MS = 1e3;
|
|
11594
|
+
/** Panel bottom offset when the composer cannot be measured, in px. */
|
|
11595
|
+
const FALLBACK_BOTTOM_PX = 128;
|
|
11596
|
+
/** Gap kept between the composer's top edge and the panel bottom, in px. */
|
|
11597
|
+
const COMPOSER_GAP_PX = 12;
|
|
11598
|
+
/** The harness composer's stable hook (ui-conversation InputBar). */
|
|
11599
|
+
const COMPOSER_SELECTOR = "[data-composer-card]";
|
|
11600
|
+
/** File-list pane width bounds for the manual split drag, in px. */
|
|
11601
|
+
const MIN_LIST_WIDTH_PX = 160;
|
|
11602
|
+
const MAX_LIST_WIDTH_PX = 560;
|
|
11586
11603
|
/** The diff body's row class per line kind. */
|
|
11587
11604
|
const ROW_CLASS = {
|
|
11588
11605
|
context: PendingPanel_module_css_default.context,
|
|
@@ -11610,10 +11627,51 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11610
11627
|
});
|
|
11611
11628
|
return blocks;
|
|
11612
11629
|
}
|
|
11613
|
-
|
|
11630
|
+
/** One row of the file list: the clickable head in the left pane. */
|
|
11631
|
+
function PendingFileRow({ file, files, selected, t, onSelect }) {
|
|
11632
|
+
const stats = (0, react.useMemo)(() => computeWholeFileDiff(file.oldText, file.newText), [file.oldText, file.newText]);
|
|
11633
|
+
return (0, react_jsx_runtime.jsx)("li", {
|
|
11634
|
+
className: PendingPanel_module_css_default.row,
|
|
11635
|
+
children: (0, react_jsx_runtime.jsxs)("button", {
|
|
11636
|
+
type: "button",
|
|
11637
|
+
className: PendingPanel_module_css_default.rowHead,
|
|
11638
|
+
"data-selected": selected || void 0,
|
|
11639
|
+
onClick: () => {
|
|
11640
|
+
onSelect(selected ? "" : file.id);
|
|
11641
|
+
},
|
|
11642
|
+
children: [
|
|
11643
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
11644
|
+
className: PendingPanel_module_css_default.rowPath,
|
|
11645
|
+
title: file.path,
|
|
11646
|
+
children: copyDisplayPath(file.path, files)
|
|
11647
|
+
}),
|
|
11648
|
+
file.kind === "create" && (0, react_jsx_runtime.jsx)("span", {
|
|
11649
|
+
className: PendingPanel_module_css_default.kindTag,
|
|
11650
|
+
children: t("row.create")
|
|
11651
|
+
}),
|
|
11652
|
+
file.missing && (0, react_jsx_runtime.jsx)("span", {
|
|
11653
|
+
className: PendingPanel_module_css_default.missing,
|
|
11654
|
+
title: t("panel.missingHint"),
|
|
11655
|
+
children: t("panel.missing")
|
|
11656
|
+
}),
|
|
11657
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
11658
|
+
className: PendingPanel_module_css_default.rowMeta,
|
|
11659
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
11660
|
+
className: PendingPanel_module_css_default.addCount,
|
|
11661
|
+
children: t("row.added", { added: stats.added })
|
|
11662
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
11663
|
+
className: PendingPanel_module_css_default.delCount,
|
|
11664
|
+
children: t("row.removed", { removed: stats.removed })
|
|
11665
|
+
})]
|
|
11666
|
+
})
|
|
11667
|
+
]
|
|
11668
|
+
})
|
|
11669
|
+
});
|
|
11670
|
+
}
|
|
11671
|
+
/** The selected file's diff, actions, jump controls, and copy toolbar. */
|
|
11672
|
+
function PendingDiff({ file, files, busy, t, onKeep, onRevert, onOpen }) {
|
|
11614
11673
|
const lang = (0, react.useMemo)(() => langFromPath(file.path), [file.path]);
|
|
11615
11674
|
const model = (0, react.useMemo)(() => {
|
|
11616
|
-
if (!selected) return void 0;
|
|
11617
11675
|
const diff = computeWholeFileDiff(file.oldText, file.newText);
|
|
11618
11676
|
return {
|
|
11619
11677
|
diff,
|
|
@@ -11622,7 +11680,6 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11622
11680
|
newRuns: highlightLines(file.newText, lang)
|
|
11623
11681
|
};
|
|
11624
11682
|
}, [
|
|
11625
|
-
selected,
|
|
11626
11683
|
file.oldText,
|
|
11627
11684
|
file.newText,
|
|
11628
11685
|
lang
|
|
@@ -11638,16 +11695,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11638
11695
|
setDrag(void 0);
|
|
11639
11696
|
setRange(void 0);
|
|
11640
11697
|
setCopied(false);
|
|
11641
|
-
}, [file.id
|
|
11698
|
+
}, [file.id]);
|
|
11642
11699
|
(0, react.useEffect)(() => {
|
|
11643
|
-
if (model
|
|
11700
|
+
if (model.blocks.length === 0) return;
|
|
11644
11701
|
const block = model.blocks[focus];
|
|
11645
11702
|
if (block === void 0) return;
|
|
11646
11703
|
rowRefs.current.get(block.start)?.scrollIntoView({ block: "center" });
|
|
11647
11704
|
}, [model, focus]);
|
|
11648
11705
|
const jump = (direction) => {
|
|
11649
11706
|
setFocus((current) => {
|
|
11650
|
-
if (model
|
|
11707
|
+
if (model.blocks.length === 0) return current;
|
|
11651
11708
|
if (direction === -1) return (current - 1 + model.blocks.length) % model.blocks.length;
|
|
11652
11709
|
const bodyRect = bodyRef.current?.getBoundingClientRect();
|
|
11653
11710
|
for (let index = current + 1; index < model.blocks.length; index++) {
|
|
@@ -11698,7 +11755,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11698
11755
|
});
|
|
11699
11756
|
});
|
|
11700
11757
|
};
|
|
11701
|
-
const selection = drag === void 0 && range !== void 0
|
|
11758
|
+
const selection = drag === void 0 && range !== void 0 ? range : void 0;
|
|
11702
11759
|
const selectionTop = (() => {
|
|
11703
11760
|
if (selection === void 0) return 0;
|
|
11704
11761
|
const row = rowRefs.current.get(selection.start);
|
|
@@ -11710,7 +11767,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11710
11767
|
return Math.min(clamped, Math.max(bodyTop, bodyTop + body.clientHeight - 36));
|
|
11711
11768
|
})();
|
|
11712
11769
|
const copySelection = async () => {
|
|
11713
|
-
if (selection === void 0
|
|
11770
|
+
if (selection === void 0) return;
|
|
11714
11771
|
const lineNumbers = model.diff.rows.slice(selection.start, selection.end + 1).map((row) => row.newLine ?? row.oldLine).filter((number) => number !== void 0);
|
|
11715
11772
|
if (lineNumbers.length === 0) return;
|
|
11716
11773
|
if (!await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(referenceOf(file.path, files, Math.min(...lineNumbers), Math.max(...lineNumbers)))) return;
|
|
@@ -11722,161 +11779,161 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11722
11779
|
/** One line's text: token spans when highlighted, plain text otherwise. */
|
|
11723
11780
|
const renderLine = (row) => {
|
|
11724
11781
|
const lineNumber = row.kind === "del" ? row.oldLine : row.newLine;
|
|
11725
|
-
const runs =
|
|
11782
|
+
const runs = lineNumber === void 0 ? void 0 : (row.kind === "del" ? model.oldRuns : model.newRuns)?.[lineNumber - 1];
|
|
11726
11783
|
if (runs === void 0 || runs.length === 0) return row.text === "" ? "\xA0" : row.text;
|
|
11727
11784
|
return runs.map((span, index) => (0, react_jsx_runtime.jsx)("span", {
|
|
11728
11785
|
style: span.style,
|
|
11729
11786
|
children: span.text
|
|
11730
11787
|
}, index));
|
|
11731
11788
|
};
|
|
11732
|
-
const focusedBlock = model
|
|
11789
|
+
const focusedBlock = model.blocks.length > 0 ? model.blocks[focus] : void 0;
|
|
11733
11790
|
const inFocusedBlock = (index) => focusedBlock !== void 0 && index >= focusedBlock.start && index <= focusedBlock.end;
|
|
11734
|
-
return (0, react_jsx_runtime.jsxs)("
|
|
11735
|
-
className: PendingPanel_module_css_default.
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
"
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
title: file.path,
|
|
11748
|
-
children: copyDisplayPath(file.path, files)
|
|
11749
|
-
}),
|
|
11750
|
-
file.kind === "create" && (0, react_jsx_runtime.jsx)("span", {
|
|
11751
|
-
className: PendingPanel_module_css_default.kindTag,
|
|
11752
|
-
children: t("row.create")
|
|
11753
|
-
}),
|
|
11754
|
-
file.missing && (0, react_jsx_runtime.jsx)("span", {
|
|
11755
|
-
className: PendingPanel_module_css_default.missing,
|
|
11756
|
-
title: t("panel.missingHint"),
|
|
11757
|
-
children: t("panel.missing")
|
|
11758
|
-
})
|
|
11759
|
-
]
|
|
11760
|
-
}), selected && model !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
11761
|
-
className: PendingPanel_module_css_default.diff,
|
|
11762
|
-
"data-diff-approval-diff": true,
|
|
11763
|
-
onMouseUp: endDrag,
|
|
11764
|
-
children: [
|
|
11765
|
-
(0, react_jsx_runtime.jsxs)("div", {
|
|
11766
|
-
className: PendingPanel_module_css_default.diffActions,
|
|
11767
|
-
children: [
|
|
11768
|
-
(0, react_jsx_runtime.jsx)("span", {
|
|
11769
|
-
className: PendingPanel_module_css_default.diffStats,
|
|
11770
|
-
children: t("panel.stats", {
|
|
11771
|
-
added: model.diff.added,
|
|
11772
|
-
removed: model.diff.removed
|
|
11773
|
-
})
|
|
11774
|
-
}),
|
|
11775
|
-
file.kind === "create" && (0, react_jsx_runtime.jsx)("span", {
|
|
11776
|
-
className: PendingPanel_module_css_default.kindHint,
|
|
11777
|
-
children: t("panel.createHint")
|
|
11778
|
-
}),
|
|
11779
|
-
model.blocks.length > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("button", {
|
|
11780
|
-
type: "button",
|
|
11781
|
-
className: PendingPanel_module_css_default.action,
|
|
11782
|
-
"data-diff-prev": true,
|
|
11783
|
-
disabled: busy,
|
|
11784
|
-
onClick: () => {
|
|
11785
|
-
jump(-1);
|
|
11786
|
-
},
|
|
11787
|
-
children: t("action.prevDiff")
|
|
11788
|
-
}), (0, react_jsx_runtime.jsx)("button", {
|
|
11789
|
-
type: "button",
|
|
11790
|
-
className: PendingPanel_module_css_default.action,
|
|
11791
|
-
"data-diff-next": true,
|
|
11792
|
-
disabled: busy,
|
|
11793
|
-
onClick: () => {
|
|
11794
|
-
jump(1);
|
|
11795
|
-
},
|
|
11796
|
-
children: t("action.nextDiff")
|
|
11797
|
-
})] }),
|
|
11798
|
-
(0, react_jsx_runtime.jsx)("span", { className: PendingPanel_module_css_default.flexSpacer }),
|
|
11799
|
-
(0, react_jsx_runtime.jsx)("button", {
|
|
11800
|
-
type: "button",
|
|
11801
|
-
className: PendingPanel_module_css_default.action,
|
|
11802
|
-
"data-diff-keep": true,
|
|
11803
|
-
disabled: busy,
|
|
11804
|
-
onClick: () => {
|
|
11805
|
-
onKeep(file.sessionId, file.id);
|
|
11806
|
-
},
|
|
11807
|
-
children: busy ? t("action.busy") : t("action.keep")
|
|
11808
|
-
}),
|
|
11809
|
-
(0, react_jsx_runtime.jsx)("button", {
|
|
11810
|
-
type: "button",
|
|
11811
|
-
className: PendingPanel_module_css_default.action,
|
|
11812
|
-
"data-diff-revert": true,
|
|
11813
|
-
disabled: busy,
|
|
11814
|
-
onClick: () => {
|
|
11815
|
-
onRevert(file.sessionId, file.id);
|
|
11816
|
-
},
|
|
11817
|
-
children: busy ? t("action.busy") : t("action.revert")
|
|
11791
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
11792
|
+
className: PendingPanel_module_css_default.diff,
|
|
11793
|
+
"data-diff-approval-diff": true,
|
|
11794
|
+
onMouseUp: endDrag,
|
|
11795
|
+
children: [
|
|
11796
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
11797
|
+
className: PendingPanel_module_css_default.diffActions,
|
|
11798
|
+
children: [
|
|
11799
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
11800
|
+
className: PendingPanel_module_css_default.diffStats,
|
|
11801
|
+
children: t("panel.stats", {
|
|
11802
|
+
added: model.diff.added,
|
|
11803
|
+
removed: model.diff.removed
|
|
11818
11804
|
})
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11805
|
+
}),
|
|
11806
|
+
file.kind === "create" && (0, react_jsx_runtime.jsx)("span", {
|
|
11807
|
+
className: PendingPanel_module_css_default.kindHint,
|
|
11808
|
+
children: t("panel.createHint")
|
|
11809
|
+
}),
|
|
11810
|
+
model.blocks.length > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("button", {
|
|
11811
|
+
type: "button",
|
|
11812
|
+
className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
|
|
11813
|
+
"data-diff-prev": true,
|
|
11814
|
+
"aria-label": t("action.prevDiff"),
|
|
11815
|
+
title: t("action.prevDiff"),
|
|
11816
|
+
disabled: busy,
|
|
11817
|
+
onClick: () => {
|
|
11818
|
+
jump(-1);
|
|
11819
|
+
},
|
|
11820
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 14 })
|
|
11821
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
11822
|
+
type: "button",
|
|
11823
|
+
className: `${PendingPanel_module_css_default.action} ${PendingPanel_module_css_default.iconAction}`,
|
|
11824
|
+
"data-diff-next": true,
|
|
11825
|
+
"aria-label": t("action.nextDiff"),
|
|
11826
|
+
title: t("action.nextDiff"),
|
|
11827
|
+
disabled: busy,
|
|
11828
|
+
onClick: () => {
|
|
11829
|
+
jump(1);
|
|
11830
|
+
},
|
|
11831
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
|
|
11832
|
+
})] }),
|
|
11833
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
11834
|
+
type: "button",
|
|
11835
|
+
className: PendingPanel_module_css_default.action,
|
|
11836
|
+
"data-diff-open": true,
|
|
11837
|
+
onClick: () => {
|
|
11838
|
+
onOpen(file.sessionId, file.id, "open");
|
|
11839
|
+
},
|
|
11840
|
+
children: t("action.openFile")
|
|
11841
|
+
}),
|
|
11842
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
11843
|
+
type: "button",
|
|
11844
|
+
className: PendingPanel_module_css_default.action,
|
|
11845
|
+
"data-diff-reveal": true,
|
|
11846
|
+
onClick: () => {
|
|
11847
|
+
onOpen(file.sessionId, file.id, "reveal");
|
|
11848
|
+
},
|
|
11849
|
+
children: t("action.revealFile")
|
|
11850
|
+
}),
|
|
11851
|
+
(0, react_jsx_runtime.jsx)("span", { className: PendingPanel_module_css_default.flexSpacer }),
|
|
11852
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
11853
|
+
type: "button",
|
|
11854
|
+
className: PendingPanel_module_css_default.action,
|
|
11855
|
+
"data-diff-keep": true,
|
|
11856
|
+
disabled: busy,
|
|
11857
|
+
onClick: () => {
|
|
11858
|
+
onKeep(file.sessionId, file.id);
|
|
11859
|
+
},
|
|
11860
|
+
children: busy ? t("action.busy") : t("action.keep")
|
|
11861
|
+
}),
|
|
11862
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
11859
11863
|
type: "button",
|
|
11860
|
-
className: PendingPanel_module_css_default.
|
|
11861
|
-
"data-diff-
|
|
11864
|
+
className: PendingPanel_module_css_default.action,
|
|
11865
|
+
"data-diff-revert": true,
|
|
11866
|
+
disabled: busy,
|
|
11862
11867
|
onClick: () => {
|
|
11863
|
-
|
|
11868
|
+
onRevert(file.sessionId, file.id);
|
|
11864
11869
|
},
|
|
11865
|
-
children:
|
|
11870
|
+
children: busy ? t("action.busy") : t("action.revert")
|
|
11866
11871
|
})
|
|
11872
|
+
]
|
|
11873
|
+
}),
|
|
11874
|
+
file.missing && (0, react_jsx_runtime.jsx)("p", {
|
|
11875
|
+
className: PendingPanel_module_css_default.missingHint,
|
|
11876
|
+
children: t("panel.missingHint")
|
|
11877
|
+
}),
|
|
11878
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
11879
|
+
className: PendingPanel_module_css_default.diffBody,
|
|
11880
|
+
ref: bodyRef,
|
|
11881
|
+
children: (0, react_jsx_runtime.jsx)("div", {
|
|
11882
|
+
className: PendingPanel_module_css_default.lines,
|
|
11883
|
+
children: model.diff.rows.map((row, index) => (0, react_jsx_runtime.jsxs)("div", {
|
|
11884
|
+
ref: registerRow(index),
|
|
11885
|
+
className: `${PendingPanel_module_css_default.line} ${ROW_CLASS[row.kind]}`,
|
|
11886
|
+
"data-diff-line": row.kind,
|
|
11887
|
+
"data-diff-focused": inFocusedBlock(index) ? "" : void 0,
|
|
11888
|
+
onMouseDown: rowMouseDown(index),
|
|
11889
|
+
onMouseEnter: rowMouseEnter(index),
|
|
11890
|
+
children: [
|
|
11891
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
11892
|
+
className: PendingPanel_module_css_default.gutter,
|
|
11893
|
+
children: row.oldLine ?? ""
|
|
11894
|
+
}),
|
|
11895
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
11896
|
+
className: PendingPanel_module_css_default.gutter,
|
|
11897
|
+
children: row.newLine ?? ""
|
|
11898
|
+
}),
|
|
11899
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
11900
|
+
className: PendingPanel_module_css_default.code,
|
|
11901
|
+
children: renderLine(row)
|
|
11902
|
+
})
|
|
11903
|
+
]
|
|
11904
|
+
}, index))
|
|
11867
11905
|
})
|
|
11868
|
-
|
|
11869
|
-
|
|
11906
|
+
}),
|
|
11907
|
+
selection !== void 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
11908
|
+
className: PendingPanel_module_css_default.toolbar,
|
|
11909
|
+
style: { top: selectionTop },
|
|
11910
|
+
"data-diff-selection-toolbar": true,
|
|
11911
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
11912
|
+
type: "button",
|
|
11913
|
+
className: PendingPanel_module_css_default.toolbarButton,
|
|
11914
|
+
"data-diff-copy": true,
|
|
11915
|
+
onClick: () => {
|
|
11916
|
+
copySelection();
|
|
11917
|
+
},
|
|
11918
|
+
children: copied ? t("action.copied") : t("action.copyRange")
|
|
11919
|
+
})
|
|
11920
|
+
})
|
|
11921
|
+
]
|
|
11870
11922
|
});
|
|
11871
11923
|
}
|
|
11872
11924
|
/** Render the pending-edit review panel and its unified footer action. */
|
|
11873
|
-
function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, t }) {
|
|
11925
|
+
function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onOpen, t }) {
|
|
11874
11926
|
const current = useSessions((state) => state.current);
|
|
11875
11927
|
const snapshot = usePending((snapshot) => snapshot);
|
|
11876
11928
|
const [open, setOpen] = (0, react.useState)(false);
|
|
11877
11929
|
const [selected, setSelected] = (0, react.useState)("");
|
|
11878
11930
|
/** Pending count captured at open time; auto-close needs a list that emptied. */
|
|
11879
11931
|
const [openedCount, setOpenedCount] = (0, react.useState)(0);
|
|
11932
|
+
/** Bottom offset tracking the chat composer's top edge so the input stays visible. */
|
|
11933
|
+
const [bottomPx, setBottomPx] = (0, react.useState)(FALLBACK_BOTTOM_PX);
|
|
11934
|
+
/** File-list pane width, adjustable by dragging the divider. */
|
|
11935
|
+
const [listWidth, setListWidth] = (0, react.useState)(240);
|
|
11936
|
+
const resizeDrag = (0, react.useRef)(null);
|
|
11880
11937
|
(0, react.useEffect)(() => {
|
|
11881
11938
|
onRefresh(current);
|
|
11882
11939
|
const timer = setInterval(() => {
|
|
@@ -11886,6 +11943,30 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11886
11943
|
clearInterval(timer);
|
|
11887
11944
|
};
|
|
11888
11945
|
}, [current, onRefresh]);
|
|
11946
|
+
(0, react.useEffect)(() => {
|
|
11947
|
+
if (!open) return;
|
|
11948
|
+
const measure = () => {
|
|
11949
|
+
const composer = document.querySelector(COMPOSER_SELECTOR);
|
|
11950
|
+
if (composer === null) {
|
|
11951
|
+
setBottomPx(FALLBACK_BOTTOM_PX);
|
|
11952
|
+
return;
|
|
11953
|
+
}
|
|
11954
|
+
const top = composer.getBoundingClientRect().top;
|
|
11955
|
+
if (top <= 0) return;
|
|
11956
|
+
setBottomPx(Math.round(window.innerHeight - top) + COMPOSER_GAP_PX);
|
|
11957
|
+
};
|
|
11958
|
+
measure();
|
|
11959
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
11960
|
+
const composer = document.querySelector(COMPOSER_SELECTOR);
|
|
11961
|
+
if (composer === null) return;
|
|
11962
|
+
const observer = new ResizeObserver(measure);
|
|
11963
|
+
observer.observe(composer);
|
|
11964
|
+
window.addEventListener("resize", measure);
|
|
11965
|
+
return () => {
|
|
11966
|
+
observer.disconnect();
|
|
11967
|
+
window.removeEventListener("resize", measure);
|
|
11968
|
+
};
|
|
11969
|
+
}, [open]);
|
|
11889
11970
|
const files = snapshot.files;
|
|
11890
11971
|
const mine = files.filter((file) => file.sessionId === current);
|
|
11891
11972
|
const theirs = files.filter((file) => file.sessionId !== current);
|
|
@@ -11905,20 +11986,45 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11905
11986
|
if (!open) setOpenedCount(files.length);
|
|
11906
11987
|
setOpen((value) => !value);
|
|
11907
11988
|
};
|
|
11908
|
-
const renderEntry = (entry) => (0, react_jsx_runtime.jsx)(
|
|
11989
|
+
const renderEntry = (entry) => (0, react_jsx_runtime.jsx)(PendingFileRow, {
|
|
11909
11990
|
file: entry,
|
|
11910
11991
|
files,
|
|
11911
11992
|
selected: selected === entry.id,
|
|
11912
|
-
busy: snapshot.busy.has(entry.id),
|
|
11913
11993
|
t,
|
|
11914
|
-
onSelect: setSelected
|
|
11915
|
-
onKeep,
|
|
11916
|
-
onRevert
|
|
11994
|
+
onSelect: setSelected
|
|
11917
11995
|
}, entry.id);
|
|
11996
|
+
const selectedFile = files.find((file) => file.id === selected);
|
|
11997
|
+
/** Drag the list/detail divider; width follows the pointer within its bounds. */
|
|
11998
|
+
const startResize = (event) => {
|
|
11999
|
+
if (event.button !== 0) return;
|
|
12000
|
+
event.preventDefault();
|
|
12001
|
+
resizeDrag.current = {
|
|
12002
|
+
startX: event.clientX,
|
|
12003
|
+
startWidth: listWidth
|
|
12004
|
+
};
|
|
12005
|
+
document.body.style.userSelect = "none";
|
|
12006
|
+
document.body.style.cursor = "col-resize";
|
|
12007
|
+
const onMove = (move) => {
|
|
12008
|
+
const start = resizeDrag.current;
|
|
12009
|
+
if (start === null) return;
|
|
12010
|
+
const next = start.startWidth + (move.clientX - start.startX);
|
|
12011
|
+
setListWidth(Math.min(Math.max(next, MIN_LIST_WIDTH_PX), MAX_LIST_WIDTH_PX));
|
|
12012
|
+
};
|
|
12013
|
+
const onUp = () => {
|
|
12014
|
+
resizeDrag.current = null;
|
|
12015
|
+
document.body.style.userSelect = "";
|
|
12016
|
+
document.body.style.cursor = "";
|
|
12017
|
+
window.removeEventListener("mousemove", onMove);
|
|
12018
|
+
window.removeEventListener("mouseup", onUp);
|
|
12019
|
+
};
|
|
12020
|
+
window.addEventListener("mousemove", onMove);
|
|
12021
|
+
window.addEventListener("mouseup", onUp);
|
|
12022
|
+
};
|
|
11918
12023
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
11919
12024
|
className: wide ? PendingPanel_module_css_default.layer : `${PendingPanel_module_css_default.layer} ${PendingPanel_module_css_default.rail}`,
|
|
11920
12025
|
children: [open && (0, react_jsx_runtime.jsxs)("section", {
|
|
11921
12026
|
className: PendingPanel_module_css_default.panel,
|
|
12027
|
+
style: { bottom: bottomPx },
|
|
11922
12028
|
"data-diff-approval-panel": true,
|
|
11923
12029
|
"aria-label": t("panel.title"),
|
|
11924
12030
|
children: [(0, react_jsx_runtime.jsx)("header", {
|
|
@@ -11927,8 +12033,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11927
12033
|
className: PendingPanel_module_css_default.title,
|
|
11928
12034
|
children: t("panel.title")
|
|
11929
12035
|
})
|
|
11930
|
-
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
11931
|
-
className: PendingPanel_module_css_default.
|
|
12036
|
+
}), snapshot.error !== void 0 || !snapshot.read || files.length === 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
12037
|
+
className: PendingPanel_module_css_default.states,
|
|
11932
12038
|
children: [
|
|
11933
12039
|
snapshot.error !== void 0 && (0, react_jsx_runtime.jsx)("p", {
|
|
11934
12040
|
className: PendingPanel_module_css_default.readError,
|
|
@@ -11942,25 +12048,49 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11942
12048
|
snapshot.read && snapshot.error === void 0 && files.length === 0 && (0, react_jsx_runtime.jsx)("p", {
|
|
11943
12049
|
className: PendingPanel_module_css_default.note,
|
|
11944
12050
|
children: t("panel.empty")
|
|
12051
|
+
})
|
|
12052
|
+
]
|
|
12053
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
12054
|
+
className: PendingPanel_module_css_default.split,
|
|
12055
|
+
children: [
|
|
12056
|
+
(0, react_jsx_runtime.jsxs)("nav", {
|
|
12057
|
+
className: PendingPanel_module_css_default.fileList,
|
|
12058
|
+
style: { width: listWidth },
|
|
12059
|
+
"data-diff-approval-file-list": true,
|
|
12060
|
+
children: [mine.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
|
|
12061
|
+
className: PendingPanel_module_css_default.group,
|
|
12062
|
+
children: t("panel.group.current")
|
|
12063
|
+
}), (0, react_jsx_runtime.jsx)("ul", {
|
|
12064
|
+
className: PendingPanel_module_css_default.rows,
|
|
12065
|
+
children: mine.map(renderEntry)
|
|
12066
|
+
})] }), theirs.length > 0 && (0, react_jsx_runtime.jsxs)("section", { children: [(0, react_jsx_runtime.jsx)("h3", {
|
|
12067
|
+
className: PendingPanel_module_css_default.group,
|
|
12068
|
+
children: t("panel.group.others")
|
|
12069
|
+
}), (0, react_jsx_runtime.jsx)("ul", {
|
|
12070
|
+
className: PendingPanel_module_css_default.rows,
|
|
12071
|
+
children: theirs.map(renderEntry)
|
|
12072
|
+
})] })]
|
|
11945
12073
|
}),
|
|
11946
|
-
|
|
11947
|
-
className: PendingPanel_module_css_default.
|
|
11948
|
-
|
|
12074
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
12075
|
+
className: PendingPanel_module_css_default.resizeHandle,
|
|
12076
|
+
"data-diff-resize": true,
|
|
12077
|
+
onMouseDown: startResize
|
|
11949
12078
|
}),
|
|
11950
|
-
|
|
11951
|
-
className: PendingPanel_module_css_default.
|
|
11952
|
-
children:
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
12079
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
12080
|
+
className: PendingPanel_module_css_default.detail,
|
|
12081
|
+
children: selectedFile === void 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
12082
|
+
className: PendingPanel_module_css_default.detailEmpty,
|
|
12083
|
+
children: t("panel.selectHint")
|
|
12084
|
+
}) : (0, react_jsx_runtime.jsx)(PendingDiff, {
|
|
12085
|
+
file: selectedFile,
|
|
12086
|
+
files,
|
|
12087
|
+
busy: snapshot.busy.has(selectedFile.id),
|
|
12088
|
+
t,
|
|
12089
|
+
onKeep,
|
|
12090
|
+
onRevert,
|
|
12091
|
+
onOpen
|
|
12092
|
+
})
|
|
12093
|
+
})
|
|
11964
12094
|
]
|
|
11965
12095
|
})]
|
|
11966
12096
|
}), (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -12018,6 +12148,13 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
12018
12148
|
sessionId,
|
|
12019
12149
|
id
|
|
12020
12150
|
}));
|
|
12151
|
+
},
|
|
12152
|
+
async open(sessionId, id, action) {
|
|
12153
|
+
return openOf(await rpc.call(DIFF_APPROVAL_CHANNEL, "open", {
|
|
12154
|
+
sessionId,
|
|
12155
|
+
id,
|
|
12156
|
+
action
|
|
12157
|
+
}));
|
|
12021
12158
|
}
|
|
12022
12159
|
};
|
|
12023
12160
|
}
|
|
@@ -12066,6 +12203,15 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
12066
12203
|
if (outcome !== "kept" && outcome !== "reverted" && outcome !== "missing") throw new Error("the action returned a malformed outcome");
|
|
12067
12204
|
return { outcome };
|
|
12068
12205
|
}
|
|
12206
|
+
/** Narrow the open endpoint's value; a malformed wire value is an open failure. */
|
|
12207
|
+
function openOf(result) {
|
|
12208
|
+
if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
12209
|
+
const value = result.value;
|
|
12210
|
+
if (typeof value !== "object" || value === null) throw new Error("the action returned a malformed value");
|
|
12211
|
+
const outcome = value.outcome;
|
|
12212
|
+
if (outcome !== "opened" && outcome !== "missing") throw new Error("the action returned a malformed outcome");
|
|
12213
|
+
return { outcome };
|
|
12214
|
+
}
|
|
12069
12215
|
//#endregion
|
|
12070
12216
|
//#region lib/types/client/store.js
|
|
12071
12217
|
/**
|
|
@@ -12158,6 +12304,16 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
12158
12304
|
await port.revert(sessionId, id);
|
|
12159
12305
|
});
|
|
12160
12306
|
},
|
|
12307
|
+
async open(sessionId, id, action) {
|
|
12308
|
+
try {
|
|
12309
|
+
await port.open(sessionId, id, action);
|
|
12310
|
+
} catch (error) {
|
|
12311
|
+
publish({
|
|
12312
|
+
...snapshot,
|
|
12313
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12314
|
+
});
|
|
12315
|
+
}
|
|
12316
|
+
},
|
|
12161
12317
|
reset() {
|
|
12162
12318
|
publish({
|
|
12163
12319
|
read: false,
|
|
@@ -12186,8 +12342,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
12186
12342
|
"panel.missingHint": "该文件已不存在,回退会恢复该文件。",
|
|
12187
12343
|
"panel.createHint": "回退会删除该新建的文件。",
|
|
12188
12344
|
"row.create": "新增文件",
|
|
12345
|
+
"row.added": "+{added}",
|
|
12346
|
+
"row.removed": "-{removed}",
|
|
12189
12347
|
"action.keep": "保留",
|
|
12190
12348
|
"action.revert": "回退",
|
|
12349
|
+
"action.openFile": "打开文件",
|
|
12350
|
+
"action.revealFile": "打开所在目录",
|
|
12191
12351
|
"action.busy": "处理中…",
|
|
12192
12352
|
"action.prevDiff": "上一处差异",
|
|
12193
12353
|
"action.nextDiff": "下一处差异",
|
|
@@ -12212,8 +12372,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
12212
12372
|
"panel.missingHint": "This file no longer exists; reverting restores it.",
|
|
12213
12373
|
"panel.createHint": "Reverting removes this created file.",
|
|
12214
12374
|
"row.create": "New file",
|
|
12375
|
+
"row.added": "+{added}",
|
|
12376
|
+
"row.removed": "-{removed}",
|
|
12215
12377
|
"action.keep": "Keep",
|
|
12216
12378
|
"action.revert": "Revert",
|
|
12379
|
+
"action.openFile": "Open file",
|
|
12380
|
+
"action.revealFile": "Reveal in folder",
|
|
12217
12381
|
"action.busy": "Working…",
|
|
12218
12382
|
"action.prevDiff": "Previous diff",
|
|
12219
12383
|
"action.nextDiff": "Next diff",
|
|
@@ -12256,7 +12420,8 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
12256
12420
|
store.refresh(sessionId);
|
|
12257
12421
|
},
|
|
12258
12422
|
onKeep: (sessionId, path) => store.keep(sessionId, path),
|
|
12259
|
-
onRevert: (sessionId, path) => store.revert(sessionId, path)
|
|
12423
|
+
onRevert: (sessionId, path) => store.revert(sessionId, path),
|
|
12424
|
+
onOpen: (sessionId, id, action) => store.open(sessionId, id, action)
|
|
12260
12425
|
})
|
|
12261
12426
|
}, PendingPanel));
|
|
12262
12427
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { dshHomePath, expandHomePath } from "@deepseek-ai/dsh-home-paths";
|
|
5
5
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
6
7
|
//#region lib/types/pending.js
|
|
7
8
|
/**
|
|
8
9
|
* In-memory pending-diff store: one entry per (session, path), holding the
|
|
@@ -269,17 +270,70 @@ var PendingPersistence = class {
|
|
|
269
270
|
}
|
|
270
271
|
};
|
|
271
272
|
//#endregion
|
|
273
|
+
//#region lib/types/open.js
|
|
274
|
+
/**
|
|
275
|
+
* OS-level "open file" and "reveal in folder" launches for the host half's
|
|
276
|
+
* `open` endpoint. The plugin's own tests inject a double instead of running
|
|
277
|
+
* these; real profiles get the platform commands below.
|
|
278
|
+
* @module dsh-diff-approval/open
|
|
279
|
+
*/
|
|
280
|
+
/** Run one detached command; resolves once the process spawns, never waits for exit. */
|
|
281
|
+
function launch(command, args) {
|
|
282
|
+
return new Promise((resolvePromise, reject) => {
|
|
283
|
+
const child = spawn(command, [...args], {
|
|
284
|
+
detached: true,
|
|
285
|
+
stdio: "ignore",
|
|
286
|
+
windowsHide: true
|
|
287
|
+
});
|
|
288
|
+
child.once("error", reject);
|
|
289
|
+
child.once("spawn", () => {
|
|
290
|
+
child.unref();
|
|
291
|
+
resolvePromise();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Launch a path through the current platform's default handler: `open` runs
|
|
297
|
+
* the file with its default application, `reveal` selects it in the file
|
|
298
|
+
* manager.
|
|
299
|
+
* @param path - backend execution-world path to act on.
|
|
300
|
+
* @param action - what to do with the path.
|
|
301
|
+
* @returns resolution once the launcher process has spawned.
|
|
302
|
+
*/
|
|
303
|
+
function defaultOpenPath(path, action) {
|
|
304
|
+
switch (process.platform) {
|
|
305
|
+
case "win32": return action === "open" ? launch("cmd", [
|
|
306
|
+
"/c",
|
|
307
|
+
"start",
|
|
308
|
+
"",
|
|
309
|
+
path
|
|
310
|
+
]) : launch("cmd", [
|
|
311
|
+
"/c",
|
|
312
|
+
"start",
|
|
313
|
+
"",
|
|
314
|
+
"explorer.exe",
|
|
315
|
+
`/select,${path}`
|
|
316
|
+
]);
|
|
317
|
+
case "darwin": return action === "open" ? launch("open", [path]) : launch("open", ["-R", path]);
|
|
318
|
+
default: return action === "open" ? launch("xdg-open", [path]) : launch("xdg-open", [dirname(path)]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
272
322
|
//#region lib/types/index.js
|
|
273
323
|
/**
|
|
274
324
|
* Pending-edit review, host half. Captures every successful `edit` and `write`
|
|
275
325
|
* tool result (an unscoped `tools/result` listener receives per-session tool
|
|
276
326
|
* executions because scoped emissions route through the shared root hook
|
|
277
|
-
* table)
|
|
327
|
+
* table) and every `str_replace_editor` mutation (whose result carries only a
|
|
328
|
+
* success message, so its pre-write basis is snapshotted at the
|
|
329
|
+
* `fs/edit-intent` / `fs/write-intent` seams and paired with the settle),
|
|
330
|
+
* folds each operation into its file's entry in the
|
|
278
331
|
* {@link PendingDiffStore} (one entry per path), serves the `/diff-approval`
|
|
279
|
-
* connection RPC channel (list/keep/revert), and applies a revert by
|
|
280
|
-
* the entry's `oldText` back through `ctx.fs` (a created file's
|
|
281
|
-
* removes it, and a tracked file that has since disappeared is
|
|
282
|
-
* its revert).
|
|
332
|
+
* connection RPC channel (list/keep/revert/open), and applies a revert by
|
|
333
|
+
* writing the entry's `oldText` back through `ctx.fs` (a created file's
|
|
334
|
+
* revert removes it, and a tracked file that has since disappeared is
|
|
335
|
+
* restored by its revert). `open` launches the file with its default
|
|
336
|
+
* application or reveals it in the file manager.
|
|
283
337
|
*
|
|
284
338
|
* Mount this row in any profile's `cordis.patch.yml`:
|
|
285
339
|
*
|
|
@@ -363,6 +417,22 @@ function writeOutcomeOf(value) {
|
|
|
363
417
|
function errorMessage(error) {
|
|
364
418
|
return error instanceof Error ? error.message : String(error);
|
|
365
419
|
}
|
|
420
|
+
/** Narrow a tool-execution-shaped value to its name, call id, and agent. */
|
|
421
|
+
function actorOf(value) {
|
|
422
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
423
|
+
const { name, callId, agent } = value;
|
|
424
|
+
return {
|
|
425
|
+
name,
|
|
426
|
+
callId,
|
|
427
|
+
agent
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
/** Narrow an agent-shaped value to its session id. */
|
|
431
|
+
function sessionOfAgent(agent) {
|
|
432
|
+
if (typeof agent !== "object" || agent === null) return void 0;
|
|
433
|
+
const id = agent.id;
|
|
434
|
+
return typeof id === "string" && id.length > 0 ? SessionId(id) : void 0;
|
|
435
|
+
}
|
|
366
436
|
/**
|
|
367
437
|
* Build one channel error in the closed RPC error vocabulary. `internal` is
|
|
368
438
|
* the catch-all: business misses ride the success branch as `outcome: 'missing'`.
|
|
@@ -389,8 +459,78 @@ function apply(ctx, config) {
|
|
|
389
459
|
if (storageDir !== void 0 && (typeof storageDir !== "string" || storageDir.trim().length === 0)) throw new Error("diff-approval: storageDir must be a non-empty string");
|
|
390
460
|
const store = new PendingDiffStore();
|
|
391
461
|
const persistence = new PendingPersistence(resolve(expandHomePath(storageDir ?? defaultStorageDir())));
|
|
462
|
+
const launchPath = config?.openPath ?? defaultOpenPath;
|
|
392
463
|
const loaded = /* @__PURE__ */ new Set();
|
|
393
464
|
const loading = /* @__PURE__ */ new Map();
|
|
465
|
+
/** Pre-write bases captured at the intent seams, keyed by the tool call id. */
|
|
466
|
+
const editorIntents = /* @__PURE__ */ new Map();
|
|
467
|
+
/**
|
|
468
|
+
* Snapshot one str_replace_editor mutation's basis at its intent seam. A
|
|
469
|
+
* `create` has an empty basis; an edit reads the pre-write content. Any
|
|
470
|
+
* failure tracks nothing — the settle-side pairing then sees no basis.
|
|
471
|
+
* @param target - the resolved target about to be written.
|
|
472
|
+
* @param actor - the tool execution running the mutation.
|
|
473
|
+
* @param kind - whether the mutation creates or edits the file.
|
|
474
|
+
*/
|
|
475
|
+
async function stashEditorIntent(target, actor, kind) {
|
|
476
|
+
const shaped = actorOf(actor);
|
|
477
|
+
if (shaped === void 0 || shaped.name !== "str_replace_editor") return;
|
|
478
|
+
if (typeof shaped.callId !== "string") return;
|
|
479
|
+
const sessionId = sessionOfAgent(shaped.agent);
|
|
480
|
+
if (sessionId === void 0) return;
|
|
481
|
+
if (kind === "create") {
|
|
482
|
+
editorIntents.set(shaped.callId, {
|
|
483
|
+
target,
|
|
484
|
+
kind,
|
|
485
|
+
before: "",
|
|
486
|
+
sessionId
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
const before = await ctx.fs.readText(target, void 0) ?? "";
|
|
492
|
+
editorIntents.set(shaped.callId, {
|
|
493
|
+
target,
|
|
494
|
+
kind,
|
|
495
|
+
before,
|
|
496
|
+
sessionId
|
|
497
|
+
});
|
|
498
|
+
} catch {}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Fold one str_replace_editor mutation into its file's entry. The tool's
|
|
502
|
+
* result carries only a success message, so the settle reads the post-write
|
|
503
|
+
* content and pairs it with the basis snapshotted at the intent seam.
|
|
504
|
+
* @param exec - the settled str_replace_editor execution.
|
|
505
|
+
* @param result - its outcome.
|
|
506
|
+
*/
|
|
507
|
+
async function captureEditorMutation(exec, result) {
|
|
508
|
+
const basis = editorIntents.get(exec.callId);
|
|
509
|
+
editorIntents.delete(exec.callId);
|
|
510
|
+
if (basis === void 0 || basis.sessionId === void 0 || result.isError) return;
|
|
511
|
+
const argumentsValue = exec.arguments;
|
|
512
|
+
const command = typeof argumentsValue === "object" && argumentsValue !== null ? argumentsValue.command : void 0;
|
|
513
|
+
if (!(basis.kind === "create" ? command === "create" : command === "str_replace" || command === "insert")) return;
|
|
514
|
+
let after;
|
|
515
|
+
try {
|
|
516
|
+
after = await ctx.fs.readText(basis.target, void 0) ?? "";
|
|
517
|
+
} catch {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (basis.kind === "edit" && after === basis.before) return;
|
|
521
|
+
const sessionId = basis.sessionId;
|
|
522
|
+
const entry = {
|
|
523
|
+
id: randomUUID(),
|
|
524
|
+
sessionId,
|
|
525
|
+
path: basis.target.displayPath,
|
|
526
|
+
kind: basis.kind,
|
|
527
|
+
oldText: basis.before,
|
|
528
|
+
newText: after,
|
|
529
|
+
updatedAt: Date.now()
|
|
530
|
+
};
|
|
531
|
+
await ensureLoaded(sessionId);
|
|
532
|
+
if (store.fold(entry)) await persistSession(sessionId);
|
|
533
|
+
}
|
|
394
534
|
/**
|
|
395
535
|
* Read one path's live state: present content, an unresolvable (missing)
|
|
396
536
|
* path, or a resolved-but-unreadable file.
|
|
@@ -504,6 +644,10 @@ function apply(ctx, config) {
|
|
|
504
644
|
}
|
|
505
645
|
}
|
|
506
646
|
ctx.on("tools/result", (exec, result) => {
|
|
647
|
+
if (exec.name === "str_replace_editor") {
|
|
648
|
+
captureEditorMutation(exec, result);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
507
651
|
if (result.isError || exec.agent === void 0) return;
|
|
508
652
|
const outcome = exec.name === "edit" ? editOutcomeOf(result.value) : exec.name === "write" ? writeOutcomeOf(result.value) : void 0;
|
|
509
653
|
if (outcome === void 0 || outcome.oldText === outcome.newText) return;
|
|
@@ -564,10 +708,38 @@ function apply(ctx, config) {
|
|
|
564
708
|
value: { outcome: "reverted" }
|
|
565
709
|
};
|
|
566
710
|
}
|
|
711
|
+
case "open": {
|
|
712
|
+
const target = openTargetOf(payload);
|
|
713
|
+
if (target === void 0) return rpcError("sessionId, id, and action must be valid");
|
|
714
|
+
await ensureLoaded(target.sessionId);
|
|
715
|
+
const entry = store.get(target.sessionId, target.id);
|
|
716
|
+
if (entry === void 0) return {
|
|
717
|
+
ok: true,
|
|
718
|
+
value: { outcome: "missing" }
|
|
719
|
+
};
|
|
720
|
+
try {
|
|
721
|
+
const resolved = await ctx.fs.resolve(entry.path, { signal });
|
|
722
|
+
await launchPath(ctx.fs.processPath(resolved), target.action);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
return rpcError(`${target.action} failed: ${errorMessage(error)}`);
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
ok: true,
|
|
728
|
+
value: { outcome: "opened" }
|
|
729
|
+
};
|
|
730
|
+
}
|
|
567
731
|
default: return rpcError(`unknown endpoint ${JSON.stringify(endpoint)}`);
|
|
568
732
|
}
|
|
569
733
|
};
|
|
570
734
|
ctx.effect(() => ctx.connection.rpc.handle(DIFF_APPROVAL_CHANNEL, handle, { authority: "trusted-host" }), "diff-approval: review channel");
|
|
735
|
+
ctx.effect(() => ctx.on("fs/edit-intent", async (target, actor, next) => {
|
|
736
|
+
await stashEditorIntent(target, actor, "edit");
|
|
737
|
+
return next();
|
|
738
|
+
}, { prepend: true }), "diff-approval: str_replace_editor edit basis");
|
|
739
|
+
ctx.effect(() => ctx.on("fs/write-intent", async (target, actor, next) => {
|
|
740
|
+
await stashEditorIntent(target, actor, "create");
|
|
741
|
+
return next();
|
|
742
|
+
}, { prepend: true }), "diff-approval: str_replace_editor create basis");
|
|
571
743
|
}
|
|
572
744
|
/** Narrow a wire payload's `sessionId` field to a branded session id. */
|
|
573
745
|
function sessionOf(payload) {
|
|
@@ -587,5 +759,16 @@ function targetOf(payload) {
|
|
|
587
759
|
id
|
|
588
760
|
};
|
|
589
761
|
}
|
|
762
|
+
/** Narrow a wire payload to one open target: the keep/revert pair plus the action. */
|
|
763
|
+
function openTargetOf(payload) {
|
|
764
|
+
const target = targetOf(payload);
|
|
765
|
+
if (target === void 0) return void 0;
|
|
766
|
+
const action = payload.action;
|
|
767
|
+
if (action !== "open" && action !== "reveal") return void 0;
|
|
768
|
+
return {
|
|
769
|
+
...target,
|
|
770
|
+
action
|
|
771
|
+
};
|
|
772
|
+
}
|
|
590
773
|
//#endregion
|
|
591
|
-
export { DIFF_APPROVAL_CHANNEL, PendingDiffStore, PendingPersistence, apply, defaultStorageDir, inject, name };
|
|
774
|
+
export { DIFF_APPROVAL_CHANNEL, PendingDiffStore, PendingPersistence, apply, defaultOpenPath, defaultStorageDir, inject, name };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/** Sidebar-foot pending-edit review action and the
|
|
1
|
+
/** Sidebar-foot pending-edit review action and the split review panel it opens. */
|
|
2
2
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
3
|
import type { PendingPanelFace } from './slots.ts';
|
|
4
4
|
/** Full panel props composed by the sidebar footer-action slot. */
|
|
5
5
|
export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'>;
|
|
6
6
|
/** Render the pending-edit review panel and its unified footer action. */
|
|
7
|
-
export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, t, }: PendingPanelProps): import("react").JSX.Element;
|
|
7
|
+
export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onOpen, t, }: PendingPanelProps): import("react").JSX.Element;
|
|
@@ -15,8 +15,12 @@ export declare const zh: {
|
|
|
15
15
|
'panel.missingHint': string;
|
|
16
16
|
'panel.createHint': string;
|
|
17
17
|
'row.create': string;
|
|
18
|
+
'row.added': string;
|
|
19
|
+
'row.removed': string;
|
|
18
20
|
'action.keep': string;
|
|
19
21
|
'action.revert': string;
|
|
22
|
+
'action.openFile': string;
|
|
23
|
+
'action.revealFile': string;
|
|
20
24
|
'action.busy': string;
|
|
21
25
|
'action.prevDiff': string;
|
|
22
26
|
'action.nextDiff': string;
|
|
@@ -49,8 +53,12 @@ export declare const en: {
|
|
|
49
53
|
'panel.missingHint': string;
|
|
50
54
|
'panel.createHint': string;
|
|
51
55
|
'row.create': string;
|
|
56
|
+
'row.added': string;
|
|
57
|
+
'row.removed': string;
|
|
52
58
|
'action.keep': string;
|
|
53
59
|
'action.revert': string;
|
|
60
|
+
'action.openFile': string;
|
|
61
|
+
'action.revealFile': string;
|
|
54
62
|
'action.busy': string;
|
|
55
63
|
'action.prevDiff': string;
|
|
56
64
|
'action.nextDiff': string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module dsh-diff-approval/client/port
|
|
6
6
|
*/
|
|
7
7
|
import type { ClientConnectionRpc, SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
8
|
-
import type { DiffApprovalActionValue, PendingFileDiff } from '../types.ts';
|
|
8
|
+
import type { DiffApprovalActionValue, DiffApprovalOpenAction, DiffApprovalOpenValue, PendingFileDiff } from '../types.ts';
|
|
9
9
|
/** The channel the host half registers and this port calls. */
|
|
10
10
|
export declare const DIFF_APPROVAL_CHANNEL = "/diff-approval";
|
|
11
11
|
/** This package's business verbs over the review channel. */
|
|
@@ -16,6 +16,8 @@ export interface DiffApprovalPort {
|
|
|
16
16
|
keep(sessionId: SessionId, id: string): Promise<DiffApprovalActionValue>;
|
|
17
17
|
/** Revert one operation. */
|
|
18
18
|
revert(sessionId: SessionId, id: string): Promise<DiffApprovalActionValue>;
|
|
19
|
+
/** Open one file with its default application or reveal it in the folder. */
|
|
20
|
+
open(sessionId: SessionId, id: string, action: DiffApprovalOpenAction): Promise<DiffApprovalOpenValue>;
|
|
19
21
|
}
|
|
20
22
|
/** Build the port over one generic RPC caller.
|
|
21
23
|
* @param rpc - the connection's channel caller.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The panel's injected business face and its observable snapshot. */
|
|
2
2
|
import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
3
|
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
4
|
-
import type { PendingFileDiff } from '../types.ts';
|
|
4
|
+
import type { DiffApprovalOpenAction, PendingFileDiff } from '../types.ts';
|
|
5
5
|
/** What the panel reads and drives: the pending list plus in-flight entries. */
|
|
6
6
|
export interface PendingDiffSnapshot {
|
|
7
7
|
/** Whether a list read has completed at least once. */
|
|
@@ -25,4 +25,6 @@ export interface PendingPanelFace {
|
|
|
25
25
|
onKeep: (sessionId: SessionId, id: string) => Promise<void>;
|
|
26
26
|
/** Revert one operation (restore its prior content, or remove a created file). */
|
|
27
27
|
onRevert: (sessionId: SessionId, id: string) => Promise<void>;
|
|
28
|
+
/** Open one file with its default application or reveal it in the folder. */
|
|
29
|
+
onOpen: (sessionId: SessionId, id: string, action: DiffApprovalOpenAction) => Promise<void>;
|
|
28
30
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
9
9
|
import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
|
|
10
|
+
import type { DiffApprovalOpenAction } from '../types.ts';
|
|
10
11
|
import type { PendingDiffSnapshot } from './slots.ts';
|
|
11
12
|
import type { DiffApprovalPort } from './port.ts';
|
|
12
13
|
/** The observable the panel reads and the plugin body drives. */
|
|
@@ -17,6 +18,8 @@ export interface PendingDiffStore extends HostObservable<PendingDiffSnapshot> {
|
|
|
17
18
|
keep: (sessionId: SessionId, id: string) => Promise<void>;
|
|
18
19
|
/** Revert one operation. */
|
|
19
20
|
revert: (sessionId: SessionId, id: string) => Promise<void>;
|
|
21
|
+
/** Open one file with its default application or reveal it in the folder. */
|
|
22
|
+
open: (sessionId: SessionId, id: string, action: DiffApprovalOpenAction) => Promise<void>;
|
|
20
23
|
/** Drop every local fact (used on connection reset). */
|
|
21
24
|
reset: () => void;
|
|
22
25
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
* Pending-edit review, host half. Captures every successful `edit` and `write`
|
|
3
3
|
* tool result (an unscoped `tools/result` listener receives per-session tool
|
|
4
4
|
* executions because scoped emissions route through the shared root hook
|
|
5
|
-
* table)
|
|
5
|
+
* table) and every `str_replace_editor` mutation (whose result carries only a
|
|
6
|
+
* success message, so its pre-write basis is snapshotted at the
|
|
7
|
+
* `fs/edit-intent` / `fs/write-intent` seams and paired with the settle),
|
|
8
|
+
* folds each operation into its file's entry in the
|
|
6
9
|
* {@link PendingDiffStore} (one entry per path), serves the `/diff-approval`
|
|
7
|
-
* connection RPC channel (list/keep/revert), and applies a revert by
|
|
8
|
-
* the entry's `oldText` back through `ctx.fs` (a created file's
|
|
9
|
-
* removes it, and a tracked file that has since disappeared is
|
|
10
|
-
* its revert).
|
|
10
|
+
* connection RPC channel (list/keep/revert/open), and applies a revert by
|
|
11
|
+
* writing the entry's `oldText` back through `ctx.fs` (a created file's
|
|
12
|
+
* revert removes it, and a tracked file that has since disappeared is
|
|
13
|
+
* restored by its revert). `open` launches the file with its default
|
|
14
|
+
* application or reveals it in the file manager.
|
|
11
15
|
*
|
|
12
16
|
* Mount this row in any profile's `cordis.patch.yml`:
|
|
13
17
|
*
|
|
@@ -29,9 +33,11 @@
|
|
|
29
33
|
* @module dsh-diff-approval
|
|
30
34
|
*/
|
|
31
35
|
import { Context } from '@deepseek-ai/cordis';
|
|
32
|
-
|
|
36
|
+
import type { DiffApprovalOpenAction } from './types.ts';
|
|
37
|
+
export type { DiffApprovalActionOutcome, DiffApprovalActionValue, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue, PendingEntry, PendingEntryKind, PendingFileDiff, } from './types.ts';
|
|
33
38
|
export { PendingDiffStore } from './pending.ts';
|
|
34
39
|
export { PendingPersistence, defaultStorageDir } from './persist.ts';
|
|
40
|
+
export { defaultOpenPath } from './open.ts';
|
|
35
41
|
/** Stable Cordis plugin name. */
|
|
36
42
|
export declare const name = "diff-approval";
|
|
37
43
|
/** Services required before the review surface activates. */
|
|
@@ -48,6 +54,11 @@ export interface DiffApprovalConfig {
|
|
|
48
54
|
* `~` prefixes expand to the OS home.
|
|
49
55
|
*/
|
|
50
56
|
storageDir?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Launcher for the `open` endpoint, defaulting to the platform commands.
|
|
59
|
+
* Injectable for tests; receives the backend execution-world path.
|
|
60
|
+
*/
|
|
61
|
+
openPath?: (path: string, action: DiffApprovalOpenAction) => Promise<void>;
|
|
51
62
|
}
|
|
52
63
|
/**
|
|
53
64
|
* Mount the pending-edit review surface.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS-level "open file" and "reveal in folder" launches for the host half's
|
|
3
|
+
* `open` endpoint. The plugin's own tests inject a double instead of running
|
|
4
|
+
* these; real profiles get the platform commands below.
|
|
5
|
+
* @module dsh-diff-approval/open
|
|
6
|
+
*/
|
|
7
|
+
/** What the open endpoint asks the OS to do with a path. */
|
|
8
|
+
export type OpenAction = 'open' | 'reveal';
|
|
9
|
+
/**
|
|
10
|
+
* Launch a path through the current platform's default handler: `open` runs
|
|
11
|
+
* the file with its default application, `reveal` selects it in the file
|
|
12
|
+
* manager.
|
|
13
|
+
* @param path - backend execution-world path to act on.
|
|
14
|
+
* @param action - what to do with the path.
|
|
15
|
+
* @returns resolution once the launcher process has spawned.
|
|
16
|
+
*/
|
|
17
|
+
export declare function defaultOpenPath(path: string, action: OpenAction): Promise<void>;
|
package/lib/types/types.d.ts
CHANGED
|
@@ -52,6 +52,13 @@ export interface DiffApprovalListValue {
|
|
|
52
52
|
/** Pending entries for the requested session, oldest capture first. */
|
|
53
53
|
files: PendingFileDiff[];
|
|
54
54
|
}
|
|
55
|
+
/** What the open endpoint asks the OS to do with a file. */
|
|
56
|
+
export type DiffApprovalOpenAction = 'open' | 'reveal';
|
|
57
|
+
/** Value returned by the channel's open endpoint. */
|
|
58
|
+
export interface DiffApprovalOpenValue {
|
|
59
|
+
/** What the request did; `missing` means no pending entry existed. */
|
|
60
|
+
outcome: 'opened' | 'missing';
|
|
61
|
+
}
|
|
55
62
|
/** Outcome of one keep/revert request. */
|
|
56
63
|
export type DiffApprovalActionOutcome = 'kept' | 'reverted' | 'missing';
|
|
57
64
|
/** Value returned by the channel's keep and revert endpoints. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-diff-approval",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "DeepSeek Harness plugin: pending-edit review with whole-file diff and Keep/Revert",
|
|
5
|
+
"packageManager": "pnpm@11.21.0",
|
|
5
6
|
"author": "Wu Zhiwei",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|