dsh-vscode-mode 0.21.0 → 0.21.1

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 CHANGED
@@ -477,18 +477,18 @@ DSH 每次启动监听随机端口,`http://127.0.0.1:<port>` 就是浏览器 o
477
477
 
478
478
  官方 `dsh plugin` 方式,三选一。下面以 `web` profile 为例;Desktop 或其他命名
479
479
  profile 请替换为实际目标,不要把示例目录当成当前运行 profile。升级前可阅读
480
- **v0.21.0** 新增 XLSX 基础编辑与浏览器导出副本,原件不自动写回,高级应用通过官方 Host 打开;官方差异定位统一 BOM 与 LF/CRLF/混合行尾口径,并区分内容缺失和多处命中。定位修复不扩展 CRLF patch Undo 能力,目标官方记录 ↑/↓ 的真实点击验收仍保留缺口。保留 **v0.20.5** 跨 DSH 实例只读互通——桌面端与 Web 端各自运行独立宿主时,官方审查列表/详情与工作区只读接口改按持久化会话头的 cwd 解析,另一实例创建的会话也能只读打开官方差异栏,写入类接口仍要求本端活跃会话。保留 **v0.20.4** 统一两套差异栏外观——非官方记录栏角标由内部术语「侧车记录」改为「历史改动」,悬停说明 Keep 只标记已看过、不改写磁盘,Undo 逐项校验后写回,两者都不影响官方审查;官方元数据不可用时给出可读原因与重试按钮,不再静默降级;「会话没在当前端激活」由红色告警改为中性说明。保留 v0.20.3 修复官方差异全部无法定位时差异栏无提示、上下箭头静默禁用(显示「无可跳转」与失效原因,Keep 说明只标记已采纳、不改写磁盘,会话缺失等宿主错误改为可读提示)、v0.20.2 的 MCP 状态告警收敛与原生确认弹窗,以及 v0.20.1 的文件内差异展示、块级/文件级安全审查和跨分页批量操作。升级后重启 DSH 并刷新页面;持久化与回退边界见 [0.20.0 发布说明](docs/release-0.20.0.md);从 0.14.2 或更旧版本升级还须阅读
480
+ **v0.21.1** 修复缺失历史文件:按完整快照显示整文件删除,Keep 保留删除、Undo 安全恢复快照,预览只读且拒绝覆盖重新出现的文件;真实 GUI 验收缺口已明确保留,见 [0.21.1 发布说明](docs/release-0.21.1.md)。保留 **v0.21.0** 新增 XLSX 基础编辑与浏览器导出副本,原件不自动写回,高级应用通过官方 Host 打开;官方差异定位统一 BOM 与 LF/CRLF/混合行尾口径,并区分内容缺失和多处命中。定位修复不扩展 CRLF patch Undo 能力,目标官方记录 ↑/↓ 的真实点击验收仍保留缺口。保留 **v0.20.5** 跨 DSH 实例只读互通——桌面端与 Web 端各自运行独立宿主时,官方审查列表/详情与工作区只读接口改按持久化会话头的 cwd 解析,另一实例创建的会话也能只读打开官方差异栏,写入类接口仍要求本端活跃会话。保留 **v0.20.4** 统一两套差异栏外观——非官方记录栏角标由内部术语「侧车记录」改为「历史改动」,悬停说明 Keep 只标记已看过、不改写磁盘,Undo 逐项校验后写回,两者都不影响官方审查;官方元数据不可用时给出可读原因与重试按钮,不再静默降级;「会话没在当前端激活」由红色告警改为中性说明。保留 v0.20.3 修复官方差异全部无法定位时差异栏无提示、上下箭头静默禁用(显示「无可跳转」与失效原因,Keep 说明只标记已采纳、不改写磁盘,会话缺失等宿主错误改为可读提示)、v0.20.2 的 MCP 状态告警收敛与原生确认弹窗,以及 v0.20.1 的文件内差异展示、块级/文件级安全审查和跨分页批量操作。升级后重启 DSH 并刷新页面;持久化与回退边界见 [0.20.0 发布说明](docs/release-0.20.0.md);从 0.14.2 或更旧版本升级还须阅读
481
481
  [0.14.3 数据迁移与回退说明](docs/release-0.14.3.md)。
