dsh-plugin-update 0.5.4 → 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/dist/panel.js CHANGED
@@ -9,8 +9,9 @@ import {
9
9
  shouldFetchChangelog,
10
10
  yankedBannerHTML
11
11
  } from "./changelog.js";
12
+ import { LOG_EVENT_CALL, LOG_EVENT_CALL_FAIL, LOG_EVENT_INSTALL_EXEC } from "./log-events.js";
12
13
  function normalizePanelTheme(value) {
13
- return value === "d5-paper" || value === "archive" ? "d5-paper" : "default";
14
+ return value === "archive" || value === "d5-paper" ? "archive" : "default";
14
15
  }
15
16
  const BLOCKED_COPY = {
16
17
  "unknown-profile": {
@@ -510,8 +511,8 @@ const UPDATE_PANEL_CSS = [
510
511
  '.dsh-upd-banner[data-kind="update"]{border-color:var(--dsh-upd-ok-line);background:var(--dsh-upd-ok-bg)}',
511
512
  '.dsh-upd-banner[data-kind="busy"]{border-color:var(--dsh-upd-busy-line);background:var(--dsh-upd-busy-bg)}',
512
513
  ".dsh-upd code{font-family:Consolas,Menlo,monospace;font-size:12px;word-break:break-all}",
513
- ".dsh-upd-manual,.dsh-upd-queue,.dsh-upd-log{margin:8px 0;font-size:13px}",
514
- ".dsh-upd-changelog-wrap{margin:8px 0;font-size:13px;border-top:1px solid var(--dsh-upd-line,#e5e7eb);padding-top:8px}",
514
+ ".dsh-upd-manual,.dsh-upd-queue,.dsh-upd-log{margin:4px 0;font-size:13px}",
515
+ ".dsh-upd-changelog-wrap{margin:4px 0 0;font-size:13px}",
515
516
  ".dsh-upd-changelog-title{font-weight:700;margin:0 0 4px}",
516
517
  ".dsh-upd-changelog-version{margin:6px 0}",
517
518
  ".dsh-upd-changelog-catname{font-weight:600;margin:6px 0 2px}",
@@ -527,7 +528,15 @@ const UPDATE_PANEL_CSS = [
527
528
  ".dsh-upd-changelog-more-note{font-size:12px;opacity:.7;margin:2px 0 4px}",
528
529
  ".dsh-upd-changelog-neutral{color:inherit;opacity:.8}",
529
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}",
530
- ".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}",
531
540
  // —— 档案头 / 版本条 / 章节 / 进度条 / 跳过行(原型 :208-245 的新结构,默认主题给最小可用样式)——
532
541
  ".dsh-upd-head{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap}",
533
542
  // 卷宗抬头「插件更新 / 更新档案 卷」:D5 档案卷才画,默认(最小)主题不画。
@@ -581,11 +590,18 @@ const UPDATE_PANEL_CSS = [
581
590
  ".dsh-upd button:active:not(:disabled){transform:translateY(1px)}",
582
591
  '.dsh-upd button[aria-busy="true"]{cursor:wait;animation:dsh-upd-pulse 1s ease-in-out infinite}',
583
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}',
584
599
  // 在途转圈:纯 CSS ::after,不加 DOM 节点(内核 DOM 冻结);转的是边框缺口,不是 emoji。
585
600
  '.dsh-upd button[aria-busy="true"]::after{content:"";display:inline-block;width:11px;height:11px;margin-left:8px;vertical-align:-1px;',
586
601
  "border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:dsh-upd-spin .8s linear infinite}",
587
602
  "@keyframes dsh-upd-spin{to{transform:rotate(360deg)}}",
588
- '@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}}",
589
605
  "@media (prefers-color-scheme: dark){.dsh-upd{--dsh-upd-fg:#e5e7eb;--dsh-upd-bg:#111827;--dsh-upd-line:#374151;",
590
606
  "--dsh-upd-btn:#1f2937;--dsh-upd-soft:#1f2937;--dsh-upd-primary:#3b82f6;--dsh-upd-focus:#93c5fd;",
591
607
  // 横幅深色覆盖:底色用低透明度同色系(不是浅色原值),边线提亮,保证「深底浅字」可读。
@@ -596,7 +612,7 @@ const UPDATE_PANEL_CSS = [
596
612
  ].join("\n");
597
613
  const UPDATE_PANEL_D5_CSS = [
598
614
  "/* D5 \u6863\u6848\u5377\u53EF\u9009\u4E3B\u9898\uFF1A\u53EA\u6362\u989C\u8272/\u5B57\u4F53/\u95F4\u8DDD\uFF1B\u5185\u6838 DOM \u987A\u5E8F\u4E00\u5B57\u4E0D\u52A8\uFF0C\u4E0D\u65AD\u590D\u5236\u8BCA\u65AD\u3002 */",
599
- '.dsh-upd[data-theme="d5-paper"]{--d5-bg:#f7f3ea;--d5-card:#fffdf6;--d5-ink:#1a1a1a;--d5-muted:#6f675a;',
615
+ '.dsh-upd[data-theme="archive"]{--d5-bg:#f7f3ea;--d5-card:#fffdf6;--d5-ink:#1a1a1a;--d5-muted:#6f675a;',
600
616
  "--d5-line:#e3d9c4;--d5-line-strong:#c4b896;--d5-accent:#c8402a;--d5-accent-deep:#9c2e1d;",
601
617
  "--d5-ok:#1a7f37;--d5-ok-bg:#e9f4ea;--d5-warn:#8a5a00;--d5-warn-bg:#fbf0d0;",
602
618
  "--d5-bad:#b3261e;--d5-bad-bg:#fbe9e5;",
@@ -607,116 +623,128 @@ const UPDATE_PANEL_D5_CSS = [
607
623
  "font-family:var(--d5-sans);color:var(--d5-ink);background:var(--d5-card);",
608
624
  "border:1px solid var(--d5-line-strong);border-radius:4px;box-shadow:var(--d5-shadow)}",
609
625
  // 按钮脸自己不透明(宿主底色未知时也读得出;卡片上渲染与 transparent 逐字同色)。
610
- '.dsh-upd[data-theme="d5-paper"] button{border-color:var(--d5-line-strong);background:var(--d5-card);color:var(--d5-ink);border-radius:3px;font-family:var(--d5-sans)}',
611
- '.dsh-upd[data-theme="d5-paper"] button:hover:not(:disabled){border-color:var(--d5-accent);color:var(--d5-accent)}',
612
- '.dsh-upd[data-theme="d5-paper"] button[data-primary="1"]{background:var(--d5-accent);border-color:var(--d5-accent);color:#fff}',
613
- '.dsh-upd[data-theme="d5-paper"] button[data-primary="1"]:hover:not(:disabled){background:var(--d5-accent-deep);color:#fff}',
614
- '.dsh-upd[data-theme="d5-paper"] button:focus-visible{outline:2px solid var(--d5-accent);outline-offset:2px}',
615
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner{background:var(--d5-bg);border-color:var(--d5-line-strong)}',
616
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="update"]{border-color:var(--d5-ok);background:var(--d5-ok-bg)}',
617
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="busy"]{border-color:var(--d5-warn);background:var(--d5-warn-bg)}',
618
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="restart"]{border-color:var(--d5-warn);background:var(--d5-warn-bg);font-family:var(--d5-serif);border-width:2px}',
619
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="failed"],.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="blocked"]{border-color:var(--d5-bad);background:var(--d5-bad-bg)}',
620
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="done"]{border-color:var(--d5-ok);background:var(--d5-ok-bg)}',
621
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-yanked{border-color:var(--d5-warn);background:var(--d5-warn-bg);color:var(--d5-ink)}',
622
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-breaking-badge{color:var(--d5-accent);border-color:var(--d5-accent)}',
623
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-count{color:var(--d5-muted)}',
624
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-more-note{color:var(--d5-muted)}',
626
+ '.dsh-upd[data-theme="archive"] button{border-color:var(--d5-line-strong);background:var(--d5-card);color:var(--d5-ink);border-radius:3px;font-family:var(--d5-sans)}',
627
+ '.dsh-upd[data-theme="archive"] button:hover:not(:disabled){border-color:var(--d5-accent);color:var(--d5-accent)}',
628
+ '.dsh-upd[data-theme="archive"] button[data-primary="1"]{background:var(--d5-accent);border-color:var(--d5-accent);color:#fff}',
629
+ '.dsh-upd[data-theme="archive"] button[data-primary="1"]:hover:not(:disabled){background:var(--d5-accent-deep);color:#fff}',
630
+ '.dsh-upd[data-theme="archive"] button:focus-visible{outline:2px solid var(--d5-accent);outline-offset:2px}',
631
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner{background:var(--d5-bg);border-color:var(--d5-line-strong)}',
632
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="update"]{border-color:var(--d5-ok);background:var(--d5-ok-bg)}',
633
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="busy"]{border-color:var(--d5-warn);background:var(--d5-warn-bg)}',
634
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]{border-color:var(--d5-warn);background:var(--d5-warn-bg);font-family:var(--d5-serif);border-width:2px}',
635
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="failed"],.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="blocked"]{border-color:var(--d5-bad);background:var(--d5-bad-bg)}',
636
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="done"]{border-color:var(--d5-ok);background:var(--d5-ok-bg)}',
637
+ '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-yanked{border-color:var(--d5-warn);background:var(--d5-warn-bg);color:var(--d5-ink)}',
638
+ '.dsh-upd[data-theme="archive"] .dsh-upd-breaking-badge{color:var(--d5-accent);border-color:var(--d5-accent)}',
639
+ '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-count{color:var(--d5-muted)}',
640
+ '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-more-note{color:var(--d5-muted)}',
625
641
  // —— 大印章(原型 :206 `.seal`:右上 88px、旋转 -7°、双细框;内容与色调来自根属性,不加节点)——
