dsh-plugin-update 0.5.5 → 0.5.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  格式为 Keep-a-Changelog 子集:`Added/Fixed/Changed` 必写,`Deprecated/Removed/Security` 有则透传,`Unreleased` 面板忽略,禁止 git-log 直倒。面板缺日志时中性提示,不挡安装。
4
4
 
5
+ ## [0.5.6] - 2026-10-06
6
+
7
+ ### Added
8
+ - 弹窗分栏滚动、更新日志折叠与动效体系;双语防倒退门禁四道检查(文案源/快照/契约/零 draft)与自测(#56)。
9
+
10
+ ### Fixed
11
+ - 用户路径抛错锁存非瞬态化(#58):查/装动作的传输抛错改为非瞬态锁存(安装抛错码为 install-failed,不再冒充查失败),轮询不再洗掉;抛错原文脱敏后进复制摘要;安装内补查失败精确归因到查阶段;轮询抛错仍瞬态自愈。
12
+
5
13
  ## [0.5.5] - 2026-10-06
6
14
 
7
15
  ### Added
@@ -383,6 +383,8 @@ const UPDATE_BATCH_PANEL_CSS = [
383
383
  ".dsh-upd-bdetail{margin:2px 0 10px;overflow:visible}",
384
384
  ".dsh-upd .dsh-upd-bdetail .dsh-upd{max-width:none}",
385
385
  ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd{max-height:none;overflow:visible}",
386
+ ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd-body{overflow:visible}",
387
+ ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd-body .dsh-upd-chap-head{position:static}",
386
388
  "@media (prefers-color-scheme: dark){.dsh-upd-batch-title i{color:var(--dsh-upd-focus,#93c5fd)}}",
387
389
  "@media (prefers-reduced-motion: reduce){.dsh-upd-batch *{transition:none !important;animation:none !important}}"
388
390
  ].join(String.fromCharCode(10));
package/dist/panel.d.ts CHANGED
@@ -18,7 +18,7 @@ export type UpdatePanelCall = (phoneName: string, args: Record<string, unknown>)
18
18
  /** 复制文本的出口:不传即尝试浏览器剪贴板,都没有则只留“已生成、请手动复制”提示。 */
19
19
  export type UpdatePanelCopyText = (text: string) => void | Promise<void>;
20
20
  /** 面板可点的动作(与 HTML 里 data-action 一一对应,测试走同一条路)。 */
21
- export type UpdatePanelActionKind = 'check' | 'install' | 'skip' | 'reset-skip' | 'copy-manual' | 'copy-diag' | 'toggle-queue' | 'restart-hint' | 'close-view' | 'dismiss-failure';
21
+ export type UpdatePanelActionKind = 'check' | 'install' | 'skip' | 'reset-skip' | 'copy-manual' | 'copy-diag' | 'toggle-queue' | 'toggle-changelog' | 'restart-hint' | 'close-view' | 'dismiss-failure';
22
22
  export interface UpdatePanelOptions {
23
23
  /** 插件标识:必填,与宿主侧 createHostUpdate 传的 pluginId 一致。 */
24
24
  pluginId: string;
@@ -86,6 +86,8 @@ export interface UpdatePanelController {
86
86
  setTheme(theme: UpdatePanelTheme): Promise<void>;
87
87
  /** 切换排队可见性(重查一次,位置口径不变)。 */
88
88
  setShowOthers(show: boolean): Promise<void>;
89
+ /** 收起/展开更新日志(纯视图态,不调电话;轮询与换肤不丢)。 */
90
+ setChangelogCollapsed(collapsed: boolean): Promise<void>;
89
91
  /**
90
92
  * 换一版更新日志后重绘(#23:宿主侧备好新文本后调用;传 null/空串即回中性提示)。
91
93
  * 只换日志节,安装门控只跟快照,一字不动。
@@ -258,6 +260,11 @@ export interface PanelRenderInput extends PanelViewInput {
258
260
  * 只读渲染下五章内容、进度条、「已跳过」提示一字不减,只是没有动作按钮。
259
261
  */
260
262
  actions?: 'default' | 'none';
263
+ /**
264
+ * 更新日志折叠(仅挂载态内部用:用户点了 02 章标题行的收起/展开)。
265
+ * 缺省即展开(安全日志必显、可查找;只读渲染下恒展开)。
266
+ */
267
+ changelogCollapsed?: boolean;
261
268
  }
262
269
  /** 内核渲染用的失败引用(只读快照:证据仍在锁存里,这里只传展示键)。 */
263
270
  export interface PanelFailureRef {
package/dist/panel.js CHANGED
@@ -528,7 +528,15 @@ const UPDATE_PANEL_CSS = [
528
528
  ".dsh-upd-changelog-more-note{font-size:12px;opacity:.7;margin:2px 0 4px}",
529
529
  ".dsh-upd-changelog-neutral{color:inherit;opacity:.8}",
530
530
  ".dsh-upd-overlay{position:fixed;inset:0;background:rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;z-index:9999}",
531
- ".dsh-upd-overlay .dsh-upd{background:var(--dsh-upd-bg,#ffffff);max-height:85vh;overflow:auto}",
531
+ ".dsh-upd-overlay .dsh-upd{background:var(--dsh-upd-bg,#ffffff);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}",
532
+ // —— 弹窗分栏滚动:头(抬头/档案头/横幅/版本条)与尾固定,只有 01–05 章节区滚动 ——
533
+ ".dsh-upd-body{min-height:0}",
534
+ ".dsh-upd-overlay .dsh-upd-body{flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dsh-upd-line,#e5e7eb) transparent}",
535
+ ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar{width:8px}",
536
+ ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar-thumb{background:var(--dsh-upd-line,#e5e7eb);border-radius:4px}",
537
+ ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar-track{background:transparent}",
538
+ // —— 章节标题磁吸:滚动时节标题贴顶(纯 CSS sticky;背景跟随主题,D5 另覆)——
539
+ ".dsh-upd-overlay .dsh-upd-body .dsh-upd-chap-head{position:sticky;top:0;z-index:1;background:var(--dsh-upd-bg,#ffffff);padding-top:2px}",
532
540
  // —— 档案头 / 版本条 / 章节 / 进度条 / 跳过行(原型 :208-245 的新结构,默认主题给最小可用样式)——
533
541
  ".dsh-upd-head{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap}",
534
542
  // 卷宗抬头「插件更新 / 更新档案 卷」:D5 档案卷才画,默认(最小)主题不画。
@@ -582,11 +590,18 @@ const UPDATE_PANEL_CSS = [
582
590
  ".dsh-upd button:active:not(:disabled){transform:translateY(1px)}",
583
591
  '.dsh-upd button[aria-busy="true"]{cursor:wait;animation:dsh-upd-pulse 1s ease-in-out infinite}',
584
592
  "@keyframes dsh-upd-pulse{0%,100%{opacity:1}50%{opacity:.55}}",
593
+ // —— 状态横幅淡入 + 日志折叠格动画(纯 CSS;重绘只发生在真变时;reduced-motion 下静止)——
594
+ ".dsh-upd-banner{animation:dsh-upd-fadein .22s ease}",
595
+ "@keyframes dsh-upd-fadein{from{opacity:.35;transform:translateY(2px)}}",
596
+ ".dsh-upd-changelog-foldbox{display:grid;grid-template-rows:1fr;opacity:1;transition:grid-template-rows .22s ease,opacity .18s ease}",
597
+ ".dsh-upd-changelog-foldbox-inner{min-height:0;overflow:hidden}",
598
+ '.dsh-upd-changelog-foldbox[data-open="0"]{grid-template-rows:0fr;opacity:0}',
585
599
  // 在途转圈:纯 CSS ::after,不加 DOM 节点(内核 DOM 冻结);转的是边框缺口,不是 emoji。
586
600
  '.dsh-upd button[aria-busy="true"]::after{content:"";display:inline-block;width:11px;height:11px;margin-left:8px;vertical-align:-1px;',
587
601
  "border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:dsh-upd-spin .8s linear infinite}",
588
602
  "@keyframes dsh-upd-spin{to{transform:rotate(360deg)}}",
589
- '@media (prefers-reduced-motion: reduce){.dsh-upd button{transition:none}.dsh-upd button:active:not(:disabled){transform:none}.dsh-upd button[aria-busy="true"]{animation:none}.dsh-upd-skv{animation:none}}',
603
+ '@media (prefers-reduced-motion: reduce){.dsh-upd button{transition:none}.dsh-upd button:active:not(:disabled){transform:none}.dsh-upd button[aria-busy="true"]{animation:none}.dsh-upd-skv{animation:none}.dsh-upd-banner{animation:none}.dsh-upd-changelog-foldbox{transition:none}}',
604
+ "@media (forced-colors: active){.dsh-upd-overlay .dsh-upd-body .dsh-upd-chap-head{background:Canvas}}",
590
605
  "@media (prefers-color-scheme: dark){.dsh-upd{--dsh-upd-fg:#e5e7eb;--dsh-upd-bg:#111827;--dsh-upd-line:#374151;",
591
606
  "--dsh-upd-btn:#1f2937;--dsh-upd-soft:#1f2937;--dsh-upd-primary:#3b82f6;--dsh-upd-focus:#93c5fd;",
592
607
  // 横幅深色覆盖:底色用低透明度同色系(不是浅色原值),边线提亮,保证「深底浅字」可读。
@@ -650,6 +665,9 @@ const UPDATE_PANEL_D5_CSS = [
650
665
  '.dsh-upd[data-theme="archive"] .dsh-upd-qn{font-family:var(--d5-mono);font-size:11.5px;color:var(--d5-muted)}',
651
666
  '.dsh-upd[data-theme="archive"] .dsh-upd-qseq{color:var(--d5-muted)}',
652
667
  '.dsh-upd[data-theme="archive"] .dsh-upd-chap-note{color:var(--d5-muted)}',
668
+ '.dsh-upd[data-theme="archive"] .dsh-upd-body .dsh-upd-chap-head{background:var(--d5-card)}',
669
+ '.dsh-upd-overlay .dsh-upd[data-theme="archive"] .dsh-upd-body{scrollbar-color:var(--d5-line-strong) transparent}',
670
+ '.dsh-upd-overlay .dsh-upd[data-theme="archive"] .dsh-upd-body::-webkit-scrollbar-thumb{background:var(--d5-line-strong)}',
653
671
  // —— 小印章(原型 :215 `.sealmini`:30px、旋转 -5°、一字)——
654
672
  // 待重启横幅一律不画印章:那一档的标记是左侧手绘 SVG(原型 :446 的 .mark 只有 SVG)。
655
673
  '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="loading"]::before,.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="idle"]::before,',
@@ -805,9 +823,11 @@ function renderUpdatePanelKernel(input, view) {
805
823
  `<div class="dsh-upd-skipline"><span class="dsh-upd-tag">\u5DF2\u8DF3\u8FC7 ${escapeHtml(snapshot.latestVersion)}</span>\u70B9\u300C\u6062\u590D\u300D\u53EF\u64A4\u9500\uFF0C\u4E4B\u540E\u8FD9\u4E00\u7248\u8FD8\u4F1A\u518D\u63D0\u9192\u3002</div>`
806
824
  );
807
825
  }
808
- parts.push(chapterOf(1, actions.join("")));
826
+ const chapters = [];
827
+ chapters.push(chapterOf(1, actions.join("")));
809
828
  {
810
829
  let inner = "";
830
+ let hasLog = false;
811
831
  if (snapshot && snapshot.latestVersion) {
812
832
  try {
813
833
  const mdText = typeof changelogMarkdown === "string" ? changelogMarkdown : "";
@@ -837,9 +857,11 @@ function renderUpdatePanelKernel(input, view) {
837
857
  } catch {
838
858
  yankedBanner = "";
839
859
  }
840
- inner = `<div class="dsh-upd-changelog-wrap"><div>${escapeHtml(rangeTitle)}</div>${yankedBanner}
860
+ const logOpen = input.changelogCollapsed !== true;
861
+ inner = `<div class="dsh-upd-changelog-wrap"><div>${escapeHtml(rangeTitle)}</div><div class="dsh-upd-changelog-foldbox" data-open="${logOpen ? "1" : "0"}"><div class="dsh-upd-changelog-foldbox-inner">${yankedBanner}
841
862
  ${changelogHTML}
842
- </div>`;
863
+ </div></div></div>`;
864
+ hasLog = Array.isArray(ranged) && ranged.length > 0;
843
865
  } catch {
844
866
  inner = "";
845
867
  }
@@ -849,7 +871,9 @@ ${changelogHTML}
849
871
  snapshot && snapshot.latestVersion ? "\u65E5\u5FD7\u8BFB\u4E0D\u51FA\u6765\uFF0C\u5B89\u88C5\u4E0D\u53D7\u5F71\u54CD\u3002" : "\u8FD8\u6CA1\u67E5\u5230\u65B0\u7248\uFF1B\u67E5\u5230\u540E\u518D\u663E\u793A\u65E5\u5FD7\u3002"
850
872
  )}</div></div>`;
851
873
  }
852
- parts.push(chapterOf(2, inner));
874
+ const logCollapsed = input.changelogCollapsed === true;
875
+ const logNote = showActions && hasLog ? `<span class="dsh-upd-chap-note"><button type="button" data-action="toggle-changelog" title="\u5C55\u5F00\u6216\u6536\u8D77\u66F4\u65B0\u65E5\u5FD7">${logCollapsed ? "\u5C55\u5F00\u66F4\u65B0\u65E5\u5FD7" : "\u6536\u8D77\u66F4\u65B0\u65E5\u5FD7"}</button></span>` : "";
876
+ chapters.push(chapterOf(2, inner, logNote));
853
877
  }
854
878
  {
855
879
  const queued = !!queue && (queue.busy || queue.waiting.length > 0);
@@ -876,9 +900,9 @@ ${changelogHTML}
876
900
  );
877
901
  }
878
902
  const note = showActions ? `<span class="dsh-upd-chap-note"><button type="button" data-action="toggle-queue">${reveal ? "\u9690\u85CF\u4ED6\u4EBA\u660E\u7EC6" : "\u663E\u793A\u5176\u4ED6\u63D2\u4EF6"}</button></span>` : "";
879
- parts.push(chapterOf(3, `<div class="dsh-upd-queue">${rows.join("")}</div>`, note));
903
+ chapters.push(chapterOf(3, `<div class="dsh-upd-queue">${rows.join("")}</div>`, note));
880
904
  } else {
881
- parts.push(
905
+ chapters.push(
882
906
  chapterOf(
883
907
  3,
884
908
  `<div class="dsh-upd-queue"><div class="dsh-upd-changelog-neutral">${escapeHtml(
@@ -930,14 +954,15 @@ ${changelogHTML}
930
954
  }
931
955
  }
932
956
  if (copyNotice) errLines.push(`<div class="dsh-upd-copy" role="status">${escapeHtml(copyNotice)}</div>`);
933
- parts.push(chapterOf(4, errLines.join("")));
957
+ chapters.push(chapterOf(4, errLines.join("")));
934
958
  }
935
- parts.push(
959
+ chapters.push(
936
960
  chapterOf(
937
961
  5,
938
962
  view.showManual && manual ? `<div class="dsh-upd-manual"><div>\u624B\u5DE5\u515C\u5E95\u547D\u4EE4\uFF08\u590D\u5236\u6574\u884C\u6267\u884C\uFF09\uFF1A</div><code>${escapeHtml(manual)}</code></div>` : `<div class="dsh-upd-manual"><div class="dsh-upd-changelog-neutral">\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u624B\u5DE5\u547D\u4EE4\uFF08\u8BA4\u4E0D\u51FA\u4F7F\u7528\u8303\u56F4\u6216\u5C5E\u6E90\u7801\u5B89\u88C5\u65F6\u4E0D\u7ED9\uFF09\u3002</div></div>`
939
963
  )
940
964
  );
965
+ parts.push('<div class="dsh-upd-body">' + chapters.join("\n") + "</div>");
941
966
  if (showActions && mode === "dialog") {
942
967
  parts.push(
943
968
  `<div class="dsh-upd-footer"><span class="dsh-upd-foot-note">\u5173\u95ED\u4E0D\u5F71\u54CD\u66F4\u65B0\uFF0C\u53EF\u968F\u65F6\u56DE\u6765\u67E5\u770B</span><button type="button" data-action="close-view" title="\u5173\u95ED\u7A97\u53E3\uFF0C\u66F4\u65B0\u4E0D\u53D7\u5F71\u54CD">\u5173\u95ED</button></div>`
@@ -1056,6 +1081,7 @@ function mountUpdatePanel(container, options) {
1056
1081
  }
1057
1082
  let theme = normalizePanelTheme(options.theme ?? "default");
1058
1083
  let showOthers = options.showOthers === true;
1084
+ let changelogCollapsed = false;
1059
1085
  const call = options.call;
1060
1086
  const onRestartRequested = options.onRestartRequested;
1061
1087
  const onCloseRequested = typeof options.onCloseRequested === "function" ? options.onCloseRequested : null;
@@ -1158,6 +1184,38 @@ function mountUpdatePanel(container, options) {
1158
1184
  atMs: latchNow()
1159
1185
  };
1160
1186
  }
1187
+ function thrownDetail(err) {
1188
+ try {
1189
+ const raw = err instanceof Error ? err.message : typeof err === "string" ? err : null;
1190
+ if (typeof raw !== "string") return null;
1191
+ const t = raw.trim();
1192
+ if (!t) return null;
1193
+ if (/^[a-z][a-z-]*$/.test(t) && t.length <= 32) return null;
1194
+ return redactForCopy(t) || null;
1195
+ } catch {
1196
+ return null;
1197
+ }
1198
+ }
1199
+ function userThrowLatch(source, err) {
1200
+ const env = currentEnvPair();
1201
+ return {
1202
+ code: source === "install" ? "install-failed" : "check-failed",
1203
+ kind: null,
1204
+ detail: thrownDetail(err),
1205
+ diag: null,
1206
+ requestId: source === "install" ? requestId : null,
1207
+ checkId: source === "install" ? receipt?.checkId ?? null : null,
1208
+ runningVersion: snapshot?.runningVersion ?? null,
1209
+ installedVersion: snapshot?.installedVersion ?? null,
1210
+ latestVersion: snapshot?.latestVersion ?? null,
1211
+ hostKind: env.hostKind,
1212
+ profileName: env.profileName,
1213
+ source,
1214
+ volatile: false,
1215
+ jobId: null,
1216
+ atMs: latchNow()
1217
+ };
1218
+ }
1161
1219
  function volatileLatch(source) {
1162
1220
  const env = currentEnvPair();
1163
1221
  return {
@@ -1244,6 +1302,7 @@ function mountUpdatePanel(container, options) {
1244
1302
  changelogMarkdown,
1245
1303
  mode,
1246
1304
  showOthers,
1305
+ changelogCollapsed,
1247
1306
  pluginId,
1248
1307
  copyNotice,
1249
1308
  theme,
@@ -1404,9 +1463,9 @@ function mountUpdatePanel(container, options) {
1404
1463
  if (!mounted) return;
1405
1464
  applyStatusReply(reply, "check");
1406
1465
  changelogFailedAt.clear();
1407
- } catch {
1466
+ } catch (err) {
1408
1467
  if (!mounted) return;
1409
- setLatch(volatileLatch("check"));
1468
+ setLatch(userThrowLatch("check", err));
1410
1469
  } finally {
1411
1470
  busyAct = null;
1412
1471
  if (copyNotice === "\u6B63\u5728\u67E5\u65B0\u7248\u2026") copyNotice = null;
@@ -1422,9 +1481,16 @@ function mountUpdatePanel(container, options) {
1422
1481
  render();
1423
1482
  try {
1424
1483
  if (!receipt) {
1425
- const checked = await call(phoneNames.updateCheck, queueArgs());
1426
- if (!mounted) return;
1427
- applyStatusReply(checked, "check");
1484
+ try {
1485
+ const checked = await call(phoneNames.updateCheck, queueArgs());
1486
+ if (!mounted) return;
1487
+ applyStatusReply(checked, "check");
1488
+ } catch (err) {
1489
+ if (!mounted) return;
1490
+ setLatch(userThrowLatch("check", err));
1491
+ render();
1492
+ return;
1493
+ }
1428
1494
  }
1429
1495
  if (!mounted) return;
1430
1496
  if (!receipt) {
@@ -1457,9 +1523,9 @@ function mountUpdatePanel(container, options) {
1457
1523
  });
1458
1524
  if (!mounted) return;
1459
1525
  applyStatusReply(reply, "install");
1460
- } catch {
1526
+ } catch (err) {
1461
1527
  if (!mounted) return;
1462
- setLatch(volatileLatch("install"));
1528
+ setLatch(userThrowLatch("install", err));
1463
1529
  } finally {
1464
1530
  busyAct = null;
1465
1531
  if (copyNotice === "\u6B63\u5728\u5B89\u88C5\u2026") copyNotice = null;
@@ -1567,6 +1633,11 @@ function mountUpdatePanel(container, options) {
1567
1633
  await refresh();
1568
1634
  return;
1569
1635
  }
1636
+ case "toggle-changelog": {
1637
+ changelogCollapsed = !changelogCollapsed;
1638
+ render();
1639
+ return;
1640
+ }
1570
1641
  // 「重启宿主」:宿主没有重启自己的电话,所以只做入口——
1571
1642
  // 调用方给了 onRestartRequested 就交给它;没给就如实说“请手动重启”,不假装。
1572
1643
  case "restart-hint": {
@@ -1607,6 +1678,10 @@ function mountUpdatePanel(container, options) {
1607
1678
  showOthers = show === true;
1608
1679
  await refresh();
1609
1680
  }
1681
+ async function setChangelogCollapsed(collapsed) {
1682
+ changelogCollapsed = collapsed === true;
1683
+ render();
1684
+ }
1610
1685
  function onClick(ev) {
1611
1686
  try {
1612
1687
  const t = ev;
@@ -1683,7 +1758,7 @@ function mountUpdatePanel(container, options) {
1683
1758
  } catch {
1684
1759
  }
1685
1760
  })();
1686
- return { refresh, act, setMode, setTheme, setShowOthers, setChangelogMarkdown, unmount };
1761
+ return { refresh, act, setMode, setTheme, setShowOthers, setChangelogCollapsed, setChangelogMarkdown, unmount };
1687
1762
  }
1688
1763
  import { buildPhoneNames as buildPhoneNames2 } from "./config.js";
1689
1764
  import { buildChangelogPhoneName as buildChangelogPhoneName2 } from "./config.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-update",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "可复用的更新系统装成的 npm 包:任何 DSH 插件照文档能集成检查更新等功能(地图 #579)",
5
5
  "license": "MIT",
6
6
  "author": "王辰浩",
@@ -65,7 +65,9 @@
65
65
  "build": "node build.mjs",
66
66
  "prepack": "npm run build",
67
67
  "pretest": "npm run build",
68
- "test": "node --test tests/*.test.mjs"
68
+ "test": "node --test tests/*.test.mjs",
69
+ "gate:bilingual": "node scripts/bilingual-gate.mjs",
70
+ "gate:bilingual:release": "node scripts/bilingual-gate.mjs --release"
69
71
  },
70
72
  "devDependencies": {
71
73
  "esbuild": "^0.28.2",