482
482
 
483
483
  ```bash
484
484
  # ① Git 安装(clone + prepare 构建;推荐打固定 tag)
485
- dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.21.0
485
+ dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.21.1
486
486
 
487
487
  # ② npm 注册表(发布到 npm 后)
488
- dsh plugin --profile web add dsh-vscode-mode@0.21.0
488
+ dsh plugin --profile web add dsh-vscode-mode@0.21.1
489
489
 
490
490
  # ③ GitHub Release tgz 直装(Release asset 可用后)
491
- dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.21.0/dsh-vscode-mode-0.21.0.tgz
491
+ dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.21.1/dsh-vscode-mode-0.21.1.tgz
492
492
  ```
493
493
 
494
494
  > **DSH 版本政策(0.17.0 起)**:六项 DSH peer 仍统一声明为 `*`,另以
@@ -829,6 +829,7 @@ Keep All / Undo All 却是亮的。修复:单文件 Keep / Undo 覆盖冲突
829
829
  完整变更见 [GitHub Releases](https://github.com/Lenonss/DSH_VsCodeMode/releases)。
830
830
  近期关键版本:
831
831
 
832
+ - **v0.21.1**:缺失历史文件读取完整快照并整文件显示删除行;Keep 保留删除,Undo 安全恢复当前展示快照,不覆盖重新出现的文件。删除预览只读,单文件和批量操作统一处理,记录变化或证据不足时停止。真实 GUI 验收缺口已获用户接受;发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.21.1.md)。
832
833
  - **v0.21.0**:新增 XLSX 基础编辑、多工作表与浏览器导出副本,原件不自动写回;不支持结构只读预览,高级应用经官方 Host 打开原件。编辑状态仅在当前页面内保留,不承诺刷新或崩溃恢复。官方差异定位统一 BOM 与 LF/CRLF/混合行尾口径,区分内容缺失与多处命中,保持原始字节及写回校验边界;不扩展 CRLF patch Undo 能力,目标官方记录 ↑/↓ 的真实点击仍未验收。升级后重启 DSH 并刷新页面;发布不自动部署当前实例。
833
834
  - **v0.20.5**:**跨实例官方审查数据只读互通**——桌面端与 Web 端各自运行独立 DSH 宿主时,会话只存在于创建它的宿主内存里,插件原先一律用本进程的活跃会话解析工作区,另一端打开官方差异栏会拿到「会话不存在」并退化成历史改动栏。现在官方审查列表/详情与工作区只读接口(文件读取、原始字节、分页文本、记录桶、归档列表、目录列表、界面状态镜像)改为按持久化会话头的 cwd 解析:只要会话在共享的 `~/.dsh/sessions` 下留有会话头,其他实例即可只读访问它的官方审查数据;写入类接口(保存、回滚、采纳/拒绝、批量决策)仍要求本端活跃会话,避免两个宿主并发写同一工作区的审查库。已知限制:会话内 `cd` 过的记录只能按创建时的 cwd 冷读;桌面端本版本未重新部署,反向仍不可用。升级后重启 DSH 并刷新页面。