626
- '.dsh-upd[data-theme="d5-paper"]{position:relative;padding:22px 26px 20px}',
627
- '.dsh-upd[data-theme="d5-paper"]::before{content:attr(data-seal);position:absolute;top:20px;right:24px;width:88px;height:88px;',
642
+ '.dsh-upd[data-theme="archive"]{position:relative;padding:16px 20px 14px}',
643
+ '.dsh-upd[data-theme="archive"]::before{content:attr(data-seal);position:absolute;top:20px;right:24px;width:88px;height:88px;',
628
644
  "display:flex;align-items:center;justify-content:center;text-align:center;letter-spacing:.18em;text-indent:.18em;line-height:1.35;",
629
645
  "border:3px solid currentColor;border-radius:14px;transform:rotate(-7deg);font-family:var(--d5-serif);font-weight:700;font-size:21px;",
630
646
  "background:color-mix(in srgb,currentColor 8%,transparent);user-select:none;pointer-events:none;",
631
647
  "box-shadow:inset 0 0 0 5px var(--d5-card),inset 0 0 0 6px currentColor,0 2px 6px rgba(0,0,0,.12)}",
632
- '.dsh-upd[data-theme="d5-paper"][data-seal-tone="ink"]::before{color:var(--d5-muted)}',
633
- '.dsh-upd[data-theme="d5-paper"][data-seal-tone="green"]::before{color:var(--d5-ok)}',
634
- '.dsh-upd[data-theme="d5-paper"][data-seal-tone="yellow"]::before{color:var(--d5-warn)}',
635
- '.dsh-upd[data-theme="d5-paper"][data-seal-tone="red"]::before{color:var(--d5-bad)}',
648
+ '.dsh-upd[data-theme="archive"][data-seal-tone="ink"]::before{color:var(--d5-muted)}',
649
+ '.dsh-upd[data-theme="archive"][data-seal-tone="green"]::before{color:var(--d5-ok)}',
650
+ '.dsh-upd[data-theme="archive"][data-seal-tone="yellow"]::before{color:var(--d5-warn)}',
651
+ '.dsh-upd[data-theme="archive"][data-seal-tone="red"]::before{color:var(--d5-bad)}',
636
652
  // 印章占位:首行(横幅/状态行)右侧留出 120px,文字不许压到印章上(原型 .filehead padding-right:120px)
637
653
  // —— 卷宗抬头(原型 :195-203 的刊头,主题切换按钮按用户口径去掉):只有 D5 档案卷才显示 ——
638
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-masthead{display:block;padding:0 0 10px;margin:0 0 12px;border-bottom:1px solid var(--d5-line)}',
639
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-masthead-kicker{display:block;font-size:11px;letter-spacing:.35em;color:var(--d5-muted);margin-bottom:3px}',
640
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-masthead-title{font-family:var(--d5-serif);font-size:26px;font-weight:700;line-height:1.2}',
641
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-masthead-title i{color:var(--d5-accent);font-style:normal}',
654
+ '.dsh-upd[data-theme="archive"] .dsh-upd-masthead{display:block;padding:0 0 8px;margin:0 0 8px;border-bottom:1px solid var(--d5-line)}',
655
+ '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-kicker{display:block;font-size:11px;letter-spacing:.35em;color:var(--d5-muted);margin-bottom:3px}',
656
+ '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title{font-family:var(--d5-serif);font-size:26px;font-weight:700;line-height:1.2}',
657
+ '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title i{color:var(--d5-accent);font-style:normal}',
642
658
  // 横幅不再给大印章留 124px:实测(headless 量盒子)印章盒底边 y=109,横幅正文顶边 y=108、
643
659
  // 状态行那句在 y=159——印章只压到横幅顶部的留白带,压不到正文。留着反而把 27px 那句话挤成两行
644
660
  // (27px 单行需 428px,留白后只剩 366px)。档案头那 120px 保留:那里是真的重叠。
645
661
  // —— 更新队列(03 章)D5 皮肤 ——
646
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-qrow{padding:8px 0}',
647
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-qk{width:66px;letter-spacing:.18em}',
648
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-qv{font-family:var(--d5-serif);font-size:16px}',
649
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-qn{font-family:var(--d5-mono);font-size:11.5px;color:var(--d5-muted)}',
650
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-qseq{color:var(--d5-muted)}',
651
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-chap-note{color:var(--d5-muted)}',
662
+ '.dsh-upd[data-theme="archive"] .dsh-upd-qrow{padding:6px 0}',
663
+ '.dsh-upd[data-theme="archive"] .dsh-upd-qk{width:66px;letter-spacing:.18em}',
664
+ '.dsh-upd[data-theme="archive"] .dsh-upd-qv{font-family:var(--d5-serif);font-size:16px}',
665
+ '.dsh-upd[data-theme="archive"] .dsh-upd-qn{font-family:var(--d5-mono);font-size:11.5px;color:var(--d5-muted)}',
666
+ '.dsh-upd[data-theme="archive"] .dsh-upd-qseq{color:var(--d5-muted)}',
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)}',
652
671
  // —— 小印章(原型 :215 `.sealmini`:30px、旋转 -5°、一字)——
653
672
  // 待重启横幅一律不画印章:那一档的标记是左侧手绘 SVG(原型 :446 的 .mark 只有 SVG)。
654
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="loading"]::before,.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="idle"]::before,',
655
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="update"]::before,.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="busy"]::before,',
656
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="blocked"]::before,.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="failed"]::before,',
657
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="done"]::before{content:attr(data-mini);display:inline-flex;align-items:center;justify-content:center;',
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,',
674
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="update"]::before,.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="busy"]::before,',
675
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="blocked"]::before,.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="failed"]::before,',
676
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="done"]::before{content:attr(data-mini);display:inline-flex;align-items:center;justify-content:center;',
658
677
  "width:30px;height:30px;margin-right:10px;vertical-align:middle;border:2px solid currentColor;border-radius:7px;",
659
678
  "font-family:var(--d5-serif);font-weight:700;font-size:16px;line-height:26px;transform:rotate(-5deg);flex:none;color:var(--d5-muted)}",
660
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="update"]::before{color:var(--d5-ok)}',
661
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="busy"]::before{color:var(--d5-warn)}',
662
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="blocked"]::before,.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="failed"]::before{color:var(--d5-bad)}',
663
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="done"]::before{color:var(--d5-ok)}',
664
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-log code{font-family:var(--d5-mono);font-size:11px;color:var(--d5-muted);border:1px solid var(--d5-line-strong);border-radius:3px;padding:0 6px;letter-spacing:.06em}',
665
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-manual code{display:block;background:var(--d5-ink);color:var(--d5-bg);font-family:var(--d5-mono);font-size:12.5px;padding:12px 14px;border-radius:4px;white-space:pre-wrap;word-break:break-all}',
679
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="update"]::before{color:var(--d5-ok)}',
680
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="busy"]::before{color:var(--d5-warn)}',
681
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="blocked"]::before,.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="failed"]::before{color:var(--d5-bad)}',
682
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="done"]::before{color:var(--d5-ok)}',
683
+ '.dsh-upd[data-theme="archive"] .dsh-upd-log code{font-family:var(--d5-mono);font-size:11px;color:var(--d5-muted);border:1px solid var(--d5-line-strong);border-radius:3px;padding:0 6px;letter-spacing:.06em}',
684
+ '.dsh-upd[data-theme="archive"] .dsh-upd-manual code{display:block;background:var(--d5-ink);color:var(--d5-bg);font-family:var(--d5-mono);font-size:12.5px;padding:12px 14px;border-radius:4px;white-space:pre-wrap;word-break:break-all}',
666
685
  // 待重启标记:手绘 SVG 当**独立 flex 标记**放在文字块左侧(原型 :446 `.mark` 是独立节点),
