@linxin666/dsh-remote-web-ui 0.1.15 → 0.1.16

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: 97f5f6b000e12fd89ad8723fd25306075a10df06
6
- README.zh.md: 30c7d55a4f31e914091318c3205d23f3b9979f60
5
+ README.md: c1e5ea0deaca90a5a188342db0d38256f5917369
6
+ README.zh.md: 9be2fbeb481ea23f05aa06f732234b2310c3d16a
package/README.md CHANGED
@@ -38,12 +38,16 @@ actions, and the update panel that probes and runs the update.
38
38
  - **Remote update**: the download trigger in the sidebar foot (left of the
39
39
  phone icon) opens the update panel, which probes the npm registry for the
40
40
  installed `@linxin666/dsh-*` family releases. When a newer release exists
41
- the panel runs the update automatically (`pnpm update` inside the owning
42
- dsh profile; when pnpm is missing it falls back to `corepack pnpm` and
43
- then `npx --yes pnpm`, and on Windows the command runs through `cmd.exe`
44
- so npm-installed `.cmd` shims resolve; the loopback-only
41
+ the panel runs the update automatically (`pnpm update --latest` inside the
42
+ owning dsh profile; when pnpm is missing it falls back to `corepack pnpm`
43
+ and then `npx --yes pnpm`, and on Windows the command runs through
44
+ `cmd.exe` so npm-installed `.cmd` shims resolve; the loopback-only
45
45
  `/api/update/status` + `/api/update/run` endpoints drive it) and asks for
46
- a dsh web restart to pick it up. Local
46
+ a dsh web restart to pick it up. After a green pnpm exit the installed
47
+ versions are re-checked against the registry: a green exit that left every
48
+ version in place (e.g. the pnpm `minimumReleaseAge` gate silently skipping
49
+ same-day releases) is reported as a stale update with configuration
50
+ guidance instead of a false success. Local
47
51
  link installs (development mode) are detected and report the npm state
48
52
  without updating.
49
53
 
package/README.zh.md CHANGED
@@ -13,7 +13,7 @@
13
13
  - **手机侧**:扫码将手机与一次性、限时令牌绑定,并落地到 **`/m` 独立移动端界面**——一款专为小屏设计的轻客户端(见[截图](#截图)),而不是把桌面 UI 塞进手机。链接携带 `workspace` 参数,手机落地到桌面正在查看的同一工作区。
14
14
  - **安全**:一个有效的一次性令牌(刷新会使旧链接失效;已接受的令牌不可复用;令牌会过期)。停止会撤销每一台已配对设备与当前令牌——已配对设备在下一次请求时被切断。当插件 `requirePairingForLan` 门开启(默认)时,每个非 loopback 的 `/api` 请求必须携带有效的已配对设备 cookie,因此二维码是进入暴露在局域网上的 dsh web 的唯一途径。
15
15
  - **实时状态**:桌面面板经 SSE 流实时镜像配对状态(等待 → 已连接 → 已断开)。
16
- - **远程更新**:侧边栏底部的下载触发按钮(手机图标左侧)打开更新面板,它探测 npm registry 上已安装的 `@linxin666/dsh-*` 全家桶版本。当存在较新版本时,面板自动执行更新(在所属 dsh profile 内 `pnpm update`;pnpm 缺失时依次回退 `corepack pnpm`、`npx --yes pnpm`,Windows 上经 `cmd.exe` 执行以解析 npm 安装的 `.cmd` shim;由仅 loopback 的 `/api/update/status` + `/api/update/run` 端点驱动)并请求重启 dsh web 以生效。本地 link 安装(开发模式)会被探测到,只报告 npm 状态而不更新。
16
+ - **远程更新**:侧边栏底部的下载触发按钮(手机图标左侧)打开更新面板,它探测 npm registry 上已安装的 `@linxin666/dsh-*` 全家桶版本。当存在较新版本时,面板自动执行更新(在所属 dsh profile 内 `pnpm update --latest`;pnpm 缺失时依次回退 `corepack pnpm`、`npx --yes pnpm`,Windows 上经 `cmd.exe` 执行以解析 npm 安装的 `.cmd` shim;由仅 loopback 的 `/api/update/status` + `/api/update/run` 端点驱动)并请求重启 dsh web 以生效。pnpm 绿色退出后还会对照 registry 复核已装版本:绿色退出但版本纹丝不动(例如 pnpm 的 `minimumReleaseAge` 门禁静默跳过同日发布的新版本)会报告为「未更新成功」并附配置指引,而不是误报成功。本地 link 安装(开发模式)会被探测到,只报告 npm 状态而不更新。
17
17
 
18
18
  ## 截图
19
19
 
package/lib/client.js CHANGED
@@ -1877,6 +1877,8 @@ window.__ModuleLoader__.load({
1877
1877
  case "not-found": return t("update.error.notFound");
1878
1878
  case "link": return t("update.error.link");
1879
1879
  case "pnpm-failed": return t("update.error.pnpmFailed", { code: String(result.exitCode ?? "?") });
1880
+ case "stale": return t("update.error.stale");
1881
+ case "verify-failed": return t("update.error.verifyFailed");
1880
1882
  default: return result.error ?? t("update.error.unknown");
1881
1883
  }
1882
1884
  }
@@ -2217,7 +2219,7 @@ window.__ModuleLoader__.load({
2217
2219
  }
2218
2220
  //#endregion
2219
2221
  //#region \0dsh-css:packages/dsh-remote-web-ui/src/client/settings-card.module.css.mjs
2220
- const css = ".Kwoi6G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Kwoi6G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Kwoi6G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Kwoi6G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Kwoi6G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Kwoi6G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Kwoi6G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Kwoi6G_chevronOpen{transform:rotate(180deg)}.Kwoi6G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Kwoi6G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Kwoi6G_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.Kwoi6G_discard,.Kwoi6G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Kwoi6G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Kwoi6G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Kwoi6G_discard:disabled,.Kwoi6G_save:disabled{opacity:.4;cursor:default}.Kwoi6G_discard:focus-visible,.Kwoi6G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Kwoi6G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Kwoi6G_field+.Kwoi6G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Kwoi6G_head{align-items:center;gap:8px;display:flex}.Kwoi6G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Kwoi6G_badges{align-items:center;gap:8px;display:inline-flex}.Kwoi6G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Kwoi6G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Kwoi6G_reset:disabled{cursor:default}.Kwoi6G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Kwoi6G_input,.Kwoi6G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_input:focus-visible,.Kwoi6G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Kwoi6G_input:disabled,.Kwoi6G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Kwoi6G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Kwoi6G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Kwoi6G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Kwoi6G_card,.Kwoi6G_header,.Kwoi6G_chevron,.Kwoi6G_chevronOpen,.Kwoi6G_discard,.Kwoi6G_save{transition:none}}";
2222
+ const css = ".Kwoi6G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Kwoi6G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Kwoi6G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Kwoi6G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Kwoi6G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Kwoi6G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Kwoi6G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Kwoi6G_chevronOpen{transform:rotate(180deg)}.Kwoi6G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Kwoi6G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Kwoi6G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Kwoi6G_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Kwoi6G_discard,.Kwoi6G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Kwoi6G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Kwoi6G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Kwoi6G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Kwoi6G_discard:disabled,.Kwoi6G_save:disabled{opacity:.4;cursor:default}.Kwoi6G_discard:focus-visible,.Kwoi6G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Kwoi6G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Kwoi6G_field+.Kwoi6G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Kwoi6G_head{align-items:center;gap:8px;display:flex}.Kwoi6G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Kwoi6G_badges{align-items:center;gap:8px;display:inline-flex}.Kwoi6G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Kwoi6G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Kwoi6G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Kwoi6G_reset:disabled{cursor:default}.Kwoi6G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Kwoi6G_input,.Kwoi6G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_input:focus-visible,.Kwoi6G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Kwoi6G_input:disabled,.Kwoi6G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Kwoi6G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Kwoi6G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Kwoi6G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Kwoi6G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Kwoi6G_card,.Kwoi6G_header,.Kwoi6G_chevron,.Kwoi6G_chevronOpen,.Kwoi6G_discard,.Kwoi6G_save{transition:none}}";
2221
2223
  const tagId = "@linxin666/dsh-remote-web-ui/settings-card.module.css";
2222
2224
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2223
2225
  const tag = document.createElement("style");
@@ -2373,10 +2375,10 @@ window.__ModuleLoader__.load({
2373
2375
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2374
2376
  className: settings_card_module_css_default.footer,
2375
2377
  children: [
2376
- state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2378
+ state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
2377
2379
  className: settings_card_module_css_default.failed,
2378
2380
  role: "status",
2379
- children: props.t("settings.saveFailed")
2381
+ children: [props.t("settings.saveFailed"), state.failedReason ? " - " + state.failedReason : ""]
2380
2382
  }) : null,
2381
2383
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2382
2384
  type: "button",
@@ -2568,6 +2570,7 @@ window.__ModuleLoader__.load({
2568
2570
  listeners = /* @__PURE__ */ new Set();
2569
2571
  saving = false;
2570
2572
  failed = false;
2573
+ failedReason;
2571
2574
  /** @param scope - the bound settings scope for this card's namespace. */
2572
2575
  constructor(scope, specs) {
2573
2576
  this.scope = scope;
@@ -2595,7 +2598,8 @@ window.__ModuleLoader__.load({
2595
2598
  dirty: plan.length > 0,
2596
2599
  invalid: plan.some((item) => item.run === void 0),
2597
2600
  saving: this.saving,
2598
- failed: this.failed
2601
+ failed: this.failed,
2602
+ ...this.failedReason === void 0 ? {} : { failedReason: this.failedReason }
2599
2603
  };
2600
2604
  }
2601
2605
  /** Read one field's state from the effective section and its staged draft. */
@@ -2636,29 +2640,50 @@ window.__ModuleLoader__.load({
2636
2640
  if (this.staged.size === 0 && !this.failed) return;
2637
2641
  this.staged.clear();
2638
2642
  this.failed = false;
2643
+ this.failedReason = void 0;
2639
2644
  this.publish();
2640
2645
  }
2641
2646
  };
2642
2647
  }
2643
2648
  /**
2644
2649
  * Write every staged edit, then re-seed from what the Host accepted.
2650
+ *
2651
+ * When the scope carries the optional batch surface (the dsh-web-ui
2652
+ * bridge scope), every planned write rides one mutation so cross-field
2653
+ * validate hooks (baseURL+model) judge the batch as a unit instead of
2654
+ * deadlocking on per-field writes. Otherwise the per-field loop runs.
2655
+ * A field lands only when the Host reports it held the staged value; a
2656
+ * landed field's draft is dropped, a failed one stays staged for the user.
2645
2657
  * @returns settlement after every write and the read-back.
2646
2658
  */
2647
2659
  async save() {
2648
2660
  const plan = this.plan();
2649
- const writes = plan.flatMap((item) => item.run === void 0 ? [] : [item.run]);
2650
- if (plan.length === 0 || this.saving || writes.length !== plan.length) return;
2661
+ const valid = plan.filter((item) => item.run !== void 0);
2662
+ if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
2663
+ const plannedWrites = valid.map((item) => item.op);
2651
2664
  const fields = new Set(plan.map((item) => item.field));
2652
2665
  this.saving = true;
2653
2666
  this.failed = false;
2667
+ this.failedReason = void 0;
2654
2668
  this.publish();
2655
- let landed = true;
2656
- for (const write of writes) landed = await write() && landed;
2657
- if (landed) for (const field of fields) this.staged.delete(field);
2669
+ const landed = /* @__PURE__ */ new Set();
2670
+ const batch = this.batchedScope();
2671
+ if (batch !== void 0) {
2672
+ const result = await batch.mutate(plannedWrites);
2673
+ if (result.ok) {
2674
+ for (const field of result.fields) if (field.landed) landed.add(field.field);
2675
+ } else this.failedReason = result.message;
2676
+ } else for (const item of valid) if (await item.run()) landed.add(item.field);
2677
+ for (const field of fields) if (landed.has(field)) this.staged.delete(field);
2658
2678
  this.saving = false;
2659
- this.failed = !landed;
2679
+ this.failed = landed.size !== fields.size;
2660
2680
  this.publish();
2661
2681
  }
2682
+ /** The scope's batch surface when it supports one; undefined conservatively otherwise. */
2683
+ batchedScope() {
2684
+ const candidate = this.scope;
2685
+ return typeof candidate?.mutate === "function" ? candidate : void 0;
2686
+ }
2662
2687
  /**
2663
2688
  * Every staged edit a save would write. An entry whose draft is not a value
2664
2689
  * its field accepts carries no write: the form is still dirty, and the save
@@ -2673,6 +2698,10 @@ window.__ModuleLoader__.load({
2673
2698
  if (staged.clear) {
2674
2699
  if (this.stored(field)) plan.push({
2675
2700
  field,
2701
+ op: {
2702
+ field,
2703
+ op: "unset"
2704
+ },
2676
2705
  run: () => this.clear(field)
2677
2706
  });
2678
2707
  continue;
@@ -2681,14 +2710,27 @@ window.__ModuleLoader__.load({
2681
2710
  const write = spec.parse(staged.text);
2682
2711
  if (write === void 0) plan.push({
2683
2712
  field,
2713
+ op: {
2714
+ field,
2715
+ op: "unset"
2716
+ },
2684
2717
  run: void 0
2685
2718
  });
2686
2719
  else if (write.kind === "clear") plan.push({
2687
2720
  field,
2721
+ op: {
2722
+ field,
2723
+ op: "unset"
2724
+ },
2688
2725
  run: () => this.clear(field)
2689
2726
  });
2690
2727
  else plan.push({
2691
2728
  field,
2729
+ op: {
2730
+ field,
2731
+ op: "set",
2732
+ value: write.value
2733
+ },
2692
2734
  run: () => this.store(field, write.value)
2693
2735
  });
2694
2736
  }
@@ -2700,11 +2742,13 @@ window.__ModuleLoader__.load({
2700
2742
  }
2701
2743
  async store(field, value) {
2702
2744
  await this.scope.set(field, value);
2745
+ if (this.specOf(field).secret) return true;
2703
2746
  return this.userLayer()?.[field] === value;
2704
2747
  }
2705
2748
  stage(field, edit) {
2706
2749
  this.staged.set(field, edit);
2707
2750
  this.failed = false;
2751
+ this.failedReason = void 0;
2708
2752
  this.publish();
2709
2753
  }
2710
2754
  specOf(field) {
@@ -3040,6 +3084,8 @@ window.__ModuleLoader__.load({
3040
3084
  "update.error.notFound": "未找到 dsh-web-ui 聚合包安装,无法更新。",
3041
3085
  "update.error.link": "当前为本地开发模式(链接安装),无法自动更新。",
3042
3086
  "update.error.pnpmFailed": "更新执行失败(pnpm 退出码 {code}),详见下方输出。",
3087
+ "update.error.stale": "更新命令已执行,但安装版本未变化。常见原因:pnpm 11 的 minimumReleaseAge 门禁会静默跳过发布不足 24 小时的新版本。请在 profile 目录(~/.dsh/profiles/<profile>)的 pnpm-workspace.yaml 中配置 minimumReleaseAgeExclude(例如 \"@linxin666/*\")或 minimumReleaseAge: 0 后重新点击更新。",
3088
+ "update.error.verifyFailed": "更新命令已执行,但无法核对安装后的版本(可能是 registry 探测失败或安装路径变化)。请查看下方输出,或稍后重试。",
3043
3089
  "update.error.unknown": "更新失败,请重试。"
3044
3090
  };
3045
3091
  /** English dictionary, checked complete against the zh key set. */
@@ -3142,6 +3188,8 @@ window.__ModuleLoader__.load({
3142
3188
  "update.error.notFound": "The dsh-web-ui aggregate package is not installed; nothing to update.",
3143
3189
  "update.error.link": "Local development mode (link install) — automatic update is unavailable.",
3144
3190
  "update.error.pnpmFailed": "The update failed (pnpm exited with code {code}); see the output below.",
3191
+ "update.error.stale": "The update command ran but the installed versions did not change. Likely cause: the pnpm 11 minimumReleaseAge gate silently skips releases published less than 24 hours ago. Add minimumReleaseAgeExclude (e.g. \"@linxin666/*\") or set minimumReleaseAge: 0 in pnpm-workspace.yaml under the profile directory (~/.dsh/profiles/<profile>), then run the update again.",
3192
+ "update.error.verifyFailed": "The update command ran but the installed versions could not be verified afterwards (registry probe failed or the install path changed). Inspect the output below, or retry later.",
3145
3193
  "update.error.unknown": "Update failed; try again."
3146
3194
  };
3147
3195
  //#endregion