834
835
  - **v0.20.4**:**两套差异栏不再互相冒充,内部术语退出界面**——插件本来并存官方审查栏与另一条历史记录栏(旧机制把每次改动留痕写进工作区 `.dsh-edit-review.json`),过去两者外观相同、无任何标识,容易被误认为插件未升级。现在非官方栏使用与官方栏同一套版式,但角标改为「历史改动」并在悬浮说明里讲清:这些改动没有进入官方审查列表,Keep 只标记看过、不改文件,Undo 会校验后写回,两者都不影响官方审查;设置页的「侧车摘要」一并对齐为日常说法。官方元数据通道失败(`edrv.review.list` 报错,如 `会话不存在`,或宿主未提供官方文件导航)时,在栏上方给出可读原因与重试按钮,不再静默降级;其中「会话没在当前端激活」由红色告警改为中性说明并存原始详情,真正的磁盘/回滚冲突错误仍保持红色。仅调整展示层,未改动数据库结构、决策语义与回退校验。
package/lib/client.js CHANGED
@@ -377810,6 +377810,44 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
377810
377810
  return regions;
377811
377811
  }
377812
377812
  //#endregion
377813
+ //#region src/client/missingReview.ts
377814
+ /** @public @author ddj 2026年10月28号 @param review Frozen complete snapshot. @returns One whole-file deletion, including empty files; no invented current lines. */
377815
+ function missingRegion(review) {
377816
+ return {
377817
+ callId: review.callIds[0],
377818
+ idx: 0,
377819
+ start: 1,
377820
+ end: 1,
377821
+ oldStart: 1,
377822
+ oldLines: splitLines(review.content.replace(/\r\n/g, "\n")),
377823
+ newLines: [],
377824
+ whole: true,
377825
+ status: "pending",
377826
+ create: false,
377827
+ superseded: false,
377828
+ sourceKind: "missing"
377829
+ };
377830
+ }
377831
+ /** @public @author ddj 2026年10月28号 @param paths Batch paths. @param api Guarded Host access. @returns Missing paths handled once; a partial failure stops before legacy hunk writes. @throws Missing evidence, changed lifecycle or Host decision failure. */
377832
+ async function missingBatch(paths, api) {
377833
+ const candidates = [];
377834
+ for (const path of new Set(paths)) {
377835
+ api.guard();
377836
+ const result = await api.query(path);
377837
+ if (!result.ok) throw new Error(result.error);
377838
+ if (result.review) candidates.push(result.review);
377839
+ }
377840
+ const completed = /* @__PURE__ */ new Set();
377841
+ for (const review of candidates) {
377842
+ api.guard();
377843
+ const result = await api.decide(review, api.operation(review));
377844
+ if (!result.ok) throw new Error(`已处理 ${completed.size} 个删除文件,其余未处理。${result.error}`);
377845
+ completed.add(review.path);
377846
+ api.committed(review, result.callIds);
377847
+ }
377848
+ return completed;
377849
+ }
377850
+ //#endregion
377813
377851
  //#region src/client/editorModelState.ts
377814
377852
  /** Monaco 编辑器实例选择器(限定在插件编辑器行内,排除官方预览等其它 Monaco)。 */
377815
377853
  const EDITOR_MODEL_SELECTOR = ".edrv-editor-row .monaco-editor";
@@ -382728,7 +382766,10 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
382728
382766
  onUndoAll: props.onUndoAllFiles,
382729
382767
  keepTitle: "把这批历史改动全部标记为已看过(只改记录,不动磁盘文件)",
382730
382768
  undoTitle: "逐项撤销这些历史改动;缺少证据或遇到冲突即停止"