667
686
  // 不能用行内背景——那样换行时三角会落在句子中间把话劈开(现场回归)。
668
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="restart"]>div:first-child{display:flex;gap:10px;align-items:flex-start}',
669
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="restart"]>div:first-child::before{content:"";flex:none;width:20px;height:20px;margin-top:3px;',
687
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]>div:first-child{display:flex;gap:10px;align-items:flex-start}',
688
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]>div:first-child::before{content:"";flex:none;width:20px;height:20px;margin-top:3px;',
670
689
  'background:url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2720%27 height=%2720%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%238a5a00%27 stroke-width=%272%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z%27/%3E%3Cline x1=%2712%27 y1=%279%27 x2=%2712%27 y2=%2713%27/%3E%3Cline x1=%2712%27 y1=%2717%27 x2=%2712.01%27 y2=%2717%27/%3E%3C/svg%3E") no-repeat center/20px 20px}',
671
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner>div:first-child{overflow:hidden;text-overflow:ellipsis}',
672
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-actions{flex-wrap:wrap}',
690
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child{overflow:hidden;text-overflow:ellipsis}',
691
+ '.dsh-upd[data-theme="archive"] .dsh-upd-actions{flex-wrap:wrap}',
673
692
  // footer 只换肤(#47 定案 A + D5 约束:不换 DOM 顺序;复制诊断永不隐藏,本串不动它)。
674
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-footer{margin-top:26px;padding-top:16px;border-top:1px solid var(--d5-line)}',
675
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-foot-note{color:var(--d5-muted)}',
693
+ '.dsh-upd[data-theme="archive"] .dsh-upd-footer{margin-top:14px;padding-top:10px;border-top:1px solid var(--d5-line)}',
694
+ '.dsh-upd[data-theme="archive"] .dsh-upd-foot-note{color:var(--d5-muted)}',
676
695
  // —— 档案头(原型 :208-211 `.filehead`:serif 插件名 22px + 使用范围 + profile 牌;右侧留章位)——
677
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-head{display:flex;gap:16px;align-items:baseline;flex-wrap:wrap;padding-right:120px}',
678
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-name{font-family:var(--d5-serif);font-size:22px;font-weight:700}',
679
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-meta{width:100%;font-size:12.5px;color:var(--d5-muted)}',
680
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-meta b{color:var(--d5-ink);font-weight:600}',
681
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-proftag{font-family:var(--d5-mono);font-size:11px;color:var(--d5-muted);border:1px solid var(--d5-line-strong);border-radius:3px;padding:0 6px;margin-left:8px;letter-spacing:.06em}',
696
+ '.dsh-upd[data-theme="archive"] .dsh-upd-head{display:flex;gap:16px;align-items:baseline;flex-wrap:wrap;padding-right:120px}',
697
+ '.dsh-upd[data-theme="archive"] .dsh-upd-name{font-family:var(--d5-serif);font-size:22px;font-weight:700}',
698
+ '.dsh-upd[data-theme="archive"] .dsh-upd-meta{width:100%;font-size:12.5px;color:var(--d5-muted)}',
699
+ '.dsh-upd[data-theme="archive"] .dsh-upd-meta b{color:var(--d5-ink);font-weight:600}',
700
+ '.dsh-upd[data-theme="archive"] .dsh-upd-proftag{font-family:var(--d5-mono);font-size:11px;color:var(--d5-muted);border:1px solid var(--d5-line-strong);border-radius:3px;padding:0 6px;margin-left:8px;letter-spacing:.06em}',
682
701
  // —— 版本条(原型 :216 `.strip`:三格,格间一线,左上小写标签 + 等宽值)——
683
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-strip{display:flex;flex-wrap:wrap;margin:10px 0 0;border:1px solid var(--d5-line);border-radius:4px;overflow:hidden;font-size:12.5px}',
684
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-strip>div{flex:1 1 120px;padding:8px 12px;border-left:1px solid var(--d5-line)}',
685
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-strip>div:first-child{border-left:0}',
686
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-strip-k{display:block;font-size:11px;letter-spacing:.2em;color:var(--d5-muted)}',
687
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-strip-v{font-family:var(--d5-mono);font-size:13px}',
702
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip{display:flex;flex-wrap:wrap;margin:8px 0 0;border:1px solid var(--d5-line);border-radius:4px;overflow:hidden;font-size:12.5px}',
703
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip>div{flex:1 1 120px;padding:6px 10px;border-left:1px solid var(--d5-line)}',
704
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip>div:first-child{border-left:0}',
705
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip-k{display:block;font-size:11px;letter-spacing:.2em;color:var(--d5-muted)}',
706
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip-v{font-family:var(--d5-mono);font-size:13px}',
688
707
  // —— 章节(原型 :218-245:01–05 编号 + 衬线标题 + 细线)——
689
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-chapter{margin-top:26px;padding-top:16px;border-top:1px solid var(--d5-line)}',
690
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-chap-head{display:flex;align-items:baseline;gap:12px;margin-bottom:10px}',
691
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-chap-no{font-family:var(--d5-serif);font-style:italic;font-size:15px;color:var(--d5-muted)}',
692
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-chap-title{font-family:var(--d5-serif);font-size:17px;margin:0;letter-spacing:.1em}',
693
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-chap-rule{flex:1;border-top:1px solid var(--d5-line);transform:translateY(-4px)}',
708
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chapter{margin-top:16px;padding-top:10px;border-top:1px solid var(--d5-line)}',
709
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chap-head{display:flex;align-items:baseline;gap:12px;margin-bottom:6px}',
710
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chap-no{font-family:var(--d5-serif);font-style:italic;font-size:15px;color:var(--d5-muted)}',
711
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chap-title{font-family:var(--d5-serif);font-size:17px;margin:0;letter-spacing:.1em}',
712
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chap-rule{flex:1;border-top:1px solid var(--d5-line);transform:translateY(-4px)}',
694
713
  // —— 横幅即状态行 / 待重启横幅(原型 :81-85 `.restart-banner`:2px 边框、圆角 4、内边距 12/16、衬线;右侧留章位)——
695
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner{border:2px solid var(--d5-line-strong);border-radius:4px;padding:12px 16px;font-size:14.5px;font-family:var(--d5-serif);display:flex;gap:10px;align-items:center;flex-wrap:wrap}',
696
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner>div:first-child{flex:1 1 auto;min-width:0}',
714
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner{border:2px solid var(--d5-line-strong);border-radius:4px;padding:10px 12px;font-size:14.5px;font-family:var(--d5-serif);display:flex;gap:10px;align-items:center;flex-wrap:wrap}',
715
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child{flex:1 1 auto;min-width:0}',
697
716
  // 状态行字号照原型 .status-line=27px(实测去掉横幅右侧占位后可写 486px > 428px,一行放得下)
698
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner>div:first-child strong{font-family:var(--d5-serif);font-size:27px;font-weight:700;line-height:1.35}',
717
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child strong{font-family:var(--d5-serif);font-size:27px;font-weight:700;line-height:1.25}',
699
718
  // —— 进度条 / 跳过行(原型 :132-133 `.prog`、:129-131 `.skipline .tag`)——
700
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-prog{height:8px;background:var(--d5-line);border-radius:4px;overflow:hidden;margin:10px 0 4px}',
701
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-prog-bar{display:block;height:100%;background:var(--d5-accent);transition:width .3s}',
702
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-progtxt{font-size:12.5px;color:var(--d5-muted)}',
703
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-skipline{font-size:13px;margin-top:8px}',
704
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-tag{display:inline-block;border:1px dashed var(--d5-line-strong);border-radius:3px;padding:1px 8px;margin-right:8px;font-family:var(--d5-mono);font-size:12px}',
705
- '.dsh-upd[data-theme="d5-paper"] .dsh-upd-err{font-size:13px;margin:0 0 6px;color:var(--d5-muted)}',
706
- '@media (max-width:640px){.dsh-upd[data-theme="d5-paper"]{padding:10px 12px}.dsh-upd[data-theme="d5-paper"]::before{top:12px;right:12px;width:56px;height:56px;font-size:15px;box-shadow:inset 0 0 0 4px var(--d5-card),inset 0 0 0 5px currentColor}.dsh-upd[data-theme="d5-paper"] .dsh-upd-masthead-title{font-size:19px}.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner{padding-right:16px}.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner::before{width:24px;height:24px;font-size:14px;line-height:20px;flex:none}.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner>div:first-child{white-space:normal}}',
707
- '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="d5-paper"]{--d5-bg:#141210;--d5-card:#1e1a15;--d5-ink:#ece5d3;--d5-muted:#a89c83;',
719
+ '.dsh-upd[data-theme="archive"] .dsh-upd-prog{height:8px;background:var(--d5-line);border-radius:4px;overflow:hidden;margin:10px 0 4px}',
720
+ '.dsh-upd[data-theme="archive"] .dsh-upd-prog-bar{display:block;height:100%;background:var(--d5-accent);transition:width .3s}',
721
+ '.dsh-upd[data-theme="archive"] .dsh-upd-progtxt{font-size:12.5px;color:var(--d5-muted)}',
722
+ '.dsh-upd[data-theme="archive"] .dsh-upd-skipline{font-size:13px;margin-top:8px}',
723
+ '.dsh-upd[data-theme="archive"] .dsh-upd-tag{display:inline-block;border:1px dashed var(--d5-line-strong);border-radius:3px;padding:1px 8px;margin-right:8px;font-family:var(--d5-mono);font-size:12px}',
724
+ '.dsh-upd[data-theme="archive"] .dsh-upd-err{font-size:13px;margin:0 0 6px;color:var(--d5-muted)}',
725
+ '@media (max-width:640px){.dsh-upd[data-theme="archive"]{padding:10px 12px}.dsh-upd[data-theme="archive"]::before{top:12px;right:12px;width:56px;height:56px;font-size:15px;box-shadow:inset 0 0 0 4px var(--d5-card),inset 0 0 0 5px currentColor}.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title{font-size:19px}.dsh-upd[data-theme="archive"] .dsh-upd-banner{padding-right:16px}.dsh-upd[data-theme="archive"] .dsh-upd-banner::before{width:24px;height:24px;font-size:14px;line-height:20px;flex:none}.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child{white-space:normal}}',
726
+ '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"]{--d5-bg:#141210;--d5-card:#1e1a15;--d5-ink:#ece5d3;--d5-muted:#a89c83;',
708
727
  "--d5-line:#3a3226;--d5-line-strong:#5c4e3b;--d5-accent:#e0684e;--d5-accent-deep:#f0866b;",
709
728
  "--d5-ok:#8fd6a4;--d5-ok-bg:rgba(80,180,120,.12);--d5-warn:#e8c15a;--d5-warn-bg:rgba(232,193,90,.12);",
710
729
  "--d5-bad:#ef8a7d;--d5-bad-bg:rgba(239,138,125,.12);--d5-shadow:0 1px 2px rgba(0,0,0,.4),0 12px 32px rgba(0,0,0,.45)}}",
711
- '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="d5-paper"] button[data-primary="1"]{color:#141210}.dsh-upd[data-theme="d5-paper"] button:focus-visible{outline-color:var(--d5-accent-deep)}}',
712
- '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="restart"]>div:first-child::before{background-image:url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2720%27 height=%2720%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%23e8c15a%27 stroke-width=%272%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z%27/%3E%3Cline x1=%2712%27 y1=%279%27 x2=%2712%27 y2=%2713%27/%3E%3Cline x1=%2712%27 y1=%2717%27 x2=%2712.01%27 y2=%2717%27/%3E%3C/svg%3E")}}',
713
- '@media (forced-colors: active){.dsh-upd[data-theme="d5-paper"]{box-shadow:none}.dsh-upd[data-theme="d5-paper"]::before{background:none;box-shadow:none;border-color:CanvasText;color:CanvasText}.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner{border:1px solid CanvasText}.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner::before{border-color:CanvasText;color:CanvasText;background:Canvas}.dsh-upd[data-theme="d5-paper"] button{border:1px solid ButtonText}.dsh-upd[data-theme="d5-paper"] button[data-primary="1"]{background:ButtonFace;color:ButtonText;border-color:ButtonText}.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="restart"]>div:first-child::before{background-image:none;content:"\u26A0"}}',
714
- '@media (prefers-reduced-motion: reduce){.dsh-upd[data-theme="d5-paper"] *{transition:none !important;animation:none !important}}'
730
+ '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] button[data-primary="1"]{color:#141210}.dsh-upd[data-theme="archive"] button:focus-visible{outline-color:var(--d5-accent-deep)}}',
731
+ '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]>div:first-child::before{background-image:url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2720%27 height=%2720%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%23e8c15a%27 stroke-width=%272%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z%27/%3E%3Cline x1=%2712%27 y1=%279%27 x2=%2712%27 y2=%2713%27/%3E%3Cline x1=%2712%27 y1=%2717%27 x2=%2712.01%27 y2=%2717%27/%3E%3C/svg%3E")}}',
732
+ '@media (forced-colors: active){.dsh-upd[data-theme="archive"]{box-shadow:none}.dsh-upd[data-theme="archive"]::before{background:none;box-shadow:none;border-color:CanvasText;color:CanvasText}.dsh-upd[data-theme="archive"] .dsh-upd-banner{border:1px solid CanvasText}.dsh-upd[data-theme="archive"] .dsh-upd-banner::before{border-color:CanvasText;color:CanvasText;background:Canvas}.dsh-upd[data-theme="archive"] button{border:1px solid ButtonText}.dsh-upd[data-theme="archive"] button[data-primary="1"]{background:ButtonFace;color:ButtonText;border-color:ButtonText}.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]>div:first-child::before{background-image:none;content:"\u26A0"}}',
733
+ '@media (prefers-reduced-motion: reduce){.dsh-upd[data-theme="archive"] *{transition:none !important;animation:none !important}}'
715
734
  ].join("\n");
