@huaqiu/dsh-tool-plugin-update 0.6.13 → 0.6.17

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/lib/index.mjs CHANGED
@@ -138,6 +138,14 @@ function routeFor(sub, method) {
138
138
  method: "POST",
139
139
  path: "/api/v1/plugin-updates/unignore"
140
140
  } : null;
141
+ if (sub === "/plugin-ignore") return method === "POST" ? {
142
+ method: "POST",
143
+ path: "/api/v1/plugin-updates/plugin-ignore"
144
+ } : null;
145
+ if (sub === "/plugin-unignore") return method === "POST" ? {
146
+ method: "POST",
147
+ path: "/api/v1/plugin-updates/plugin-unignore"
148
+ } : null;
141
149
  return null;
142
150
  }
143
151
  /** Every static sub-path this forwarder serves — used to tell a 404 from a
@@ -149,6 +157,8 @@ const STATIC_SUB_PATHS = /* @__PURE__ */ new Set([
149
157
  "/install",
150
158
  "/ignore",
151
159
  "/unignore",
160
+ "/plugin-ignore",
161
+ "/plugin-unignore",
152
162
  "/runtime",
153
163
  "/runtime-request",
154
164
  "/runtime-source",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huaqiu/dsh-tool-plugin-update",
3
- "version": "0.6.13",
3
+ "version": "0.6.17",
4
4
  "type": "module",
5
5
  "main": "./lib/index.mjs",
6
6
  "types": "./lib/index.d.mts",
@@ -31,8 +31,8 @@
31
31
  "@deepseek-ai/dsh-host-webserver": "^0.2.1-rc.2"
32
32
  },
33
33
  "dependencies": {
34
- "@huaqiu/dsh-plugin-log": "0.6.13",
35
- "@huaqiu/dsh-sidebar-entry": "0.6.13"
34
+ "@huaqiu/dsh-plugin-log": "0.6.17",
35
+ "@huaqiu/dsh-sidebar-entry": "0.6.17"
36
36
  },
37
37
  "files": [
38
38
  "lib",
package/src/client/api.ts CHANGED
@@ -148,6 +148,25 @@ export function runtimeLatestVersion(
148
148
  return null
149
149
  }
150
150
 
151
+ /**
152
+ * Whether the report offers an HQ Edge runtime update the user can act on:
153
+ * hq-edge reports that a newer distribution exists AND it actually named one.
154
+ *
155
+ * Both halves matter, and sharing one predicate keeps the sidebar badge and the
156
+ * runtime card from disagreeing: the card can only render its 升级核心 row when
157
+ * it knows the target version, so an `updateAvailable` without a `latest` entry
158
+ * must not add a count anywhere. hq-edge already fails closed here (it only
159
+ * sets the flag when `semver.gt(latest, current)` holds on a comparable pair),
160
+ * so this is a shape guard, not a second compatibility decision.
161
+ */
162
+ export function runtimeUpdateOffered(
163
+ runtime: RuntimeUpdateInfo | null | undefined,
164
+ ): boolean {
165
+ if (runtime === null || runtime === undefined) return false
166
+ if (runtime.updateAvailable !== true) return false
167
+ return runtimeLatestVersion(runtime) !== null
168
+ }
169
+
151
170
  /**
152
171
  * Set-level release metadata from a schemaVersion-2 plugin manifest. ALL
153
172
  * fields optional: v1 manifests send none, and the panel must fall back to
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The sidebar toolbutton's numeric badge — one pure function, no DOM, no
3
+ * store, so the rule can be tested on its own.
4
+ *
5
+ * It answers one question: how many updates does this EDA install still have
6
+ * for the user? That is every actionable plugin item plus the HQ Edge runtime
7
+ * offer when hq-edge reports one:
8
+ *
9
+ * - **plugin items** — compatible `update` + `install`, minus whatever the
10
+ * user muted ({@link actionableUpdateCount});
11
+ * - **the HQ Edge runtime update, as one** — a different deliverable with its
12
+ * own card and its own confirm flow, but still an available update, and
13
+ * leaving it out of the only number on the sidebar hid a pending runtime
14
+ * upgrade until the panel was opened. It counts as ONE (a runtime swap is a
15
+ * single action, however many manifest entries it was picked from) and it
16
+ * survives a muted plugin offer, because the two offers are independent —
17
+ * ignoring this release set must not hide a newer runtime.
18
+ *
19
+ * What is NOT counted: the durable job history. It is permanent
20
+ * (`listRecent(20)`, survives restarts), so turning any settled job — even a
21
+ * successful install — into a "1" latched the badge on forever. That was the
22
+ * original bug; a job is only allowed to speak while it is still RUNNING.
23
+ *
24
+ * @module
25
+ */
26
+ import {
27
+ TERMINAL_PHASES,
28
+ runtimeUpdateOffered,
29
+ type PluginUpdateReport,
30
+ type UpdateJob,
31
+ } from './api.js'
32
+ import { actionableUpdateCount } from './version.js'
33
+
34
+ /**
35
+ * The badge value for one snapshot of `(report, active job)`.
36
+ *
37
+ * @param report - the latest plugin-update report, or null before the first
38
+ * read. Null contributes nothing (never a placeholder count).
39
+ * @param activeJob - the job the server reports as active, or null when idle.
40
+ * A RUNNING job counts as 1 only when there is nothing actionable to show,
41
+ * so the row still signals in-flight work; a TERMINAL job contributes
42
+ * nothing — it is history.
43
+ * @returns the number to render; 0 means "no badge at all".
44
+ */
45
+ export function badgeCountFor(
46
+ report: PluginUpdateReport | null,
47
+ activeJob: UpdateJob | null,
48
+ ): number {
49
+ if (report !== null) {
50
+ // Plugin items already collapse to 0 when the offer is muted; the runtime
51
+ // is an independent offer and keeps its own count.
52
+ const plugin = actionableUpdateCount(report)
53
+ const runtime = runtimeUpdateOffered(report.runtime) ? 1 : 0
54
+ const total = plugin + runtime
55
+ if (total > 0) return total
56
+ }
57
+ if (activeJob !== null && !TERMINAL_PHASES.has(activeJob.phase)) return 1
58
+ return 0
59
+ }
@@ -6,38 +6,46 @@
6
6
  * helpers). The wording follows the shell's own plugin manager
7
7
  * (`ui-plugin-manager/src/client/locales.ts`) so both read as one product.
8
8
  *
9
+ * Two vocabulary rules hold throughout, and both come from the redesign brief:
10
+ *
11
+ * - **日志 means the JOB log** (安装日志与记录, the header's drawer). What a
12
+ * plugin release changed is 更新内容 / 版本日志 — never 日志 — so the two
13
+ * affordances can never be confused for one another.
14
+ * - **Counts are plugin-set counts.** "3 项插件可更新" counts compatible
15
+ * `update` + `install` items; the runtime is always named separately
16
+ * ("1 项运行时可升级") because it is a different, riskier action.
17
+ *
9
18
  * @module
10
19
  */
11
20
 
12
21
  const zh = {
13
22
  entryLabel: '插件更新',
14
- entryTooltip: '查看可用的插件更新',
23
+ entryTooltip: '查看可用的插件与运行时更新',
15
24
  viewTitle: '插件更新',
16
- viewIntro: '检查并安装可用的插件更新',
17
- viewInfo: '在这里查看插件集的更新;已是最新的插件无需操作。',
18
25
  refresh: '刷新',
19
26
  close: '关闭',
27
+ cancel: '取消',
20
28
  loading: '正在读取插件更新…',
21
29
  empty: '没有可用的插件更新',
22
30
  checkFailed: '检查更新失败,请重试',
23
- // The button installs the ENTIRE offered set at once (not one card) — label
24
- // it honestly rather than per-plugin.
25
- installAll: (count: number) => `安装整套更新(${count} 项)`,
26
- installOne: '安装整套',
27
- installOnePlugin: '安装此插件',
28
- installOneTip: '将安装整套 N 项更新并自动重启 DSH,而不仅是此插件',
29
- installing: '正在安装…(DSH 将自动重启)',
30
- retry: '重试',
31
- checkedAt: (when: string) => `上次检查:${when}`,
31
+ checkFailedTitle: '检查更新失败',
32
32
  checkFailedBanner: (message: string) => `检查更新失败:${message}`,
33
- staleNote: '以下为上次成功检查的结果,可能已过期',
34
- nothingToInstall: '暂无可安装的更新',
33
+ staleNote: '以下为上次成功检查的结果(可能已过期)',
34
+ retry: '重试',
35
+ retryNow: '重新尝试',
35
36
  ignore: '忽略此版本',
36
37
  unignore: '恢复提醒',
37
- ignoredOffer: '已忽略当前插件集更新',
38
- groupCount: (count: number) => `${count} 个插件`,
38
+ installError: '安装失败',
39
+ installSuccess: '更新已安装,正在重启 DSH…',
40
+ ignoreError: '忽略失败',
41
+ confirmInstall: '确认安装',
42
+ lastError: '上次检查失败',
43
+ // Origin tags, shared by both source editors (hq-edge serves one type).
44
+ originOverride: '自定义',
45
+ originConfig: '默认配置',
46
+ originUnset: '未配置',
39
47
  group: {
40
- actionable: '待处理',
48
+ actionable: '待处理更新',
41
49
  ignored: '已忽略',
42
50
  current: '已是最新',
43
51
  },
@@ -54,7 +62,7 @@ const zh = {
54
62
  sizeLabel: '包大小',
55
63
  update: '安装运行时更新',
56
64
  updateConfirm: (version: string) =>
57
- `安装 HQ Edge 运行时 ${version}?HQ Edge 会先关闭并完成替换,然后重新启动。`,
65
+ `确认升级 HQ Edge 运行时至 v${version}?升级将自动关闭后台服务并完成替换,随后重启。`,
58
66
  // No fixed "takes one minute" claim: duration depends on download speed
59
67
  // and package size (the bundle is hundreds of MB).
60
68
  updating: '正在下载并校验运行时包…',
@@ -90,16 +98,7 @@ const zh = {
90
98
  inputLabel: '插件更新清单地址',
91
99
  placeholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json',
92
100
  },
93
- // Origin tags, shared by both source editors (hq-edge serves one type).
94
- originOverride: '自定义',
95
- originConfig: '默认配置',
96
- originUnset: '未配置',
97
- lastError: '上次检查失败',
98
- installError: '安装失败',
99
- installSuccess: '更新已安装,正在重启 DSH…',
100
- confirmInstall: '确认安装',
101
- cancel: '取消',
102
- // ── Durable update job (active / result / recent) ──
101
+ // ── Durable update job (active / result / log drawer) ──
103
102
  job: {
104
103
  kindPluginSet: '插件集更新',
105
104
  kindRuntime: 'HQ Edge 运行时更新',
@@ -130,7 +129,8 @@ const zh = {
130
129
  resultFailed: '更新失败',
131
130
  resultRolledBack: '更新已回滚',
132
131
  resultAbandoned: '更新未完成',
133
- retry: '重新尝试(发起新的更新)',
132
+ retry: '重新尝试',
133
+ retryNote: '将发起一次新的更新',
134
134
  recentTitle: '最近更新',
135
135
  stepsTitle: '各组件进度',
136
136
  logHint: (hint: string) => `日志标识:${hint}`,
@@ -144,26 +144,80 @@ const zh = {
144
144
  advanced: '高级:更新源',
145
145
  publishedAt: (when: string) => `发布于 ${when}`,
146
146
  },
147
- // ── Refactored dock layout (interactive_refactored_ui_mockup.html) ──
147
+ // ── Update-centre panel ──
148
148
  ui: {
149
149
  title: '更新中心',
150
- gearTooltip: '高级设置',
150
+ gearTooltip: '更新源配置',
151
+ // Header sub-line: "版本集 v0.6.14 · 14 分钟前".
152
+ setVersion: (v: string) => `版本集 v${v}`,
153
+ subJoined: (set: string, ago: string) => `${set} · ${ago}`,
154
+ checkedAgo: (ago: string) => `上次检查:${ago}`,
155
+ checkedUnknown: '尚未检查更新',
156
+ checkedNever: '尚未检查',
157
+ agoJust: '刚刚',
158
+ agoMinutes: (n: number) => `${n} 分钟前`,
159
+ agoHours: (n: number) => `${n} 小时前`,
160
+ agoDays: (n: number) => `${n} 天前`,
161
+ // Decision band
162
+ summaryPlugins: (n: number) => `${n} 项插件可更新`,
163
+ summaryRuntime: '1 项运行时可升级',
164
+ // Section disclosures
165
+ expandLogs: '展开版本日志',
166
+ collapseLogs: '折叠版本日志',
167
+ upToDateNote: '全部正常运行',
168
+ upToDateRow: (v: string) => `${v} · 已是最新`,
169
+ ignoredNote: '已忽略,不再提醒',
170
+ releaseTitle: '版本说明',
171
+ // Cards
172
+ singleUpdate: '单独更新',
173
+ singleInstall: '单独安装',
174
+ singleUpdateTip: '仅安装此插件并重启 DSH,不影响其他插件',
175
+ ignorePlugin: '忽略此插件',
176
+ restorePlugin: '恢复此插件',
177
+ changesToggle: '更新内容',
178
+ changesTitle: (v: string) => `v${v} 变更明细`,
179
+ // Footer
180
+ cta: (n: number) => `一键更新选中的 ${n} 项插件`,
181
+ ctaInstalling: '正在更新插件集中…',
182
+ ctaFailed: '检查更新失败,请重试',
183
+ ctaUpToDate: '已是最新版本',
184
+ ctaNoPlugins: '插件均已是最新',
185
+ ctaNoSet: '暂无可安装的更新',
186
+ footerCount: (n: number) => `准备安装 ${n} 项兼容插件`,
187
+ footerBusy: '后台处理中,请勿关闭 DSH',
188
+ footerFailed: '无法确认插件版本兼容性',
189
+ footerAllGood: '所有插件与运行时均正常',
190
+ footerRuntimeOnly: '运行时更新需在上方单独确认',
191
+ ignoreBtn: '忽略此版本',
192
+ unignoreBtn: '恢复版本提醒',
193
+ footnote: '更新将自动应用并重启 DSH 视图,不影响 EDA 图纸数据',
194
+ // Settings drawer
151
195
  settingsTitle: '更新源配置 (高级)',
152
196
  originDefault: '默认源',
153
197
  originCustom: '自定义源',
154
198
  pluginUrlLabel: '插件 Manifest URL',
155
199
  runtimeUrlLabel: '运行时 Manifest URL',
156
200
  saveAndRefresh: '保存并刷新',
157
- checkedAgo: (ago: string) => `上次检查: ${ago}`,
158
- checkedUnknown: '尚未检查更新',
159
- agoJust: '刚刚',
160
- agoMinutes: (n: number) => `${n} 分钟前`,
161
- agoHours: (n: number) => `${n} 小时前`,
162
- agoDays: (n: number) => `${n} 天前`,
163
- // Active-job banner
164
- jobPluginTitle: (v: string) => `正在升级全套插件 (v${v})`,
201
+ resetSource: '恢复默认',
202
+ // Log drawer
203
+ logShow: '查看任务日志',
204
+ logHide: '收起任务日志',
205
+ logNone: '暂无可查看的更新日志',
206
+ logBusy: '更新进行中,完成后方可查看日志',
207
+ logTitle: '安装日志与记录',
208
+ jobIdLabel: 'JOB_ID',
209
+ copyLogId: '复制日志 ID',
210
+ logCopied: '日志 ID 已复制到剪贴板',
211
+ logCopyFailed: '复制失败,请手动记录日志 ID',
212
+ viewLog: '查看日志',
213
+ stepSkipped: '已跳过',
214
+ stepPending: '待处理',
215
+ confirmRuntime: '确认升级并重启',
216
+ // Active job
217
+ jobPluginTitle: (v: string) => `正在升级插件集 (v${v})`,
165
218
  jobRuntimeTitle: (v: string) => `正在升级 HQ Edge 运行时 (v${v})`,
166
219
  jobSteps: ['下载', '校验', '替换', '重启'],
220
+ activeCloseNote: '关闭此面板不会取消更新,完成后将自动重启。',
167
221
  // Runtime result alert
168
222
  rollbackTitle: '上次 HQ Edge 运行时更新已自动回滚',
169
223
  failedTitle: '上次 HQ Edge 运行时更新失败',
@@ -174,27 +228,16 @@ const zh = {
174
228
  successInline: '上次运行时更新成功',
175
229
  // Runtime engine card
176
230
  runtimeEngine: 'HQ Edge 核心运行时',
177
- currentChip: (v: string) => `v${v} (当前)`,
178
- newVersion: (v: string) => `新版本 v${v} 可用`,
231
+ currentChip: (v: string) => `v${v} 当前`,
232
+ newVersion: (v: string) => `v${v} 可用`,
179
233
  breakingTag: '关键修复',
180
- sizeLine: (size: string, summary: string) => `体积: ${size}${summary ? ` · ${summary}` : ''}`,
234
+ sizeLine: (size: string, summary: string) => `体积 ${size}${summary ? ` • ${summary}` : ''}`,
181
235
  upgradeCore: '升级核心',
182
236
  runtimeRestartNote: '升级运行时需自动重启 KiCad 进程服务',
183
- // Plugin list
184
- pendingTitle: '待更新插件',
185
- allChangelog: '展开全部更新日志',
186
- changelogBtn: '日志',
187
- changelogTitle: '更新内容:',
188
- // Footer
189
- footerCount: (n: number) => `准备安装 ${n} 项兼容插件`,
190
- footerSet: (v: string) => `版本集: v${v}`,
191
- installAllSet: (v: string) => `更新插件集 (v${v})`,
192
- ignoreBtn: '忽略',
193
- footnote: '更新将自动应用并重启 DSH 视图,不影响 EDA 图纸数据',
194
237
  },
195
238
  }
196
239
 
197
- /** Compact a byte count for display: "约 12.3 MB" / "约 480 MB". */
240
+ /** Compact a byte count for display: "12.3 MB" / "480 MB". */
198
241
  export function formatMb(bytes: number): string {
199
242
  if (!Number.isFinite(bytes) || bytes <= 0) return ''
200
243
  const mb = bytes / (1024 * 1024)
@@ -206,31 +249,31 @@ export type UpdateCopy = typeof zh
206
249
 
207
250
  const en: UpdateCopy = {
208
251
  entryLabel: 'Plugin Updates',
209
- entryTooltip: 'Review available plugin updates',
252
+ entryTooltip: 'Review available plugin and runtime updates',
210
253
  viewTitle: 'Plugin Updates',
211
- viewIntro: 'Check for and install available plugin updates',
212
- viewInfo: 'Review the plugin set updates here. Plugins that are up to date need no action.',
213
254
  refresh: 'Refresh',
214
255
  close: 'Close',
256
+ cancel: 'Cancel',
215
257
  loading: 'Reading plugin updates…',
216
258
  empty: 'No plugin updates available',
217
259
  checkFailed: 'Failed to check for updates — please retry',
218
- installAll: (count: number) => `Install whole set (${count})`,
219
- installOne: 'Install set',
220
- installOnePlugin: 'Install this plugin',
221
- installOneTip: 'Installs the whole set of N updates and restarts DSH — not just this plugin',
222
- installing: 'Installing… (DSH will restart)',
223
- retry: 'Retry',
224
- checkedAt: (when: string) => `Last checked: ${when}`,
260
+ checkFailedTitle: 'Update check failed',
225
261
  checkFailedBanner: (message: string) => `Update check failed: ${message}`,
226
262
  staleNote: 'Showing the last successful check — it may be stale',
227
- nothingToInstall: 'Nothing to install right now',
263
+ retry: 'Retry',
264
+ retryNow: 'Try again',
228
265
  ignore: 'Ignore this release',
229
266
  unignore: 'Show again',
230
- ignoredOffer: 'The current plugin-set update is ignored',
231
- groupCount: (count: number) => `${count} plugin${count === 1 ? '' : 's'}`,
267
+ installError: 'Install failed',
268
+ installSuccess: 'Update installed — restarting DSH…',
269
+ ignoreError: 'Ignore failed',
270
+ confirmInstall: 'Confirm install',
271
+ lastError: 'Last check failed',
272
+ originOverride: 'Custom',
273
+ originConfig: 'Configured',
274
+ originUnset: 'Not configured',
232
275
  group: {
233
- actionable: 'Actionable',
276
+ actionable: 'Needs action',
234
277
  ignored: 'Ignored',
235
278
  current: 'Up to date',
236
279
  },
@@ -247,7 +290,7 @@ const en: UpdateCopy = {
247
290
  sizeLabel: 'Package size',
248
291
  update: 'Install runtime update',
249
292
  updateConfirm: (version: string) =>
250
- `Install HQ Edge runtime ${version}? HQ Edge shuts down to swap the runtime, then restarts.`,
293
+ `Upgrade the HQ Edge runtime to v${version}? The background service shuts down so the runtime can be swapped, then restarts.`,
251
294
  updating: 'Downloading and verifying the runtime bundle…',
252
295
  durationNote: 'Duration depends on download speed and package size; the runtime bundle is large, and HQ Edge restarts after the swap.',
253
296
  updateAccepted: 'Runtime update accepted — HQ Edge is restarting…',
@@ -259,7 +302,6 @@ const en: UpdateCopy = {
259
302
  rolledBack: 'Runtime update rolled back (previous version restored)',
260
303
  elevation: 'A system-wide install may show a UAC elevation prompt; declining it fails the swap and rolls back to the previous version.',
261
304
  },
262
- // ── Update-source editor ──
263
305
  sourceHint: 'Where the runtime update manifest is fetched from; saved changes take effect immediately',
264
306
  sourceEdit: 'Edit',
265
307
  sourceSave: 'Save',
@@ -269,7 +311,6 @@ const en: UpdateCopy = {
269
311
  sourceInputLabel: 'Runtime update manifest URL',
270
312
  sourcePlaceholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/edge-headless-release-manifest.json',
271
313
  },
272
- // ── Plugin release-manifest source editor ──
273
314
  pluginSource: {
274
315
  title: 'Plugin update source',
275
316
  hint: 'Where the plugin update manifest is fetched from; saved changes take effect immediately',
@@ -282,16 +323,6 @@ const en: UpdateCopy = {
282
323
  placeholder:
283
324
  'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json',
284
325
  },
285
- // Origin tags, shared by both source editors (hq-edge serves one type).
286
- originOverride: 'Custom',
287
- originConfig: 'Configured',
288
- originUnset: 'Not configured',
289
- lastError: 'Last check failed',
290
- installError: 'Install failed',
291
- installSuccess: 'Update installed — restarting DSH…',
292
- confirmInstall: 'Confirm install',
293
- cancel: 'Cancel',
294
- // ── Durable update job (active / result / recent) ──
295
326
  job: {
296
327
  kindPluginSet: 'Plugin-set update',
297
328
  kindRuntime: 'HQ Edge runtime update',
@@ -321,12 +352,12 @@ const en: UpdateCopy = {
321
352
  resultFailed: 'Update failed',
322
353
  resultRolledBack: 'Update rolled back',
323
354
  resultAbandoned: 'Update did not complete',
324
- retry: 'Retry (start a new update)',
355
+ retry: 'Retry',
356
+ retryNote: 'Starts a new update',
325
357
  recentTitle: 'Recent updates',
326
358
  stepsTitle: 'Per-component progress',
327
359
  logHint: (hint: string) => `Log id: ${hint}`,
328
360
  },
329
- // ── Release metadata ──
330
361
  release: {
331
362
  summaryTitle: 'Release notes',
332
363
  notesLink: 'View release notes',
@@ -335,25 +366,71 @@ const en: UpdateCopy = {
335
366
  advanced: 'Advanced: update sources',
336
367
  publishedAt: (when: string) => `Published ${when}`,
337
368
  },
338
- // ── Refactored dock layout (interactive_refactored_ui_mockup.html) ──
339
369
  ui: {
340
370
  title: 'Update Center',
341
- gearTooltip: 'Advanced settings',
342
- settingsTitle: 'Update source (advanced)',
343
- originDefault: 'Default source',
344
- originCustom: 'Custom source',
345
- pluginUrlLabel: 'Plugin manifest URL',
346
- runtimeUrlLabel: 'Runtime manifest URL',
347
- saveAndRefresh: 'Save & refresh',
371
+ gearTooltip: 'Update sources',
372
+ setVersion: (v: string) => `Set v${v}`,
373
+ subJoined: (set: string, ago: string) => `${set} · ${ago}`,
348
374
  checkedAgo: (ago: string) => `Last checked: ${ago}`,
349
375
  checkedUnknown: 'No check yet',
376
+ checkedNever: 'never',
350
377
  agoJust: 'just now',
351
378
  agoMinutes: (n: number) => `${n} min ago`,
352
379
  agoHours: (n: number) => `${n} h ago`,
353
380
  agoDays: (n: number) => `${n} d ago`,
381
+ summaryPlugins: (n: number) => `${n} plugin${n === 1 ? '' : 's'} can be updated`,
382
+ summaryRuntime: '1 runtime can be upgraded',
383
+ expandLogs: 'Expand changelogs',
384
+ collapseLogs: 'Collapse changelogs',
385
+ upToDateNote: 'All running normally',
386
+ upToDateRow: (v: string) => `${v} · up to date`,
387
+ ignoredNote: 'Ignored, no longer offered',
388
+ releaseTitle: 'Release notes',
389
+ singleUpdate: 'Update this one',
390
+ singleInstall: 'Install this one',
391
+ singleUpdateTip: 'Installs only this plugin and restarts DSH — the rest of the set is untouched',
392
+ ignorePlugin: 'Ignore this plugin',
393
+ restorePlugin: 'Offer this plugin again',
394
+ changesToggle: 'What changed',
395
+ changesTitle: (v: string) => `v${v} changes`,
396
+ cta: (n: number) => `Update the ${n} selected plugin${n === 1 ? '' : 's'}`,
397
+ ctaInstalling: 'Updating the plugin set…',
398
+ ctaFailed: 'Check failed — retry first',
399
+ ctaUpToDate: 'Everything up to date',
400
+ ctaNoPlugins: 'All plugins are current',
401
+ ctaNoSet: 'Nothing to install yet',
402
+ footerCount: (n: number) => `Ready to install ${n} compatible plugin${n === 1 ? '' : 's'}`,
403
+ footerBusy: 'Running in the background — keep DSH open',
404
+ footerFailed: 'Cannot confirm plugin compatibility',
405
+ footerAllGood: 'All plugins and the runtime are current',
406
+ footerRuntimeOnly: 'Confirm the runtime upgrade on its card',
407
+ ignoreBtn: 'Ignore this release',
408
+ unignoreBtn: 'Offer it again',
409
+ footnote: 'Updates apply automatically and restart the DSH view — your EDA schematic data is untouched',
410
+ settingsTitle: 'Update source (advanced)',
411
+ originDefault: 'Default source',
412
+ originCustom: 'Custom source',
413
+ pluginUrlLabel: 'Plugin manifest URL',
414
+ runtimeUrlLabel: 'Runtime manifest URL',
415
+ saveAndRefresh: 'Save & refresh',
416
+ resetSource: 'Reset to default',
417
+ logShow: 'Show the job log',
418
+ logHide: 'Hide the job log',
419
+ logNone: 'No update log to show',
420
+ logBusy: 'Update in progress — the log is available when it finishes',
421
+ logTitle: 'Install log & history',
422
+ jobIdLabel: 'JOB_ID',
423
+ copyLogId: 'Copy log id',
424
+ logCopied: 'Log id copied to the clipboard',
425
+ logCopyFailed: 'Could not copy — please note the log id',
426
+ viewLog: 'View log',
427
+ stepSkipped: 'skipped',
428
+ stepPending: 'pending',
429
+ confirmRuntime: 'Confirm & restart',
354
430
  jobPluginTitle: (v: string) => `Updating plugin set (v${v})`,
355
431
  jobRuntimeTitle: (v: string) => `Upgrading HQ Edge runtime (v${v})`,
356
432
  jobSteps: ['Download', 'Verify', 'Swap', 'Restart'],
433
+ activeCloseNote: 'Closing this panel does not cancel the update; DSH restarts when it finishes.',
357
434
  rollbackTitle: 'Last HQ Edge runtime update rolled back automatically',
358
435
  failedTitle: 'Last HQ Edge runtime update failed',
359
436
  rollbackBody: (oldVer: string, code: string) =>
@@ -362,21 +439,12 @@ const en: UpdateCopy = {
362
439
  dismiss: 'Dismiss',
363
440
  successInline: 'Last runtime update succeeded',
364
441
  runtimeEngine: 'HQ Core Runtime',
365
- currentChip: (v: string) => `v${v} (current)`,
366
- newVersion: (v: string) => `New v${v} available`,
442
+ currentChip: (v: string) => `v${v} current`,
443
+ newVersion: (v: string) => `v${v} available`,
367
444
  breakingTag: 'Critical fix',
368
- sizeLine: (size: string, summary: string) => `Size: ${size}${summary ? ` · ${summary}` : ''}`,
445
+ sizeLine: (size: string, summary: string) => `Size ${size}${summary ? ` • ${summary}` : ''}`,
369
446
  upgradeCore: 'Upgrade core',
370
447
  runtimeRestartNote: 'Upgrading the runtime restarts the KiCad process service',
371
- pendingTitle: 'Plugins to update',
372
- allChangelog: 'Expand all changelogs',
373
- changelogBtn: 'Log',
374
- changelogTitle: 'What changed:',
375
- footerCount: (n: number) => `Ready to install ${n} compatible plugin${n === 1 ? '' : 's'}`,
376
- footerSet: (v: string) => `Set: v${v}`,
377
- installAllSet: (v: string) => `Update all plugins (v${v})`,
378
- ignoreBtn: 'Ignore',
379
- footnote: 'Updates apply automatically and restart the DSH view — your EDA schematic data is untouched',
380
448
  },
381
449
  }
382
450
 
@@ -51,6 +51,20 @@ export const REFRESH_ICON = (size = 16): string =>
51
51
  'M14.4999 1.5V5.1H10.8999',
52
52
  ])
53
53
 
54
+ /**
55
+ * The toolbar's log toggle: a page with ruled lines — "the update log".
56
+ *
57
+ * Not a shell primitive (the shell has no equivalent glyph), so it is drawn to
58
+ * the same 16×16 / 1px-stroke contract as the rest of this module.
59
+ */
60
+ export const LOG_ICON = (size = 16): string =>
61
+ glyph(size, REGULAR, [
62
+ 'M9.5 1.5H4.5C3.94772 1.5 3.5 1.94772 3.5 2.5V13.5C3.5 14.0523 3.94772 14.5 4.5 14.5H11.5C12.0523 14.5 12.5 14.0523 12.5 13.5V4.5L9.5 1.5Z',
63
+ 'M9.5 1.5V4.5H12.5',
64
+ 'M6 8.25H10',
65
+ 'M6 10.75H10',
66
+ ])
67
+
54
68
  /** The panel's close action (`IconCloseOutlineMedium`). */
55
69
  export const CLOSE_ICON = (size = 16): string =>
56
70
  glyph(size, MEDIUM, ['M2.5 2.5L13.5 13.5', 'M13.5 2.5L2.5 13.5'])
@@ -89,3 +103,34 @@ export const INFO_ICON = (size = 11): string =>
89
103
  /** A folded section's chevron (`IconChevronDownOutlineRegular`). */
90
104
  export const CHEVRON_DOWN_ICON = (size = 14): string =>
91
105
  glyph(size, REGULAR, ['M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6'])
106
+
107
+ /** A right-pointing chevron — a collapsed row, rotated by CSS when it opens. */
108
+ export const CHEVRON_RIGHT_ICON = (size = 14): string =>
109
+ glyph(size, REGULAR, ['M6 4L9.29289 7.29289C9.68342 7.68342 9.68342 8.31658 9.29289 8.70711L6 12'])
110
+
111
+ /**
112
+ * The per-card mute action: an eye with a slash (忽略此插件).
113
+ *
114
+ * Drawn here rather than borrowed: the shell's icon set has no
115
+ * "stop offering this" glyph, and a bare 忽略 text button competed with the
116
+ * card's primary action for the same 40 px.
117
+ */
118
+ export const EYE_OFF_ICON = (size = 14): string =>
119
+ glyph(size, REGULAR, [
120
+ 'M1.5 8C3 4.6 5.4 3 8 3C10.6 3 13 4.6 14.5 8C13 11.4 10.6 13 8 13C5.4 13 3 11.4 1.5 8Z',
121
+ 'M10 8C10 9.10457 9.10457 10 8 10C6.89543 10 6 9.10457 6 8C6 6.89543 6.89543 6 8 6C9.10457 6 10 6.89543 10 8Z',
122
+ 'M2.5 2.5L13.5 13.5',
123
+ ])
124
+
125
+ /**
126
+ * Copy-to-clipboard (the log drawer's 复制日志 ID): two offset rounded frames.
127
+ */
128
+ export const COPY_ICON = (size = 14): string =>
129
+ glyph(size, REGULAR, [
130
+ 'M5.5 5.5V2.5C5.5 2.22386 5.72386 2 6 2H13C13.2761 2 13.5 2.22386 13.5 2.5V9.5C13.5 9.77614 13.2761 10 13 10H10',
131
+ 'M2.5 6C2.5 5.72386 2.72386 5.5 3 5.5H10C10.2761 5.5 10.5 5.72386 10.5 6V13C10.5 13.2761 10.2761 13.5 10 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V6Z',
132
+ ])
133
+
134
+ /** The runtime card's badge: a bolt (the HQ Edge core distribution). */
135
+ export const BOLT_ICON = (size = 14): string =>
136
+ glyph(size, REGULAR, ['M9.33333 2L4.66667 8H8L6.66667 14L11.33333 8H8L9.33333 2Z'])