dsh-vscode-mode 0.23.0 → 0.23.2
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/README.md +7 -3
- package/lib/client.js +146 -67
- package/lib/client.js.map +2 -2
- package/lib/index.js +167 -41
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/reviewBatch.ts +22 -21
- package/src/client/ui/EditorView.ts +110 -16
- package/src/client/ui/OfficialDiffBar.tsx +18 -3
- package/src/client/ui/SidecarDiffBar.tsx +3 -2
- package/src/client/ui/reviewAllRow.tsx +5 -4
- package/src/reviewRpc.ts +149 -36
- package/src/shared/review.ts +2 -1
- package/src/shared/rpc.ts +1 -1
package/README.md
CHANGED
|
@@ -521,6 +521,8 @@ DSH 每次启动监听随机端口,`http://127.0.0.1:<port>` 就是浏览器 o
|
|
|
521
521
|
|
|
522
522
|
官方 `dsh plugin` 方式,三选一。下面以 `web` profile 为例;Desktop 或其他命名
|
|
523
523
|
profile 请替换为实际目标,不要把示例目录当成当前运行 profile。升级前可阅读
|
|
524
|
+
**v0.23.2** 修复 Keep 在非活跃端(桌面端)点了没反应,共两处成因。①权限:`edrv.review.decide` 原先一律要求本端活跃会话,而同端详情接口的 `capability.keep` 只按审查库推导,于是会话在另一实例时按钮亮着、点击后抛错并被兜底成 HTTP 500「DSH 请求失败(HTTP 500)」。现按写入副作用区分——官方记录 Keep 只写共享审查库(`BEGIN IMMEDIATE` + operationId 幂等 + `id/seq/revision/current/decision` CAS,库为每工作区 SQLite 且 `WAL + busy_timeout=2000`),冷端放行;官方 Undo 需校验会话证据并写回源文件、待审投影条目写本机待审 JSON(无跨进程锁),两者仍限本端活跃会话,并返回可读的跨端原因而非 500。详情响应新增 `capability.live`,冷端差异栏禁用 Undo 并给出跨端说明、Keep 提示补一句「写入共享审查库,仍可执行」,批量预检对冷端待审条目给出准确原因。②选中目标:已结算(`oldText === newText`,累计净变化为零)的待审投影 `capability` 恒为不可 Keep,但单文件通道把它排在官方记录之前、客户端取 `items[0]`,同路径官方待审行又被历史过滤掉,于是差异栏「待审 0」、Keep 禁用、点击无反应。现 `pendingPageOf()` 在交给官方审查通道前剔除已结算投影,官方待审记录重新成为审查目标。另外详情未加载时点 Keep 不再静默返回,改为可读提示;打开文件时若某条官方记录的改动已全部不在当前文件中(无可定位差异且缓冲区干净),自动按 Keep 结案(只标记不写盘),差异栏不再显示「本记录 N 处改动均已不在当前文件中」这类失效叙事;官方元数据首次载入期间差异栏显示 `—` 而不是按历史记录报数,避免重启瞬间闪出几十条假待审。验证:typecheck 0 error、目标单测 57/57、`pnpm build` 通过,并用真实工作区审查库探针确认差异栏首条已回到官方记录且 `capability={keep:true,undo:true,live:true}`;桌面端 GUI 点按验收因 App 重启后恢复的是另一个会话而未完成,缺口保留,见 [0.23.2 发布说明](docs/release-0.23.2.md)。以下保留历史版本事实:
|
|
525
|
+
**v0.23.1** 修复「Keep All / Undo All」在工作区待审超过一页时必定中止:批量按钮原先复用官方审查的分页接口逐页取待审条目,并把每页返回的展示用计数当成全量总数校验,因此官方待审超过 50 条(本仓库常态约 150 条)时会先报「待审记录在分页期间变化」或最终报「待审记录数量不完整」,实际一条都不会被决策(已完成 0 项)。现在批量操作改走一次性完整快照——`edrv.review.list` 新增 `all: true` 参数,服务端循环分页取全官方待审行,与工作区历史改动投影按路径去重后返回 `nextCursor: null` 与真实总数(`pending`),客户端单次取快照、校验条目唯一且仍为待审后才逐项决策;快照总量超过 2000 条时明确拒绝并提示分批处理,条目在决策途中变化则继续整批中止、不做部分写入。差异栏的日常浏览仍走原分页接口,展示行为不变。本版修复只经单测与真实审查库只读核对,未在重启后的 GUI 上点按批量按钮验证,缺口保留,见 [0.23.1 发布说明](docs/release-0.23.1.md)。以下保留历史版本事实:
|
|
524
526
|
**v0.23.0** 的快捷菜单:编辑器内一个可拖动悬浮按钮,点开是自定义菜单树(组 / 按钮,最多 5 层),按钮可携带提示词并直接发送给 AI 或只填入输入框;配置存于 `<DSH_HOME>/dsh-vscode-mode/data/quick-menu.json`,带 `revision` 乐观锁与损坏降级;节点可限定生效工作区(默认全部),按钮可绑定技能与规则,发送时按当前会话可见性校验后只拼装通过的绑定。新增 `skills.list` 只读 RPC 枚举「全局技能 + 当前工作区技能」作为候选,配置界面的工作区、技能、规则全部改为下拉弹层/多选下拉,菜单设置重排为四段并统一按钮盒模型(修掉「导入 JSON」下移不居中)。本版**未做 GUI 真机验收**(本机 pnpm store 损坏、插件未部署),缺口已获用户接受,见 [0.23.0 发布说明](docs/release-0.23.0.md)。以下保留历史版本事实:
|
|
525
527
|
**v0.22.0** 的累计基线待审(非 UI 能力对齐 dsh-diff-approval):同一文件的多次编辑折叠成一条待审、基线保留最早内容,A→B→C 显示 A→C、A→B→A 自然归零;Keep 只推进基线不改磁盘,Revert 走版本守卫写回并读回校验,撤销/重做用游标模型跨重启保留;新增 `str_replace_editor` 预像配对、`fs/observed` 终像、已入列路径的外部改动吸收与缺失/不可读生命周期、git/svn/Perforce 基线导入,以及旧 sidecar 写入口对已接管路径的服务端拒绝。差异仍在编辑器内行内展示、块级 Keep/Undo 用药丸,文件级在差异栏;详情面板与批注(含「让 AI 应答」)本版移除。验收边界:p4 仅有假 shell 单测(本机无 Perforce 客户端),「让 AI 应答」随批注一并下线故不再验证;本版未在宿主上重启验收 GUI 全流程,见 [0.22.0 发布说明](docs/release-0.22.0.md)。以下保留历史版本事实:
|
|
526
528
|
**v0.21.4** 的任务发送结果分类与诚实草稿降级:只有明确 accepted 才等待分析方案;unknown/submitted/cancelled 不另填草稿或重发,安全降级只填实际当前会话草稿并提示手动发送。旧 boolean 接口保留,legacy true 不等于受理。开发 link 客户端 rev458288ddb012 的真实公开入口与目标受理已验;完整 SVN 分析/方案投递因 `SetNamedSecurityInfoW failed (Win32 5)` 未完成,用户仅本批接受此缺口,不代表 Host 整体升级或当前实例已安装 npm 0.21.4。见 [0.21.4 发布说明](docs/release-0.21.4.md)。保留以下历史版本事实:
|
|
@@ -531,13 +533,13 @@ profile 请替换为实际目标,不要把示例目录当成当前运行 profi
|
|
|
531
533
|
|
|
532
534
|
```bash
|
|
533
535
|
# ① Git 安装(clone + prepare 构建;推荐打固定 tag)
|
|
534
|
-
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.23.
|
|
536
|
+
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.23.1
|
|
535
537
|
|
|
536
538
|
# ② npm 注册表(发布到 npm 后)
|
|
537
|
-
dsh plugin --profile web add dsh-vscode-mode@0.23.
|
|
539
|
+
dsh plugin --profile web add dsh-vscode-mode@0.23.1
|
|
538
540
|
|
|
539
541
|
# ③ GitHub Release tgz 直装(Release asset 可用后)
|
|
540
|
-
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.23.
|
|
542
|
+
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.23.1/dsh-vscode-mode-0.23.1.tgz
|
|
541
543
|
```
|
|
542
544
|
|
|
543
545
|
> **DSH 版本政策(0.17.0 起)**:六项 DSH peer 仍统一声明为 `*`,另以
|
|
@@ -878,6 +880,8 @@ Keep All / Undo All 却是亮的。修复:单文件 Keep / Undo 覆盖冲突
|
|
|
878
880
|
完整变更见 [GitHub Releases](https://github.com/Lenonss/DSH_VsCodeMode/releases)。
|
|
879
881
|
近期关键版本:
|
|
880
882
|
|
|
883
|
+
- **v0.23.2**:修复 Keep 在非活跃端(桌面端)点了没反应,两处成因。①`edrv.review.decide` 原先一律要求本端活跃会话,而能力位只按审查库推导,会话在另一实例时按钮亮着、点击后抛错被兜底成 HTTP 500。现按写入副作用区分:官方记录 Keep 只写共享审查库(`BEGIN IMMEDIATE` + 幂等键 + 版本 CAS),冷端放行;官方 Undo(校验证据并写回源文件)与待审投影条目(本机 JSON 无跨进程锁)仍限活跃端,返回可读跨端原因而非 500;详情新增 `capability.live`,冷端禁用 Undo 并说明、Keep 提示补「写入共享审查库,仍可执行」。②已结算待审投影的 `capability.keep` 恒为假,却因排在 `items[0]`、同路径官方待审行被历史过滤,把官方记录挤出差异栏(表现为「待审 0」+Keep 禁用+点击无反应);现 `pendingPageOf()` 剔除已结算投影,官方记录重新成为审查目标。另详情未加载时点 Keep 不再静默失败;打开文件时若某条官方记录的改动已全部不在当前文件中(无可定位差异且缓冲区干净),自动按 Keep 结案(只标记不写盘),差异栏不再显示「本记录 N 处改动均已不在当前文件中」;官方元数据首次载入期间差异栏显示 `—` 而非按历史记录报数,重启瞬间不再闪出几十条假待审。验证:typecheck 0 error、目标单测 57/57、build 通过、真实审查库探针确认首条回到官方记录且 `capability={keep:true,undo:true,live:true}`;桌面端 GUI 点按验收因重启后恢复的是另一个会话未完成,缺口保留。发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.23.2.md)。
|
|
884
|
+
- **v0.23.1**:修复 Keep All / Undo All 在待审超过一页时必定中止——批量按钮原先复用分页接口并把每页的展示用计数当全量总数校验,官方待审 150 条时先报「待审记录在分页期间变化」或最终报「待审记录数量不完整」,实际 0 项被决策。现改为一次性完整快照(`edrv.review.list` 新增 `all: true`,服务端循环取全并与历史改动投影去重,`nextCursor: null`、`pending` 为真实总数),客户端单次取快照、校验唯一且仍待审后逐项决策;超过 2000 条明确拒绝并提示分批,途中变化仍整批中止不部分写入。差异栏浏览仍走原分页接口。本版未在重启后的 GUI 点按验证,缺口保留;发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.23.1.md)。
|
|
881
885
|
- **v0.23.0**:新增快捷菜单——可拖动悬浮按钮 + 自定义菜单树(组/按钮,最多 5 层),按钮携带提示词并「直接发送给 AI」或「只填入输入框」;配置存 `<DSH_HOME>/dsh-vscode-mode/data/quick-menu.json`,带 `revision` 乐观锁、冲突重拉不覆盖、损坏降级。节点可限定工作区(默认全部、子节点继承),按钮可绑定技能与规则,发送时按会话可见性校验后只拼装通过的绑定(规则支持引用/内联)。新增 `skills.list` 只读 RPC 枚举全局技能 + 当前工作区技能;配置界面工作区改下拉弹层、技能/规则改分组多选下拉并移除手输技能名,菜单设置重排四段、统一按钮盒模型并修掉「导入 JSON」下移不居中。本版 GUI 未真机验收(pnpm store 损坏、插件未部署),缺口已获用户接受;发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.23.0.md)。
|
|
882
886
|
- **v0.22.0**:新增累计基线待审——同文件多次编辑折叠成一条、基线保留最早内容(A→B→C 显示 A→C、A→B→A 归零);Keep 只推进基线不改磁盘,Revert 带版本守卫写回并读回,撤销/重做游标模型跨重启保留;补 `str_replace_editor` 预像配对、外部改动吸收与缺失生命周期、git/svn/Perforce 基线导入、旧入口服务端拒绝。差异仍在编辑器内行内展示,详情面板与批注(含「让 AI 应答」)移除。p4 仅假 shell 单测(本机无 Perforce 客户端),GUI 全流程未重启验收;发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.22.0.md)。
|
|
883
887
|
- **v0.21.4**:区分任务明确受理、拒绝、不明、仅提交与取消;只有 accepted 开始等待,unknown/submitted/cancelled 不另填草稿、重试或轮询。安全降级只填实际当前会话草稿并提示手动发送,缺详细 API 明确本次未自动发送;旧 boolean 兼容但 true 不等于受理。真实开发客户端公开按钮→目标受理已验,完整 SVN 分析/方案投递因 Win32 5 未验,用户仅本批接受;Host 未重启,不自动部署。见 [发布说明](docs/release-0.21.4.md)。
|
package/lib/client.js
CHANGED
|
@@ -3554,30 +3554,28 @@ window.__ModuleLoader__.load({
|
|
|
3554
3554
|
}
|
|
3555
3555
|
}
|
|
3556
3556
|
//#endregion
|
|
3557
|
+
//#region src/shared/pendingReview.ts
|
|
3558
|
+
/** 待审条目在面板列表里的 id 前缀;客户端与 Host 共用这一份口径。 */
|
|
3559
|
+
const PENDING_PREFIX = "pending:";
|
|
3560
|
+
//#endregion
|
|
3557
3561
|
//#region src/client/reviewBatch.ts
|
|
3558
|
-
/**
|
|
3562
|
+
/**
|
|
3563
|
+
* @public
|
|
3564
|
+
* @author ddj 2026年10月31号
|
|
3565
|
+
* @description: 取一次完整待审快照。分页计数只用于展示,批量写入必须一次取全,避免把页内数字当成总数。
|
|
3566
|
+
* @param port 已鉴权的 RPC 适配器
|
|
3567
|
+
* @returns 冻结的完整待审集;任何不一致都在首个写入前中止
|
|
3568
|
+
* @throws 快照重复、版本已变或数量不完整
|
|
3569
|
+
*/
|
|
3559
3570
|
async function loadPending(port) {
|
|
3560
|
-
const
|
|
3571
|
+
const page = await port.snapshot();
|
|
3561
3572
|
const seen = /* @__PURE__ */ new Set();
|
|
3562
|
-
const
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
if (page.pending !== expected) throw new Error("待审记录在分页期间变化,请刷新后重试");
|
|
3569
|
-
for (const item of page.items) {
|
|
3570
|
-
if (seen.has(item.id) || !item.current || item.decision !== "pending") throw new Error("待审分页发生重复或版本变化,请刷新后重试");
|
|
3571
|
-
seen.add(item.id);
|
|
3572
|
-
items.push(item);
|
|
3573
|
-
}
|
|
3574
|
-
if (page.nextCursor === null) break;
|
|
3575
|
-
if (cursors.has(page.nextCursor) || page.items.length === 0) throw new Error("待审分页未完整返回,请刷新后重试");
|
|
3576
|
-
cursor = page.nextCursor;
|
|
3577
|
-
cursors.add(cursor);
|
|
3578
|
-
} while (true);
|
|
3579
|
-
if (items.length !== expected) throw new Error("待审记录数量不完整,未执行批量操作");
|
|
3580
|
-
return items;
|
|
3573
|
+
for (const item of page.items) {
|
|
3574
|
+
if (seen.has(item.id) || !item.current || item.decision !== "pending") throw new Error("待审快照发生重复或版本变化,请刷新后重试");
|
|
3575
|
+
seen.add(item.id);
|
|
3576
|
+
}
|
|
3577
|
+
if (page.nextCursor !== null || page.items.length !== page.pending) throw new Error("待审记录数量不完整,未执行批量操作");
|
|
3578
|
+
return page.items;
|
|
3581
3579
|
}
|
|
3582
3580
|
/** @public @author ddj 2026年10月27号 @param port Guarded existing per-file decision adapter. @param reject Undo instead of Keep. @returns Precise partial result; Undo stops at first conflict, never claims atomic multi-file rollback. */
|
|
3583
3581
|
async function runReviewBatch(port, reject) {
|
|
@@ -3595,6 +3593,7 @@ window.__ModuleLoader__.load({
|
|
|
3595
3593
|
for (const item of items) {
|
|
3596
3594
|
port.guard();
|
|
3597
3595
|
const detail = await port.detail(item.id);
|
|
3596
|
+
if (detail.capability.live === false && item.id.startsWith("pending:")) throw new Error(`${item.display}:待审条目需要在会话活跃的一端判定,请先在那一端处理或在本端打开该会话`);
|
|
3598
3597
|
if (!detail.item.current || detail.item.seq !== item.seq || detail.item.revision !== item.revision || !detail.capability.keep) throw new Error(`${item.display}:审查版本变化,请刷新后重试`);
|
|
3599
3598
|
if (reject && !detail.capability.undo) throw new Error(`${item.display}:${detail.capability.reason}`);
|
|
3600
3599
|
details.push(detail);
|
|
@@ -3760,10 +3759,6 @@ window.__ModuleLoader__.load({
|
|
|
3760
3759
|
return reviewBlocks(diff).map(placeRegion);
|
|
3761
3760
|
}
|
|
3762
3761
|
//#endregion
|
|
3763
|
-
//#region src/shared/pendingReview.ts
|
|
3764
|
-
/** 待审条目在面板列表里的 id 前缀;客户端与 Host 共用这一份口径。 */
|
|
3765
|
-
const PENDING_PREFIX = "pending:";
|
|
3766
|
-
//#endregion
|
|
3767
3762
|
//#region src/shared/pathKey.ts
|
|
3768
3763
|
/**
|
|
3769
3764
|
* 路径比较口径:Host 与客户端共用同一套「远端 / 盘符 / 相对路径」判定,只在平台折叠上分工。
|
|
@@ -382537,7 +382532,8 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
382537
382532
|
* @returns 持久化的全工作区控制条
|
|
382538
382533
|
*/
|
|
382539
382534
|
function ReviewAllRow(props) {
|
|
382540
|
-
const
|
|
382535
|
+
const known = typeof props.total === "number";
|
|
382536
|
+
const total = known ? props.total : 0;
|
|
382541
382537
|
const current = total && props.current > 0 ? props.current : 0;
|
|
382542
382538
|
const disabled = props.busy || !!props.unavailable || !props.pending;
|
|
382543
382539
|
return /* @__PURE__ */ react$1.default.createElement("div", {
|
|
@@ -382550,7 +382546,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
382550
382546
|
className: "edrv-review-total",
|
|
382551
382547
|
onClick: props.onOpen,
|
|
382552
382548
|
title: "打开当前工作区全部待审差异文件"
|
|
382553
|
-
}, "差异文件 ", /* @__PURE__ */ react$1.default.createElement("strong", null, current
|
|
382549
|
+
}, "差异文件 ", /* @__PURE__ */ react$1.default.createElement("strong", null, known ? `${current} / ${total}` : "—")), /* @__PURE__ */ react$1.default.createElement("span", { className: "edrv-review-all-actions" }, /* @__PURE__ */ react$1.default.createElement(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
382554
382550
|
size: "sm",
|
|
382555
382551
|
variant: "ghost",
|
|
382556
382552
|
onClick: props.onKeepAll,
|
|
@@ -382597,6 +382593,19 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
382597
382593
|
"aria-hidden": "true"
|
|
382598
382594
|
}, /* @__PURE__ */ react$1.default.createElement("path", { d: marks[mark] }));
|
|
382599
382595
|
}
|
|
382596
|
+
/**
|
|
382597
|
+
* @private
|
|
382598
|
+
* @author ddj 2026年10月31号
|
|
382599
|
+
* @description: Keep 按钮提示。会话不在本端活跃时 Keep 仍可执行(只写共享审查库),
|
|
382600
|
+
* 提示里补一句说明,免得用户把它当成跨端不可用的按钮。
|
|
382601
|
+
* @param hint 差异栏给出的 Keep 说明
|
|
382602
|
+
* @param live 会话是否在本端活跃
|
|
382603
|
+
* @returns 按钮 title 文本
|
|
382604
|
+
*/
|
|
382605
|
+
function keepTitle(hint, live) {
|
|
382606
|
+
if (live) return hint;
|
|
382607
|
+
return `${hint}(会话在另一端激活:Keep 写入共享审查库,仍可执行)`;
|
|
382608
|
+
}
|
|
382600
382609
|
/** @public @author ddj 2026年10月27号 @param props Existing review callbacks and immutable record metadata. @returns Responsive review toolbar; no disk or decision side effects until an explicit action. */
|
|
382601
382610
|
function OfficialDiffBar(props) {
|
|
382602
382611
|
const { official, pendingRegions = [], staleRegions = [], diffIdx = 0 } = props;
|
|
@@ -382653,7 +382662,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
382653
382662
|
role: "region",
|
|
382654
382663
|
"aria-label": "文件差异审查"
|
|
382655
382664
|
}, /* @__PURE__ */ react$1.default.createElement(ReviewAllRow, {
|
|
382656
|
-
total: props.fileTotal ??
|
|
382665
|
+
total: props.fileTotal ?? null,
|
|
382657
382666
|
current: (props.fileTotal ?? 0) && (props.fileIdx ?? -1) >= 0 ? (props.fileIdx ?? 0) + 1 : 0,
|
|
382658
382667
|
busy: props.reviewBatchBusy,
|
|
382659
382668
|
unavailable: props.batchUnavailable,
|
|
@@ -382698,7 +382707,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
382698
382707
|
icon: barIcon("check"),
|
|
382699
382708
|
onClick: props.onAcceptFile,
|
|
382700
382709
|
disabled: props.reviewBatchBusy || !detail?.capability.keep,
|
|
382701
|
-
title: detail?.capability.keep ? view.keepHint : detail?.capability.reason
|
|
382710
|
+
title: detail?.capability.keep ? keepTitle(view.keepHint, detail.capability.live !== false) : detail?.capability.reason
|
|
382702
382711
|
}, "Keep"), /* @__PURE__ */ react$1.default.createElement(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
382703
382712
|
open: menu === "more",
|
|
382704
382713
|
anchor: moreTrigger,
|
|
@@ -382786,7 +382795,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
382786
382795
|
role: "region",
|
|
382787
382796
|
"aria-label": "文件差异审查"
|
|
382788
382797
|
}, /* @__PURE__ */ react$1.default.createElement(ReviewAllRow, {
|
|
382789
|
-
total: props.fileTotal ??
|
|
382798
|
+
total: props.fileTotal ?? null,
|
|
382790
382799
|
current: (props.fileTotal ?? 0) && (props.fileIdx ?? -1) >= 0 ? (props.fileIdx ?? 0) + 1 : 0,
|
|
382791
382800
|
busy,
|
|
382792
382801
|
pending: props.allPendingCount,
|
|
@@ -393808,6 +393817,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
393808
393817
|
review.page
|
|
393809
393818
|
]);
|
|
393810
393819
|
const reviewOpsRef = react$1.default.useRef(/* @__PURE__ */ new Map());
|
|
393820
|
+
const settledRef = react$1.default.useRef(/* @__PURE__ */ new Set());
|
|
393811
393821
|
const [officialDetail, setOfficialDetail] = react$1.default.useState(null);
|
|
393812
393822
|
const [officialRecords, setOfficialRecords] = react$1.default.useState([]);
|
|
393813
393823
|
const [selectedReview, setSelectedReview] = react$1.default.useState(null);
|
|
@@ -393822,7 +393832,9 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
393822
393832
|
react$1.default.useEffect(() => {
|
|
393823
393833
|
const controller = new AbortController();
|
|
393824
393834
|
const pendingPath = (review.page?.items ?? []).filter((item) => String(item.id).startsWith(PENDING_PREFIX)).map((item) => item.currentPath || item.path).find((path) => sameFile(path, active));
|
|
393825
|
-
const
|
|
393835
|
+
const coveredPath = review.page?.overview?.coveredPaths.find((path) => sameFile(path, active));
|
|
393836
|
+
const reviewPath = coveredPath ?? pendingPath;
|
|
393837
|
+
dbg(sessionId, `[DEBUG reviewPath] active=${active ?? "(null)"} covered=${coveredPath ?? "(none)"} pending=${pendingPath ?? "(none)"} coveredCount=${review.page?.overview?.coveredPaths.length ?? -1} items=${review.page?.items?.length ?? -1} overview=${review.page?.overview ? 1 : 0}`);
|
|
393826
393838
|
if (!active || !reviewPath) {
|
|
393827
393839
|
setOfficialDetail(null);
|
|
393828
393840
|
setOfficialRecords([]);
|
|
@@ -393838,6 +393850,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
393838
393850
|
if (controller.signal.aborted) return;
|
|
393839
393851
|
if (!page.ok) throw new Error(page.error);
|
|
393840
393852
|
const selected = page.page.items.find((item) => item.id === selectedReview) ?? page.page.items[0];
|
|
393853
|
+
dbg(sessionId, `[DEBUG reviewList] path=${reviewPath} items=${page.page.items.length} waiting=${page.page.pending} selected=${selected?.id ?? "(none)"}`);
|
|
393841
393854
|
if (!selected) {
|
|
393842
393855
|
setOfficialRecords([]);
|
|
393843
393856
|
setOfficialDetail(null);
|
|
@@ -393848,6 +393861,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
393848
393861
|
id: selected.id
|
|
393849
393862
|
}, controller.signal);
|
|
393850
393863
|
if (controller.signal.aborted) return;
|
|
393864
|
+
dbg(sessionId, `[DEBUG reviewDetail] id=${selected.id} ok=${result.ok ? 1 : 0} keep=${result.ok ? result.detail.capability?.keep === true ? 1 : 0 : "-"} live=${result.ok ? String(result.detail.capability?.live) : "-"} err=${result.ok ? "" : String(result.error).slice(0, 120)}`);
|
|
393851
393865
|
if (!result.ok) throw new Error(result.error);
|
|
393852
393866
|
setOfficialRecords((previous) => JSON.stringify(previous) === JSON.stringify(page.page.items) ? previous : page.page.items);
|
|
393853
393867
|
setOfficialDetail((previous) => JSON.stringify(previous) === JSON.stringify(result.detail) ? previous : result.detail);
|
|
@@ -393935,6 +393949,20 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
393935
393949
|
const staleAmbiguous = staleRegions.filter((region) => region.staleReason === "ambiguous").length;
|
|
393936
393950
|
const noBlocks = !!officialDetail && (officialDetail.diff.kind !== "text" || officialDetail.diff.hunks.length === 0);
|
|
393937
393951
|
const decideRegions = react$1.default.useMemo(() => [...pendingRegions, ...staleRegions], [pendingRegions, staleRegions]);
|
|
393952
|
+
react$1.default.useEffect(() => {
|
|
393953
|
+
if (!officialDetail || contentReady !== true || batchBusyRef.current) return;
|
|
393954
|
+
if (pendingRegions.length > 0 || staleRegions.length === 0) return;
|
|
393955
|
+
if (noBlocks || officialDetail.capability?.keep !== true) return;
|
|
393956
|
+
if (dirtyRef.current[active]) return;
|
|
393957
|
+
settleInvisibleRecord(officialDetail);
|
|
393958
|
+
}, [
|
|
393959
|
+
officialDetail,
|
|
393960
|
+
contentReady,
|
|
393961
|
+
pendingRegions,
|
|
393962
|
+
staleRegions,
|
|
393963
|
+
noBlocks,
|
|
393964
|
+
active
|
|
393965
|
+
]);
|
|
393938
393966
|
hoverRegionsRef.current = pendingRegions;
|
|
393939
393967
|
lineRegionMapRef.current = react$1.default.useMemo(() => {
|
|
393940
393968
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -396789,18 +396817,17 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
396789
396817
|
hunkIndex: r.idx,
|
|
396790
396818
|
decision: reject ? "rejected" : "accepted"
|
|
396791
396819
|
});
|
|
396792
|
-
/**
|
|
396793
|
-
|
|
396794
|
-
|
|
396795
|
-
|
|
396796
|
-
|
|
396797
|
-
|
|
396798
|
-
|
|
396799
|
-
|
|
396800
|
-
|
|
396801
|
-
|
|
396802
|
-
const
|
|
396803
|
-
const undoVersion = undoModel?.getVersionId?.();
|
|
396820
|
+
/**
|
|
396821
|
+
* @private
|
|
396822
|
+
* @author ddj 2026年10月31号
|
|
396823
|
+
* @description: 提交一条官方记录的判定;operationId 按决策键复用,网络重试保持幂等。
|
|
396824
|
+
* @param detail 目标记录(含冻结的 seq / revision)
|
|
396825
|
+
* @param reject true=Undo
|
|
396826
|
+
* @param blockId 可选的连续块;缺省为整文件
|
|
396827
|
+
* @returns 成功返回提交结果,失败抛出 host 给出的原因
|
|
396828
|
+
*/
|
|
396829
|
+
async function submitOfficialDecision(detail, reject, blockId) {
|
|
396830
|
+
const source = detail.item;
|
|
396804
396831
|
const opKey = JSON.stringify([
|
|
396805
396832
|
sessionId,
|
|
396806
396833
|
source.id,
|
|
@@ -396814,25 +396841,68 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
396814
396841
|
operationId = crypto.randomUUID();
|
|
396815
396842
|
reviewOpsRef.current.set(opKey, operationId);
|
|
396816
396843
|
}
|
|
396844
|
+
const result = await rpc("edrv.review.decide", {
|
|
396845
|
+
sessionId,
|
|
396846
|
+
id: source.id,
|
|
396847
|
+
seq: source.seq,
|
|
396848
|
+
revision: source.revision,
|
|
396849
|
+
operationId,
|
|
396850
|
+
scope: blockId ? "hunk" : "file",
|
|
396851
|
+
blockId,
|
|
396852
|
+
decision: reject ? "rejected" : "accepted"
|
|
396853
|
+
});
|
|
396854
|
+
if (!result.ok) throw new Error(result.error);
|
|
396855
|
+
reviewOpsRef.current.delete(opKey);
|
|
396856
|
+
return result;
|
|
396857
|
+
}
|
|
396858
|
+
/**
|
|
396859
|
+
* @private
|
|
396860
|
+
* @author ddj 2026年10月31号
|
|
396861
|
+
* @description: 结案一条改动已不在当前文件中的官方记录:等价于 Keep(只标记已采纳,不改写磁盘)。
|
|
396862
|
+
* 这类记录没有可见差异可审,留在差异栏只会让用户去处理一个不存在的东西。
|
|
396863
|
+
* @param detail 待结案的记录
|
|
396864
|
+
*/
|
|
396865
|
+
async function settleInvisibleRecord(detail) {
|
|
396866
|
+
if (batchBusyRef.current) return;
|
|
396867
|
+
const settled = settledRef.current;
|
|
396868
|
+
if (settled.has(detail.item.id)) return;
|
|
396869
|
+
settled.add(detail.item.id);
|
|
396870
|
+
batchBusyRef.current = true;
|
|
396817
396871
|
try {
|
|
396818
|
-
|
|
396819
|
-
|
|
396820
|
-
|
|
396821
|
-
|
|
396822
|
-
|
|
396823
|
-
|
|
396824
|
-
|
|
396825
|
-
|
|
396826
|
-
|
|
396827
|
-
|
|
396828
|
-
|
|
396829
|
-
|
|
396872
|
+
await submitOfficialDecision(detail, false);
|
|
396873
|
+
refreshReview(sessionId);
|
|
396874
|
+
} catch (cause) {
|
|
396875
|
+
settled.delete(detail.item.id);
|
|
396876
|
+
dbg(sessionId, `[DEBUG settleInvisible] id=${detail.item.id} err=${String(cause).slice(0, 160)}`);
|
|
396877
|
+
} finally {
|
|
396878
|
+
batchBusyRef.current = false;
|
|
396879
|
+
}
|
|
396880
|
+
}
|
|
396881
|
+
/** @private @author ddj 2026年10月27号 @param reject Undo instead of Keep. @param blockId Optional contiguous block. @returns Persisted decision result; never reloads over a dirty buffer. */
|
|
396882
|
+
async function decideOfficial(reject, blockId) {
|
|
396883
|
+
dbg(sessionId, `[DEBUG decideOfficial] reject=${reject ? 1 : 0} detail=${officialDetail ? 1 : 0} busy=${batchBusyRef.current ? 1 : 0} active=${active ?? "(null)"}`);
|
|
396884
|
+
if (!officialDetail || batchBusyRef.current) {
|
|
396885
|
+
if (!officialDetail && !batchBusyRef.current) setError("官方差异详情尚未加载完成,请稍后重试;若一直如此,请切换文件或刷新页面。");
|
|
396886
|
+
return false;
|
|
396887
|
+
}
|
|
396888
|
+
if (reject && (dirtyRef.current[active] || savingRef.current.has(active) || saveTimerRef.current.isPending())) {
|
|
396889
|
+
setError("存在未保存或正在保存的修改,请等待完成后重试");
|
|
396890
|
+
return false;
|
|
396891
|
+
}
|
|
396892
|
+
batchBusyRef.current = true;
|
|
396893
|
+
const detail = officialDetail;
|
|
396894
|
+
const undoPath = active;
|
|
396895
|
+
const undoModel = editorRef.current?.getModel?.();
|
|
396896
|
+
const undoVersion = undoModel?.getVersionId?.();
|
|
396897
|
+
try {
|
|
396898
|
+
await submitOfficialDecision(detail, reject, blockId);
|
|
396830
396899
|
if (reject && activeRef.current === undoPath && !dirtyRef.current[undoPath] && !savingRef.current.has(undoPath) && undoModel?.getVersionId?.() === undoVersion) reloadFile(true);
|
|
396831
396900
|
else if (reject) setError("Undo 已完成,编辑器在等待期间变化;未覆盖当前缓冲区,请核对磁盘后刷新");
|
|
396832
396901
|
refreshReview(sessionId);
|
|
396833
396902
|
setStatus(reject ? "已 Undo 选中差异" : "已 Keep 选中差异");
|
|
396834
396903
|
return true;
|
|
396835
396904
|
} catch (cause) {
|
|
396905
|
+
dbg(sessionId, `[DEBUG decideOfficialFail] id=${detail.item.id} reject=${reject ? 1 : 0} err=${String(cause).slice(0, 160)}`);
|
|
396836
396906
|
setError(String(cause));
|
|
396837
396907
|
return false;
|
|
396838
396908
|
} finally {
|
|
@@ -397049,13 +397119,24 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
397049
397119
|
}
|
|
397050
397120
|
return operationId;
|
|
397051
397121
|
}
|
|
397122
|
+
/**
|
|
397123
|
+
* @private
|
|
397124
|
+
* @author ddj 2026年10月31号
|
|
397125
|
+
* @description: 批量操作要求官方待审列表已就绪;切换会话后列表还在载入时,
|
|
397126
|
+
* 先触发一次刷新并给出可读提示,而不是直接报错中止(用户看到红色「尚未就绪」会以为功能坏了)。
|
|
397127
|
+
* @param action 面向用户的动作名
|
|
397128
|
+
* @returns 列表已就绪时为 true
|
|
397129
|
+
*/
|
|
397130
|
+
function reviewReadyForBatch(action) {
|
|
397131
|
+
if (review.page?.overview) return true;
|
|
397132
|
+
refreshReview(sessionId);
|
|
397133
|
+
setStatus(`官方待审列表正在载入,载入完成后请再次点击${action}`);
|
|
397134
|
+
return false;
|
|
397135
|
+
}
|
|
397052
397136
|
/** @private @author ddj 2026年10月27号 @param reject Undo or Keep all workspace pending records, including previous rounds. @returns Explicit partial results; legacy proceeds only when official stage fully succeeds. */
|
|
397053
397137
|
async function actReviewAll(reject) {
|
|
397054
397138
|
if (batchBusyRef.current) return;
|
|
397055
|
-
if (!
|
|
397056
|
-
setError("官方待审列表尚未就绪,未执行批量操作");
|
|
397057
|
-
return;
|
|
397058
|
-
}
|
|
397139
|
+
if (!reviewReadyForBatch(reject ? "Undo All" : "Keep All")) return;
|
|
397059
397140
|
if (!sum.pendingFiles.length) return;
|
|
397060
397141
|
const token = reviewLifeRef.current;
|
|
397061
397142
|
const undoPath = activeRef.current;
|
|
@@ -397066,10 +397147,10 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
397066
397147
|
setStatus(reject ? "正在检查并 Undo 全部待审记录…" : "正在 Keep 全部待审记录…");
|
|
397067
397148
|
try {
|
|
397068
397149
|
const result = await runReviewBatch({
|
|
397069
|
-
|
|
397150
|
+
snapshot: async () => {
|
|
397070
397151
|
const res = await rpc("edrv.review.list", {
|
|
397071
397152
|
sessionId,
|
|
397072
|
-
|
|
397153
|
+
all: true
|
|
397073
397154
|
});
|
|
397074
397155
|
if (!res.ok) throw new Error(res.error);
|
|
397075
397156
|
return res.page;
|
|
@@ -397122,10 +397203,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
397122
397203
|
/** @private @author ddj 2026年10月27号 @param reject Whether to undo pending files. @description Open a DSH confirmation without starting any mutations. */
|
|
397123
397204
|
function askReviewAll(reject) {
|
|
397124
397205
|
if (batchBusyRef.current || !sum.pendingFiles.length) return;
|
|
397125
|
-
if (!
|
|
397126
|
-
setError("官方待审列表尚未就绪,未执行批量操作");
|
|
397127
|
-
return;
|
|
397128
|
-
}
|
|
397206
|
+
if (!reviewReadyForBatch(reject ? "Undo All" : "Keep All")) return;
|
|
397129
397207
|
setReviewAllPrompt({
|
|
397130
397208
|
reject,
|
|
397131
397209
|
count: sum.pendingFiles.length,
|
|
@@ -398623,6 +398701,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
398623
398701
|
const sessionDown = isSessionMissingError(rawReviewError);
|
|
398624
398702
|
const reviewErrorTone = sessionDown ? "info" : "error";
|
|
398625
398703
|
const reviewErrorText = !rawReviewError ? void 0 : sessionDown ? SESSION_DOWN_NOTICE : sessionErrorText(rawReviewError);
|
|
398704
|
+
const reviewLoading = review.page === null && !review.error;
|
|
398626
398705
|
const buildDockSnapshot = () => ({
|
|
398627
398706
|
mode: editorDockMode(active),
|
|
398628
398707
|
missingDeletion: !!missingNow,
|
|
@@ -398650,7 +398729,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
398650
398729
|
onUndoFile: undoFile,
|
|
398651
398730
|
onAcceptAllFiles: acceptAllFiles,
|
|
398652
398731
|
onUndoAllFiles: undoAllFiles,
|
|
398653
|
-
allPendingCount: allPending.length + (review.page?.pending ?? 0),
|
|
398732
|
+
allPendingCount: reviewLoading ? 0 : allPending.length + (review.page?.pending ?? 0),
|
|
398654
398733
|
reviewBatchBusy,
|
|
398655
398734
|
batchUnavailable: !review.page?.overview ? "官方待审列表尚未就绪" : void 0,
|
|
398656
398735
|
onRollback: rollbackFile,
|
|
@@ -398667,7 +398746,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
|
|
|
398667
398746
|
contentReady,
|
|
398668
398747
|
diffTotal: displayDiffTotal(contentReady, pendingRegions.length, sum.files.find((file) => sameFile(file.path, active))?.pending ?? 0),
|
|
398669
398748
|
fileIdx,
|
|
398670
|
-
fileTotal: sum.pendingFiles.length,
|
|
398749
|
+
fileTotal: reviewLoading ? null : sum.pendingFiles.length,
|
|
398671
398750
|
onPrevDiff: () => gotoDiff(-1),
|
|
398672
398751
|
onNextDiff: () => gotoDiff(1),
|
|
398673
398752
|
onPrevFile: () => gotoFile(-1),
|
package/lib/client.js.map
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/lib/client.js.map
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35670356 bytes
|
|
4
|
+
sha256: 4a298d4c08a69a99f2aa5a4626c9aa23ff928609a2af4f4cb9328d3ba4b25114
|