716
735
  function escapeHtml(text) {
717
736
  return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
718
737
  }
719
738
  const CHAPTER_TITLES = ["\u68C0\u67E5\u4E0E\u5B89\u88C5", "\u66F4\u65B0\u65E5\u5FD7", "\u66F4\u65B0\u961F\u5217", "\u9519\u8BEF\u4FE1\u606F", "\u624B\u5DE5\u547D\u4EE4"];
739
+ function formatLatchTime(atMs) {
740
+ if (typeof atMs !== "number" || !Number.isFinite(atMs) || atMs <= 0) return null;
741
+ try {
742
+ const s = new Date(atMs).toLocaleTimeString("zh-CN", { hour12: false });
743
+ return s ? s : null;
744
+ } catch {
745
+ return null;
746
+ }
747
+ }
720
748
  function chapterOf(index, inner, note = "") {
721
749
  const no = String(index).padStart(2, "0");
722
750
  const title = CHAPTER_TITLES[index - 1];
@@ -781,6 +809,9 @@ function renderUpdatePanelKernel(input, view) {
781
809
  if (b.kind === "restart") {
782
810
  actions.push(`<button type="button" data-action="restart-hint" data-primary="1" title="\u5BBF\u4E3B\u6CA1\u6709\u81EA\u91CD\u542F\u7535\u8BDD\uFF1A\u8BF7\u624B\u52A8\u91CD\u542F\u5BBF\u4E3B">\u91CD\u542F\u5BBF\u4E3B</button>`);
783
811
  }
812
+ if (b.kind === "failed") {
813
+ actions.push(`<button type="button" data-action="dismiss-failure" title="\u786E\u8BA4\u5DF2\u77E5\u6653\u8BE5\u5931\u8D25\uFF1A\u56DE\u5230\u53EF\u88C5\u9875\uFF0C\u4E0B\u6B21\u67E5/\u88C5\u5C06\u91CD\u65B0\u8BC4\u4F30">\u77E5\u9053\u4E86</button>`);
814
+ }
784
815
  if (snapshot) {
785
816
  actions.push(`<button type="button" data-action="copy-diag" title="\u590D\u5236\u5DF2\u8131\u654F\u8BCA\u65AD\uFF0C\u76F4\u63A5\u7C98\u7ED9\u63D2\u4EF6\u4F5C\u8005">\u590D\u5236\u8BCA\u65AD</button>`);
786
817
  }
@@ -792,9 +823,11 @@ function renderUpdatePanelKernel(input, view) {
792
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>`
793
824
  );
794
825
  }
795
- parts.push(chapterOf(1, actions.join("")));
826
+ const chapters = [];
827
+ chapters.push(chapterOf(1, actions.join("")));
796
828
  {
797
829
  let inner = "";
830
+ let hasLog = false;
798
831
  if (snapshot && snapshot.latestVersion) {
799
832
  try {
800
833
  const mdText = typeof changelogMarkdown === "string" ? changelogMarkdown : "";
@@ -824,9 +857,11 @@ function renderUpdatePanelKernel(input, view) {
824
857
  } catch {
825
858
  yankedBanner = "";
826
859
  }
827
- 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}
828
862
  ${changelogHTML}
829
- </div>`;
863
+ </div></div></div>`;
864
+ hasLog = Array.isArray(ranged) && ranged.length > 0;
830
865
  } catch {
831
866
  inner = "";
832
867
  }
@@ -836,7 +871,9 @@ ${changelogHTML}
836
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"
837
872
  )}</div></div>`;
838
873
  }
839
- 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));
840
877
  }