382731
- }), busy ? /* @__PURE__ */ react$1.default.createElement("div", {
382769
+ }), props.missingDeletion ? /* @__PURE__ */ react$1.default.createElement("div", {
382770
+ className: "edrv-review-notice",
382771
+ role: "status"
382772
+ }, "文件已删除 · 展示最后记录快照。Keep 保留删除;Undo 恢复展示的完整快照。") : null, busy ? /* @__PURE__ */ react$1.default.createElement("div", {
382732
382773
  className: "edrv-review-notice",
382733
382774
  role: "status"
382734
382775
  }, "正在处理历史改动…") : null, down ? /* @__PURE__ */ react$1.default.createElement("div", {
@@ -382766,7 +382807,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
382766
382807
  icon: barIcon("undo"),
382767
382808
  onClick: props.onUndoFile,
382768
382809
  disabled: !canDecide,
382769
- title: SIDECAR_UNDO_TITLE
382810
+ title: props.missingDeletion ? "恢复展示的完整历史快照(不会覆盖重新出现的文件)" : SIDECAR_UNDO_TITLE
382770
382811
  }, "Undo"), /* @__PURE__ */ react$1.default.createElement(_deepseek_ai_dsh_client_ui_primitives.Button, {
382771
382812
  size: "sm",
382772
382813
  variant: "primary",
@@ -382774,7 +382815,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
382774
382815
  icon: barIcon("check"),
382775
382816
  onClick: props.onAcceptFile,
382776
382817
  disabled: !canDecide,
382777
- title: SIDECAR_KEEP_TITLE
382818
+ title: props.missingDeletion ? "保持文件删除并结清覆盖的待审历史记录,不写文件" : SIDECAR_KEEP_TITLE
382778
382819
  }, "Keep"), /* @__PURE__ */ react$1.default.createElement(_deepseek_ai_dsh_client_ui_primitives.Button, {
382779
382820
  size: "sm",
382780
382821
  variant: "ghost",
@@ -393389,6 +393430,9 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
393389
393430
  const [dirtyMap, setDirtyMap] = react$1.default.useState({});
393390
393431
  const [content, setContent] = react$1.default.useState(null);
393391
393432
  const [contentPath, setContentPath] = react$1.default.useState(null);
393433
+ const [missingPreview, setMissingPreview] = react$1.default.useState(null);
393434
+ const missingRef = react$1.default.useRef(/* @__PURE__ */ new Map());
393435
+ const missingNow = missingPreview?.path === active && missingPreview?.sessionId === sessionId ? missingPreview : null;
393392
393436
  const [receiptLoad, setReceiptLoad] = react$1.default.useState(null);
393393
393437
  const [imageSrc, setImageSrc] = react$1.default.useState(null);
393394
393438
  const [imgSize, setImgSize] = react$1.default.useState(null);
@@ -393751,7 +393795,10 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
393751
393795
  const regions = react$1.default.useMemo(() => {
393752
393796
  const trace = isDebug() && contentReady && currentRecords.length > 0;
393753
393797
  const start = trace ? performance.now() : 0;
393754
- const next = officialDetail ? reviewRegions(officialDetail.diff, officialDetail.item.id, officialDetail.blocks ?? [], contentReady ? content : null) : diffRegions(currentRecords, contentReady ? content : null).filter((r) => !r.superseded);
393798
+ let next;
393799
+ if (officialDetail) next = reviewRegions(officialDetail.diff, officialDetail.item.id, officialDetail.blocks ?? [], contentReady ? content : null);
393800
+ else if (missingNow && contentReady) next = missingNow.resolved ? [] : [missingRegion(missingNow)];
393801
+ else next = diffRegions(currentRecords, contentReady ? content : null).filter((r) => !r.superseded);
393755
393802
  if (trace) try {
393756
393803
  dbg(sessionId, `[DEBUG diffRegions] records=${currentRecords.length} bytes=${content.length} regions=${next.length} ms=${(performance.now() - start).toFixed(1)}`);
393757
393804
  } catch (e) {}
@@ -393761,7 +393808,16 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
393761
393808
  content,
393762
393809
  contentPath,
393763
393810
  active,
393764
- officialDetail
393811
+ officialDetail,
393812
+ missingNow
393813
+ ]);
393814
+ react$1.default.useEffect(() => {
393815
+ editorRef.current?.updateOptions({ readOnly: !!missingNow });
393816
+ }, [
393817
+ missingNow,
393818
+ active,
393819
+ content,
393820
+ monaco
393765
393821
  ]);
393766
393822
  const pendingRegions = react$1.default.useMemo(() => regions.filter((r) => r.status === ST.PENDING && !r.stale), [regions]);
393767
393823
  const staleRegions = react$1.default.useMemo(() => regions.filter((r) => r.status === ST.PENDING && r.stale), [regions]);
@@ -394236,7 +394292,13 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
394236
394292
  /** @author ddj 2026年09月28号 @param res Disk content for a still-clean cached model. */
394237
394293
  const accept = (res) => {
394238
394294
  if (!loadCurrent(path, sequence) || dirtyRef.current[path] || model.getValue() !== before) return;
394295
+ if (res?.code === "file-missing") {
394296
+ loadMissing(path, sid, sequence);
394297
+ return;
394298
+ }
394239
394299
  if (!res?.ok) return;
394300
+ missingRef.current.delete(path);
394301
+ setMissingPreview(null);
394240
394302
  mark(res.version);
394241
394303
  if (res.content === before) return;
394242
394304
  saveViewState(path);
@@ -394259,6 +394321,48 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
394259
394321
  * @param force 强制走 RPC(reloadFile 用,跳过 model 复用)
394260
394322
  * @param version 已知磁盘版本(缺省走 RPC 返回值)
394261
394323
  */
394324
+ /** @private @author ddj 2026年10月28号 @param path Missing text file. @param sid Owning session. @param seq Loader generation. @sideEffects Loads deletion evidence without creating or saving the file. */
394325
+ async function loadMissing(path, sid, seq) {
394326
+ try {
394327
+ const result = await rpc("edrv.missingReview", {
394328
+ sessionId: sid,
394329
+ path
394330
+ });
394331
+ if (!loadCurrent(path, seq)) return;
394332
+ if (!result.ok) {
394333
+ failLoad(path, sid, seq, result.error);
394334
+ return;
394335
+ }
394336
+ if (!result.review) {
394337
+ loadContent(path, sid, true);
394338
+ return;
394339
+ }
394340
+ if (dirtyRef.current[path] || savingRef.current.has(path)) {
394341
+ failLoad(path, sid, seq, "文件已删除,但有未保存编辑;请先处理本地缓冲");
394342
+ return;
394343
+ }
394344
+ const preview = {
394345
+ ...result.review,
394346
+ sessionId: sid,
394347
+ keepId: crypto.randomUUID(),
394348
+ undoId: crypto.randomUUID()
394349
+ };
394350
+ missingRef.current.set(path, preview);
394351
+ setMissingPreview(preview);
394352
+ setContent("");
394353
+ setContentPath(path);
394354
+ setLoadError(null);
394355
+ setError(null);
394356
+ stampLoad(path, sid, seq);
394357
+ setLoadStage({
394358
+ progress: 100,
394359
+ message: "文件已删除 · 展示最后记录快照"
394360
+ });
394361
+ setStatus("文件已删除 · 展示最后记录快照(只读)");
394362
+ } catch (cause) {
394363
+ if (loadCurrent(path, seq)) failLoad(path, sid, seq, String(cause));
394364
+ }
394365
+ }
394262
394366
  const loadContent = (path, sid, force, version) => {
394263
394367
  const seq = ++loadSeqRef.current;
394264
394368
  setReceiptLoad({
@@ -394319,7 +394423,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
394319
394423
  loadPdf(path, sid, seq, force === true, version);
394320
394424
  return;
394321
394425
  }
394322
- const cachedModel = force ? null : modelsRef.current.get(path);
394426
+ const cachedModel = force || missingRef.current.has(path) ? null : modelsRef.current.get(path);
394323
394427
  if (cachedModel) {
394324
394428
  setContent(cachedModel.getValue());
394325
394429
  setContentPath(path);
@@ -394346,6 +394450,8 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
394346
394450
  } catch (e) {}
394347
394451
  if (!loadCurrent(path, seq)) return;
394348
394452
  if (res && res.ok) {
394453
+ missingRef.current.delete(path);
394454
+ setMissingPreview(null);
394349
394455
  mark(res.version, true);
394350
394456
  setContent(res.content);
394351
394457
  setContentPath(path);
@@ -394356,6 +394462,10 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
394356
394462
  }));
394357
394463
  setStatus("已加载");
394358
394464
  } else {
394465
+ if (res?.code === "file-missing") {
394466
+ loadMissing(path, sid, seq);
394467
+ return;
394468
+ }
394359
394469
  if (res?.code === "file-too-large") setLargeCandidate(JSON.stringify([
394360
394470
  scope,
394361
394471
  sid,
@@ -394606,6 +394716,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
394606
394716
  });
394607
394717
  return;
394608
394718
  }
394719
+ if (change.kind === "deleted" && change.path === activeRef.current && !dirtyRef.current[change.path] && !savingRef.current.has(change.path)) loadContent(change.path, sessionId, true);
394609
394720
  const kind = change.kind === "deleted" ? "deleted" : "conflict";
394610
394721
  markDiskFlag(readSync(scope, change.path) ?? {
394611
394722
  path: change.path,
@@ -395591,6 +395702,10 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
395591
395702
  /** @private @author ddj 2026年10月14号 @param path Canonical path. @param manual Explicit save. @returns Guarded save, never another pane's content. */
395592
395703
  function saveDocument(path, manual) {
395593
395704
  if (isExcelPath(path)) return Promise.resolve({ ok: false });
395705
+ if (missingRef.current.has(path)) {
395706
+ setStatus("删除预览只读;请使用 Keep / Undo");
395707
+ return Promise.resolve({ ok: false });
395708
+ }
395594
395709
  const model = modelsRef.current.get(path);
395595
395710
  if (!model) return Promise.resolve({ ok: false });
395596
395711
  const store = documents();
@@ -395662,7 +395777,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
395662
395777
  const ed = editorRef.current;
395663
395778
  const model = ed?.getModel();
395664
395779
  const path = [...modelsRef.current].find(([, item]) => item === model)?.[0];
395665
- if (!ed || !path) return;
395780
+ if (!ed || !path || missingRef.current.has(path)) return;
395666
395781
  setDirtyMap((d) => Object.assign({}, d, { [path]: true }));
395667
395782
  touchTab(path);
395668
395783
  setStatus("编辑中…");
@@ -395796,6 +395911,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
395796
395911
  const side = layoutRef.current === "side";
395797
395912
  const ed = m.editor.create(node, {
395798
395913
  value: "",
395914
+ readOnly: missingRef.current.has(activeRef.current),
395799
395915
  language: "plaintext",
395800
395916
  theme: themeNameOf(),
395801
395917
  fontFamily: "var(--ds-font-family-code, ui-monospace, monospace)",
@@ -396443,6 +396559,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
396443
396559
  * @returns Promise<boolean> 是否成功
396444
396560
  */
396445
396561
  const actHunk = (region, reject, silent) => {
396562
+ if (region.sourceKind === "missing") return decideMissing(reject);
396446
396563
  if (region.sourceKind === "official") return decideOfficial(reject, region.id);
396447
396564
  const method = reject ? "edrv.reject" : "edrv.accept";
396448
396565
  const trace = isDebug();
@@ -396612,7 +396729,57 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
396612
396729
  batchBusyRef.current = false;
396613
396730
  }
396614
396731
  }
396732
+ /** @private @author ddj 2026年10月28号 @param reject Restore the shown snapshot instead of keeping absence. @returns Safe whole-file decision, never a hunk reversal. */
396733
+ async function decideMissing(reject) {
396734
+ if (batchBusyRef.current || !missingNow || missingNow.resolved) return false;
396735
+ const preview = missingNow;
396736
+ const lifetime = reviewLifeRef.current;
396737
+ batchBusyRef.current = true;
396738
+ setReviewBatchBusy(true);
396739
+ try {
396740
+ guardReviewAll(reject, lifetime);
396741
+ const result = await rpc("edrv.missingDecide", {
396742
+ sessionId,
396743
+ token: preview.token,
396744
+ operationId: reject ? preview.undoId : preview.keepId,
396745
+ undo: reject
396746
+ });
396747
+ if (!lifetime?.live) return false;
396748
+ if (!result.ok) {
396749
+ setError(result.error);
396750
+ return false;
396751
+ }
396752
+ setRecords((previous) => Object.fromEntries(Object.entries(previous).filter(([id]) => !result.callIds.includes(id))));
396753
+ missingRef.current.set(preview.path, {
396754
+ ...preview,
396755
+ resolved: true
396756
+ });
396757
+ if (activeRef.current === preview.path) {
396758
+ if (reject) loadContent(preview.path, sessionId, true);
396759
+ else setMissingPreview({
396760
+ ...preview,
396761
+ resolved: true
396762
+ });
396763
+ }
396764
+ refreshRecords();
396765
+ emitRefresh({
396766
+ scope,
396767
+ paths: [preview.path],
396768
+ kind: "structure"
396769
+ });
396770
+ setStatus(reject ? "已恢复展示的完整历史快照" : "已保留文件删除");
396771
+ setError(null);
396772
+ return true;
396773
+ } catch (cause) {
396774
+ if (lifetime?.live) setError(String(cause));
396775
+ return false;
396776
+ } finally {
396777
+ batchBusyRef.current = false;
396778
+ if (lifetime?.live) setReviewBatchBusy(false);
396779
+ }
396780
+ }
396615
396781
  const acceptFile = () => {
396782
+ if (missingNow && !officialDetail) return decideMissing(false);
396616
396783
  if (officialDetail) return decideOfficial(false);
396617
396784
  if (batchBusyRef.current || !decideRegions.length) return;
396618
396785
  batchBusyRef.current = true;
@@ -396631,6 +396798,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
396631
396798
  });
396632
396799
  };
396633
396800
  const undoFile = () => {
396801
+ if (missingNow && !officialDetail) return decideMissing(true);
396634
396802
  if (officialDetail) return decideOfficial(true);
396635
396803
  if (batchBusyRef.current || !decideRegions.length) return;
396636
396804
  batchBusyRef.current = true;
@@ -396668,24 +396836,85 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
396668
396836
  * @author ddj 2026年08月20号
396669
396837
  * @param reject true=全部不采纳(回滚),false=全部采纳
396670
396838
  */
396671
- const actAllPending = (reject) => {
396839
+ const actAllPending = async (reject) => {
396672
396840
  if (batchBusyRef.current || !allPending.length) return;
396841
+ const lifetime = reviewLifeRef.current;
396673
396842
  batchBusyRef.current = true;
396674
396843
  const list = reject ? [...allPending].sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : b.idx - a.idx) : allPending;
396675
396844
  const paths = [...new Set(list.map((r) => records[r.callId]?.path).filter(Boolean))];
396676
- return actMany(list.map((r) => itemOf(r, reject))).then((result) => {
396677
- batchBusyRef.current = false;
396678
- reloadFile(true);
396845
+ try {
396846
+ const handled = await missingBatch(paths, {
396847
+ query: async (path) => {
396848
+ const cached = missingRef.current.get(path);
396849
+ if (cached?.sessionId === sessionId && !cached.resolved) return {
396850
+ ok: true,
396851
+ review: cached
396852
+ };
396853
+ const result = await rpc("edrv.missingReview", {
396854
+ sessionId,
396855
+ path
396856
+ });
396857
+ if (result.ok && result.review) {
396858
+ const preview = {
396859
+ ...result.review,
396860
+ sessionId,
396861
+ keepId: crypto.randomUUID(),
396862
+ undoId: crypto.randomUUID()
396863
+ };
396864
+ missingRef.current.set(path, preview);
396865
+ return {
396866
+ ok: true,
396867
+ review: preview
396868
+ };
396869
+ }
396870
+ return result;
396871
+ },
396872
+ decide: (preview, operationId) => rpc("edrv.missingDecide", {
396873
+ sessionId,
396874
+ token: preview.token,
396875
+ operationId,
396876
+ undo: reject
396877
+ }),
396878
+ guard: () => guardReviewAll(reject, lifetime),
396879
+ operation: (preview) => reject ? preview.undoId : preview.keepId,
396880
+ committed: (preview, callIds) => {
396881
+ if (!lifetime?.live) return;
396882
+ missingRef.current.set(preview.path, {
396883
+ ...preview,
396884
+ resolved: true
396885
+ });
396886
+ setRecords((previous) => Object.fromEntries(Object.entries(previous).filter(([id]) => !callIds.includes(id))));
396887
+ if (!reject && sameFile(activeRef.current, preview.path)) setMissingPreview({
396888
+ ...preview,
396889
+ path: activeRef.current,
396890
+ sessionId,
396891
+ resolved: true
396892
+ });
396893
+ emitRefresh({
396894
+ scope,
396895
+ paths: [preview.path],
396896
+ kind: "structure"
396897
+ });
396898
+ }
396899
+ });
396900
+ guardReviewAll(reject, lifetime);
396901
+ const remaining = list.filter((item) => !handled.has(records[item.callId]?.path));
396902
+ const result = await actMany(remaining.map((item) => itemOf(item, reject)));
396903
+ if (!lifetime?.live) return;
396904
+ if (reject || !missingNow) reloadFile(true);
396679
396905
  emitRefresh({
396680
396906
  scope,
396681
- paths: paths.length ? paths : void 0,
396907
+ paths,
396682
396908
  kind: "structure"
396683
396909
  });
396684
- setStatus(decideStatus(reject ? "已不采纳 " : "已采纳 ", result));
396685
- if (result.fail) setError(result.fail + " 处差异处理失败(可能已被后续修改影响),可刷新后重试");
396686
- }).catch(() => {
396910
+ setStatus(decideStatus(reject ? "已不采纳 " : "已采纳 ", result) + `;删除文件 ${handled.size} 个`);
396911
+ if (result.fail) setError(result.fail + " 处差异处理失败,可刷新后重试");
396912
+ } catch (cause) {
396913
+ if (lifetime?.live) setError(String(cause));
396914
+ } finally {
396687
396915
  batchBusyRef.current = false;
396688
- });
396916
+ if (lifetime?.live) refreshRecords();
396917
+ }
396689
396918
  };
396690
396919
  /** @private @author ddj 2026年10月27号 @param reject Undo requires every local buffer to be saved. @param token Starting session lifetime. @returns Throws before the next mutation when unsafe. */
396691
396920
  function guardReviewAll(reject, token) {
@@ -398286,6 +398515,7 @@ td.UniverTableCell, th.UniverTableCell{mso-padding-alt:0cm 5.4pt 0cm 5.4pt;}
398286
398515
  const reviewErrorText = !rawReviewError ? void 0 : sessionDown ? SESSION_DOWN_NOTICE : sessionErrorText(rawReviewError);
398287
398516
  const buildDockSnapshot = () => ({
398288
398517
  mode: editorDockMode(active),
398518
+ missingDeletion: !!missingNow,
398289
398519
  official: officialFile ? {
398290
398520
  sessionId,
398291
398521
  page: review.page,
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: 35414345 bytes
4
- sha256: 66610acd6b8afc0e307a13515199fdabb4bba568823a767d67129ce48b960dfc
3
+ size: 35428115 bytes
4
+ sha256: 63bc86413f1d5024f07a58442185c1317ec83567e46c3ab47e5320250dff9c76