dsh-plugin-update 0.7.0 → 0.8.0

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,25 @@
2
2
 
3
3
  格式为 Keep-a-Changelog 子集:`Added/Fixed/Changed` 必写,`Deprecated/Removed/Security` 有则透传,`Unreleased` 面板忽略,禁止 git-log 直倒。面板缺日志时中性提示,不挡安装。
4
4
 
5
+ ## [0.8.0] - 2026-10-07
6
+
7
+ ### Added
8
+ - 入口件无新版摆法 `upToDateDisplay`(`note` / `button`(默认) / `tooltip`):默认按钮本身即`已是最新 X.Y.Z`(单按钮不占宽,可点再查,hover 回`检查更新`暗示动作;无新版走中性弱边、hover 才走主色,复用既有 token,不新增文案与 token);`tooltip` 按钮仍是`检查更新`、版本收进悬停;`note` 回右侧小字旧样子(显式传回)。入口 `label()` 与按钮正文同口径,`badge` 形态不受影响;README 2.5 节补自由度与参数说明,单测覆盖三摆法与非法校验。
9
+
10
+ ### Fixed
11
+ - 查新版瞬时抖动补强(面板):忙闲两帧同高(动作区按钮 nowrap + 最小宽预留、转圈槽常驻)+ 横幅/复制区最小高占位,查新版与轮询回包帧不再跳动。
12
+ - 入口件深色默认主题补 `text-muted`(`#9ca3af`),按钮直显弱边在深底可读。
13
+ - 批量 prefs-only 存偏好回归测试补齐(#74):偏好保存不闪空行、失败保留行(测试覆盖,行为零变更)。
14
+
15
+ ## [0.7.1] - 2026-10-06
16
+
17
+ ### Fixed
18
+ - 批量行三列对齐 + pending 显示最新版 + 新版红(#71):行主区改网格(名 5em/版 18ch/态 1fr,版 nowrap + tabular-nums,态换行同列起,失败跨整行);版本三段式只红新版(老版本弱化/箭头中性/专用红 token `newText`,深浅主题与档案皮肤及 forced-colors 回退,加粗 + 箭头 + 状态词为非颜色信号);版本会话优先、知识仅展示,无轮次无知识回还没查过(不再误 wait-turn),轮次中仍执行态;会话/安装语义未动。
19
+ - 默认弹窗 01~05 横滑消除 + 疏朗化(#72,A 方案落地)。
20
+
21
+ ### Added
22
+ - 主题变量新增 `newText`(`--dsh-update-new-text`,有新版版本号专用红,可覆盖,非法即抛)。
23
+
5
24
  ## [0.7.0] - 2026-10-06
6
25
 
7
26
  ### Added
package/README.md CHANGED
@@ -144,28 +144,28 @@ mountUpdatePanel(slot, { pluginId: 'my-notes-plugin', prefix: 'notes', call: hos
144
144
 
145
145
  可选档案卷主题(纸面浅色案卷风,不替换默认):挂载时加 `theme: 'archive'` 即换肤(右上大印章「待查/可装/安装中/待重启/受阻/已最新」+ 横幅小印章一字 + profile 牌 + 待重启衬线横幅配手绘 SVG 标 + 窄屏印章固定 + 省略号逐字折叠 + 浅深双主题跟随系统),内核 DOM 顺序不动、复制诊断常在;不传即最小可用默认深色样式。运行时用 `panel.setTheme('archive' | 'default')` 可切。入口件与批量面板是同一个 `theme` 参数,取值同一套。
146
146
 
147
- 主题变量覆盖(第三方换肤):四个 UI 口(单面板、批量面板、入口件、批量入口件)都收 `themeTokens`,键为语义名,一次传入即换肤,不传即零回归。入口件打开的 dialog 面板同步透传;运行时用 `setThemeTokens({...})` 即时换肤,传 `undefined` 即清掉覆盖:
148
-
149
- ```js
150
- mountUpdatePanel(el, { pluginId: 'p', prefix: 'notes', call,
151
- theme: 'archive',
152
- themeTokens: { primary: '#c8402a', bg: '#fffdf6', radiusButton: '3px' },
153
- })
154
- ```
155
-
156
- 键与样式表里的 `--dsh-update-*` 变量一一对应(括号里是变量名),手写 CSS 变量等价生效:
157
-
158
- | 组 | 键(变量) |
159
- |---|---|
160
- | 文字背景边线 | `text`、`textMuted`、`bg`、`bgSoft`、`border`、`borderStrong`、`buttonBg` |
161
- | 主色焦点 | `primary`、`primaryDeep`、`focus` |
162
- | 状态四组 | `okBg / okBorder / okText`、`warnBg / warnBorder / warnText`、`badBg / badBorder / badText`、`busyBg / busyBorder / busyText` |
163
- | 字体阴影 | `fontSans`、`fontSerif`、`fontMono`、`shadow` |
164
- | 圆角 | `radiusPanel`、`radiusButton`、`radiusBadge` |
165
- | 入口件尺寸 | `entryFontSize`、`entryPadding`、`entryBorderRadius`、`entryScale`(数字,大于 0) |
166
-
167
- 三条约束:颜色只收 hex(`#rgb`/`#rrggbb`/`#rrggbbaa`)或英文名单词(含 `transparent`),`rgb()`/`color-mix()` 请自行换算成 hex;其余值走注入检查(分号引号括号等不许过,见 `sizing` 同口径);未知键与非法值挂载即抛错,不静默吞掉。被覆盖的 token 不再跟随深色模式(内联值固定,调用方自己保证深浅可读)。
168
-
147
+ 主题变量覆盖(第三方换肤):四个 UI 口(单面板、批量面板、入口件、批量入口件)都收 `themeTokens`,键为语义名,一次传入即换肤,不传即零回归。入口件打开的 dialog 面板同步透传;运行时用 `setThemeTokens({...})` 即时换肤,传 `undefined` 即清掉覆盖:
148
+
149
+ ```js
150
+ mountUpdatePanel(el, { pluginId: 'p', prefix: 'notes', call,
151
+ theme: 'archive',
152
+ themeTokens: { primary: '#c8402a', bg: '#fffdf6', radiusButton: '3px' },
153
+ })
154
+ ```
155
+
156
+ 键与样式表里的 `--dsh-update-*` 变量一一对应(括号里是变量名),手写 CSS 变量等价生效:
157
+
158
+ | 组 | 键(变量) |
159
+ |---|---|
160
+ | 文字背景边线 | `text`、`textMuted`、`bg`、`bgSoft`、`border`、`borderStrong`、`buttonBg` |
161
+ | 主色焦点 | `primary`、`primaryDeep`、`focus` |
162
+ | 状态四组 | `okBg / okBorder / okText`、`warnBg / warnBorder / warnText`、`badBg / badBorder / badText`、`busyBg / busyBorder / busyText` |
163
+ | 字体阴影 | `fontSans`、`fontSerif`、`fontMono`、`shadow` |
164
+ | 圆角 | `radiusPanel`、`radiusButton`、`radiusBadge` |
165
+ | 入口件尺寸 | `entryFontSize`、`entryPadding`、`entryBorderRadius`、`entryScale`(数字,大于 0) |
166
+
167
+ 三条约束:颜色只收 hex(`#rgb`/`#rrggbb`/`#rrggbbaa`)或英文名单词(含 `transparent`),`rgb()`/`color-mix()` 请自行换算成 hex;其余值走注入检查(分号引号括号等不许过,见 `sizing` 同口径);未知键与非法值挂载即抛错,不静默吞掉。被覆盖的 token 不再跟随深色模式(内联值固定,调用方自己保证深浅可读)。
168
+
169
169
  类型定义随包分发(`dsh-plugin-update/panel` 的 `.d.ts`),不用自编译;面板离线可读,与包版本绑定。
170
170
 
171
171
  整组件 HTTP 版(无 `host.call` 环境即跑:浏览器面板经 HTTP POST 直达宿主网关,只传三样):
@@ -204,12 +204,13 @@ const entry = mountUpdateEntry(document.getElementById('upd-entry'), {
204
204
  | 摆什么 | `button` / `badge` / `inline` | `button` | 按钮;只给一个状态点;面板本体直接嵌进来 |
205
205
  | 什么时候查 | `mount` / `never` | `mount` | 进页面静默查一次(**只调 `.updateStatus`,只读**);`never` 则只在点击时查 |
206
206
  | 点了做什么 | `has-update` / `always` / `manual` / `direct` | `has-update` | 有新版才开面板;检查完总是开;交给 `onActivate` 自己跳;点开即弹窗、不预查 |
207
+ | 无新版摆哪 | `button` / `tooltip` / `note` | `button` | 版本即按钮本身;收进悬停;回右侧小字旧样子 |
207
208
 
208
- 按钮文案随状态自己变(按宿主语言单语渲染:中文界面纯中文、英文界面纯英文,一次只出现一种语言):`检查更新` / `有新版 1.1.0` / `正在安装…` / `待重启` / `更新失败,点此查看`(英文见集中字典同 key)。
209
+ 按钮文案随状态自己变(按宿主语言单语渲染:中文界面纯中文、英文界面纯英文,一次只出现一种语言):`检查更新`(还没查到)/ `已是最新 X.Y.Z`(无新版,默认即按钮本身)/ `有新版 1.1.0` / `正在安装…` / `待重启` / `更新失败,点此查看`(英文见集中字典同 key)。
209
210
 
210
211
  语言跟随(单语):入口件默认跟随 `document.documentElement.lang` > `navigator.languages` > 中文兜底;显式覆盖传 `locale: 'zh' | 'en' | { getActive(): string; subscribe?(cb): () => void }`(对象形态可接宿主语言服务,切换即时重绘,`unmount` 后停订;无 DOM 环境不抛,落中文)。
211
212
 
212
- 无新版时原地那句小字(`已是最新 X.Y.Z`)只在 `has-update` 下出现:不想看它就用 `openOn: 'always'`(检查完总是开弹窗,无新版在弹窗里看“已是最新”)或 `openOn: 'direct'`(点开即弹窗,连预查都省了,面板挂载即自查;徽标形态仍走回调口径)。
213
+ 无新版时版本号摆哪(`upToDateDisplay`,缺省 `'button'`):`button` 即按钮本身就是`已是最新 X.Y.Z`(单按钮不占宽,可点再查,hover 回`检查更新`暗示动作;无新版中性弱边,hover 才走主色);`tooltip` 按钮还是`检查更新`、版本收进悬停;`note` 回旧样子(按钮旁右侧小字,只在 `openOn: 'has-update'` 下点击确认无新版后出现)。旧版默认即 `note` 的样子,要回去就显式传 `upToDateDisplay: 'note'`。想连按钮都不留痕,就用 `openOn: 'always'`(检查完总是开弹窗,无新版在弹窗里看“已是最新”)或 `openOn: 'direct'`(点开即弹窗,连预查都省了,面板挂载即自查;徽标形态仍走回调口径)。
213
214
 
214
215
  按钮尺寸(跟头行其他控件同高/整体缩放):传 `sizing` 只改按钮本体,不碰面板;不传即默认外观。`scale` 是整体缩放(默认 `1`,如 `1.2` 放大、`0.9` 缩小),原来写在容器上的 `zoom` 可整体搬进来:
215
216
 
package/dist/entry.d.ts CHANGED
@@ -12,6 +12,8 @@ export type EntryAutoCheck = 'mount' | 'never';
12
12
  * `manual` 交给 onActivate;`direct` 点开即弹窗、不预查(面板挂载即自查;badge 仍走回调口径)。
13
13
  */
14
14
  export type EntryOpenOn = 'has-update' | 'always' | 'manual' | 'direct';
15
+ /** 无新版时版本号摆哪:`note` 右侧小字(旧默认);`button` 按钮本身即版本(默认);`tooltip` 收进悬停。 */
16
+ export type UpToDateDisplay = 'note' | 'button' | 'tooltip';
15
17
  /** 入口件主题(与面板同一套取值,切换即换肤)。 */
16
18
  export type EntryTheme = UpdatePanelTheme;
17
19
  /** 入口件的状态档:只用来算文案与 data-state,不另立状态机。 */
@@ -52,6 +54,8 @@ export interface UpdateEntryOptions {
52
54
  autoCheck?: EntryAutoCheck;
53
55
  /** 缺省 'has-update':有新版才开面板,没有就只在原地给一句提示。 */
54
56
  openOn?: EntryOpenOn;
57
+ /** 无新版时版本号摆哪:缺省 'button'(按钮本身即版本,单按钮不占宽);'note' 回旧样子(右侧小字);'tooltip' 收进悬停。 */
58
+ upToDateDisplay?: UpToDateDisplay;
55
59
  /** 覆盖默认按钮文案(不传就用状态联动文案)。 */
56
60
  label?: string;
57
61
  /** 按钮尺寸覆盖(#69):见 EntrySizing;不传即零回归,badge/inline 不受影响。 */
package/dist/entry.js CHANGED
@@ -8,6 +8,11 @@ import {
8
8
  } from "./panel.js";
9
9
  import { BILINGUAL_CSS, copyHTML, copyText } from "./bilingual.js";
10
10
  import { resolveLang, subscribeLang } from "./lang.js";
11
+ function upToDateVersionOf(snapshot, error) {
12
+ if (error || !snapshot || hasUpdateOf(snapshot)) return null;
13
+ const v = snapshot.runningVersion;
14
+ return typeof v === "string" && v.trim() ? v.trim() : null;
15
+ }
11
16
  function hasUpdateOf(snapshot) {
12
17
  if (!snapshot) return false;
13
18
  const latest = snapshot.latestVersion;
@@ -123,6 +128,9 @@ const UPDATE_ENTRY_CSS = [
123
128
  '.dsh-upd-entry[data-state="update"] .dsh-upd-entry-dot{background:var(--dsh-update-ok-border,#059669)}',
124
129
  '.dsh-upd-entry[data-state="busy"] .dsh-upd-entry-dot,.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-dot{background:var(--dsh-update-warn-border,#d97706)}',
125
130
  '.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-dot{background:var(--dsh-update-bad-border,#dc2626)}',
131
+ // C 直显(upToDateDisplay='button'):无新版时按钮本身即版本,走中性弱边(不抢有新版的红/绿),hover 才走 primary 暗示可再查;复用既有 token,不加新键。
132
+ '.dsh-upd-entry[data-known="uptodate"] .dsh-upd-entry-btn{border-color:var(--dsh-update-border,#d1d5db);color:var(--dsh-update-text-muted,#6b7280)}',
133
+ '.dsh-upd-entry[data-known="uptodate"] .dsh-upd-entry-btn:hover{border-color:var(--dsh-update-primary,#2563eb);color:var(--dsh-update-primary,#2563eb)}',
126
134
  // 小字自带底(深色宿主 + 浅色变量时也读得出;浅底宿主上只是多一圈细线,不抢戏)。
127
135
  ".dsh-upd-entry-note{font-size:12.5px;opacity:.9;background:var(--dsh-update-bg,#ffffff);border:1px solid var(--dsh-update-border,#e5e7eb);border-radius:var(--dsh-update-radius-badge,4px);padding:1px 8px}",
128
136
  '.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-note{background:var(--dsh-update-bg);border-color:var(--dsh-update-border-strong);color:var(--dsh-update-text)}',
@@ -133,7 +141,7 @@ const UPDATE_ENTRY_CSS = [
133
141
  '.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-btn{border-color:var(--dsh-update-border-strong);background:var(--dsh-update-bg);color:var(--dsh-update-text);border-radius:var(--dsh-update-entry-border-radius,3px)}',
134
142
  '.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-btn:hover{border-color:var(--dsh-update-primary);color:var(--dsh-update-primary)}',
135
143
  '@media (prefers-color-scheme: dark){.dsh-upd-entry[data-theme="archive"]{--dsh-update-text:#ece5d3;--dsh-update-text-muted:#a89c83;--dsh-update-border-strong:#5c4e3b;--dsh-update-primary:#e0684e;--dsh-update-bg:#1e1a15}}',
136
- "@media (prefers-color-scheme: dark){.dsh-upd-entry{color:#e5e7eb}",
144
+ "@media (prefers-color-scheme: dark){.dsh-upd-entry{color:#e5e7eb;--dsh-update-text-muted:#9ca3af}",
137
145
  ".dsh-upd-entry-btn{--dsh-update-button-bg:#1f2937;--dsh-update-border:#374151}}"
138
146
  ].join("\n");
139
147
  const ENTRY_ATTR = "data-dsh-upd-entry";
@@ -179,6 +187,10 @@ function mountUpdateEntry(container, options) {
179
187
  if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual" && openOn !== "direct") {
180
188
  throw new Error(`[dsh-plugin-update] \u70B9\u51FB\u53BB\u5411\u975E\u6CD5\uFF1A\u53EA\u6536 has-update / always / manual / direct\uFF08\u6536\u5230 ${JSON.stringify(options.openOn)}\uFF09`);
181
189
  }
190
+ const upToDateDisplay = options.upToDateDisplay ?? "button";
191
+ if (upToDateDisplay !== "note" && upToDateDisplay !== "button" && upToDateDisplay !== "tooltip") {
192
+ throw new Error(`[dsh-plugin-update] invalid upToDateDisplay: expected note / button / tooltip (got ${JSON.stringify(options.upToDateDisplay)})`);
193
+ }
182
194
  if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive") {
183
195
  throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
184
196
  }
@@ -237,7 +249,13 @@ function mountUpdateEntry(container, options) {
237
249
  return { snapshot, error };
238
250
  }
239
251
  function currentLabel() {
240
- return labelOverride ?? entryLabelFor(stateOf(), currentLang());
252
+ if (labelOverride) return labelOverride;
253
+ const lang = currentLang();
254
+ if (!activating && upToDateDisplay === "button" && entryStateKind(stateOf()) === "idle") {
255
+ const v = upToDateVersionOf(snapshot, error);
256
+ if (v) return copyText("entry.note.up-to-date", lang, { version: v });
257
+ }
258
+ return entryLabelFor(stateOf(), lang);
241
259
  }
242
260
  function hasUpdate() {
243
261
  return hasUpdateOf(snapshot);
@@ -248,18 +266,24 @@ function mountUpdateEntry(container, options) {
248
266
  function entryHTML() {
249
267
  const kind = entryStateKind(stateOf());
250
268
  const lang = currentLang();
251
- const labelHTML = labelOverride ? escapeHtml(labelOverride) : activating ? copyHTML("entry.action.checking", lang) : copyHTML(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf()));
252
- const labelText = labelOverride ?? (activating ? copyText("entry.action.checking", lang) : copyText(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf())));
269
+ const knownUpToDate = !activating && kind === "idle" ? upToDateVersionOf(snapshot, error) : null;
270
+ const buttonMode = !labelOverride && upToDateDisplay === "button" && knownUpToDate;
271
+ const tooltipMode = !labelOverride && upToDateDisplay === "tooltip" && knownUpToDate;
272
+ const labelHTML = labelOverride ? escapeHtml(labelOverride) : activating ? copyHTML("entry.action.checking", lang) : buttonMode && knownUpToDate ? copyHTML("entry.note.up-to-date", lang, { version: knownUpToDate }) : copyHTML(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf()));
273
+ const labelText = labelOverride ?? (activating ? copyText("entry.action.checking", lang) : buttonMode && knownUpToDate ? copyText("entry.note.up-to-date", lang, { version: knownUpToDate }) : copyText(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf())));
274
+ const titleText = labelOverride ? labelOverride : activating ? copyText("entry.action.checking", lang) : buttonMode && knownUpToDate ? copyText(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf())) : tooltipMode && knownUpToDate ? copyText("entry.note.up-to-date", lang, { version: knownUpToDate }) : labelText;
253
275
  const busyAttr = activating ? ' disabled aria-busy="true"' : "";
254
276
  const themeAttr = theme === "archive" ? ' data-theme="archive"' : "";
255
277
  const tokensStyle = themeTokensStyleFor(themeTokens ?? void 0);
256
278
  const styleBody = [sizingStyle, tokensStyle].filter((part) => part).join(";");
257
279
  const styleAttr = styleBody ? ` style="${styleBody}"` : "";
258
- const control = variant === "badge" ? `<button type="button" class="dsh-upd-entry-dot" ${ENTRY_ATTR}="activate" title="${escapeHtml(labelText)}" aria-label="${escapeHtml(labelText)}"${busyAttr}></button>` : `<button type="button" class="dsh-upd-entry-btn" ${ENTRY_ATTR}="activate"${busyAttr}>${labelHTML}</button>`;
259
- const noteHTML = noteVersion ? `<span class="dsh-upd-entry-note" data-dsh-upd-note="1">${copyHTML("entry.note.up-to-date", lang, { version: noteVersion })}</span>` : "";
280
+ const knownAttr = buttonMode ? ' data-known="uptodate"' : "";
281
+ const displayAttr = ` data-uptodate="${upToDateDisplay}"`;
282
+ const control = variant === "badge" ? `<button type="button" class="dsh-upd-entry-dot" ${ENTRY_ATTR}="activate" title="${escapeHtml(titleText)}" aria-label="${escapeHtml(labelText)}"${busyAttr}></button>` : `<button type="button" class="dsh-upd-entry-btn" ${ENTRY_ATTR}="activate" title="${escapeHtml(titleText)}" aria-label="${escapeHtml(labelText)}"${busyAttr}>${labelHTML}</button>`;
283
+ const noteHTML = upToDateDisplay === "note" && noteVersion ? `<span class="dsh-upd-entry-note" data-dsh-upd-note="1">${copyHTML("entry.note.up-to-date", lang, { version: noteVersion })}</span>` : "";
260
284
  return `<style>${UPDATE_ENTRY_CSS}
261
285
  ${BILINGUAL_CSS}</style>
262
- <span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${themeAttr}${styleAttr}>${control}${noteHTML}</span>`;
286
+ <span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${displayAttr}${knownAttr}${themeAttr}${styleAttr}>${control}${noteHTML}</span>`;
263
287
  }
264
288
  function render() {
265
289
  if (!mounted) return;
@@ -361,7 +385,7 @@ ${BILINGUAL_CSS}</style>
361
385
  openDialog();
362
386
  return;
363
387
  }
364
- noteVersion = snapshot.runningVersion;
388
+ if (upToDateDisplay === "note") noteVersion = snapshot.runningVersion;
365
389
  render();
366
390
  }
367
391
  function setTheme(next) {
@@ -127,8 +127,11 @@ export interface BatchLedgerCounts {
127
127
  * 失败优先(失败行永远算失败,不算待重启);其余按相位落档;不用重启的终态才算「已最新」。
128
128
  * 例外一处:「忙失败占位」(phase=failed 但 error=update-busy)其实是**排队**不是失败——
129
129
  * 宿主忙时先写占位再回 update-busy,面板把它翻回「可更新」,免得总账把它报成失败。
130
+ * 无轮次 pending 行看知识(#73:与行展示同口径,四态镜像 batchRowKnowledgeText):
131
+ * 有新版进可更新、已是最新进已最新、查失败进失败、无知识仍待查;
132
+ * checking 行永远走执行态(待查),不吃知识。不传 inventory 即无知识(旧语义)。
130
133
  */
131
- export declare function batchLedgerCounts(rows: readonly BatchRowView[]): BatchLedgerCounts;
134
+ export declare function batchLedgerCounts(rows: readonly BatchRowView[], inventory?: unknown): BatchLedgerCounts;
132
135
  /** 分类计数 → 一句话总账(只出现非零档,顺序固定:可更新 · 安装中 · 待查 · 待重启 · 失败 · 已跳过 · 已最新;#64 单语:缺省 zh 零回归)。 */
133
136
  export declare function batchLedgerText(counts: BatchLedgerCounts, lang?: unknown): string;
134
137
  /**
@@ -160,6 +163,25 @@ export declare function batchRowStatus(row: BatchRowView, skippedVersion?: strin
160
163
  * 无知识(从没查过)回 null,调用方回退旧 pending 文案。
161
164
  */
162
165
  export declare function batchRowKnowledgeText(row: BatchRowView, entry: BatchKnowledgeEntry | null, lang?: unknown): string | null;
166
+ /**
167
+ * 一行的版本展示(#71 Q3:永远显示当前版;有新版才 `当前 → 最新`)。
168
+ * latest 只做展示,不参与安装决策:会话目标版优先(安装语义),无会话目标时才看知识最新版。
169
+ * - 会话有目标版:current(快照运行版优先)→ 会话目标(current 缺失时占位 `?`,与旧口径一致)。
170
+ * - 会话无目标:current(快照优先,缺失退知识 installedVersion)→ 知识 latest(仅当两者皆有且不等;相等即已是最新,只显当前)。
171
+ * - 两者皆无:current 为 null、latest 为 null、hasUpdate 为 false(调用方不画版本 span)。
172
+ */
173
+ export interface BatchRowVersionParts {
174
+ current: string | null;
175
+ latest: string | null;
176
+ hasUpdate: boolean;
177
+ }
178
+ export declare function batchRowVersionParts(row: BatchRowView, knowledge: BatchKnowledgeEntry | null): BatchRowVersionParts;
179
+ /**
180
+ * 无轮次 pending 行的状态词(#71 Q3 四态:有新版 / 已是最新 / 还没查过 / 查失败)。
181
+ * 有知识走知识分支;无知识(inventory 缺失或该家无条目)回「还没查过」,不再误显示 wait-turn。
182
+ * 有轮次未做完(inRound)仍走执行态,由调用方按旧口径显示 wait-turn + 未终态标记。
183
+ */
184
+ export declare function batchRowPendingStatus(row: BatchRowView, knowledge: BatchKnowledgeEntry | null, lang?: unknown): string | null;
163
185
  export interface BatchPanelRenderInput {
164
186
  rows?: readonly BatchRowView[];
165
187
  theme?: UpdatePanelTheme;
@@ -35,7 +35,7 @@ function buildBatchPhoneNames(prefix) {
35
35
  prefsSave: p + ".batchPrefsSave"
36
36
  };
37
37
  }
38
- function batchLedgerCounts(rows) {
38
+ function batchLedgerCounts(rows, inventory) {
39
39
  const counts = {
40
40
  updatable: 0,
41
41
  installing: 0,
@@ -51,13 +51,22 @@ function batchLedgerCounts(rows) {
51
51
  else if (phase === "failed") counts.failed += 1;
52
52
  else if (phase === "installing") counts.installing += 1;
53
53
  else if (phase === "ready") counts.updatable += 1;
54
- else if (phase === "pending" || phase === "checking") counts.pending += 1;
54
+ else if (phase === "pending") counts[ledgerKnowledgeBucket(knowledgeEntryOf(inventory, row.key))] += 1;
55
+ else if (phase === "checking") counts.pending += 1;
55
56
  else if (phase === "skipped") counts.skipped += 1;
56
57
  else if (row.restartRequired === true) counts.restart += 1;
57
58
  else counts.settled += 1;
58
59
  }
59
60
  return counts;
60
61
  }
62
+ function ledgerKnowledgeBucket(entry) {
63
+ if (!entry) return "pending";
64
+ if (entry.error) return "failed";
65
+ if (entry.latestVersion && entry.installedVersion) return entry.latestVersion !== entry.installedVersion ? "updatable" : "settled";
66
+ if (entry.latestVersion && !entry.installedVersion) return "updatable";
67
+ if (entry.lastCheckedAt > 0) return "settled";
68
+ return "pending";
69
+ }
61
70
  function batchLedgerText(counts, lang) {
62
71
  const l = langOf(lang);
63
72
  const parts = [];
@@ -175,6 +184,38 @@ function batchRowKnowledgeText(row, entry, lang) {
175
184
  if (entry.lastCheckedAt > 0) return batchText("batch.row.current", lang);
176
185
  return batchText("batch.row.never", lang);
177
186
  }
187
+ function batchRowVersionParts(row, knowledge) {
188
+ const sessionTarget = typeof row.targetVersion === "string" && row.targetVersion ? row.targetVersion : null;
189
+ if (sessionTarget) {
190
+ const current2 = currentVersionOf(row);
191
+ if (current2 === null) return { current: null, latest: sessionTarget, hasUpdate: true };
192
+ if (current2 === sessionTarget) return { current: current2, latest: null, hasUpdate: false };
193
+ return { current: current2, latest: sessionTarget, hasUpdate: true };
194
+ }
195
+ const snapshotCurrent = currentVersionOf(row);
196
+ const current = snapshotCurrent ?? (knowledge && typeof knowledge.installedVersion === "string" && knowledge.installedVersion ? knowledge.installedVersion : null);
197
+ const latest = knowledge && typeof knowledge.latestVersion === "string" && knowledge.latestVersion ? knowledge.latestVersion : null;
198
+ if (current && latest && latest !== current) return { current, latest, hasUpdate: true };
199
+ if (current) return { current, latest: null, hasUpdate: false };
200
+ if (latest) return { current: latest, latest: null, hasUpdate: false };
201
+ return { current: null, latest: null, hasUpdate: false };
202
+ }
203
+ function batchRowVersionHTML(row, knowledge) {
204
+ const parts = batchRowVersionParts(row, knowledge);
205
+ if (parts.hasUpdate && parts.latest) {
206
+ const cur = parts.current ?? "?";
207
+ return '<span class="dsh-upd-bver"><span class="dsh-upd-bver-cur">' + escapeHtml(cur) + '</span><span class="dsh-upd-bver-arrow" aria-hidden="true"> \u2192 </span><span class="dsh-upd-bver-new">' + escapeHtml(parts.latest) + "</span></span>";
208
+ }
209
+ if (parts.current) return '<span class="dsh-upd-bver">' + escapeHtml(parts.current) + "</span>";
210
+ return "";
211
+ }
212
+ function batchRowPendingStatus(row, knowledge, lang) {
213
+ if (asBatchPhase(row.phase) !== "pending") return null;
214
+ const known = batchRowKnowledgeText(row, knowledge, lang);
215
+ if (known !== null) return known;
216
+ if (!knowledge) return batchText("batch.row.never", lang);
217
+ return null;
218
+ }
178
219
  function renderBatchPanelHTML(input) {
179
220
  const rows = Array.isArray(input.rows) ? input.rows : [];
180
221
  const theme = normalizePanelTheme(input.theme);
@@ -184,7 +225,7 @@ function renderBatchPanelHTML(input) {
184
225
  const expandedKey = typeof input.expandedKey === "string" && input.expandedKey ? input.expandedKey : null;
185
226
  const lastError = typeof input.lastError === "string" && input.lastError ? input.lastError : null;
186
227
  const loaded = input.loaded === void 0 ? rows.length > 0 : input.loaded === true;
187
- const counts = batchLedgerCounts(rows);
228
+ const counts = batchLedgerCounts(rows, input.inventory);
188
229
  const installing = counts.installing > 0;
189
230
  const macroBusy = input.inFlight === true || installing;
190
231
  const total = rows.length;
@@ -353,8 +394,8 @@ function rowSetHTML(row, ctx) {
353
394
  const skipped = ctx.skipped;
354
395
  const queued = isQueuedRow(row);
355
396
  const position = queued ? queuePositionOf(row) : null;
356
- const current = currentVersionOf(row);
357
- const version = row.targetVersion ? (current === null ? "?" : current) + " \u2192 " + row.targetVersion : current === null ? "" : current;
397
+ const versionHTML = batchRowVersionHTML(row, ctx.knowledge);
398
+ const rowStatusText = queued ? batchQueuedStatus(position, ctx.lang) : ctx.inRound ? batchRowKnowledgeText(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang) : batchRowPendingStatus(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang);
358
399
  const actions = [];
359
400
  const off = ctx.inFlight ? " disabled" : "";
360
401
  if (phase === "installing") {
@@ -395,7 +436,7 @@ function rowSetHTML(row, ctx) {
395
436
  const withCode = escaped.includes(codeEsc) ? escaped.replace(codeEsc, "<code>" + codeEsc + "</code>") : escaped;
396
437
  return '<span class="dsh-upd-bfail">' + withCode + "</span>";
397
438
  })() : "";
398
- const main = '<button type="button" class="dsh-upd-brow-main" data-act="toggle-details" data-key="' + keyAttr + '" aria-expanded="' + (ctx.expanded ? "true" : "false") + '"><span class="dsh-upd-updot" data-tone="' + dotToneOf(row, phase, skipped, queued) + '"></span><span class="dsh-upd-bname">' + escapeHtml(titleOf(row, ctx.titles)) + "</span>" + (version ? '<span class="dsh-upd-bver">' + escapeHtml(version) + "</span>" : "") + '<span class="dsh-upd-bstat">' + escapeHtml(queued ? batchQueuedStatus(position, ctx.lang) : batchRowKnowledgeText(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang)) + (ctx.inRound && !queued ? "\uFF08" + escapeHtml(batchText("batch.row.unfinished-tag", ctx.lang)) + "\uFF09" : "") + "</span>" + failLine + "</button>";
439
+ const main = '<button type="button" class="dsh-upd-brow-main" data-act="toggle-details" data-key="' + keyAttr + '" aria-expanded="' + (ctx.expanded ? "true" : "false") + '"><span class="dsh-upd-updot" data-tone="' + dotToneOf(row, phase, skipped, queued) + '"></span><span class="dsh-upd-bname">' + escapeHtml(titleOf(row, ctx.titles)) + "</span>" + versionHTML + '<span class="dsh-upd-bstat">' + escapeHtml(rowStatusText) + (ctx.inRound && !queued ? "\uFF08" + escapeHtml(batchText("batch.row.unfinished-tag", ctx.lang)) + "\uFF09" : "") + "</span>" + failLine + "</button>";
399
440
  const detail = ctx.expanded ? '<div class="dsh-upd-bdetail" data-key="' + keyAttr + '">' + detailActionsHTML(row, ctx) + (ctx.notice !== null ? '<div class="dsh-upd-bdetail-notice" role="status">' + escapeHtml(ctx.notice) + "</div>" : "") + detailHTML(row, ctx) + "</div>" : "";
400
441
  return '<div class="dsh-upd-brow-set" data-key="' + keyAttr + '"><div class="dsh-upd-brow" data-phase="' + escapeHtml(phase) + '" data-key="' + keyAttr + '"' + (queued ? ' data-queued="1"' : "") + ">" + main + '<span class="dsh-upd-brow-actions">' + actions.join("") + "</span></div>" + detail + "</div>";
401
442
  }
@@ -486,7 +527,7 @@ const UPDATE_BATCH_PANEL_CSS = [
486
527
  ".dsh-upd-brow-set{border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
487
528
  ".dsh-upd-brow-set:first-child{border-top:0}",
488
529
  ".dsh-upd-brow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 0}",
489
- ".dsh-upd .dsh-upd-brow-main{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:1 1 260px;min-width:0;",
530
+ ".dsh-upd .dsh-upd-brow-main{display:grid;grid-template-columns:auto minmax(5em,max-content) minmax(18ch,max-content) minmax(0,1fr);align-items:baseline;column-gap:8px;row-gap:2px;flex:1 1 260px;min-width:0;",
490
531
  "text-align:left;cursor:pointer;color:inherit;font:inherit;background:transparent;border:0;padding:2px 0;margin:0}",
491
532
  ".dsh-upd .dsh-upd-brow-main:hover{color:var(--dsh-update-primary,#2563eb)}",
492
533
  ".dsh-upd-brow-actions{flex:none;margin-left:auto}",
@@ -497,10 +538,16 @@ const UPDATE_BATCH_PANEL_CSS = [
497
538
  '.dsh-upd-updot[data-tone="busy"]{background:var(--dsh-update-busy-border,#2563eb);opacity:1}',
498
539
  '.dsh-upd-updot[data-tone="warn"]{background:var(--dsh-update-warn-border,#d97706);opacity:1}',
499
540
  '.dsh-upd-updot[data-tone="bad"]{background:var(--dsh-update-bad-border,#dc2626);opacity:1}',
500
- ".dsh-upd-bname{font-weight:700;flex:none}",
501
- ".dsh-upd-bver{font-family:Consolas,Menlo,monospace;font-size:12px;opacity:.8;flex:none}",
502
- ".dsh-upd-bstat{font-size:12.5px;opacity:.9}",
503
- ".dsh-upd-bfail{flex:1 1 100%;display:block;font-size:12.5px;color:var(--dsh-update-bad-border,#dc2626)}",
541
+ ".dsh-upd-bname{font-weight:700;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
542
+ ".dsh-upd-bver{font-family:Consolas,Menlo,monospace;font-size:12px;white-space:nowrap;font-variant-numeric:tabular-nums;min-width:0}",
543
+ ".dsh-upd-bver-cur{opacity:.65}",
544
+ ".dsh-upd-bver-arrow{opacity:.6}",
545
+ ".dsh-upd-bver-new{color:var(--dsh-update-new-text,#dc2626);font-weight:700}",
546
+ ".dsh-upd-bstat{font-size:12.5px;opacity:.9;min-width:0;overflow-wrap:anywhere}",
547
+ ".dsh-upd-bfail{grid-column:1/-1;display:block;font-size:12.5px;color:var(--dsh-update-bad-border,#dc2626)}",
548
+ ".dsh-upd-batch{--dsh-update-new-text:#dc2626}",
549
+ "@media (prefers-color-scheme: dark){.dsh-upd-batch{--dsh-update-new-text:#f87171}}",
550
+ "@media (forced-colors: active){.dsh-upd-bver-new{color:CanvasText}}",
504
551
  ".dsh-upd-blist{font-size:12.5px;margin-top:2px}",
505
552
  ".dsh-upd-batch-ledger{margin:8px 0 0;font-size:13px;opacity:.85}",
506
553
  ".dsh-upd-batch-more{margin-top:6px}",
@@ -536,6 +583,10 @@ const UPDATE_BATCH_PANEL_ARCHIVE_CSS = [
536
583
  '.dsh-upd[data-theme="archive"] .dsh-upd-brow-main:hover{color:var(--dsh-update-primary)}',
537
584
  '.dsh-upd[data-theme="archive"] .dsh-upd-bname{font-family:var(--dsh-update-font-serif)}',
538
585
  '.dsh-upd[data-theme="archive"] .dsh-upd-bver{font-family:var(--dsh-update-font-mono);color:var(--dsh-update-text-muted)}',
586
+ '.dsh-upd[data-theme="archive"] .dsh-upd-bver-cur{opacity:.75}',
587
+ '.dsh-upd[data-theme="archive"] .dsh-upd-bver-new{color:var(--dsh-update-new-text,#b3261e)}',
588
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch{--dsh-update-new-text:#b3261e}',
589
+ '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] .dsh-upd-batch{--dsh-update-new-text:#ef8a7d}}',
539
590
  '.dsh-upd[data-theme="archive"] .dsh-upd-batch-ledger{color:var(--dsh-update-text-muted)}',
540
591
  '.dsh-upd[data-theme="archive"] .dsh-upd-batch-sum{color:var(--dsh-update-text)}',
541
592
  '.dsh-upd[data-theme="archive"] .dsh-upd-brow-set{border-top-color:var(--dsh-update-border)}'
@@ -1030,10 +1081,25 @@ function mountUpdateBatchPanel(container, options) {
1030
1081
  return;
1031
1082
  }
1032
1083
  if (reply["ok"] === true) {
1033
- const session = normalizeBatchSession(reply["session"]);
1034
- const parsed = readRows(reply["rows"]);
1035
- rows = parsed.length > 0 ? parsed : rowsFromSession(session);
1036
- lastSession = session;
1084
+ const hasSession = "session" in reply && reply["session"] !== void 0;
1085
+ const hasRows = "rows" in reply && reply["rows"] !== void 0;
1086
+ if (!hasSession && !hasRows) {
1087
+ if (isObject(reply["prefs"])) lastPrefs = reply["prefs"];
1088
+ return;
1089
+ }
1090
+ if (hasSession) {
1091
+ const session = normalizeBatchSession(reply["session"]);
1092
+ lastSession = session;
1093
+ if (hasRows) {
1094
+ const parsed = readRows(reply["rows"]);
1095
+ rows = parsed.length > 0 ? parsed : rowsFromSession(session);
1096
+ } else {
1097
+ rows = rowsFromSession(session);
1098
+ }
1099
+ } else {
1100
+ const parsed = readRows(reply["rows"]);
1101
+ if (parsed.length > 0) rows = parsed;
1102
+ }
1037
1103
  if (isObject(reply["inventory"])) {
1038
1104
  lastInventory = reply["inventory"];
1039
1105
  const at = reply["inventory"]["updatedAt"];
@@ -1419,7 +1485,9 @@ export {
1419
1485
  batchLedgerText,
1420
1486
  batchQueuedStatus,
1421
1487
  batchRowKnowledgeText,
1488
+ batchRowPendingStatus,
1422
1489
  batchRowStatus,
1490
+ batchRowVersionParts,
1423
1491
  buildBatchPhoneNames,
1424
1492
  hasUnfinishedRows,
1425
1493
  mountUpdateBatchPanel,
package/dist/panel.d.ts CHANGED
@@ -37,6 +37,8 @@ export interface UpdateThemeTokens {
37
37
  busyBg?: string;
38
38
  busyBorder?: string;
39
39
  busyText?: string;
40
+ /** 有新版版本号的醒目红(#71 Q1 专用;缺省跟随坏项红的对比度口径,深浅主题另覆)。 */
41
+ newText?: string;
40
42
  fontSans?: string;
41
43
  fontSerif?: string;
42
44
  fontMono?: string;
package/dist/panel.js CHANGED
@@ -38,6 +38,7 @@ const THEME_TOKEN_VARS = {
38
38
  busyBg: "--dsh-update-busy-bg",
39
39
  busyBorder: "--dsh-update-busy-border",
40
40
  busyText: "--dsh-update-busy-text",
41
+ newText: "--dsh-update-new-text",
41
42
  fontSans: "--dsh-update-font-sans",
42
43
  fontSerif: "--dsh-update-font-serif",
43
44
  fontMono: "--dsh-update-font-mono",
@@ -72,7 +73,8 @@ const THEME_TOKEN_COLOR_KEYS = /* @__PURE__ */ new Set([
72
73
  "badText",
73
74
  "busyBg",
74
75
  "busyBorder",
75
- "busyText"
76
+ "busyText",
77
+ "newText"
76
78
  ]);
77
79
  function themeTokensError(raw) {
78
80
  return new Error(`[dsh-plugin-update] \u4E3B\u9898\u53C2\u6570 themeTokens \u975E\u6CD5\uFF1A\u53EA\u6536\u5DF2\u77E5 token \u952E\uFF08\u989C\u8272\u7528 hex \u6216\u82F1\u6587\u540D\uFF0C\u5B57\u4F53/\u5706\u89D2/\u9634\u5F71/\u5C3A\u5BF8\u4E3A\u5B89\u5168 CSS \u503C\uFF0CentryScale \u4E3A\u5927\u4E8E 0 \u7684\u6709\u9650\u6570\uFF09\uFF08\u6536\u5230 ${JSON.stringify(raw ?? null)})`);
@@ -575,7 +577,7 @@ function panelViewModelCore(input, lang) {
575
577
  };
576
578
  }
577
579
  const UPDATE_PANEL_CSS = [
578
- '.dsh-upd{font:14px/1.6 system-ui,"Microsoft YaHei",sans-serif;color:var(--dsh-update-text,#1f2937);',
580
+ '.dsh-upd{font:14px/1.75 system-ui,"Microsoft YaHei",sans-serif;color:var(--dsh-update-text,#1f2937);',
579
581
  "background:var(--dsh-update-bg,#ffffff);border:1px solid var(--dsh-update-border,#e5e7eb);border-radius:8px;padding:12px 14px;max-width:560px;",
580
582
  // 横幅配色走变量(浅色默认 + 深色覆盖,见下方 dark 媒体块):硬编码浅色会让深色下
581
583
  // 「浅底 + 浅字」读不出来(现场回归:默认主题深色模式更新横幅白底浅字)。
@@ -583,7 +585,7 @@ const UPDATE_PANEL_CSS = [
583
585
  "--dsh-update-bad-bg:#fef2f2;--dsh-update-bad-border:#dc2626;--dsh-update-busy-bg:#eff6ff;--dsh-update-busy-border:#2563eb}",
584
586
  ".dsh-upd *{box-sizing:border-box}",
585
587
  ".dsh-upd button{font:inherit;border:1px solid var(--dsh-update-border-strong,#d1d5db);border-radius:var(--dsh-update-radius-button,6px);background:var(--dsh-update-button-bg,#f9fafb);",
586
- "color:inherit;padding:4px 12px;cursor:pointer;margin:2px 6px 2px 0}",
588
+ "color:inherit;padding:7px 14px;cursor:pointer;margin:2px 6px 2px 0}",
587
589
  ".dsh-upd button:disabled{opacity:.45;cursor:not-allowed}",
588
590
  ".dsh-upd button:focus-visible{outline:2px solid var(--dsh-update-focus,#2563eb);outline-offset:1px}",
589
591
  '.dsh-upd button[data-primary="1"]{background:var(--dsh-update-primary,#2563eb);border-color:var(--dsh-update-primary,#2563eb);color:#fff}',
@@ -613,7 +615,8 @@ const UPDATE_PANEL_CSS = [
613
615
  ".dsh-upd-overlay .dsh-upd{background:var(--dsh-update-bg,#ffffff);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}",
614
616
  // —— 弹窗分栏滚动:头(抬头/档案头/横幅/版本条)与尾固定,只有 01–05 章节区滚动 ——
615
617
  ".dsh-upd-body{min-height:0}",
616
- ".dsh-upd-overlay .dsh-upd-body{flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dsh-update-border,#e5e7eb) transparent}",
618
+ ".dsh-upd-body *{min-width:0}",
619
+ ".dsh-upd-overlay .dsh-upd-body{flex:1 1 auto;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dsh-update-border,#e5e7eb) transparent}",
617
620
  ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar{width:8px}",
618
621
  ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar-thumb{background:var(--dsh-update-border,#e5e7eb);border-radius:4px}",
619
622
  ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar-track{background:transparent}",
@@ -628,11 +631,11 @@ const UPDATE_PANEL_CSS = [
628
631
  ".dsh-upd-meta{font-size:12.5px;opacity:.75}",
629
632
  ".dsh-upd-proftag{font-family:Consolas,Menlo,monospace;font-size:11px;border:1px solid var(--dsh-update-border,#d1d5db);border-radius:3px;padding:0 5px;margin-left:6px;letter-spacing:.06em}",
630
633
  ".dsh-upd-strip{display:flex;flex-wrap:wrap;margin:8px 0 0;border:1px solid var(--dsh-update-border,#e5e7eb);border-radius:4px;overflow:hidden;font-size:12.5px}",
631
- ".dsh-upd-strip>div{flex:1 1 110px;padding:6px 10px;border-left:1px solid var(--dsh-update-border,#e5e7eb)}",
634
+ ".dsh-upd-strip>div{flex:1 1 110px;min-width:0;padding:6px 10px;border-left:1px solid var(--dsh-update-border,#e5e7eb)}",
632
635
  ".dsh-upd-strip>div:first-child{border-left:0}",
633
636
  ".dsh-upd-strip-k{display:block;font-size:11px;letter-spacing:.14em;opacity:.7}",
634
- ".dsh-upd-strip-v{font-family:Consolas,Menlo,monospace;font-size:12.5px}",
635
- ".dsh-upd-chapter{margin-top:14px;padding-top:10px;border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
637
+ ".dsh-upd-strip-v{font-family:Consolas,Menlo,monospace;font-size:12.5px;word-break:break-all}",
638
+ ".dsh-upd-chapter{margin-top:18px;padding-top:14px;border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
636
639
  // 右下角独立 footer 区(#47 定案 A):与第一章 actions 脱钩,右对齐,一次找到。
637
640
  ".dsh-upd-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:14px;padding-top:10px;border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
638
641
  ".dsh-upd-foot-note{margin-right:auto;font-size:12px;opacity:.7}",
@@ -645,7 +648,7 @@ const UPDATE_PANEL_CSS = [
645
648
  ".dsh-upd-chap-note{flex:none;font-size:12px;opacity:.75}",
646
649
  ".dsh-upd-chap-note button{margin:0}",
647
650
  // 更新队列两行键值(用户定案的设计):结构两主题共用,皮肤各自收敛。
648
- ".dsh-upd-qrow{display:flex;align-items:baseline;gap:10px;padding:6px 0}",
651
+ ".dsh-upd-qrow{display:flex;align-items:baseline;flex-wrap:wrap;gap:10px;padding:6px 0;min-width:0}",
649
652
  ".dsh-upd-qrow+.dsh-upd-qrow{border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
650
653
  ".dsh-upd-qdot{width:8px;height:8px;border-radius:50%;flex:none;align-self:center;background:currentColor;opacity:.5}",
651
654
  '.dsh-upd-qdot[data-tone="busy"]{background:var(--dsh-update-warn-border,#d97706);opacity:1}',
@@ -693,6 +696,15 @@ const UPDATE_PANEL_CSS = [
693
696
  '.dsh-upd-actions button:first-child:not([aria-busy="true"])::after{content:"";display:inline-block;width:11px;height:11px;margin-left:8px;visibility:hidden}',
694
697
  ".dsh-upd-banner{min-height:1.2em}",
695
698
  ".dsh-upd-strip{min-height:48px}",
699
+ // —— 查新版瞬时抖动补强:忙闲两帧同高 + 横幅不动(只追加覆盖,不改上面既有串)——
700
+ ".dsh-upd-actions{align-content:flex-start}",
701
+ ".dsh-upd-actions button{white-space:nowrap}",
702
+ ".dsh-upd-actions button:first-child{min-width:10em}",
703
+ '.dsh-upd-actions button[data-primary="1"]{min-width:9em}',
704
+ ".dsh-upd-banner{min-height:3.4em;display:flex;flex-direction:column;justify-content:center}",
705
+ ".dsh-upd-banner{animation:none}",
706
+ ".dsh-upd-copy{min-height:1.75em}",
707
+ ".dsh-upd-copy--empty{visibility:hidden}",
696
708
  "@media (prefers-color-scheme: dark){.dsh-upd{--dsh-update-text:#e5e7eb;--dsh-update-bg:#111827;--dsh-update-border:#374151;",
697
709
  "--dsh-update-button-bg:#1f2937;--dsh-update-bg-soft:#1f2937;--dsh-update-primary:#3b82f6;--dsh-update-focus:#93c5fd;",
698
710
  // 横幅深色覆盖:底色用低透明度同色系(不是浅色原值),边线提亮,保证「深底浅字」可读。
@@ -1081,6 +1093,7 @@ ${changelogHTML}
1081
1093
  }
1082
1094
  }
1083
1095
  if (copyNotice) errLines.push(`<div class="dsh-upd-copy" role="status">${escapeHtml(copyNotice)}</div>`);
1096
+ else errLines.push('<div class="dsh-upd-copy dsh-upd-copy--empty" aria-hidden="true"></div>');
1084
1097
  chapters.push(chapterOf(4, errLines.join(""), "", l));
1085
1098
  }
1086
1099
  chapters.push(
package/package.json CHANGED
@@ -1,76 +1,76 @@
1
- {
2
- "name": "dsh-plugin-update",
3
- "version": "0.7.0",
4
- "description": "可复用的更新系统装成的 npm 包:任何 DSH 插件照文档能集成检查更新等功能(地图 #579)",
5
- "license": "MIT",
6
- "author": "王辰浩",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/FeatherHunter/dsh-plugin-update.git"
10
- },
11
- "engines": {
12
- "node": ">=22"
13
- },
14
- "type": "module",
15
- "types": "./dist/host.d.ts",
16
- "exports": {
17
- ".": {
18
- "types": "./dist/host.d.ts",
19
- "default": "./dist/host.js"
20
- },
21
- "./panel": {
22
- "types": "./dist/panel.d.ts",
23
- "default": "./dist/panel.js"
24
- },
25
- "./client": {
26
- "types": "./dist/client.d.ts",
27
- "default": "./dist/client.js"
28
- },
29
- "./batch": {
30
- "types": "./dist/host-batch.d.ts",
31
- "default": "./dist/host-batch.js"
32
- },
33
- "./panel-batch": {
34
- "types": "./dist/panel-batch.d.ts",
35
- "default": "./dist/panel-batch.js"
36
- },
37
- "./entry": {
38
- "types": "./dist/entry.d.ts",
39
- "default": "./dist/entry.js"
40
- },
41
- "./entry-batch": {
42
- "types": "./dist/entry-batch.d.ts",
43
- "default": "./dist/entry-batch.js"
44
- },
45
- "./http": {
46
- "types": "./dist/http.d.ts",
47
- "default": "./dist/http.js"
48
- },
49
- "./package.json": "./package.json"
50
- },
51
- "files": [
52
- "dist",
53
- "derive-client-values.mjs",
54
- "event-list.template.json",
55
- "README.md",
56
- "CHANGELOG.md",
57
- "LICENSE"
58
- ],
59
- "publishConfig": {
60
- "access": "public",
61
- "registry": "https://registry.npmjs.org/"
62
- },
63
- "scripts": {
64
- "typecheck": "tsc -p tsconfig.json",
65
- "build": "node build.mjs",
66
- "prepack": "npm run build",
67
- "pretest": "npm run build",
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"
71
- },
72
- "devDependencies": {
73
- "esbuild": "^0.28.2",
74
- "typescript": "5.6.3"
75
- }
76
- }
1
+ {
2
+ "name": "dsh-plugin-update",
3
+ "version": "0.8.0",
4
+ "description": "可复用的更新系统装成的 npm 包:任何 DSH 插件照文档能集成检查更新等功能(地图 #579)",
5
+ "license": "MIT",
6
+ "author": "王辰浩",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/FeatherHunter/dsh-plugin-update.git"
10
+ },
11
+ "engines": {
12
+ "node": ">=22"
13
+ },
14
+ "type": "module",
15
+ "types": "./dist/host.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/host.d.ts",
19
+ "default": "./dist/host.js"
20
+ },
21
+ "./panel": {
22
+ "types": "./dist/panel.d.ts",
23
+ "default": "./dist/panel.js"
24
+ },
25
+ "./client": {
26
+ "types": "./dist/client.d.ts",
27
+ "default": "./dist/client.js"
28
+ },
29
+ "./batch": {
30
+ "types": "./dist/host-batch.d.ts",
31
+ "default": "./dist/host-batch.js"
32
+ },
33
+ "./panel-batch": {
34
+ "types": "./dist/panel-batch.d.ts",
35
+ "default": "./dist/panel-batch.js"
36
+ },
37
+ "./entry": {
38
+ "types": "./dist/entry.d.ts",
39
+ "default": "./dist/entry.js"
40
+ },
41
+ "./entry-batch": {
42
+ "types": "./dist/entry-batch.d.ts",
43
+ "default": "./dist/entry-batch.js"
44
+ },
45
+ "./http": {
46
+ "types": "./dist/http.d.ts",
47
+ "default": "./dist/http.js"
48
+ },
49
+ "./package.json": "./package.json"
50
+ },
51
+ "files": [
52
+ "dist",
53
+ "derive-client-values.mjs",
54
+ "event-list.template.json",
55
+ "README.md",
56
+ "CHANGELOG.md",
57
+ "LICENSE"
58
+ ],
59
+ "publishConfig": {
60
+ "access": "public",
61
+ "registry": "https://registry.npmjs.org/"
62
+ },
63
+ "scripts": {
64
+ "typecheck": "tsc -p tsconfig.json",
65
+ "build": "node build.mjs",
66
+ "prepack": "npm run build",
67
+ "pretest": "npm run build",
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"
71
+ },
72
+ "devDependencies": {
73
+ "esbuild": "^0.28.2",
74
+ "typescript": "5.6.3"
75
+ }
76
+ }