841
878
  {
842
879
  const queued = !!queue && (queue.busy || queue.waiting.length > 0);
@@ -863,9 +900,9 @@ ${changelogHTML}
863
900
  );
864
901
  }
865
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>` : "";
866
- 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));
867
904
  } else {
868
- parts.push(
905
+ chapters.push(
869
906
  chapterOf(
870
907
  3,
871
908
  `<div class="dsh-upd-queue"><div class="dsh-upd-changelog-neutral">${escapeHtml(
@@ -877,26 +914,58 @@ ${changelogHTML}
877
914
  }
878
915
  {
879
916
  const errLines = [];
880
- if (b.kind === "failed" || b.kind === "blocked") {
917
+ const failedNow = b.kind === "failed" || b.kind === "blocked";
918
+ const failRef = input.failure;
919
+ if (failedNow) {
920
+ const shownCode = b.kind === "blocked" ? snapshot?.blockedReason ?? b.kind : typeof input.lastError === "string" && input.lastError.trim() ? input.lastError : "install-failed";
881
921
  errLines.push(
882
- `<div class="dsh-upd-err">\u7A33\u5B9A\u7801 <code>${escapeHtml(String(b.kind === "blocked" ? snapshot?.blockedReason ?? b.kind : "install-failed"))}</code>\uFF1A\u4E0A\u4E00\u6761\u4E2D\u6587\u8BF4\u660E\u5C31\u662F\u8981\u7528\u6237\u505A\u7684\u4E8B\uFF1B\u8981\u5F80\u4E0A\u6E38\u62A5\uFF0C\u7528\u300C\u590D\u5236\u8BCA\u65AD\u300D\u6574\u6BB5\u7C98\uFF08\u5DF2\u8131\u654F\uFF09\u3002</div>`
922
+ `<div class="dsh-upd-err">\u7A33\u5B9A\u7801 <code>${escapeHtml(String(shownCode))}</code>\uFF1A\u4E0A\u4E00\u6761\u4E2D\u6587\u8BF4\u660E\u5C31\u662F\u8981\u7528\u6237\u505A\u7684\u4E8B\uFF1B\u8981\u5F80\u4E0A\u6E38\u62A5\uFF0C\u7528\u300C\u590D\u5236\u8BCA\u65AD\u300D\u6574\u6BB5\u7C98\uFF08\u5DF2\u8131\u654F\uFF09\u3002</div>`
883
923
  );
924
+ if (b.kind === "failed" && failRef) {
925
+ const keys = [];
926
+ if (typeof failRef.requestId === "string" && failRef.requestId) keys.push(`\u8BF7\u6C42 <code>${escapeHtml(failRef.requestId)}</code>`);
927
+ if (typeof failRef.checkId === "string" && failRef.checkId) keys.push(`\u68C0\u67E5 <code>${escapeHtml(failRef.checkId)}</code>`);
928
+ const at = formatLatchTime(failRef.atMs);
929
+ if (at) keys.push(`\u5931\u8D25\u4E8E ${escapeHtml(at)}`);
930
+ if (keys.length > 0) {
931
+ errLines.push(`<div class="dsh-upd-err">\u672C\u6B21\u67E5\u8BE2\u952E\uFF1A${keys.join(" \xB7 ")}\uFF08\u62FF\u7740\u5B83\u4EEC\u53BB\u65E5\u5FD7\u91CC\u5BF9\uFF09\u3002</div>`);
932
+ }
933
+ if (!failRef.volatile) {
934
+ errLines.push(
935
+ `<div class="dsh-upd-err">\u8BC1\u636E\u5DF2\u51BB\u7ED3\uFF1A\u590D\u5236\u8BCA\u65AD\u91CC\u7684\u7801\u3001\u7248\u672C\u3001\u7F16\u53F7\u90FD\u53D6\u81EA\u5931\u8D25\u65F6\u523B\uFF0C\u4E0D\u968F\u8F6E\u8BE2\u5237\u65B0\uFF1B\u4E0B\u4E00\u6B21\u67E5\u65B0\u7248\u6216\u5B89\u88C5\u4F1A\u66F4\u65B0\u5B83\u3002</div>`
936
+ );
937
+ } else {
938
+ errLines.push(
939
+ `<div class="dsh-upd-err">\u8BFB\u6570\u77AC\u6001\u5931\u8D25\uFF1A\u4E0B\u4E00\u6B21\u6210\u529F\u8BFB\u6570\u4F1A\u81EA\u52A8\u89E3\u9664\uFF1B\u4E00\u76F4\u51FA\u73B0\u518D\u6309\u7A33\u5B9A\u7801\u6392\u67E5\u3002</div>`
940
+ );
941
+ }
942
+ }
943
+ } else {
944
+ errLines.push(`<div class="dsh-upd-changelog-neutral">\u6682\u65E0\u5931\u8D25\uFF1A\u6B64\u65F6\u590D\u5236\u8BCA\u65AD\u7ED9\u51FA\u7684\u662F\u5F53\u524D\u72B6\u6001\u5FEB\u7167\u3002</div>`);
945
+ }
946
+ if (input.showLogHint !== false) {
947
+ errLines.push(
948
+ `<div class="dsh-upd-log">\u6DF1\u6316\u770B\u65E5\u5FD7\uFF1A\u6309\u63D2\u4EF6\u6807\u8BC6 <code>${escapeHtml(pluginId)}</code> \u8FC7\u6EE4 <code>${LOG_EVENT_CALL}</code>\u3001<code>${LOG_EVENT_CALL_FAIL}</code>\u3001<code>${LOG_EVENT_INSTALL_EXEC}</code> \u4E09\u4E2A\u4E8B\u4EF6\u3002</div>`
949
+ );
950
+ if (b.kind === "failed" && failRef && (failRef.requestId || failRef.checkId)) {
951
+ errLines.push(
952
+ `<div class="dsh-upd-log">\u51ED\u4E0A\u9762\u7684\u8BF7\u6C42\uFF0F\u68C0\u67E5\u7F16\u53F7\u5728 <code>${LOG_EVENT_CALL_FAIL}</code> \u91CC\u5BF9\u4E0A\uFF1B\u57FA\u7EBF\u8017\u65F6\u770B <code>${LOG_EVENT_CALL}</code>\uFF0C\u6267\u884C\u7ED3\u679C\u770B <code>${LOG_EVENT_INSTALL_EXEC}</code>\u3002</div>`
953
+ );
954
+ }
884
955
  }
885
- errLines.push(
886
- `<div class="dsh-upd-log">\u6DF1\u6316\u770B\u65E5\u5FD7\uFF1A\u6309\u63D2\u4EF6\u6807\u8BC6 <code>${escapeHtml(pluginId)}</code> \u8FC7\u6EE4 <code>host.call</code>\u3001<code>host.call.fail</code>\u3001<code>update.install.exec</code> \u4E09\u4E2A\u4E8B\u4EF6\u3002</div>`
887
- );
888
956
  if (copyNotice) errLines.push(`<div class="dsh-upd-copy" role="status">${escapeHtml(copyNotice)}</div>`);
889
- parts.push(chapterOf(4, errLines.join("")));
957
+ chapters.push(chapterOf(4, errLines.join("")));
890
958
  }
891
- parts.push(
959
+ chapters.push(
892
960
  chapterOf(
893
961
  5,
894
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>`
895
963
  )
896
964
  );
965
+ parts.push('<div class="dsh-upd-body">' + chapters.join("\n") + "</div>");
897
966
  if (showActions && mode === "dialog") {
898
967
  parts.push(
899
- `<div class="dsh-upd-footer"><span class="dsh-upd-foot-note">\u5B89\u88C5\u5728\u5BBF\u4E3B\u4FA7\u7EE7\u7EED\u8DD1\uFF0C\u91CD\u5F00\u6062\u590D\u663E\u793A</span><button type="button" data-action="close-view" title="\u5173\u95ED\u9762\u677F\uFF08\u5B89\u88C5\u5728\u5BBF\u4E3B\u4FA7\u7EE7\u7EED\u8DD1\uFF0C\u53EF\u91CD\u5F00\u6062\u590D\u663E\u793A\uFF09">\u5173\u95ED</button></div>`
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>`
900
969
  );
901
970
  }
902
971
  return parts.join("\n");
@@ -904,8 +973,8 @@ ${changelogHTML}
904
973
  function renderUpdatePanelHTML(input) {
905
974
  const view = panelViewModel(input);
906
975
  const kernel = renderUpdatePanelKernel(input, view);
907
- const d5 = normalizePanelTheme(input.theme) === "d5-paper";
908
- const attr = d5 ? ' data-theme="d5-paper"' : "";
976
+ const d5 = normalizePanelTheme(input.theme) === "archive";
977
+ const attr = d5 ? ' data-theme="archive"' : "";
909
978
  const sealAttr = ` data-seal="${escapeHtml(view.seal.text)}" data-seal-tone="${view.seal.tone}"`;
910
979
  const body = input.mode === "dialog" ? `<div class="dsh-upd-overlay" data-mode="dialog"><div class="dsh-upd" data-mode="dialog" data-plugin="${escapeHtml(input.pluginId)}"${sealAttr}${attr}>
911
980
  ${kernel}
@@ -917,6 +986,7 @@ ${UPDATE_PANEL_D5_CSS}` : UPDATE_PANEL_CSS;
917
986
  return `<style>${css}</style>
918
987
  ${body}`;
919
988
  }
989
+ const PANEL_FAILURE_LATCHES = /* @__PURE__ */ new Map();
920
990
  function isObject(value) {
921
991
  return !!value && typeof value === "object" && !Array.isArray(value);
922
992
  }
@@ -1011,6 +1081,7 @@ function mountUpdatePanel(container, options) {
1011
1081
  }
1012
1082
  let theme = normalizePanelTheme(options.theme ?? "default");
1013
1083
  let showOthers = options.showOthers === true;
1084
+ let changelogCollapsed = false;
1014
1085
  const call = options.call;
1015
1086
  const onRestartRequested = options.onRestartRequested;
1016
1087
  const onCloseRequested = typeof options.onCloseRequested === "function" ? options.onCloseRequested : null;
@@ -1019,6 +1090,7 @@ function mountUpdatePanel(container, options) {
1019
1090
  const skipStore = options.skipStore ?? createBrowserSkipStore(pluginId);
1020
1091
  const hostKind = typeof options.hostKind === "string" && options.hostKind ? options.hostKind : null;
1021
1092
  const profileNameOption = typeof options.profileName === "string" && options.profileName ? options.profileName : null;
1093
+ const showLogHintOption = options.showLogHint !== false;
1022
1094
  const diagCopyFormat = options.diagCopyFormat === "line" ? "line" : "block";
1023
1095
  let changelogMarkdown = typeof options.changelogMarkdown === "string" ? options.changelogMarkdown : null;
1024
1096
  let snapshot = null;
@@ -1026,9 +1098,171 @@ function mountUpdatePanel(container, options) {
1026
1098
  let queue = null;
1027
1099
  let receipt = null;
1028
1100
  let requestId = null;
1029
- let lastError = null;
1030
- let lastErrorKind = null;
1031
- let lastDiag = null;
1101
+ const latchKey = `${pluginId}${profileNameOption ?? ""}`;
1102
+ let latch = null;
1103
+ try {
1104
+ latch = PANEL_FAILURE_LATCHES.get(latchKey) ?? null;
1105
+ if (latch?.volatile) latch = null;
1106
+ } catch {
1107
+ latch = null;
1108
+ }
1109
+ function saveLatch() {
1110
+ try {
1111
+ if (latch && !latch.volatile) {
1112
+ if (!PANEL_FAILURE_LATCHES.has(latchKey) && PANEL_FAILURE_LATCHES.size >= 100) {
1113
+ const oldest = PANEL_FAILURE_LATCHES.keys().next();
1114
+ if (!oldest.done) PANEL_FAILURE_LATCHES.delete(oldest.value);
1115
+ }
1116
+ PANEL_FAILURE_LATCHES.set(latchKey, latch);
1117
+ } else PANEL_FAILURE_LATCHES.delete(latchKey);
1118
+ } catch {
1119
+ }
1120
+ }
1121
+ function latchNow() {
1122
+ try {
1123
+ const n = Date.now();
1124
+ return Number.isFinite(n) ? n : null;
1125
+ } catch {
1126
+ return null;
1127
+ }
1128
+ }
1129
+ function setLatch(next) {
1130
+ latch = next;
1131
+ saveLatch();
1132
+ }
1133
+ function clearLatch() {
1134
+ if (!latch) return;
1135
+ latch = null;
1136
+ saveLatch();
1137
+ }
1138
+ function currentEnvPair() {
1139
+ return { hostKind: hostKind ?? envHostKind, profileName: profileNameOption ?? envProfileName };
1140
+ }
1141
+ function latchFromReply(reply, source) {
1142
+ const env = currentEnvPair();
1143
+ return {
1144
+ code: failureCodeOf(
1145
+ { error: reply["error"], errorKind: reply["errorKind"] },
1146
+ source === "install" ? "install-failed" : "check-failed"
1147
+ ),
1148
+ kind: typeof reply["errorKind"] === "string" && reply["errorKind"].trim() ? reply["errorKind"].trim() : null,
1149
+ detail: null,
1150
+ diag: Object.prototype.hasOwnProperty.call(reply, "diag") ? reply["diag"] : null,
1151
+ requestId: source === "install" ? requestId : null,
1152
+ checkId: source === "install" ? receipt?.checkId ?? null : null,
1153
+ runningVersion: snapshot?.runningVersion ?? null,
1154
+ installedVersion: snapshot?.installedVersion ?? null,
1155
+ latestVersion: snapshot?.latestVersion ?? null,
1156
+ hostKind: env.hostKind,
1157
+ profileName: env.profileName,
1158
+ source,
1159
+ volatile: false,
1160
+ jobId: null,
1161
+ atMs: latchNow()
1162
+ };
1163
+ }
1164
+ function latchFromJob(job, snap) {
1165
+ const env = currentEnvPair();
1166
+ const message = typeof job.message === "string" ? job.message : null;
1167
+ const detail = message && message.includes(":") ? message.slice(message.indexOf(":") + 1).trim() || null : message;
1168
+ return {
1169
+ code: messageCodeOf(message) || "install-failed",
1170
+ kind: null,
1171
+ detail,
1172
+ // 快照里没有 diag:保留锁存里既有的(多半是致命安装回包自带的),没有即缺省说人话
1173
+ diag: latch?.diag ?? null,
1174
+ requestId: job.requestId ?? latch?.requestId ?? requestId,
1175
+ checkId: receipt?.checkId ?? latch?.checkId ?? null,
1176
+ runningVersion: snap?.runningVersion ?? snapshot?.runningVersion ?? null,
1177
+ installedVersion: snap?.installedVersion ?? snapshot?.installedVersion ?? null,
1178
+ latestVersion: snap?.latestVersion ?? snapshot?.latestVersion ?? null,
1179
+ hostKind: env.hostKind ?? latch?.hostKind ?? null,
1180
+ profileName: env.profileName ?? latch?.profileName ?? null,
1181
+ source: "install",
1182
+ volatile: false,
1183
+ jobId: typeof job.id === "string" ? job.id : null,
1184
+ atMs: latchNow()
1185
+ };
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
+ }
1219
+ function volatileLatch(source) {
1220
+ const env = currentEnvPair();
1221
+ return {
1222
+ code: "check-failed",
1223
+ kind: null,
1224
+ detail: null,
1225
+ diag: null,
1226
+ requestId,
1227
+ checkId: receipt?.checkId ?? null,
1228
+ runningVersion: snapshot?.runningVersion ?? null,
1229
+ installedVersion: snapshot?.installedVersion ?? null,
1230
+ latestVersion: snapshot?.latestVersion ?? null,
1231
+ hostKind: env.hostKind,
1232
+ profileName: env.profileName,
1233
+ source,
1234
+ volatile: true,
1235
+ jobId: null,
1236
+ atMs: latchNow()
1237
+ };
1238
+ }
1239
+ function backfillLatch(s) {
1240
+ if (!latch) return;
1241
+ let touched = false;
1242
+ if (latch.runningVersion === null && s.runningVersion) {
1243
+ latch.runningVersion = s.runningVersion;
1244
+ touched = true;
1245
+ }
1246
+ if (latch.installedVersion === null && s.installedVersion) {
1247
+ latch.installedVersion = s.installedVersion;
1248
+ touched = true;
1249
+ }
1250
+ if (latch.latestVersion === null && s.latestVersion) {
1251
+ latch.latestVersion = s.latestVersion;
1252
+ touched = true;
1253
+ }
1254
+ if (touched) saveLatch();
1255
+ }
1256
+ function tripleChanged(s) {
1257
+ if (!latch) return false;
1258
+ backfillLatch(s);
1259
+ const pairs = [
1260
+ [latch.runningVersion, s.runningVersion ?? null],
1261
+ [latch.installedVersion, s.installedVersion ?? null],
1262
+ [latch.latestVersion, s.latestVersion ?? null]
1263
+ ];
1264
+ return pairs.some(([a, b]) => a !== null && b !== null && a !== b);
1265
+ }
1032
1266
  let copyNotice = null;
1033
1267
  let noticeExpiresAt = 0;
1034
1268
  let busyAct = null;
@@ -1061,11 +1295,14 @@ function mountUpdatePanel(container, options) {
1061
1295
  queue,
1062
1296
  busyAct,
1063
1297
  skippedLatest,
1064
- lastError,
1065
- errorKind: lastErrorKind,
1298
+ lastError: latch?.code ?? null,
1299
+ errorKind: latch?.kind ?? null,
1300
+ failure: latch ? { requestId: latch.requestId, checkId: latch.checkId, atMs: latch.atMs, source: latch.source, volatile: latch.volatile } : null,
1301
+ showLogHint: showLogHintOption,
1066
1302
  changelogMarkdown,
1067
1303
  mode,
1068
1304
  showOthers,
1305
+ changelogCollapsed,
1069
1306
  pluginId,
1070
1307
  copyNotice,
1071
1308
  theme,
@@ -1078,7 +1315,7 @@ function mountUpdatePanel(container, options) {
1078
1315
  container.innerHTML = nextHTML;
1079
1316
  }
1080
1317
  }
1081
- function applyStatusReply(reply) {
1318
+ function applyStatusReply(reply, via) {
1082
1319
  if (!isObject(reply)) return;
1083
1320
  if (reply["ok"] === true) {
1084
1321
  const s = asSnapshot(reply["snapshot"]);
@@ -1096,13 +1333,29 @@ function mountUpdatePanel(container, options) {
1096
1333
  if (typeof pn === "string" && pn.trim()) envProfileName = pn.trim();
1097
1334
  if (typeof hk === "string" && hk.trim()) envHostKind = hk.trim();
1098
1335
  }
1099
- lastError = null;
1100
- lastErrorKind = null;
1101
- lastDiag = Object.prototype.hasOwnProperty.call(reply, "diag") ? reply["diag"] : null;
1336
+ const jobState = snapshot?.job?.state ?? null;
1337
+ if (jobState === "installing" || jobState === "verifying") {
1338
+ clearLatch();
1339
+ } else if (jobState === "completed" || jobState === "restart-required") {
1340
+ clearLatch();
1341
+ } else if ((jobState === "failed" || jobState === "interrupted") && snapshot?.job) {
1342
+ setLatch(latchFromJob(snapshot.job, snapshot));
1343
+ } else if (via === "install") {
1344
+ clearLatch();
1345
+ } else if (snapshot && tripleChanged(snapshot)) {
1346
+ clearLatch();
1347
+ } else if (via === "check" && latch?.source === "check") {
1348
+ clearLatch();
1349
+ } else if (latch?.volatile) {
1350
+ clearLatch();
1351
+ }
1102
1352
  } else {
1103
- lastError = typeof reply["error"] === "string" ? reply["error"] : "check-failed";
1104
- lastErrorKind = typeof reply["errorKind"] === "string" && reply["errorKind"].trim() ? reply["errorKind"].trim() : null;
1105
- lastDiag = Object.prototype.hasOwnProperty.call(reply, "diag") ? reply["diag"] : null;
1353
+ const errText = typeof reply["error"] === "string" ? reply["error"].trim() : "";
1354
+ const kindText = typeof reply["errorKind"] === "string" ? reply["errorKind"].trim() : "";
1355
+ if ((kindText || errText) === "update-busy") {
1356
+ } else {
1357
+ setLatch(latchFromReply(reply, via === "install" ? "install" : "check"));
1358
+ }
1106
1359
  try {
1107
1360
  const env = reply["env"];
1108
1361
  if (isObject(env)) {
@@ -1188,12 +1441,10 @@ function mountUpdatePanel(container, options) {
1188
1441
  try {
1189
1442
  const reply = await call(phoneNames.updateStatus, queueArgs());
1190
1443
  if (!mounted) return;
1191
- applyStatusReply(reply);
1444
+ applyStatusReply(reply, "refresh");
1192
1445
  } catch {
1193
1446
  if (!mounted) return;
1194
- lastError = "check-failed";
1195
- lastErrorKind = null;
1196
- lastDiag = null;
1447
+ setLatch(volatileLatch("check"));
1197
1448
  }
1198
1449
  render();
1199
1450
  maybeAutoChangelog();
@@ -1210,13 +1461,11 @@ function mountUpdatePanel(container, options) {
1210
1461
  try {
1211
1462
  const reply = await call(phoneNames.updateCheck, queueArgs());
1212
1463
  if (!mounted) return;
1213
- applyStatusReply(reply);
1464
+ applyStatusReply(reply, "check");
1214
1465
  changelogFailedAt.clear();
1215
- } catch {
1466
+ } catch (err) {
1216
1467
  if (!mounted) return;
1217
- lastError = "check-failed";
1218
- lastErrorKind = null;
1219
- lastDiag = null;
1468
+ setLatch(userThrowLatch("check", err));
1220
1469
  } finally {
1221
1470
  busyAct = null;
1222
1471
  if (copyNotice === "\u6B63\u5728\u67E5\u65B0\u7248\u2026") copyNotice = null;
@@ -1232,15 +1481,37 @@ function mountUpdatePanel(container, options) {
1232
1481
  render();
1233
1482
  try {
1234
1483
  if (!receipt) {
1235
- const checked = await call(phoneNames.updateCheck, queueArgs());
1236
- if (!mounted) return;
1237
- applyStatusReply(checked);
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
+ }
1238
1494
  }
1239
1495
  if (!mounted) return;
1240
1496
  if (!receipt) {
1241
- lastError = "check-expired";
1242
- lastErrorKind = "check-expired";
1243
- lastDiag = null;
1497
+ const expiredEnv = currentEnvPair();
1498
+ setLatch({
1499
+ code: "check-expired",
1500
+ kind: "check-expired",
1501
+ detail: null,
1502
+ diag: null,
1503
+ requestId,
1504
+ checkId: null,
1505
+ runningVersion: snapshot?.runningVersion ?? null,
1506
+ installedVersion: snapshot?.installedVersion ?? null,
1507
+ latestVersion: snapshot?.latestVersion ?? null,
1508
+ hostKind: expiredEnv.hostKind,
1509
+ profileName: expiredEnv.profileName,
1510
+ source: "install",
1511
+ volatile: false,
1512
+ jobId: null,
1513
+ atMs: latchNow()
1514
+ });
1244
1515
  render();
1245
1516
  return;
1246
1517
  }
@@ -1251,12 +1522,10 @@ function mountUpdatePanel(container, options) {
1251
1522
  ...queueArgs()
1252
1523
  });
1253
1524
  if (!mounted) return;
1254
- applyStatusReply(reply);
1255
- } catch {
1525
+ applyStatusReply(reply, "install");
1526
+ } catch (err) {
1256
1527
  if (!mounted) return;
1257
- lastError = "check-failed";
1258
- lastErrorKind = null;
1259
- lastDiag = null;
1528
+ setLatch(userThrowLatch("install", err));
1260
1529
  } finally {
1261
1530
  busyAct = null;
1262
1531
  if (copyNotice === "\u6B63\u5728\u5B89\u88C5\u2026") copyNotice = null;
@@ -1298,25 +1567,49 @@ function mountUpdatePanel(container, options) {
1298
1567
  }
1299
1568
  case "copy-diag": {
1300
1569
  const jobCode = snapshot?.job?.state === "failed" ? messageCodeOf(snapshot.job.message) || "install-failed" : "";
1301
- const code = failureCodeOf(
1302
- { error: lastError, errorKind: lastErrorKind },
1303
- jobCode || snapshot?.blockedReason || "check-failed"
1304
- );
1305
- const detail = snapshot?.job?.message && snapshot.job.message.includes(":") ? snapshot.job.message.slice(snapshot.job.message.indexOf(":") + 1) : snapshot?.job?.message;
1570
+ if (!latch && !jobCode && !snapshot?.blockedReason && snapshot) {
1571
+ const stateLine = snapshot.canInstall && snapshot.latestVersion ? `\u6709\u65B0\u7248 ${snapshot.latestVersion} \u53EF\u88C5\uFF08\u5F53\u524D ${snapshot.runningVersion}\uFF09` : "\u5DF2\u662F\u6700\u65B0\uFF0C\u65E0\u9700\u66F4\u65B0\u3002";
1572
+ const queueName = queueTextOf(queue?.position ?? null);
1573
+ const envBits = [
1574
+ `\u63D2\u4EF6=${pluginId}`,
1575
+ `\u5BBF\u4E3B=${hostKind ?? envHostKind ?? "\u672A\u77E5"}`,
1576
+ `\u4F7F\u7528\u8303\u56F4=${profileNameOption ?? envProfileName ?? "\u672A\u77E5"}`,
1577
+ `\u961F\u5217=${queueName}`
1578
+ ];
1579
+ if (requestId) envBits.push(`\u8BF7\u6C42=${requestId}`);
1580
+ if (receipt?.checkId) envBits.push(`\u68C0\u67E5=${receipt.checkId}`);
1581
+ const segs = [
1582
+ `[update-diag] \u5F53\u524D\u65E0\u5931\u8D25\uFF1A${stateLine}`,
1583
+ `\u7248\u672C\uFF1A\u8FD0\u884C ${snapshot.runningVersion}\uFF0F\u78C1\u76D8 ${snapshot.installedVersion ?? "\u672A\u77E5"}\uFF0F\u8FDC\u7AEF ${snapshot.latestVersion ?? "\u672A\u67E5\u8FC7"}`,
1584
+ `\u6765\u6E90\uFF1A${envBits.join(" \xB7 ")}`
1585
+ ].map((s) => redactForCopy(s));
1586
+ const text2 = diagCopyFormat === "line" ? segs.join(" \xB7 ") : segs.join("\n");
1587
+ try {
1588
+ await copyText(text2);
1589
+ sayCopy("\u5DF2\u590D\u5236\u5F53\u524D\u72B6\u6001\uFF08\u65E0\u5931\u8D25\uFF09\uFF0C\u76F4\u63A5\u7C98\u7ED9\u63D2\u4EF6\u4F5C\u8005\u5373\u53EF\uFF08\u5DF2\u8131\u654F\uFF09\u3002");
1590
+ } catch {
1591
+ sayCopy("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0A\u9762\u7684\u4FE1\u606F\u3002");
1592
+ }
1593
+ render();
1594
+ return;
1595
+ }
1596
+ const frozen = latch;
1597
+ const code = frozen?.code ?? failureCodeOf({ error: null, errorKind: null }, jobCode || snapshot?.blockedReason || "check-failed");
1598
+ const detail = frozen?.detail ?? (snapshot?.job?.message && snapshot.job.message.includes(":") ? snapshot.job.message.slice(snapshot.job.message.indexOf(":") + 1) : snapshot?.job?.message);
1306
1599
  const text = buildUpdateDiagCopy({
1307
1600
  pluginId,
1308
1601
  code,
1309
1602
  detail,
1310
- runningVersion: snapshot?.runningVersion ?? null,
1311
- installedVersion: snapshot?.installedVersion ?? null,
1312
- latestVersion: snapshot?.latestVersion ?? null,
1313
- hostKind: hostKind ?? envHostKind,
1314
- profileName: profileNameOption ?? envProfileName,
1603
+ runningVersion: frozen?.runningVersion ?? snapshot?.runningVersion ?? null,
1604
+ installedVersion: frozen?.installedVersion ?? snapshot?.installedVersion ?? null,
1605
+ latestVersion: frozen?.latestVersion ?? snapshot?.latestVersion ?? null,
1606
+ hostKind: frozen?.hostKind ?? hostKind ?? envHostKind,
1607
+ profileName: frozen?.profileName ?? profileNameOption ?? envProfileName,
1315
1608
  queuePosition: queue?.position ?? null,
1316
- requestId,
1317
- checkId: receipt?.checkId ?? null,
1609
+ requestId: frozen?.requestId ?? requestId,
1610
+ checkId: frozen?.checkId ?? receipt?.checkId ?? null,
1318
1611
  route: null,
1319
- diag: lastDiag,
1612
+ diag: frozen?.diag ?? null,
1320
1613
  manual,
1321
1614
  format: diagCopyFormat
1322
1615
  });
@@ -1329,11 +1622,22 @@ function mountUpdatePanel(container, options) {
1329
1622
  render();
1330
1623
  return;
1331
1624
  }
1625
+ case "dismiss-failure": {
1626
+ clearLatch();
1627
+ sayCopy("\u5DF2\u786E\u8BA4\u8BE5\u5931\u8D25\u63D0\u793A\uFF1B\u4E0B\u6B21\u67E5\u65B0\u7248\u6216\u5B89\u88C5\u5C06\u91CD\u65B0\u8BC4\u4F30\u3002");
1628
+ render();
1629
+ return;
1630
+ }
1332
1631
  case "toggle-queue": {
1333
1632
  showOthers = !showOthers;
1334
1633
  await refresh();
1335
1634
  return;
1336
1635
  }
1636
+ case "toggle-changelog": {
1637
+ changelogCollapsed = !changelogCollapsed;
1638
+ render();
1639
+ return;
1640
+ }
1337
1641
  // 「重启宿主」:宿主没有重启自己的电话,所以只做入口——
1338
1642
  // 调用方给了 onRestartRequested 就交给它;没给就如实说“请手动重启”,不假装。
1339
1643
  case "restart-hint": {
@@ -1374,6 +1678,10 @@ function mountUpdatePanel(container, options) {
1374
1678
  showOthers = show === true;
1375
1679
  await refresh();
1376
1680
  }
1681
+ async function setChangelogCollapsed(collapsed) {
1682
+ changelogCollapsed = collapsed === true;
1683
+ render();
1684
+ }
1377
1685
  function onClick(ev) {
1378
1686
  try {
1379
1687
  const t = ev;
@@ -1450,7 +1758,7 @@ function mountUpdatePanel(container, options) {
1450
1758
  } catch {
1451
1759
  }
1452
1760
  })();
1453
- return { refresh, act, setMode, setTheme, setShowOthers, setChangelogMarkdown, unmount };
1761
+ return { refresh, act, setMode, setTheme, setShowOthers, setChangelogCollapsed, setChangelogMarkdown, unmount };
1454
1762
  }
1455
1763
  import { buildPhoneNames as buildPhoneNames2 } from "./config.js";
1456
1764
  import { buildChangelogPhoneName as buildChangelogPhoneName2 } from "./config.js";
@@ -1514,6 +1822,7 @@ export {
1514
1822
  emptyQueueState,
1515
1823
  failureCodeOf,
1516
1824
  failureCopy,
1825
+ formatLatchTime,
1517
1826
  hasVisibleSections,
1518
1827
  isHeadOfQueue,
1519
1828
  isKnownFailureCode,