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

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/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../dsh-sidebar-entry/lib/index.mjs","../src/client/version.ts","../src/client/api.ts","../src/client/copy.ts","../src/client/icons.ts","../src/client/update-view.ts","../src/client/badge.ts","../src/client/index.ts"],"sourcesContent":["//#region src/index.ts\n/** Find the sidebar shell root element, or undefined while not yet mounted. */\nfunction sidebarRoot() {\n\tconst column = document.querySelector(\"[data-pane=\\\"sidebar\\\"], [class*=\\\"sidebarCol\\\"]\");\n\tif (column === null) return void 0;\n\treturn column.querySelector(\"[class*=\\\"logoRow\\\"]\")?.parentElement ?? column.firstElementChild;\n}\n/** The New Session button: nested in the logo row on current shells, a direct child on legacy shells. */\nfunction newSessionButton(root) {\n\tconst nested = root.querySelector(\"button[class*=\\\"newSession\\\"]\");\n\tif (nested !== null) return nested;\n\tfor (const child of root.children) if (child.tagName === \"BUTTON\") return child;\n}\n/** Build the entry row (a detached button; insert once the shell is up). */\nfunction createEntry(options) {\n\tconst entry = document.createElement(\"button\");\n\tentry.type = \"button\";\n\tentry.setAttribute(options.rowAttribute, \"\");\n\tif (options.plugin !== void 0) {\n\t\tentry.setAttribute(\"data-dsh-plugin\", options.plugin);\n\t\tentry.setAttribute(\"data-dsh-part\", \"sidebar-entry\");\n\t}\n\tentry.className = options.css[\"entry\"] ?? \"\";\n\tconst labelSpan = document.createElement(\"span\");\n\tlabelSpan.className = options.css[\"entryLabel\"] ?? \"\";\n\tconst iconSpan = document.createElement(\"span\");\n\ticonSpan.className = options.css[\"entryIcon\"] ?? \"\";\n\ticonSpan.innerHTML = options.icon;\n\tentry.append(iconSpan, labelSpan);\n\tlet badgeSpan = null;\n\tconst applyBadge = () => {\n\t\tif (options.badge === void 0) return;\n\t\tconst count = options.badge.count();\n\t\tif (count <= 0) {\n\t\t\tbadgeSpan?.remove();\n\t\t\tbadgeSpan = null;\n\t\t\tentry.removeAttribute(\"data-has-badge\");\n\t\t\treturn;\n\t\t}\n\t\tif (badgeSpan === null) {\n\t\t\tbadgeSpan = document.createElement(\"span\");\n\t\t\tbadgeSpan.className = options.css[\"entryBadge\"] ?? \"\";\n\t\t\tentry.append(badgeSpan);\n\t\t}\n\t\tbadgeSpan.textContent = String(count);\n\t\tentry.setAttribute(\"data-has-badge\", \"\");\n\t};\n\tconst applyLabel = () => {\n\t\tentry.setAttribute(\"aria-label\", options.label());\n\t\tif (options.tooltip !== void 0) entry.setAttribute(\"title\", options.tooltip());\n\t\tlabelSpan.textContent = options.label();\n\t};\n\tapplyLabel();\n\tapplyBadge();\n\tentry.addEventListener(\"click\", options.onToggle);\n\treturn {\n\t\tentry,\n\t\tapplyLabel,\n\t\tapplyBadge\n\t};\n}\n/**\n* Re-insert the entry after the New Session row, in the family's intended\n* order.\n*\n* The `familySelectors` array order is the single source of truth for visual\n* order, so the rendered order is stable regardless of which plugin mounts\n* first or of observer callback order — a re-render can never swap two rows.\n* (Earlier builds used the `position: 'before' | 'after'` convention, which\n* clustered rows by mount order and let a late-mounting `before` row steal the\n* lead. The array order supersedes it; `position` remains only as a fallback\n* for a row whose selector is absent from the family list.)\n*/\nfunction placeEntry(root, entry, options) {\n\tconst button = newSessionButton(root);\n\tif (button === void 0) return false;\n\tif (entry.parentElement !== root) {\n\t\tconst row = button.closest(\"[class*=\\\"logoRow\\\"]\");\n\t\tconst base = row !== null && row.parentElement === root ? row : button;\n\t\tconst order = options.familySelectors;\n\t\tconst present = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches(order.join(\", \"))).map((el) => ({\n\t\t\tel,\n\t\t\tidx: order.findIndex((sel) => el.matches(sel))\n\t\t}));\n\t\tconst selfIndex = order.indexOf(options.rowSelector);\n\t\tlet anchor;\n\t\tif (selfIndex === -1) anchor = options.position === \"before\" ? present.length > 0 ? present[0].el : base.nextElementSibling : present.length > 0 ? present[present.length - 1].el.nextElementSibling : base.nextElementSibling;\n\t\telse {\n\t\t\tconst later = present.find((member) => member.idx > selfIndex);\n\t\t\tif (later !== void 0) anchor = later.el;\n\t\t\telse {\n\t\t\t\tconst earlier = [...present].reverse().find((member) => member.idx < selfIndex);\n\t\t\t\tanchor = earlier !== void 0 ? earlier.el.nextElementSibling : base.nextElementSibling;\n\t\t\t}\n\t\t}\n\t\troot.insertBefore(entry, anchor ?? null);\n\t}\n\treturn true;\n}\n/**\n* Mount the sidebar entry, waiting for the shell to render and self-healing\n* on later React re-renders.\n* @param options - the row's attribute/icon/copy/action/ordering configuration.\n* @returns disposer removing the entry and its observers.\n*/\nfunction mountSidebarEntry(options) {\n\tif (typeof document !== \"undefined\" && document.querySelector(options.rowSelector) !== null) return () => {};\n\tconst { entry, applyLabel, applyBadge } = createEntry(options);\n\tlet root;\n\tlet placed = false;\n\tlet unsubscribeRefresh;\n\tif (options.refresh !== void 0) try {\n\t\tunsubscribeRefresh = options.refresh.subscribe(applyLabel);\n\t} catch {}\n\tlet unsubscribeBadge;\n\tif (options.badge !== void 0) try {\n\t\tunsubscribeBadge = options.badge.subscribe(applyBadge);\n\t} catch {}\n\tconst tryPlace = () => {\n\t\tif (root !== void 0 && !root.isConnected) {\n\t\t\trootObserver.disconnect();\n\t\t\troot = void 0;\n\t\t\tplaced = false;\n\t\t}\n\t\tif (placed) {\n\t\t\tif (document.body.contains(entry)) return;\n\t\t\trootObserver.disconnect();\n\t\t\troot = void 0;\n\t\t\tplaced = false;\n\t\t}\n\t\troot ??= sidebarRoot();\n\t\tif (root === void 0) return;\n\t\tplaced = placeEntry(root, entry, options);\n\t\tif (placed) rootObserver.observe(root, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true\n\t\t});\n\t};\n\tconst waitObserver = new MutationObserver(() => {\n\t\ttryPlace();\n\t});\n\twaitObserver.observe(document.body, {\n\t\tchildList: true,\n\t\tsubtree: true\n\t});\n\tconst rootObserver = new MutationObserver(() => {\n\t\tif (root === void 0 || !root.isConnected) {\n\t\t\tplaced = false;\n\t\t\ttryPlace();\n\t\t\treturn;\n\t\t}\n\t\tif (!root.contains(entry)) placed = placeEntry(root, entry, options);\n\t});\n\toptions.active === void 0 || (() => {\n\t\tconst syncActive = () => {\n\t\t\tif (options.active.isOpen()) entry.dataset.active = \"true\";\n\t\t\telse delete entry.dataset.active;\n\t\t};\n\t\tconst unsubscribe = options.active.subscribe(syncActive);\n\t\tsyncActive();\n\t\treturn unsubscribe;\n\t})();\n\ttryPlace();\n\treturn () => {\n\t\twaitObserver.disconnect();\n\t\trootObserver.disconnect();\n\t\tunsubscribeRefresh?.();\n\t\tunsubscribeBadge?.();\n\t\tentry.remove();\n\t};\n}\n/**\n* Every row selector in the Huaqiu sidebar family, in intended visual order.\n*\n* Family ordering is only stable when every participating plugin passes the\n* SAME list to {@link mountSidebarEntry} — the core positions a row relative to\n* the family block, never relative to transient shell geometry. Adding a new\n* family row means adding its selector here (and to no plugin-local list).\n*/\nconst HUAQIU_SIDEBAR_ENTRY_FAMILY = [\n\t\"[data-hqcg-footprint-entry]\",\n\t\"[data-hqcg-symbol-entry]\",\n\t\"[data-hqcg-schematic-entry]\",\n\t\"[data-hq-dummy-entry]\",\n\t\"[data-hq-plugin-update-entry]\"\n];\n/**\n* Read the host UI language from `<html lang>`.\n*\n* `dsh-client-locale` rewrites the attribute on every language change (dsh\n* web) and HQ Edge mirrors it, so this is the one source every family plugin\n* can read without importing a `locale` service.\n* @returns 'zh' / 'en', or undefined when the tag is absent or unsupported\n* (callers fall back to their own default).\n*/\nfunction detectHostLocale() {\n\tif (typeof document === \"undefined\") return void 0;\n\tconst tag = document.documentElement?.getAttribute(\"lang\");\n\tif (tag === null || tag === void 0 || tag === \"\") return void 0;\n\tconst primary = tag.toLowerCase().split(\"-\")[0];\n\treturn primary === \"zh\" || primary === \"en\" ? primary : void 0;\n}\n/**\n* Watch `<html lang>` and report every resolved change.\n*\n* Idempotent-safe: repeated calls share one observer and each caller gets its\n* own disposer; the observer disconnects when the last listener leaves.\n* @param listener - called with the newly resolved language.\n* @returns disposer removing this listener.\n*/\n/** Shared observer/listener set backing {@link observeHostLocale}. */\nconst localeListeners = /* @__PURE__ */ new Set();\nlet localeObserver = null;\nfunction observeHostLocale(listener) {\n\tif (typeof document === \"undefined\" || typeof MutationObserver === \"undefined\" || document.documentElement === null) return () => {};\n\tlet current = detectHostLocale();\n\tconst listeners = localeListeners;\n\tlisteners.add(listener);\n\tif (localeObserver === null) {\n\t\tlocaleObserver = new MutationObserver(() => {\n\t\t\tconst next = detectHostLocale();\n\t\t\tif (next === current) return;\n\t\t\tcurrent = next;\n\t\t\tfor (const fn of [...listeners]) try {\n\t\t\t\tfn(next);\n\t\t\t} catch {}\n\t\t});\n\t\tlocaleObserver.observe(document.documentElement, {\n\t\t\tattributes: true,\n\t\t\tattributeFilter: [\"lang\"]\n\t\t});\n\t}\n\treturn () => {\n\t\tlisteners.delete(listener);\n\t\tif (listeners.size === 0 && localeObserver !== null) {\n\t\t\tlocaleObserver.disconnect();\n\t\t\tlocaleObserver = null;\n\t\t}\n\t};\n}\n//#endregion\nexport { HUAQIU_SIDEBAR_ENTRY_FAMILY, detectHostLocale, mountSidebarEntry, observeHostLocale };\n","/**\n * SemVer direction gate for plugin-update items (browser half).\n *\n * The rule this module exists to enforce, for EVERY plugin in the report:\n * an item is only \"update available\" — and only gets an install control —\n * when the version offered by the release manifest is strictly GREATER than\n * the version installed in the active bundle. A manifest entry that merely\n * differs from the installed version offers nothing: equality is the\n * already-installed case, and a manifest that LAGS behind the installed\n * build (a stale mirror or a rollback channel publishing 0.6.2 against an\n * installed 0.6.4) must never surface as a downgrade \"update\".\n *\n * Why this lives on the client even though hq-edge owns the decision: the\n * report crosses a process boundary, and an older hq-edge still carries the\n * old \"differs ⇒ update\" rule — which rendered `analytics 可更新\n * 0.6.4 → 0.6.2` with a live 安装 button for the whole plugin set. This is\n * the same defensive second pass the badge already applies to the ignored\n * offer: one authoritative decision upstream, one guard where it is shown.\n *\n * The comparator is a strict SemVer 2.0.0 implementation (no dependency —\n * this package ships no runtime dependencies beyond the workspace log +\n * sidebar helpers). Anything that is not clean SemVer on either side makes\n * the comparison `null`, and an unprovable direction fails CLOSED: the item\n * reads as current, never as an update.\n *\n * @module\n */\nimport type { PluginUpdateItem, PluginUpdateReport } from './api.js'\n\n/** Strict SemVer 2.0.0: MAJOR.MINOR.PATCH[-prerelease][+build]. */\nconst SEMVER_RE =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/\n\ninterface ParsedVersion {\n major: number\n minor: number\n patch: number\n /** Prerelease identifiers (empty = a release, which outranks any prerelease). */\n prerelease: string[]\n}\n\n/** Parse a strict SemVer string, or null when it is not one. */\nfunction parseSemver(version: string): ParsedVersion | null {\n const match = SEMVER_RE.exec(version.trim())\n if (match === null) return null\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n prerelease: match[4] === undefined ? [] : match[4].split('.'),\n }\n}\n\n/** True when one prerelease identifier is purely numeric. */\nfunction isNumericIdentifier(id: string): boolean {\n return /^\\d+$/.test(id)\n}\n\n/**\n * Compare two prerelease identifier lists per SemVer §11:\n * numeric identifiers compare numerically and rank below alphanumeric ones;\n * a larger set of identifiers ranks higher when all shared ones are equal.\n */\nfunction comparePrerelease(a: string[], b: string[]): number {\n // No prerelease outranks any prerelease.\n if (a.length === 0 && b.length === 0) return 0\n if (a.length === 0) return 1\n if (b.length === 0) return -1\n const shared = Math.min(a.length, b.length)\n for (let index = 0; index < shared; index += 1) {\n const left = a[index]\n const right = b[index]\n if (left === undefined || right === undefined) continue\n if (left === right) continue\n const leftNumeric = isNumericIdentifier(left)\n const rightNumeric = isNumericIdentifier(right)\n if (leftNumeric && rightNumeric) {\n return Number(left) < Number(right) ? -1 : 1\n }\n if (leftNumeric !== rightNumeric) {\n // Numeric identifiers always have lower precedence than alphanumeric.\n return leftNumeric ? -1 : 1\n }\n return left < right ? -1 : 1\n }\n return a.length === b.length ? 0 : a.length < b.length ? -1 : 1\n}\n\n/**\n * Compare two strict SemVer strings: negative when `a < b`, positive when\n * `a > b`, 0 when equal, and null when either side is not SemVer (build\n * metadata is ignored in precedence, per spec).\n */\nexport function compareSemver(a: string, b: string): number | null {\n const left = parseSemver(a)\n const right = parseSemver(b)\n if (left === null || right === null) return null\n if (left.major !== right.major) return left.major < right.major ? -1 : 1\n if (left.minor !== right.minor) return left.minor < right.minor ? -1 : 1\n if (left.patch !== right.patch) return left.patch < right.patch ? -1 : 1\n return comparePrerelease(left.prerelease, right.prerelease)\n}\n\n/**\n * Whether the offered release is an actionable upgrade over the installed\n * version: an absent plugin (nothing installed) is always installable, and\n * an installed one only when the offered SemVer is strictly greater. An\n * unprovable direction (non-SemVer on either side) is NOT an upgrade.\n */\nexport function isVersionUpgrade(currentVersion: string | null, availableVersion: string): boolean {\n if (currentVersion === null) return true\n const order = compareSemver(availableVersion, currentVersion)\n return order !== null && order > 0\n}\n\n/**\n * Re-evaluate one report item against the direction rule.\n *\n * A server-reported `update` that is not a strict SemVer upgrade is demoted\n * to `current`: the plugin is at (or ahead of) the offered release, there is\n * nothing to install, and no install control may be offered for it. Every\n * other state passes through untouched — `install` (plugin absent locally)\n * has no local version to compare, and `ignored`/`current` are already\n * non-actionable. Unknown server fields are preserved verbatim: the\n * client stays a thin reader of the hq-edge report shape.\n */\nexport function normalizePluginUpdateItem<T extends PluginUpdateItem>(item: T): T {\n if (item.state !== 'update') return item\n if (isVersionUpgrade(item.currentVersion, item.availableVersion)) return item\n return { ...item, state: 'current' }\n}\n\n/** States that carry an install action (the badge/actionable definition). */\nfunction isActionableState(item: PluginUpdateItem): boolean {\n // A muted item carries no action even if an older hq-edge left its state at\n // `update` (it also flags the item, and the whole set, when it can).\n if (item.ignored === true) return false\n return item.state === 'update' || item.state === 'install'\n}\n\n/**\n * How many PLUGIN updates a report makes actionable — the ONE definition the\n * footer bar, the panel's sections and `actionableCount` share, so they can\n * never disagree about how much plugin work is left.\n *\n * Deliberately narrow (spec §6.3/§7.1):\n * - compatible `update` + `install` items only;\n * - nothing once the user has muted the offer (`ignoredSet`), and nothing\n * for an individually-ignored item;\n * - plugin items only. The HQ Edge runtime update is a separate deliverable\n * with its own card, so it is not a plugin item here — the sidebar badge\n * adds it on top, once, in `badge.ts`. Never fold it into this number:\n * that is what made the footer claim \"准备安装 N 项兼容插件\" one too many.\n *\n * Note what is absent: no durable-job input. The job history is permanent\n * (`listRecent(20)`), so any rule that counts a settled job latches on forever.\n */\nexport function actionableUpdateCount(report: PluginUpdateReport | null | undefined): number {\n if (report === null || report === undefined) return 0\n // The whole offered set is muted. An older hq-edge sets this flag but leaves\n // the item states at `update`, so it must be checked before counting.\n if (report.ignoredSet === true) return 0\n if (!Array.isArray(report.items)) return 0\n let count = 0\n for (const item of report.items) {\n if (isActionableState(item)) count += 1\n }\n return count\n}\n\n/**\n * Apply {@link normalizePluginUpdateItem} to a whole report and recompute\n * `actionableCount` from the corrected states, so the server's count can\n * never disagree with what the panel and the sidebar badge render.\n */\nexport function normalizePluginUpdateReport<T extends PluginUpdateReport>(report: T): T {\n if (!Array.isArray(report.items)) return report\n const items = report.items.map(normalizePluginUpdateItem)\n const next = { ...report, items }\n return {\n ...next,\n actionableCount: actionableUpdateCount(next),\n }\n}\n","/**\n * Plugin-update API client (browser half).\n *\n * Every call is a same-origin fetch against the DSH webServer prefix route\n * (`/api/v1/huaqiu/plugin-update/…`) which the node half forwards to hq-edge.\n * Response shapes mirror hq-edge `apps/server/src/routes/plugin-updates.ts`.\n *\n * @module\n */\nimport { normalizePluginUpdateReport } from './version.js'\n\nexport const UPDATE_BASE = '/api/v1/huaqiu/plugin-update'\n\nexport interface PluginUpdateItem {\n name: string\n /** Version installed in the active bundle; null when the plugin is absent. */\n currentVersion: string | null\n availableVersion: string\n /**\n * Server state. `update` is re-checked on arrival against the SemVer\n * direction rule (`version.ts`): an offer that is not strictly newer than\n * `currentVersion` is demoted to `current`, so the panel and the badge can\n * never offer a downgrade even against an older hq-edge.\n */\n state: 'current' | 'update' | 'install' | 'blocked' | 'ignored'\n /** The block reason, or null when the item is not blocked. The server sends a\n * single code (`\"runtime-unknown\"`), so it is normalized before use. */\n blockedBy?: string | string[] | null\n ignored?: boolean\n reasons?: string[]\n notes?: string[]\n /**\n * User-facing release metadata (schemaVersion-2 manifests). ALL optional and\n * absent for v1 manifests — the view falls back to the package short name\n * and version line when they are missing.\n */\n displayName?: string\n description?: string\n sizeBytes?: number\n changes?: string[]\n}\n\n/**\n * One downloadable HQ Edge runtime distribution, as hq-edge reports it\n * (`RuntimeUpdateEntry` in `apps/server/src/dsh/PluginUpdateService.ts`).\n *\n * Note the shape: `runtime.latest` is this ENTRY OBJECT, not a bare version\n * string. Reading it as a string is what once rendered \"[object Object]\".\n */\nexport interface RuntimeUpdateEntry {\n /** Clean SemVer of the available distribution. */\n version: string\n /** SHA-256 of the release archive, lowercase hex (64 chars). */\n sha256: string\n /** Platform the archive targets: win | darwin | linux | all. */\n platform: string\n /** Optional CPU architecture: x64 | arm64 (absent = any). */\n arch?: string\n /** Exact absolute HTTPS URL of the release archive. */\n url: string\n /**\n * Optional user-facing metadata (schemaVersion-2 runtime manifest). All\n * absent for v1 manifests; the panel degrades gracefully without them.\n */\n publishedAt?: string\n sizeBytes?: number\n summary?: string\n notesUrl?: string\n notes?: string[]\n breaking?: string[]\n channel?: string\n}\n\n/**\n * The EDA host's own report of the last runtime replacement, read from\n * `runtime-update-result.json` by hq-edge. This is the authoritative outcome\n * after HQ Edge exits and the host swaps the runtime — it must be rendered,\n * not discarded. `state` is `succeeded`, `rolled_back` (swap failed but the\n * previous runtime was restored) or `failed` (no confirmed rollback).\n */\nexport interface RuntimeLastResult {\n state: 'succeeded' | 'rolled_back' | 'failed'\n targetVersion?: string | null\n installedVersion?: string | null\n at?: string\n errorCode?: string\n message?: string\n}\n\nexport interface RuntimeUpdateInfo {\n configured: boolean\n currentVersion?: string | null\n /** The latest compatible distribution, or null when the manifest had none. */\n latest?: RuntimeUpdateEntry | null\n updateAvailable: boolean\n checkedAt?: string | null\n lastError?: string | null\n /** The host-reported outcome of the last runtime swap; null when none. */\n lastResult?: RuntimeLastResult | null\n /**\n * Where the runtime-update manifest is read from. Optional: an older hq-edge\n * does not send it, and the panel must degrade to a read-only source row.\n */\n source?: RuntimeUpdateSource\n}\n\n/**\n * The runtime-update manifest source, editable from this panel.\n *\n * `origin` says which URL is in effect: `override` is a URL the user set here\n * (persisted by hq-edge per host), `config` is the packaged/env default, and\n * `unset` means runtime updates are not configured at all.\n */\nexport interface RuntimeUpdateSource {\n /** The manifest URL currently in effect; empty when none is known. */\n url: string\n origin: 'override' | 'config' | 'unset'\n /** The URL configuration supplies, independent of any user override. */\n configuredUrl: string\n /** True when a user override is shadowing the configured URL. */\n override: boolean\n}\n\n/**\n * The plugin release-manifest source (the gitee\n * `plugins-release-manifest.json` by default) — same shape and same edit flow\n * as {@link RuntimeUpdateSource}, because hq-edge serves both from one shared\n * type.\n */\nexport type PluginUpdateSource = RuntimeUpdateSource\n\n/**\n * The latest runtime version to display, or null when there is none.\n *\n * hq-edge sends the full release entry; an older or future server could send\n * a bare string. Both are accepted here so the facts row can never again fall\n * back to `String(object)` — i.e. \"[object Object]\" — in the DOM.\n */\nexport function runtimeLatestVersion(\n runtime: RuntimeUpdateInfo | null | undefined,\n): string | null {\n const latest: unknown = runtime?.latest\n if (typeof latest === 'string') return latest.length > 0 ? latest : null\n if (latest !== null && typeof latest === 'object' && 'version' in latest) {\n const version = (latest as { version?: unknown }).version\n if (typeof version === 'string' && version.length > 0) return version\n }\n return null\n}\n\n/**\n * Whether the report offers an HQ Edge runtime update the user can act on:\n * hq-edge reports that a newer distribution exists AND it actually named one.\n *\n * Both halves matter, and sharing one predicate keeps the sidebar badge and the\n * runtime card from disagreeing: the card can only render its 升级核心 row when\n * it knows the target version, so an `updateAvailable` without a `latest` entry\n * must not add a count anywhere. hq-edge already fails closed here (it only\n * sets the flag when `semver.gt(latest, current)` holds on a comparable pair),\n * so this is a shape guard, not a second compatibility decision.\n */\nexport function runtimeUpdateOffered(\n runtime: RuntimeUpdateInfo | null | undefined,\n): boolean {\n if (runtime === null || runtime === undefined) return false\n if (runtime.updateAvailable !== true) return false\n return runtimeLatestVersion(runtime) !== null\n}\n\n/**\n * Set-level release metadata from a schemaVersion-2 plugin manifest. ALL\n * fields optional: v1 manifests send none, and the panel must fall back to\n * showing just the version then.\n */\nexport interface ReleaseInfo {\n publishedAt?: string\n channel?: string\n requiresRestart?: boolean\n summary?: string\n notesUrl?: string\n notes?: string[]\n breaking?: string[]\n}\n\nexport interface PluginUpdateReport {\n /** The evaluated release set; null while no manifest has been read. */\n manifestVersion: string | null\n pluginSetVersion?: string\n items: PluginUpdateItem[]\n actionableCount: number\n ignoredSet: boolean\n fetchedAt?: string\n /** When the last plugin discovery ran (ISO); drives the \"上次检查\" row. */\n checkedAt?: string | null\n /** Non-empty when the last plugin discovery FAILED — the panel must show\n * an error row with a Retry, never the silent \"no updates\" empty state. */\n lastError?: string | null\n /** Set-level release metadata; absent for v1 manifests. */\n release?: ReleaseInfo | null\n runtime: RuntimeUpdateInfo\n /**\n * Where the plugin release manifest is read from. Optional: an older\n * hq-edge does not send it, and the panel must degrade to no source row.\n */\n pluginSource?: PluginUpdateSource\n}\n\n// ── Durable update-job lifecycle (GET /status, GET /status/:id) ────────────\n\n/** Lifecycle phases of one durable update job. Terminal phases end the poll. */\nexport type UpdatePhase =\n | 'queued'\n | 'resolving'\n | 'downloading'\n | 'verifying'\n | 'staging'\n | 'activating'\n | 'awaiting-host'\n | 'restarting'\n | 'verifying-after-restart'\n | 'done'\n | 'failed'\n | 'rolled-back'\n | 'abandoned'\n\n/** Phases after which a job never runs again — polling stops and a result view\n * replaces the active-job view. */\nexport const TERMINAL_PHASES: ReadonlySet<UpdatePhase> = new Set<UpdatePhase>([\n 'done',\n 'failed',\n 'rolled-back',\n 'abandoned',\n])\n\nexport type UpdateKind = 'plugin-set' | 'runtime'\n\nexport interface UpdateJobProgress {\n /** 0..100 when the phase has a meaningful percentage; null otherwise. */\n percent: number | null\n bytesDone?: number\n bytesTotal?: number\n bytesPerSecond?: number\n etaSeconds?: number\n}\n\nexport interface UpdateStep {\n name: string\n state: 'pending' | 'running' | 'installed' | 'skipped' | 'failed'\n percent?: number\n message?: string\n}\n\nexport interface UpdateJobResult {\n ok: boolean\n installedVersion: string | null\n errorCode?: string\n message?: string\n rolledBack: boolean\n finishedAt: string\n}\n\n/**\n * One durable update job, as hq-edge reports it (`GET /status` and\n * `/status/:id`). The UI is a projection of this job: opening the panel\n * resumes whatever the server says is active, rather than owning the\n * lifecycle in a DOM closure.\n */\nexport interface UpdateJob {\n id: string\n kind: UpdateKind\n targetVersion: string\n previousVersion: string | null\n startedAt: string\n updatedAt: string\n phase: UpdatePhase\n message?: string\n progress: UpdateJobProgress\n steps?: UpdateStep[]\n result?: UpdateJobResult\n logHint?: string\n}\n\n/** `GET /status`: the current active job plus a bounded list of recent ones. */\nexport interface UpdateJobStatus {\n activeJob: UpdateJob | null\n recentJobs: UpdateJob[]\n}\n\nconst PHASE_VALUES: ReadonlySet<string> = new Set<string>([\n 'queued',\n 'resolving',\n 'downloading',\n 'verifying',\n 'staging',\n 'activating',\n 'awaiting-host',\n 'restarting',\n 'verifying-after-restart',\n 'done',\n 'failed',\n 'rolled-back',\n 'abandoned',\n])\n\nfunction asStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined\n const out = value.filter((entry): entry is string => typeof entry === 'string')\n return out.length > 0 ? out : undefined\n}\n\nfunction asFiniteNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined\n}\n\n/** Defensively normalize one job payload from the wire into {@link UpdateJob}.\n * Unknown/older servers may omit fields; a malformed `phase` falls back to\n * `queued` rather than crashing the view. */\nexport function normalizeJob(raw: unknown): UpdateJob | null {\n if (raw === null || typeof raw !== 'object') return null\n const r = raw as Record<string, unknown>\n if (typeof r.id !== 'string' || r.id.length === 0) return null\n const phase = typeof r.phase === 'string' && PHASE_VALUES.has(r.phase)\n ? (r.phase as UpdatePhase)\n : 'queued'\n const progressRaw = (r.progress ?? {}) as Record<string, unknown>\n const percentRaw = asFiniteNumber(progressRaw.percent)\n const progress: UpdateJobProgress = {\n percent: percentRaw === undefined ? null : Math.min(100, Math.max(0, percentRaw)),\n }\n const bytesDone = asFiniteNumber(progressRaw.bytesDone)\n const bytesTotal = asFiniteNumber(progressRaw.bytesTotal)\n const bytesPerSecond = asFiniteNumber(progressRaw.bytesPerSecond)\n const etaSeconds = asFiniteNumber(progressRaw.etaSeconds)\n if (bytesDone !== undefined) progress.bytesDone = bytesDone\n if (bytesTotal !== undefined) progress.bytesTotal = bytesTotal\n if (bytesPerSecond !== undefined) progress.bytesPerSecond = bytesPerSecond\n if (etaSeconds !== undefined) progress.etaSeconds = etaSeconds\n\n const job: UpdateJob = {\n id: r.id,\n kind: r.kind === 'runtime' ? 'runtime' : 'plugin-set',\n targetVersion: typeof r.targetVersion === 'string' ? r.targetVersion : '',\n previousVersion:\n typeof r.previousVersion === 'string' ? r.previousVersion : null,\n startedAt: typeof r.startedAt === 'string' ? r.startedAt : '',\n updatedAt: typeof r.updatedAt === 'string' ? r.updatedAt : '',\n phase,\n progress,\n }\n if (typeof r.message === 'string' && r.message.length > 0) job.message = r.message\n if (typeof r.logHint === 'string' && r.logHint.length > 0) job.logHint = r.logHint\n\n if (Array.isArray(r.steps)) {\n const steps: UpdateStep[] = []\n for (const entry of r.steps) {\n if (entry === null || typeof entry !== 'object') continue\n const s = entry as Record<string, unknown>\n if (typeof s.name !== 'string') continue\n const state =\n s.state === 'running' || s.state === 'installed' || s.state === 'skipped' || s.state === 'failed'\n ? s.state\n : 'pending'\n const step: UpdateStep = { name: s.name, state }\n const pct = asFiniteNumber(s.percent)\n if (pct !== undefined) step.percent = pct\n if (typeof s.message === 'string' && s.message.length > 0) step.message = s.message\n steps.push(step)\n }\n if (steps.length > 0) job.steps = steps\n }\n\n if (r.result !== null && typeof r.result === 'object') {\n const res = r.result as Record<string, unknown>\n const result: UpdateJobResult = {\n ok: res.ok === true,\n installedVersion:\n typeof res.installedVersion === 'string' ? res.installedVersion : null,\n rolledBack: res.rolledBack === true,\n finishedAt: typeof res.finishedAt === 'string' ? res.finishedAt : '',\n }\n if (typeof res.errorCode === 'string' && res.errorCode.length > 0) result.errorCode = res.errorCode\n if (typeof res.message === 'string' && res.message.length > 0) result.message = res.message\n job.result = result\n }\n return job\n}\n\n/** Normalize a `GET /status` payload; an empty/absent body degrades to \"no\n * active job\" rather than throwing. */\nexport function normalizeJobStatus(raw: unknown): UpdateJobStatus {\n if (raw === null || typeof raw !== 'object') return { activeJob: null, recentJobs: [] }\n const r = raw as Record<string, unknown>\n const activeJob = normalizeJob(r.activeJob)\n const recentJobs = Array.isArray(r.recentJobs)\n ? r.recentJobs.map(normalizeJob).filter((j): j is UpdateJob => j !== null)\n : []\n return { activeJob, recentJobs }\n}\n\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n const res = await fetch(`${UPDATE_BASE}${path}`, {\n cache: 'no-store',\n ...init,\n headers: { 'content-type': 'application/json; charset=utf-8', ...(init?.headers ?? {}) },\n })\n const body = (await res.json().catch(() => null)) as T | null\n if (!res.ok) {\n const err = (body as { error?: string } | null)?.error ?? `HTTP ${res.status}`\n throw new Error(err)\n }\n if (body === null) throw new Error('empty response')\n return body\n}\n\n/** GET the cached (or empty-shell) plugin-update report. */\nexport function fetchReport(): Promise<PluginUpdateReport> {\n return request<PluginUpdateReport>('/report').then(normalizePluginUpdateReport)\n}\n\n/** POST an explicit refresh; returns the freshly computed report. */\nexport function checkUpdates(): Promise<PluginUpdateReport> {\n return request<PluginUpdateReport>('/check', { method: 'POST' }).then(normalizePluginUpdateReport)\n}\n\nexport interface InstallResult {\n /**\n * New durable contract: `202 { jobId, state: 'accepted' }`. The install is\n * now an async job — poll `/status/:jobId` for progress, don't read success\n * off this response. Legacy boolean fields are kept for older hq-edge.\n */\n jobId?: string\n state?: string\n ok?: boolean\n installedSetVersion?: string\n installed?: number\n skipped?: number\n restarting?: boolean\n error?: string\n}\n\n/** POST the install transaction for the confirmed set version. */\nexport function installSet(setVersion: string, pluginNames?: string[]): Promise<InstallResult> {\n return request<InstallResult>('/install', {\n method: 'POST',\n body: JSON.stringify({ setVersion, ...(pluginNames ? { pluginNames } : {}) }),\n })\n}\n\n/** Persist a per-plugin decision for this release set. */\nexport function ignorePlugin(setVersion: string, name: string): Promise<unknown> {\n return request<unknown>('/plugin-ignore', { method: 'POST', body: JSON.stringify({ setVersion, name }) })\n}\n\nexport function unignorePlugin(setVersion: string, name: string): Promise<unknown> {\n return request<unknown>('/plugin-unignore', { method: 'POST', body: JSON.stringify({ setVersion, name }) })\n}\n\n/** POST ignore for the current offer (set-scoped, persists across restarts). */\nexport function ignoreOffer(setVersion: string): Promise<unknown> {\n return request<unknown>('/ignore', { method: 'POST', body: JSON.stringify({ setVersion }) })\n}\n\n/**\n * The outcome of a user-confirmed HQ Edge runtime update.\n *\n * hq-edge answers `202 { requestId, targetVersion, state: 'accepted' }` and\n * then shuts itself down for the EDA host to swap the runtime, so a success\n * here means \"accepted\", not \"installed\". Failures arrive as 409 (already\n * updating → `busy`), 422 (preflight/manifest/download refused) or 503.\n */\nexport interface RuntimeUpdateRequestResult {\n /** Durable job id — poll `/status/:jobId` for the accepted job's progress. */\n jobId?: string\n requestId?: string\n targetVersion?: string\n state?: string\n ok?: boolean\n error?: string\n busy?: boolean\n}\n\n/** POST the user-confirmed HQ Edge runtime update for one target version. */\nexport function requestRuntimeUpdate(\n targetVersion: string,\n): Promise<RuntimeUpdateRequestResult> {\n return request<RuntimeUpdateRequestResult>('/runtime-request', {\n method: 'POST',\n body: JSON.stringify({ targetVersion }),\n })\n}\n\n/** POST unignore for the set version (reverses an ignore). */\nexport function unignoreOffer(setVersion: string): Promise<unknown> {\n return request<unknown>('/unignore', { method: 'POST', body: JSON.stringify({ setVersion }) })\n}\n\n/**\n * GET the runtime-update manifest source: the URL in effect, its origin\n * (`override` | `config` | `unset`) and the configured URL behind it.\n *\n * Read-only upstream, so this is open like `/report` — only the mutation\n * below is loopback-guarded (and reaches it same-origin anyway).\n */\nexport function getRuntimeUpdateSource(): Promise<RuntimeUpdateSource> {\n return request<RuntimeUpdateSource>('/runtime-source')\n}\n\n/**\n * POST a new runtime-update manifest URL, or `\"\"` to clear a user override\n * and fall back to the configured URL.\n *\n * hq-edge validates the URL itself (`https` required except for loopback, no\n * embedded credentials) and answers 422 with a message meant for a person —\n * this client surfaces it verbatim rather than pre-validating, so the rule\n * lives in exactly one place. A successful answer is the NEW source; the\n * caller must then re-run discovery, because the cached report still names\n * the old manifest's offer.\n */\nexport function setRuntimeUpdateSource(url: string): Promise<RuntimeUpdateSource> {\n return request<RuntimeUpdateSource>('/runtime-source', {\n method: 'POST',\n body: JSON.stringify({ url }),\n })\n}\n\n/**\n * GET the plugin release-manifest source: the URL discovery reads, its origin\n * (`override` | `config` | `unset`) and the configured URL behind it. Same\n * open/read-only contract as the runtime source.\n */\nexport function getPluginUpdateSource(): Promise<PluginUpdateSource> {\n return request<PluginUpdateSource>('/plugin-source')\n}\n\n/**\n * POST a new plugin release-manifest URL, or `\"\"` to clear a user override.\n *\n * hq-edge persists the override per host AND re-runs discovery in the\n * background, so the next report (and the sidebar badge) already reflect the\n * new manifest. This client still re-checks explicitly after a save — the\n * background pass is a safety net for other consumers, not a substitute.\n */\nexport function setPluginUpdateSource(url: string): Promise<PluginUpdateSource> {\n return request<PluginUpdateSource>('/plugin-source', {\n method: 'POST',\n body: JSON.stringify({ url }),\n })\n}\n\n/**\n * GET the durable job status: the currently active job (if any) plus a bounded\n * list of recent jobs. This is what the panel reads BEFORE running discovery\n * on open, so an in-flight install/runtime update is resumed instead of\n * forgotten. An older hq-edge without the status route 404s — callers catch\n * that and degrade to \"no active job\".\n */\nexport function fetchJobStatus(): Promise<UpdateJobStatus> {\n return request<unknown>('/status').then(normalizeJobStatus)\n}\n\n/** GET one job by id; rejects (upstream 404 `unknown job`) for an unknown id. */\nexport function fetchJob(id: string): Promise<UpdateJob> {\n return request<unknown>(`/status/${encodeURIComponent(id)}`).then((raw) => {\n const job = normalizeJob(raw)\n if (job === null) throw new Error('malformed job payload')\n return job\n })\n}\n","/**\n * Localized copy for the plugin-update UI (browser half).\n *\n * zh is the fallback, matching the rest of the Huaqiu plugin family\n * (`<html lang>` drives the resolution via the shared sidebar-entry locale\n * helpers). The wording follows the shell's own plugin manager\n * (`ui-plugin-manager/src/client/locales.ts`) so both read as one product.\n *\n * Two vocabulary rules hold throughout, and both come from the redesign brief:\n *\n * - **日志 means the JOB log** (安装日志与记录, the header's drawer). What a\n * plugin release changed is 更新内容 / 版本日志 — never 日志 — so the two\n * affordances can never be confused for one another.\n * - **Counts are plugin-set counts.** \"3 项插件可更新\" counts compatible\n * `update` + `install` items; the runtime is always named separately\n * (\"1 项运行时可升级\") because it is a different, riskier action.\n *\n * @module\n */\n\nconst zh = {\n entryLabel: '插件更新',\n entryTooltip: '查看可用的插件与运行时更新',\n viewTitle: '插件更新',\n refresh: '刷新',\n close: '关闭',\n cancel: '取消',\n loading: '正在读取插件更新…',\n empty: '没有可用的插件更新',\n checkFailed: '检查更新失败,请重试',\n checkFailedTitle: '检查更新失败',\n checkFailedBanner: (message: string) => `检查更新失败:${message}`,\n staleNote: '以下为上次成功检查的结果(可能已过期)',\n retry: '重试',\n retryNow: '重新尝试',\n ignore: '忽略此版本',\n unignore: '恢复提醒',\n installError: '安装失败',\n installSuccess: '更新已安装,正在重启 DSH…',\n ignoreError: '忽略失败',\n confirmInstall: '确认安装',\n lastError: '上次检查失败',\n // Origin tags, shared by both source editors (hq-edge serves one type).\n originOverride: '自定义',\n originConfig: '默认配置',\n originUnset: '未配置',\n group: {\n actionable: '待处理更新',\n ignored: '已忽略',\n current: '已是最新',\n },\n runtime: {\n title: 'HQ Edge 运行时',\n current: '当前版本',\n latest: '最新版本',\n set: '发布插件集',\n checkedAt: '上次检查',\n source: '更新源',\n unknown: '版本未知',\n notConfigured: '未配置更新源',\n note: '运行时更新由 EDA 安装器处理',\n sizeLabel: '包大小',\n update: '安装运行时更新',\n updateConfirm: (version: string) =>\n `确认升级 HQ Edge 运行时至 v${version}?升级将自动关闭后台服务并完成替换,随后重启。`,\n // No fixed \"takes one minute\" claim: duration depends on download speed\n // and package size (the bundle is hundreds of MB).\n updating: '正在下载并校验运行时包…',\n durationNote: '耗时取决于下载速度与包体大小;运行时包较大,替换后 HQ Edge 会自动重启。',\n updateAccepted: '运行时更新已受理,HQ Edge 正在重启…',\n size: (bytes: number) => `约 ${formatMb(bytes)}`,\n lastResult: {\n title: '上次运行时更新结果',\n succeeded: '运行时更新成功',\n failed: '运行时更新失败',\n rolledBack: '运行时更新已回滚(已恢复旧版本)',\n elevation: '系统级安装可能弹出 UAC 提权窗口;拒绝提权会导致替换失败并自动回滚到旧版本。',\n },\n // ── Update-source editor ──\n sourceHint: '运行时更新清单的下载地址,保存后立即生效',\n sourceEdit: '修改',\n sourceSave: '保存',\n sourceReset: '恢复默认',\n saved: '更新源已保存,正在重新检查更新…',\n saveFailed: '保存更新源失败',\n sourceInputLabel: '运行时更新清单地址',\n sourcePlaceholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/edge-headless-release-manifest.json',\n },\n // ── Plugin release-manifest source editor ──\n pluginSource: {\n title: '插件更新源',\n hint: '插件更新清单的下载地址,保存后立即生效',\n edit: '修改',\n save: '保存',\n reset: '恢复默认',\n saved: '更新源已保存,正在重新检查更新…',\n saveFailed: '保存更新源失败',\n inputLabel: '插件更新清单地址',\n placeholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json',\n },\n // ── Durable update job (active / result / log drawer) ──\n job: {\n kindPluginSet: '插件集更新',\n kindRuntime: 'HQ Edge 运行时更新',\n phase: {\n queued: '排队中',\n resolving: '解析版本',\n downloading: '下载中',\n verifying: '校验完整性',\n staging: '暂存文件',\n activating: '启用插件集',\n 'awaiting-host': '等待主机替换运行时',\n restarting: '正在重启',\n 'verifying-after-restart': '重启后校验',\n done: '已完成',\n failed: '失败',\n 'rolled-back': '已回滚',\n abandoned: '已中断',\n } as Record<string, string>,\n willRestartDsh: '完成后 DSH 将自动重启,更新本身会在后台继续。',\n willSwapRuntime: 'HQ Edge 会下载完整运行时包并退出,由 EDA 主机替换后重启;系统级安装可能弹出 UAC 提权窗口。',\n closeNote: '关闭此面板不会取消更新。',\n bytes: (done: string, total: string) => `${done} / ${total}`,\n speed: (v: string) => `${v}/s`,\n eta: (s: string) => `剩余约 ${s}`,\n nextDone: '正在完成,请稍候…',\n // Terminal result view\n resultDone: '更新已完成',\n resultFailed: '更新失败',\n resultRolledBack: '更新已回滚',\n resultAbandoned: '更新未完成',\n retry: '重新尝试',\n retryNote: '将发起一次新的更新',\n recentTitle: '最近更新',\n stepsTitle: '各组件进度',\n logHint: (hint: string) => `日志标识:${hint}`,\n },\n // ── Release metadata ──\n release: {\n summaryTitle: '版本说明',\n notesLink: '查看发布说明',\n breakingTitle: '破坏性变更',\n changesTitle: '更新内容',\n advanced: '高级:更新源',\n publishedAt: (when: string) => `发布于 ${when}`,\n },\n // ── Update-centre panel ──\n ui: {\n title: '更新中心',\n gearTooltip: '更新源配置',\n // Header sub-line: \"版本集 v0.6.14 · 14 分钟前\".\n setVersion: (v: string) => `版本集 v${v}`,\n subJoined: (set: string, ago: string) => `${set} · ${ago}`,\n checkedAgo: (ago: string) => `上次检查:${ago}`,\n checkedUnknown: '尚未检查更新',\n checkedNever: '尚未检查',\n agoJust: '刚刚',\n agoMinutes: (n: number) => `${n} 分钟前`,\n agoHours: (n: number) => `${n} 小时前`,\n agoDays: (n: number) => `${n} 天前`,\n // Decision band\n summaryPlugins: (n: number) => `${n} 项插件可更新`,\n summaryRuntime: '1 项运行时可升级',\n // Section disclosures\n expandLogs: '展开版本日志',\n collapseLogs: '折叠版本日志',\n upToDateNote: '全部正常运行',\n upToDateRow: (v: string) => `${v} · 已是最新`,\n ignoredNote: '已忽略,不再提醒',\n releaseTitle: '版本说明',\n // Cards\n singleUpdate: '单独更新',\n singleInstall: '单独安装',\n singleUpdateTip: '仅安装此插件并重启 DSH,不影响其他插件',\n ignorePlugin: '忽略此插件',\n restorePlugin: '恢复此插件',\n changesToggle: '更新内容',\n changesTitle: (v: string) => `v${v} 变更明细`,\n // Footer\n cta: (n: number) => `一键更新选中的 ${n} 项插件`,\n ctaInstalling: '正在更新插件集中…',\n ctaFailed: '检查更新失败,请重试',\n ctaUpToDate: '已是最新版本',\n ctaNoPlugins: '插件均已是最新',\n ctaNoSet: '暂无可安装的更新',\n footerCount: (n: number) => `准备安装 ${n} 项兼容插件`,\n footerBusy: '后台处理中,请勿关闭 DSH',\n footerFailed: '无法确认插件版本兼容性',\n footerAllGood: '所有插件与运行时均正常',\n footerRuntimeOnly: '运行时更新需在上方单独确认',\n ignoreBtn: '忽略此版本',\n unignoreBtn: '恢复版本提醒',\n footnote: '更新将自动应用并重启 DSH 视图,不影响 EDA 图纸数据',\n // Settings drawer\n settingsTitle: '更新源配置 (高级)',\n originDefault: '默认源',\n originCustom: '自定义源',\n pluginUrlLabel: '插件 Manifest URL',\n runtimeUrlLabel: '运行时 Manifest URL',\n saveAndRefresh: '保存并刷新',\n resetSource: '恢复默认',\n // Log drawer\n logShow: '查看任务日志',\n logHide: '收起任务日志',\n logNone: '暂无可查看的更新日志',\n logBusy: '更新进行中,完成后方可查看日志',\n logTitle: '安装日志与记录',\n jobIdLabel: 'JOB_ID',\n copyLogId: '复制日志 ID',\n logCopied: '日志 ID 已复制到剪贴板',\n logCopyFailed: '复制失败,请手动记录日志 ID',\n viewLog: '查看日志',\n stepSkipped: '已跳过',\n stepPending: '待处理',\n confirmRuntime: '确认升级并重启',\n // Active job\n jobPluginTitle: (v: string) => `正在升级插件集 (v${v})`,\n jobRuntimeTitle: (v: string) => `正在升级 HQ Edge 运行时 (v${v})`,\n jobSteps: ['下载', '校验', '替换', '重启'],\n activeCloseNote: '关闭此面板不会取消更新,完成后将自动重启。',\n // Runtime result alert\n rollbackTitle: '上次 HQ Edge 运行时更新已自动回滚',\n failedTitle: '上次 HQ Edge 运行时更新失败',\n rollbackBody: (oldVer: string, code: string) =>\n `系统已安全恢复至旧版本 (v${oldVer})。代码: ${code}`,\n failedBody: (oldVer: string, code: string) => `未能完成运行时替换 (旧版本 v${oldVer})。代码: ${code}`,\n dismiss: '忽略',\n successInline: '上次运行时更新成功',\n // Runtime engine card\n runtimeEngine: 'HQ Edge 核心运行时',\n currentChip: (v: string) => `v${v} 当前`,\n newVersion: (v: string) => `v${v} 可用`,\n breakingTag: '关键修复',\n sizeLine: (size: string, summary: string) => `体积 ${size}${summary ? ` • ${summary}` : ''}`,\n upgradeCore: '升级核心',\n runtimeRestartNote: '升级运行时需自动重启 KiCad 进程服务',\n },\n}\n\n/** Compact a byte count for display: \"12.3 MB\" / \"480 MB\". */\nexport function formatMb(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes <= 0) return ''\n const mb = bytes / (1024 * 1024)\n if (mb >= 100) return `${Math.round(mb)} MB`\n return `${mb.toFixed(1)} MB`\n}\n\nexport type UpdateCopy = typeof zh\n\nconst en: UpdateCopy = {\n entryLabel: 'Plugin Updates',\n entryTooltip: 'Review available plugin and runtime updates',\n viewTitle: 'Plugin Updates',\n refresh: 'Refresh',\n close: 'Close',\n cancel: 'Cancel',\n loading: 'Reading plugin updates…',\n empty: 'No plugin updates available',\n checkFailed: 'Failed to check for updates — please retry',\n checkFailedTitle: 'Update check failed',\n checkFailedBanner: (message: string) => `Update check failed: ${message}`,\n staleNote: 'Showing the last successful check — it may be stale',\n retry: 'Retry',\n retryNow: 'Try again',\n ignore: 'Ignore this release',\n unignore: 'Show again',\n installError: 'Install failed',\n installSuccess: 'Update installed — restarting DSH…',\n ignoreError: 'Ignore failed',\n confirmInstall: 'Confirm install',\n lastError: 'Last check failed',\n originOverride: 'Custom',\n originConfig: 'Configured',\n originUnset: 'Not configured',\n group: {\n actionable: 'Needs action',\n ignored: 'Ignored',\n current: 'Up to date',\n },\n runtime: {\n title: 'HQ Edge Runtime',\n current: 'Current version',\n latest: 'Latest version',\n set: 'Release set',\n checkedAt: 'Last checked',\n source: 'Update source',\n unknown: 'Version unknown',\n notConfigured: 'No update source configured',\n note: 'Runtime updates are handled by the EDA installer',\n sizeLabel: 'Package size',\n update: 'Install runtime update',\n updateConfirm: (version: string) =>\n `Upgrade the HQ Edge runtime to v${version}? The background service shuts down so the runtime can be swapped, then restarts.`,\n updating: 'Downloading and verifying the runtime bundle…',\n durationNote: 'Duration depends on download speed and package size; the runtime bundle is large, and HQ Edge restarts after the swap.',\n updateAccepted: 'Runtime update accepted — HQ Edge is restarting…',\n size: (bytes: number) => `~ ${formatMb(bytes)}`,\n lastResult: {\n title: 'Last runtime update result',\n succeeded: 'Runtime update succeeded',\n failed: 'Runtime update failed',\n rolledBack: 'Runtime update rolled back (previous version restored)',\n elevation: 'A system-wide install may show a UAC elevation prompt; declining it fails the swap and rolls back to the previous version.',\n },\n sourceHint: 'Where the runtime update manifest is fetched from; saved changes take effect immediately',\n sourceEdit: 'Edit',\n sourceSave: 'Save',\n sourceReset: 'Reset to default',\n saved: 'Update source saved — re-checking for updates…',\n saveFailed: 'Failed to save the update source',\n sourceInputLabel: 'Runtime update manifest URL',\n sourcePlaceholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/edge-headless-release-manifest.json',\n },\n pluginSource: {\n title: 'Plugin update source',\n hint: 'Where the plugin update manifest is fetched from; saved changes take effect immediately',\n edit: 'Edit',\n save: 'Save',\n reset: 'Reset to default',\n saved: 'Update source saved — re-checking for updates…',\n saveFailed: 'Failed to save the update source',\n inputLabel: 'Plugin update manifest URL',\n placeholder:\n 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json',\n },\n job: {\n kindPluginSet: 'Plugin-set update',\n kindRuntime: 'HQ Edge runtime update',\n phase: {\n queued: 'Queued',\n resolving: 'Resolving version',\n downloading: 'Downloading',\n verifying: 'Verifying integrity',\n staging: 'Staging files',\n activating: 'Activating plugin set',\n 'awaiting-host': 'Waiting for host to swap runtime',\n restarting: 'Restarting',\n 'verifying-after-restart': 'Verifying after restart',\n done: 'Done',\n failed: 'Failed',\n 'rolled-back': 'Rolled back',\n abandoned: 'Abandoned',\n } as Record<string, string>,\n willRestartDsh: 'DSH restarts automatically once this finishes; the update continues in the background.',\n willSwapRuntime: 'HQ Edge downloads the full runtime bundle and exits; the EDA host swaps and relaunches it. A system-wide install may show a UAC prompt.',\n closeNote: 'Closing this panel does not cancel the update.',\n bytes: (done: string, total: string) => `${done} / ${total}`,\n speed: (v: string) => `${v}/s`,\n eta: (s: string) => `~${s} left`,\n nextDone: 'Finishing — please wait…',\n resultDone: 'Update completed',\n resultFailed: 'Update failed',\n resultRolledBack: 'Update rolled back',\n resultAbandoned: 'Update did not complete',\n retry: 'Retry',\n retryNote: 'Starts a new update',\n recentTitle: 'Recent updates',\n stepsTitle: 'Per-component progress',\n logHint: (hint: string) => `Log id: ${hint}`,\n },\n release: {\n summaryTitle: 'Release notes',\n notesLink: 'View release notes',\n breakingTitle: 'Breaking changes',\n changesTitle: 'What changed',\n advanced: 'Advanced: update sources',\n publishedAt: (when: string) => `Published ${when}`,\n },\n ui: {\n title: 'Update Center',\n gearTooltip: 'Update sources',\n setVersion: (v: string) => `Set v${v}`,\n subJoined: (set: string, ago: string) => `${set} · ${ago}`,\n checkedAgo: (ago: string) => `Last checked: ${ago}`,\n checkedUnknown: 'No check yet',\n checkedNever: 'never',\n agoJust: 'just now',\n agoMinutes: (n: number) => `${n} min ago`,\n agoHours: (n: number) => `${n} h ago`,\n agoDays: (n: number) => `${n} d ago`,\n summaryPlugins: (n: number) => `${n} plugin${n === 1 ? '' : 's'} can be updated`,\n summaryRuntime: '1 runtime can be upgraded',\n expandLogs: 'Expand changelogs',\n collapseLogs: 'Collapse changelogs',\n upToDateNote: 'All running normally',\n upToDateRow: (v: string) => `${v} · up to date`,\n ignoredNote: 'Ignored, no longer offered',\n releaseTitle: 'Release notes',\n singleUpdate: 'Update this one',\n singleInstall: 'Install this one',\n singleUpdateTip: 'Installs only this plugin and restarts DSH — the rest of the set is untouched',\n ignorePlugin: 'Ignore this plugin',\n restorePlugin: 'Offer this plugin again',\n changesToggle: 'What changed',\n changesTitle: (v: string) => `v${v} changes`,\n cta: (n: number) => `Update the ${n} selected plugin${n === 1 ? '' : 's'}`,\n ctaInstalling: 'Updating the plugin set…',\n ctaFailed: 'Check failed — retry first',\n ctaUpToDate: 'Everything up to date',\n ctaNoPlugins: 'All plugins are current',\n ctaNoSet: 'Nothing to install yet',\n footerCount: (n: number) => `Ready to install ${n} compatible plugin${n === 1 ? '' : 's'}`,\n footerBusy: 'Running in the background — keep DSH open',\n footerFailed: 'Cannot confirm plugin compatibility',\n footerAllGood: 'All plugins and the runtime are current',\n footerRuntimeOnly: 'Confirm the runtime upgrade on its card',\n ignoreBtn: 'Ignore this release',\n unignoreBtn: 'Offer it again',\n footnote: 'Updates apply automatically and restart the DSH view — your EDA schematic data is untouched',\n settingsTitle: 'Update source (advanced)',\n originDefault: 'Default source',\n originCustom: 'Custom source',\n pluginUrlLabel: 'Plugin manifest URL',\n runtimeUrlLabel: 'Runtime manifest URL',\n saveAndRefresh: 'Save & refresh',\n resetSource: 'Reset to default',\n logShow: 'Show the job log',\n logHide: 'Hide the job log',\n logNone: 'No update log to show',\n logBusy: 'Update in progress — the log is available when it finishes',\n logTitle: 'Install log & history',\n jobIdLabel: 'JOB_ID',\n copyLogId: 'Copy log id',\n logCopied: 'Log id copied to the clipboard',\n logCopyFailed: 'Could not copy — please note the log id',\n viewLog: 'View log',\n stepSkipped: 'skipped',\n stepPending: 'pending',\n confirmRuntime: 'Confirm & restart',\n jobPluginTitle: (v: string) => `Updating plugin set (v${v})`,\n jobRuntimeTitle: (v: string) => `Upgrading HQ Edge runtime (v${v})`,\n jobSteps: ['Download', 'Verify', 'Swap', 'Restart'],\n activeCloseNote: 'Closing this panel does not cancel the update; DSH restarts when it finishes.',\n rollbackTitle: 'Last HQ Edge runtime update rolled back automatically',\n failedTitle: 'Last HQ Edge runtime update failed',\n rollbackBody: (oldVer: string, code: string) =>\n `Safely restored to the previous version (v${oldVer}). Code: ${code}`,\n failedBody: (oldVer: string, code: string) => `Runtime swap did not complete (old v${oldVer}). Code: ${code}`,\n dismiss: 'Dismiss',\n successInline: 'Last runtime update succeeded',\n runtimeEngine: 'HQ Core Runtime',\n currentChip: (v: string) => `v${v} current`,\n newVersion: (v: string) => `v${v} available`,\n breakingTag: 'Critical fix',\n sizeLine: (size: string, summary: string) => `Size ${size}${summary ? ` • ${summary}` : ''}`,\n upgradeCore: 'Upgrade core',\n runtimeRestartNote: 'Upgrading the runtime restarts the KiCad process service',\n },\n}\n\nexport const COPY = { zh, en } as const\n","/**\n * Official DSH icon artwork, inlined as SVG strings.\n *\n * The paths are copied verbatim from `@deepseek-ai/dsh-client-ui-primitives`\n * (deepseek-harness `packages/client/ui-primitives/src/icons/index.tsx`) so the\n * Huaqiu plugin-update view draws the same glyphs as the shell's own plugin\n * manager. Only `currentColor` is used, so every glyph inherits the label ink\n * of whatever surface it sits on — the same contract the primitives rely on.\n *\n * The plugin renders plain DOM (never a React tree), so the artwork is exposed\n * as markup strings rather than components.\n *\n * @module\n */\n\n/** Stroke width of the primitives' \"Regular\" weight. */\nconst REGULAR = 1\n\n/** Stroke width of the primitives' \"Medium\" weight. */\nconst MEDIUM = 1.3\n\n/** Wrap paths in a 16×16 stroked glyph. */\nfunction glyph(size: number, strokeWidth: number, paths: readonly string[], viewBox = '0 0 16 16'): string {\n const body = paths.map((d) => `<path d=\"${d}\" stroke=\"currentColor\" />`).join('')\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"${viewBox}\" fill=\"none\" aria-hidden=\"true\" stroke-width=\"${strokeWidth}\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${body}</svg>`\n}\n\n/** Wrap fill-only paths (the check glyph) in their own viewBox. */\nfunction fillGlyph(size: number, paths: readonly string[], viewBox: string): string {\n const body = paths.map((d) => `<path d=\"${d}\" fill=\"currentColor\" />`).join('')\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"${viewBox}\" fill=\"none\" aria-hidden=\"true\">${body}</svg>`\n}\n\n/**\n * The plugin pinwheel — the glyph the shell's own Plugins sidebar entry uses\n * (`IconPluginPinwheelOutlineRegular`). Shared by the sidebar row and the\n * package cards so both read as the same feature.\n */\nexport const PLUGIN_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n 'M7.84457 5.06199C11.6605 4.93876 14.7962 6.14848 14.8484 7.76397C14.8875 8.97461 13.1838 10.0696 10.7215 10.5942',\n 'M5.12742 8.07731C5.00419 4.26138 6.21391 1.12568 7.8294 1.07351C9.04004 1.03441 10.135 2.73808 10.6596 5.20037',\n 'M8.02457 10.6802C4.20865 10.8034 1.07294 9.5937 1.02077 7.97821C0.981678 6.76758 2.68535 5.67262 5.14763 5.14798',\n 'M10.7476 7.89535C10.8708 11.7113 9.66109 14.847 8.0456 14.8991C6.83496 14.9382 5.74 13.2346 5.21536 10.7723',\n ])\n\n/** The toolbar's refresh action (`IconRefreshOutlineRegular`). */\nexport const REFRESH_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n 'M14.5001 8C14.5 9.28552 14.1188 10.5422 13.4045 11.611C12.6903 12.6799 11.6752 13.5129 10.4875 14.0049C9.29982 14.4968 7.99295 14.6255 6.73212 14.3747C5.4713 14.124 4.31314 13.505 3.4041 12.596C2.49514 11.687 1.87614 10.5288 1.62537 9.26798C1.37459 8.00716 1.50331 6.70028 1.99525 5.51261C2.48719 4.32494 3.32025 3.30981 4.3891 2.59557C5.45795 1.88134 6.71458 1.50008 8.0001 1.5C9.9001 1.5 11.7001 2.3 13.0001 3.6L14.5001 5.1',\n 'M14.4999 1.5V5.1H10.8999',\n ])\n\n/**\n * The toolbar's log toggle: a page with ruled lines — \"the update log\".\n *\n * Not a shell primitive (the shell has no equivalent glyph), so it is drawn to\n * the same 16×16 / 1px-stroke contract as the rest of this module.\n */\nexport const LOG_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n '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',\n 'M9.5 1.5V4.5H12.5',\n 'M6 8.25H10',\n 'M6 10.75H10',\n ])\n\n/** The panel's close action (`IconCloseOutlineMedium`). */\nexport const CLOSE_ICON = (size = 16): string =>\n glyph(size, MEDIUM, ['M2.5 2.5L13.5 13.5', 'M13.5 2.5L2.5 13.5'])\n\n/** The notice glyph (`IconWarningOutlineRegular`). */\nexport const WARNING_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n 'M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5Z',\n 'M8 4.29199V9.79199',\n 'M8 10.708V11.708',\n ])\n\n/** The settled-state glyph (`IconCheckCircleFillRegular`). */\nexport const CHECK_CIRCLE_ICON = (size = 16): string =>\n fillGlyph(size, [\n 'M28.1936 14.6936L19.8066 23.0806C19.2373 23.65 18.7159 24.1742 18.24 24.5571C17.7389 24.9602 17.1365 25.3359 16.3657 25.458C15.9581 25.5225 15.5428 25.5225 15.1353 25.458C14.3645 25.3359 13.7621 24.9602 13.261 24.5571C12.7851 24.1742 12.2637 23.65 11.6943 23.0806L7.80737 19.1936L10.1936 16.8074L14.0806 20.6943C14.7033 21.317 15.0763 21.6873 15.377 21.9292C15.6523 22.1507 15.7109 22.1325 15.6626 22.1248C15.7208 22.1339 15.7802 22.1339 15.8384 22.1248C15.7901 22.1325 15.8486 22.1507 16.124 21.9292C16.4247 21.6873 16.7977 21.317 17.4204 20.6943L25.8074 12.3074L28.1936 14.6936Z',\n 'M32.8496 18.0005C32.8496 9.79906 26.2019 3.15137 18.0005 3.15137C9.79906 3.15137 3.15137 9.79906 3.15137 18.0005C3.15137 26.2019 9.79906 32.8496 18.0005 32.8496C26.2019 32.8496 32.8496 26.2019 32.8496 18.0005ZM35.7764 18.0005C35.7764 27.8173 27.8173 35.7764 18.0005 35.7764C8.18363 35.7764 0.224609 27.8173 0.224609 18.0005C0.224609 8.18363 8.18363 0.224609 18.0005 0.224609C27.8173 0.224609 35.7764 8.18363 35.7764 18.0005Z',\n ], '0 0 36 36')\n\n/** The install action (`IconDownloadOutlineRegular`). */\nexport const DOWNLOAD_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n 'M8 1.95317V10.0469',\n 'M4.25 6.29688L8 10.0469L11.75 6.29688',\n 'M1.5 10.0469V13.158C1.5 13.3937 1.60536 13.6198 1.79289 13.7865C1.98043 13.9532 2.23478 14.0469 2.5 14.0469H13.5C13.7652 14.0469 14.0196 13.9532 14.2071 13.7865C14.3946 13.6198 14.5 13.3937 14.5 13.158V10.0469',\n ])\n\n/** The head's info affordance (`IconInfoOutlineRegular`, fill-only artwork). */\nexport const INFO_ICON = (size = 11): string =>\n fillGlyph(size, [\n 'M12.5757 7.00012C12.5757 3.92085 10.0794 1.42463 7.00012 1.42456C3.9208 1.42456 1.42456 3.9208 1.42456 7.00012C1.42463 10.0794 3.92085 12.5757 7.00012 12.5757C10.0793 12.5756 12.5756 10.0793 12.5757 7.00012ZM13.8002 7.00012C13.8001 10.7559 10.7559 13.8001 7.00012 13.8002C3.2443 13.8002 0.199291 10.7559 0.199219 7.00012C0.199219 3.24426 3.24426 0.199219 7.00012 0.199219C10.7559 0.199291 13.8002 3.2443 13.8002 7.00012Z',\n 'M7.6127 3.18921V4.55986H6.38735V3.18921H7.6127Z',\n 'M7.6127 5.68921V10.8109H6.38735V5.68921H7.6127Z',\n ], '0 0 14 14')\n\n/** A folded section's chevron (`IconChevronDownOutlineRegular`). */\nexport const CHEVRON_DOWN_ICON = (size = 14): string =>\n glyph(size, REGULAR, ['M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6'])\n\n/** A right-pointing chevron — a collapsed row, rotated by CSS when it opens. */\nexport const CHEVRON_RIGHT_ICON = (size = 14): string =>\n glyph(size, REGULAR, ['M6 4L9.29289 7.29289C9.68342 7.68342 9.68342 8.31658 9.29289 8.70711L6 12'])\n\n/**\n * The per-card mute action: an eye with a slash (忽略此插件).\n *\n * Drawn here rather than borrowed: the shell's icon set has no\n * \"stop offering this\" glyph, and a bare 忽略 text button competed with the\n * card's primary action for the same 40 px.\n */\nexport const EYE_OFF_ICON = (size = 14): string =>\n glyph(size, REGULAR, [\n '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',\n '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',\n 'M2.5 2.5L13.5 13.5',\n ])\n\n/**\n * Copy-to-clipboard (the log drawer's 复制日志 ID): two offset rounded frames.\n */\nexport const COPY_ICON = (size = 14): string =>\n glyph(size, REGULAR, [\n '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',\n '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',\n ])\n\n/** The runtime card's badge: a bolt (the HQ Edge core distribution). */\nexport const BOLT_ICON = (size = 14): string =>\n glyph(size, REGULAR, ['M9.33333 2L4.66667 8H8L6.66667 14L11.33333 8H8L9.33333 2Z'])\n","/**\n * Plugin-update review view (browser half) — the \"更新中心\" panel.\n *\n * Plain-DOM (no React tree) so it can never disturb the DSH shell's\n * reconciliation — the same rule as the sidebar entry rows. The layout follows\n * `docs/prototype/plugin_update_ui_ux_prototype.html`; the reasoning behind the\n * order is in `docs/report/plugin-update-ui-ux-baseline.md`.\n *\n * Information architecture, top to bottom, and why:\n *\n * 1. **header** — brand, title, `版本集 vX · N 分钟前`, and the four icon\n * actions (refresh · sources · job log · close). The set version and the\n * relative check time live here exactly once; the body never repeats them.\n * 2. **settings strip** (gear) — the two manifest URLs, editable in place.\n * 3. **check-failed banner** — an error, never the silent empty state.\n * 4. **active job card** — pulsing title + percent + bar + telemetry +\n * 4-step pipeline, resumed on open and polled to its end.\n * 5. **failed-result banner** — error code + Retry + 查看日志. The full log\n * lives in the header's drawer; the *outcome* must not hide behind it.\n * 6. **decision band** — one line: how many plugins, and whether the runtime\n * is separately upgradable. This is the \"what do I have to decide\" row.\n * 7. **actionable cards** — 单独更新 / 忽略 promoted onto each card; the\n * collapsible drawer keeps only the changelog.\n * 8. **runtime card** — amber, because a swap is not a plugin install.\n * 9. **已忽略 / 已是最新 / 版本说明** — collapsed by default: nothing that\n * needs no action may sit above something that does.\n * 10. **footer** — the primary CTA plus its honest meta line, both reactive to\n * every state (idle / installing / failed check / up to date).\n *\n * The job log is a drawer inside the panel (`.hqpu-log`), not an inline block:\n * a full set lists one row per component, which as inline content pushed every\n * actionable card below the fold.\n *\n * @module\n */\nimport {\n checkUpdates,\n ignoreOffer,\n ignorePlugin,\n installSet,\n requestRuntimeUpdate,\n runtimeLatestVersion,\n runtimeUpdateOffered,\n setPluginUpdateSource,\n setRuntimeUpdateSource,\n unignoreOffer,\n unignorePlugin,\n TERMINAL_PHASES,\n type PluginUpdateItem,\n type PluginUpdateReport,\n type ReleaseInfo,\n type RuntimeUpdateSource,\n type UpdateJob,\n type UpdateStep,\n} from './api.js'\nimport { formatMb, type UpdateCopy } from './copy.js'\nimport { actionableUpdateCount } from './version.js'\nimport {\n BOLT_ICON,\n CHECK_CIRCLE_ICON,\n CHEVRON_RIGHT_ICON,\n CLOSE_ICON,\n COPY_ICON,\n EYE_OFF_ICON,\n LOG_ICON,\n REFRESH_ICON,\n WARNING_ICON,\n} from './icons.js'\n\nexport interface UpdateStoreLike {\n report(): PluginUpdateReport | null\n installing(): boolean\n /** Mark the installing footer state (set by the panel before the restart). */\n setInstalling(flag: boolean): void\n /** The durable job the server currently reports as active (resumed). */\n activeJob(): UpdateJob | null\n /** Bounded recent jobs (newest first), for the log drawer. */\n recentJobs(): UpdateJob[]\n refresh(): Promise<PluginUpdateReport | null>\n /** Force a fresh update check (used by the failed-check Retry action). */\n check(): Promise<PluginUpdateReport | null>\n /** Re-read `/status` (resume an active job immediately after submitting). */\n fetchStatus(): Promise<void>\n subscribe(listener: () => void): () => void\n}\n\n/** How long the outcome toast stays up, in milliseconds. */\nconst TOAST_HOLD_MS = 4000\n\n/** Which manifest an editor belongs to — drives data attributes + CSS hook. */\nexport type SourceKind = 'runtime' | 'plugin'\n\n// ── Small DOM helpers ──────────────────────────────────────────────────────\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className = '',\n text?: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag)\n if (className.length > 0) node.className = className\n if (text !== undefined) node.textContent = text\n return node\n}\n\n/** A button carrying inline SVG artwork (the icon modules emit markup). */\nfunction iconButton(\n className: string,\n markup: string,\n label: string,\n onClick: () => void,\n): HTMLButtonElement {\n const button = el('button', className)\n button.type = 'button'\n button.setAttribute('aria-label', label)\n button.title = label\n button.innerHTML = markup\n button.addEventListener('click', onClick)\n return button\n}\n\n/** A text-link button (重新尝试, 展开版本日志, 忽略此版本 …). */\nfunction linkButton(className: string, label: string, onClick: () => void): HTMLButtonElement {\n const button = el('button', className, label)\n button.type = 'button'\n button.addEventListener('click', onClick)\n return button\n}\n\n/**\n * Compact a package name to what a person calls it — the shell plugin\n * manager's own rule (`presentation.ts` `shortName`), so both UIs read the\n * same plugin by the same name.\n */\nexport function shortName(name: string): string {\n const unscoped = name.startsWith('@') ? name.slice(name.indexOf('/') + 1) : name\n return unscoped.replace(/^dsh-(?:host-|client-)?/, '')\n}\n\n/** Two-letter avatar initials for a card (CJK-safe). */\nfunction avatarInitials(item: PluginUpdateItem): string {\n const src =\n typeof item.displayName === 'string' && item.displayName.trim().length > 0\n ? item.displayName.trim()\n : shortName(item.name)\n if (/[\\u2E80-\\u9FFF]/.test(src)) return src.slice(0, 2)\n const words = src.split(/[\\s_\\-./@]+/).filter(Boolean)\n if (words.length >= 2) return (words[0]![0]! + words[1]![0]!).toUpperCase()\n return src.replace(/^@/, '').slice(0, 2).toUpperCase()\n}\n\n/** The name a card shows: the manifest's displayName, else the short name. */\nfunction displayNameOf(item: PluginUpdateItem): string {\n return typeof item.displayName === 'string' && item.displayName.trim().length > 0\n ? item.displayName.trim()\n : shortName(item.name)\n}\n\nfunction isActionable(item: PluginUpdateItem): boolean {\n return item.state === 'update' || item.state === 'install'\n}\n\n// ── Time / size formatting ────────────────────────────────────────────────\n\n/** Format a byte count for the progress line, or null when unknown. */\nfunction formatBytes(n: number | undefined): string | null {\n if (n === undefined || !Number.isFinite(n) || n < 0) return null\n if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(1)} GB`\n if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`\n if (n >= 1024) return `${(n / 1024).toFixed(0)} KB`\n return `${n} B`\n}\n\n/** \"N 分钟前\" — relative time from an ISO timestamp. */\nfunction relativeAgo(iso: string | null | undefined, copy: UpdateCopy): string | null {\n if (typeof iso !== 'string' || iso.length === 0) return null\n const then = new Date(iso).getTime()\n if (!Number.isFinite(then)) return null\n const diffMin = Math.round((Date.now() - then) / 60_000)\n if (diffMin < 1) return copy.ui.agoJust\n if (diffMin < 60) return copy.ui.agoMinutes(diffMin)\n const hours = Math.round(diffMin / 60)\n if (hours < 24) return copy.ui.agoHours(hours)\n return copy.ui.agoDays(Math.round(hours / 24))\n}\n\n/** Short absolute time for log rows: \"10/29 14:34\". */\nfunction shortWhen(iso: string | null | undefined): string {\n if (typeof iso !== 'string' || iso.length === 0) return ''\n const at = new Date(iso)\n if (Number.isNaN(at.getTime())) return ''\n return at.toLocaleString(undefined, {\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n })\n}\n\nfunction jobKindLabel(job: UpdateJob, copy: UpdateCopy): string {\n return job.kind === 'runtime' ? copy.job.kindRuntime : copy.job.kindPluginSet\n}\n\n/** The phase in human words, falling back to the raw code. */\nfunction phaseLabel(job: UpdateJob, copy: UpdateCopy): string {\n return copy.job.phase[job.phase] ?? job.phase\n}\n\n/** Map a job phase onto the 4-step indicator index (下载/校验/替换/重启). */\nfunction stepIndex(phase: UpdateJob['phase']): number {\n switch (phase) {\n case 'downloading':\n case 'resolving':\n return 0\n case 'verifying':\n return 1\n case 'staging':\n case 'activating':\n case 'awaiting-host':\n return 2\n case 'restarting':\n case 'verifying-after-restart':\n return 3\n case 'queued':\n default:\n return -1\n }\n}\n\n// ── Section pieces ────────────────────────────────────────────────────────\n\n/**\n * A collapsible section: chevron + title + count, an optional right-hand note,\n * and a body that only exists in the DOM while open.\n *\n * Native `<details>` was the alternative, but it cannot express the\n * \"collapsed by default, opened by state\" rule the 已是最新 group needs without\n * a JS patch anyway, and its summary cannot host the trailing note in one row.\n */\nfunction disclosure(opts: {\n id: string\n title: string\n count?: string\n note?: string\n open: boolean\n onToggle: () => void\n build: () => HTMLElement[]\n}): HTMLElement {\n const section = el('section', 'hqpu-disc')\n // The title doubles as the group hook (`[data-group=\"已是最新\"]`), matching\n // the actionable group's convention so callers need one selector style.\n section.dataset.group = opts.title\n const head = el('button', 'hqpu-disc-head')\n head.type = 'button'\n head.setAttribute('aria-expanded', opts.open ? 'true' : 'false')\n head.setAttribute('aria-controls', opts.id)\n\n const left = el('span', 'hqpu-disc-left')\n const chevron = el('span', 'hqpu-disc-chevron')\n chevron.innerHTML = CHEVRON_RIGHT_ICON(12)\n left.append(chevron, el('span', 'hqpu-disc-title', opts.title))\n if (opts.count !== undefined) left.append(el('span', 'hqpu-disc-count', opts.count))\n head.append(left)\n if (opts.note !== undefined && opts.note.length > 0) {\n head.append(el('span', 'hqpu-disc-note', opts.note))\n }\n head.addEventListener('click', opts.onToggle)\n section.append(head)\n\n if (opts.open) {\n const body = el('div', 'hqpu-disc-body')\n body.id = opts.id\n for (const node of opts.build()) body.append(node)\n section.append(body)\n }\n return section\n}\n\n/** One muted row in a collapsed group (current or ignored plugin). */\nfunction pluginRow(\n item: PluginUpdateItem,\n copy: UpdateCopy,\n onToggleIgnore: () => void,\n): HTMLLIElement {\n const row = el('li', 'hqpu-row')\n row.dataset.pluginName = item.name\n row.dataset.state = item.state\n row.append(el('span', 'hqpu-row-name', displayNameOf(item)))\n const end = el('span', 'hqpu-row-end')\n const version = item.currentVersion ?? item.availableVersion\n end.append(el('span', 'hqpu-row-meta', copy.ui.upToDateRow(version)))\n if (item.state === 'ignored') {\n end.append(\n linkButton('hqpu-link hqpu-link--tiny', copy.unignore, onToggleIgnore),\n )\n }\n row.append(end)\n return row\n}\n\n/**\n * One actionable plugin card.\n *\n * Actions live on the card, not inside the changelog drawer: the drawer is for\n * content (what changed), and hiding 单独更新 / 忽略 behind a disclosure made\n * the set-level footer CTA the only discoverable action.\n */\nfunction pluginCard(\n item: PluginUpdateItem,\n copy: UpdateCopy,\n opts: { changesOpen: boolean; onToggleChanges: () => void; onInstall: () => void; onIgnore: () => void; busy: boolean },\n): HTMLLIElement {\n const card = el('li', 'hqpu-card')\n card.dataset.pluginName = item.name\n card.dataset.state = item.state\n\n const head = el('div', 'hqpu-card-head')\n const id = el('div', 'hqpu-card-id')\n const avatar = el('span', 'hqpu-avatar', avatarInitials(item))\n avatar.setAttribute('aria-hidden', 'true')\n const main = el('div', 'hqpu-card-main')\n main.append(el('span', 'hqpu-card-title', displayNameOf(item)))\n main.append(el('span', 'hqpu-card-sub', shortName(item.name)))\n id.append(avatar, main)\n\n const end = el('div', 'hqpu-card-end')\n const install = el(\n 'button',\n 'hqpu-card-action hqpu-card-action--install',\n item.state === 'install' ? copy.ui.singleInstall : copy.ui.singleUpdate,\n )\n install.type = 'button'\n install.title = copy.ui.singleUpdateTip\n install.disabled = opts.busy\n install.addEventListener('click', opts.onInstall)\n const ignore = iconButton(\n 'hqpu-icon-btn hqpu-icon-btn--sm',\n EYE_OFF_ICON(14),\n copy.ui.ignorePlugin,\n opts.onIgnore,\n )\n ignore.disabled = opts.busy\n end.append(install, ignore)\n head.append(id, end)\n card.append(head)\n\n // Version row + the changelog toggle, on one line.\n const foot = el('div', 'hqpu-card-foot')\n const ver = el('div', 'hqpu-version-row')\n if (item.currentVersion !== null && item.currentVersion !== undefined) {\n ver.append(el('span', 'hqpu-ver-old', item.currentVersion))\n ver.append(el('span', 'hqpu-ver-arrow', '→'))\n }\n ver.append(el('span', 'hqpu-ver-new', item.availableVersion))\n const size = formatMb(item.sizeBytes ?? 0)\n if (size.length > 0) ver.append(el('span', 'hqpu-ver-size', `(${size})`))\n foot.append(ver)\n\n const changes = Array.isArray(item.changes)\n ? item.changes.filter((c) => typeof c === 'string' && c.length > 0)\n : []\n if (changes.length > 0) {\n const toggle = el('button', 'hqpu-toggle')\n toggle.type = 'button'\n toggle.dataset.changesToggle = ''\n toggle.dataset.open = opts.changesOpen ? 'true' : 'false'\n toggle.setAttribute('aria-expanded', opts.changesOpen ? 'true' : 'false')\n toggle.append(el('span', '', copy.ui.changesToggle))\n const chevron = el('span', 'hqpu-toggle-chevron')\n chevron.innerHTML = CHEVRON_RIGHT_ICON(12)\n toggle.append(chevron)\n toggle.addEventListener('click', opts.onToggleChanges)\n foot.append(toggle)\n }\n card.append(foot)\n\n if (changes.length > 0 && opts.changesOpen) {\n const drawer = el('div', 'hqpu-changes')\n drawer.append(el('p', 'hqpu-changes-title', copy.ui.changesTitle(item.availableVersion)))\n const ul = el('ul')\n for (const change of changes) ul.append(el('li', '', change))\n drawer.append(ul)\n card.append(drawer)\n }\n return card\n}\n\n/** The \"what do I actually have to decide\" band, above every list. */\nfunction decisionBand(report: PluginUpdateReport, copy: UpdateCopy): HTMLElement | null {\n const plugins = actionableUpdateCount(report)\n const runtime = runtimeUpdateOffered(report.runtime)\n if (plugins === 0 && !runtime) return null\n const band = el('div', 'hqpu-decision')\n band.dataset.role = 'decision-band'\n if (plugins > 0) band.append(el('span', 'hqpu-decision-main', copy.ui.summaryPlugins(plugins)))\n if (plugins > 0 && runtime) {\n const dot = el('span', 'hqpu-decision-dot')\n dot.setAttribute('aria-hidden', 'true')\n band.append(dot)\n }\n if (runtime) band.append(el('span', 'hqpu-decision-sub', copy.ui.summaryRuntime))\n return band\n}\n\n/**\n * Check-status banner. A FAILED check is a red alert with its reason and a\n * retry — never the silent \"no updates\" empty state, which reads as success.\n */\nfunction checkBanner(\n report: PluginUpdateReport,\n copy: UpdateCopy,\n onRetry: () => void,\n): HTMLElement | null {\n const failed = typeof report.lastError === 'string' && report.lastError.length > 0\n if (!failed) return null\n const section = el('section', 'hqpu-group')\n section.dataset.group = 'check-status'\n const card = el('div', 'hqpu-error')\n card.dataset.tone = 'danger'\n const head = el('div', 'hqpu-error-head')\n const icon = el('span', 'hqpu-error-icon')\n icon.innerHTML = WARNING_ICON(14)\n const text = el('div', 'hqpu-error-text')\n text.append(el('p', 'hqpu-error-title', copy.checkFailedTitle))\n text.append(el('p', 'hqpu-error-body', report.lastError as string))\n head.append(icon, text)\n card.append(head)\n\n const foot = el('div', 'hqpu-error-foot')\n if (report.items.length > 0) foot.append(el('span', '', copy.staleNote))\n foot.append(linkButton('hqpu-link', copy.retryNow, onRetry))\n card.append(foot)\n section.append(card)\n return section\n}\n\n/**\n * The active job card: title (with the target version) + percent, a\n * determinate bar when the server publishes one, phase + telemetry, the honest\n * \"closing does not cancel\" note, and the 4-step pipeline.\n */\nfunction activeJobView(job: UpdateJob, copy: UpdateCopy): HTMLElement {\n const section = el('section', 'hqpu-group')\n section.dataset.group = 'active-job'\n const card = el('div', 'hqpu-active')\n\n const top = el('div', 'hqpu-active-top')\n const titleWrap = el('div', 'hqpu-active-title')\n const pulse = el('span', 'hqpu-pulse-dot')\n pulse.append(el('span', 'hqpu-pulse-ring'))\n const title =\n job.kind === 'runtime'\n ? copy.ui.jobRuntimeTitle(job.targetVersion)\n : copy.ui.jobPluginTitle(job.targetVersion)\n titleWrap.append(pulse, el('span', 'hqpu-active-label', title))\n top.append(titleWrap)\n const pct = job.progress.percent\n if (pct !== null) top.append(el('span', 'hqpu-active-percent', `${Math.round(pct)}%`))\n card.append(top)\n\n if (pct !== null) {\n const bar = el('div', 'hqpu-progress')\n bar.setAttribute('role', 'progressbar')\n bar.setAttribute('aria-valuemin', '0')\n bar.setAttribute('aria-valuemax', '100')\n bar.setAttribute('aria-valuenow', String(Math.round(pct)))\n const fill = el('div', 'hqpu-progress-fill')\n fill.style.width = `${Math.min(100, Math.max(0, pct))}%`\n bar.append(fill)\n card.append(bar)\n }\n\n const meta = el('div', 'hqpu-active-meta')\n const phaseText =\n typeof job.message === 'string' && job.message.length > 0 ? job.message : phaseLabel(job, copy)\n meta.append(el('span', '', phaseText))\n const speed = formatBytes(job.progress.bytesPerSecond)\n const done = formatBytes(job.progress.bytesDone)\n const total = formatBytes(job.progress.bytesTotal)\n const telemetry =\n speed !== null && done !== null && total !== null\n ? `${speed}/s (${done} / ${total})`\n : speed !== null\n ? `${speed}/s`\n : done !== null && total !== null\n ? copy.job.bytes(done, total)\n : ''\n if (telemetry.length > 0) meta.append(el('span', 'hqpu-num', telemetry))\n card.append(meta)\n\n // previous → target on its own line (mono), then what happens next. A\n // plugin set keeps the \"closing does not cancel\" note; a runtime swap has a\n // stronger one of its own (HQ Edge exits, the host swaps it out).\n card.append(\n el(\n 'div',\n 'hqpu-active-versions',\n job.previousVersion !== null && job.previousVersion.length > 0\n ? `${job.previousVersion} → ${job.targetVersion}`\n : job.targetVersion,\n ),\n )\n card.append(\n el(\n 'p',\n 'hqpu-active-note',\n job.kind === 'runtime' ? copy.job.willSwapRuntime : copy.ui.activeCloseNote,\n ),\n )\n\n const current = stepIndex(job.phase)\n const stepsRow = el('div', 'hqpu-job-steps4')\n copy.ui.jobSteps.forEach((label: string, index: number) => {\n const cls =\n current === -1\n ? 'hqpu-job-step--todo'\n : index < current\n ? 'hqpu-job-step--done'\n : index === current\n ? 'hqpu-job-step--active'\n : 'hqpu-job-step--todo'\n // Numbered like the prototype's pipeline (\"1. 下载\"): the count is the\n // only cue for how much is left when the active step blinks.\n stepsRow.append(el('div', cls, `${index + 1}. ${label}`))\n })\n card.append(stepsRow)\n\n section.append(card)\n return section\n}\n\n/** Phase → the word the log rows use for a component. */\nfunction stepWord(state: UpdateStep['state'], copy: UpdateCopy): string {\n switch (state) {\n case 'installed':\n return copy.job.phase.done ?? ''\n case 'skipped':\n return copy.ui.stepSkipped\n case 'failed':\n return copy.job.phase.failed ?? ''\n case 'running':\n return copy.job.phase.downloading ?? ''\n default:\n return copy.ui.stepPending\n }\n}\n\n/** Dot tone for one step row. */\nfunction stepDotState(state: UpdateStep['state']): string {\n switch (state) {\n case 'installed':\n return 'done'\n case 'failed':\n return 'error'\n case 'running':\n return 'warning'\n default:\n return 'idle'\n }\n}\n\n/**\n * The terminal-result banner for a job that did NOT succeed.\n *\n * A successful install needs no banner — the list and the footer already say\n * \"everything is current\". A failure does: the error code and the retry must be\n * on screen, not one drawer-click away.\n */\nfunction jobResultBanner(\n job: UpdateJob,\n copy: UpdateCopy,\n onRetry: () => void,\n onOpenLog: () => void,\n): HTMLElement {\n const section = el('section', 'hqpu-group')\n section.dataset.group = 'job-result'\n const tone = job.phase === 'failed' ? 'danger' : 'warning'\n const card = el('div', 'hqpu-error')\n card.dataset.tone = tone\n\n const head = el('div', 'hqpu-error-head')\n const icon = el('span', 'hqpu-error-icon')\n icon.innerHTML = WARNING_ICON(14)\n const text = el('div', 'hqpu-error-text')\n const title =\n job.phase === 'rolled-back' ? copy.job.resultRolledBack : copy.job.resultFailed\n text.append(el('p', 'hqpu-error-title', title))\n const res = job.result\n const from = job.previousVersion ?? res?.installedVersion ?? '—'\n const to = res?.installedVersion ?? job.targetVersion\n const when = res !== undefined && res.finishedAt.length > 0 ? ` · ${shortWhen(res.finishedAt)}` : ''\n text.append(el('p', 'hqpu-error-body', `${from} → ${to}${when}`))\n if (res?.message !== undefined && res.message.length > 0) {\n text.append(el('p', 'hqpu-error-body', res.message))\n }\n if (res?.errorCode !== undefined && res.errorCode.length > 0) {\n text.append(el('code', 'hqpu-error-code', res.errorCode))\n }\n head.append(icon, text)\n card.append(head)\n\n const foot = el('div', 'hqpu-error-foot')\n foot.append(el('span', '', jobKindLabel(job, copy)))\n const actions = el('span', 'hqpu-error-actions')\n actions.append(linkButton('hqpu-link', copy.ui.viewLog, onOpenLog))\n actions.append(linkButton('hqpu-link', copy.job.retry, onRetry))\n foot.append(actions)\n card.append(foot)\n section.append(card)\n return section\n}\n\n/**\n * The host's own report of the last runtime swap. Rolled-back / failed renders\n * as an AMBER alert card; succeeded renders as a quiet green line. The plugin\n * has no log-open affordance for this, so none is invented.\n */\nfunction runtimeResultAlert(\n report: PluginUpdateReport,\n copy: UpdateCopy,\n dismissed: boolean,\n onDismiss: () => void,\n): HTMLElement | null {\n const lastResult = report.runtime.lastResult\n if (lastResult === null || lastResult === undefined) return null\n\n if (lastResult.state === 'succeeded') {\n const line = el('p', 'hqpu-alert-green')\n line.dataset.role = 'runtime-success'\n line.textContent = copy.ui.successInline\n return line\n }\n if (dismissed) return null\n\n const section = el('section', 'hqpu-group')\n section.dataset.group = 'runtime-alert'\n const card = el('div', 'hqpu-alert hqpu-alert--amber')\n const icon = el('span', 'hqpu-alert-icon')\n icon.innerHTML = WARNING_ICON(14)\n card.append(icon)\n\n const body = el('div', 'hqpu-alert-main')\n body.append(\n el(\n 'div',\n 'hqpu-alert-title',\n lastResult.state === 'failed' ? copy.ui.failedTitle : copy.ui.rollbackTitle,\n ),\n )\n const oldVer = lastResult.installedVersion ?? lastResult.targetVersion ?? '—'\n const bodyP = el('p', 'hqpu-alert-body')\n bodyP.append(\n document.createTextNode(\n lastResult.state === 'failed'\n ? copy.ui.failedBody(oldVer, '')\n : copy.ui.rollbackBody(oldVer, ''),\n ),\n )\n if (typeof lastResult.errorCode === 'string' && lastResult.errorCode.length > 0) {\n bodyP.append(el('code', '', lastResult.errorCode))\n }\n if (typeof lastResult.message === 'string' && lastResult.message.length > 0) {\n bodyP.append(el('br'), document.createTextNode(lastResult.message))\n }\n body.append(bodyP)\n const actions = el('div', 'hqpu-alert-actions')\n actions.append(linkButton('hqpu-alert-dismiss', copy.ui.dismiss, onDismiss))\n body.append(actions)\n card.append(body)\n section.append(card)\n return section\n}\n\n/**\n * The HQ Edge runtime card: current chip, the offered version with its size and\n * summary, and the 升级核心 action — arm → confirm, because a runtime swap\n * shuts HQ Edge down and is not undoable from here.\n */\nfunction runtimeEngineCard(\n report: PluginUpdateReport,\n copy: UpdateCopy,\n actions: RuntimeActions,\n): HTMLElement | null {\n const runtime = report.runtime\n const latestVersion = runtimeLatestVersion(runtime)\n const card = el('section', 'hqpu-engine')\n card.dataset.group = 'runtime'\n\n const head = el('div', 'hqpu-engine-head')\n const titleWrap = el('div', 'hqpu-engine-title')\n const bolt = el('span', 'hqpu-engine-bolt')\n bolt.innerHTML = BOLT_ICON(14)\n titleWrap.append(bolt, el('span', '', copy.ui.runtimeEngine))\n head.append(titleWrap)\n if (typeof runtime.currentVersion === 'string' && runtime.currentVersion.length > 0) {\n head.append(el('span', 'hqpu-chip', copy.ui.currentChip(runtime.currentVersion)))\n }\n card.append(head)\n\n const offered = runtime.updateAvailable && latestVersion !== null\n if (!offered && typeof runtime.lastError === 'string' && runtime.lastError.length > 0) {\n card.append(el('p', 'hqpu-engine-error', `${copy.lastError}: ${runtime.lastError}`))\n return card\n }\n if (!offered) return card\n\n const latestEntry = runtime.latest\n const row = el('div', 'hqpu-engine-row')\n const info = el('div', 'hqpu-engine-info')\n const newLine = el('div', 'hqpu-engine-new')\n newLine.append(el('span', 'hqpu-ver-new hqpu-ver-new--lg', copy.ui.newVersion(latestVersion as string)))\n const hasCritical =\n (Array.isArray(latestEntry?.breaking) && (latestEntry?.breaking?.length ?? 0) > 0) ||\n (Array.isArray(latestEntry?.notes) && (latestEntry?.notes?.length ?? 0) > 0)\n if (hasCritical) newLine.append(el('span', 'hqpu-tag--brand', copy.ui.breakingTag))\n info.append(newLine)\n const size = typeof latestEntry?.sizeBytes === 'number' ? formatMb(latestEntry.sizeBytes) : ''\n const summary = typeof latestEntry?.summary === 'string' ? latestEntry.summary : ''\n if (size.length > 0 || summary.length > 0) {\n info.append(el('p', 'hqpu-engine-size', copy.ui.sizeLine(size, summary)))\n }\n row.append(info)\n\n if (actions.accepted) {\n row.append(el('span', 'hqpu-engine-state', copy.runtime.updateAccepted))\n } else if (actions.busy) {\n row.append(el('span', 'hqpu-engine-state', copy.runtime.updating))\n } else if (!actions.armed) {\n const start = el('button', 'hqpu-btn--engine', copy.ui.upgradeCore)\n start.type = 'button'\n start.dataset.runtimeUpdate = ''\n start.addEventListener('click', actions.onArm)\n row.append(start)\n }\n card.append(row)\n\n // Confirm replaces the action: a full-width block, never a 150 px column.\n if (actions.armed && !actions.busy && !actions.accepted) {\n const confirm = el('div', 'hqpu-engine-confirm')\n confirm.append(\n el('p', 'hqpu-engine-confirm-text', copy.runtime.updateConfirm(latestVersion as string)),\n )\n const buttons = el('div', 'hqpu-engine-confirm-actions')\n const no = el('button', 'hqpu-btn hqpu-btn--ghost hqpu-btn--sm', copy.cancel)\n no.type = 'button'\n no.addEventListener('click', actions.onCancel)\n const yes = el('button', 'hqpu-btn--engine hqpu-btn--engine-solid', copy.ui.confirmRuntime)\n yes.type = 'button'\n yes.dataset.runtimeConfirm = ''\n yes.addEventListener('click', actions.onConfirm)\n buttons.append(no, yes)\n confirm.append(buttons)\n card.append(confirm)\n }\n\n const note = el('p', 'hqpu-engine-note')\n note.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"10\" height=\"10\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 2L14.4 13.4H1.6L8 2Z\"/><path d=\"M8 6.4V9.2\"/><path d=\"M8 10.6V11.6\"/></svg>`\n note.append(el('span', '', copy.ui.runtimeRestartNote))\n card.append(note)\n return card\n}\n\n/** Set-level release metadata (v2 manifest): summary, notes, breaking, link. */\nfunction releaseBody(release: ReleaseInfo, copy: UpdateCopy): HTMLElement[] {\n const out: HTMLElement[] = []\n const notes = Array.isArray(release.notes)\n ? release.notes.filter((n) => typeof n === 'string' && n.length > 0)\n : []\n const breaking = Array.isArray(release.breaking)\n ? release.breaking.filter((n) => typeof n === 'string' && n.length > 0)\n : []\n if (typeof release.summary === 'string' && release.summary.length > 0) {\n out.push(el('p', 'hqpu-release-summary', release.summary))\n }\n if (notes.length > 0) {\n const ul = el('ul', 'hqpu-release-notes')\n for (const note of notes) ul.append(el('li', '', note as string))\n out.push(ul)\n }\n if (breaking.length > 0) {\n const banner = el('div', 'hqpu-banner')\n banner.dataset.tone = 'danger'\n const icon = el('span')\n icon.innerHTML = WARNING_ICON(14)\n banner.append(icon)\n const inner = el('span')\n inner.append(el('strong', '', `${copy.release.breakingTitle}: `))\n inner.append(document.createTextNode(breaking.join(' · ')))\n banner.append(inner)\n out.push(banner)\n }\n if (typeof release.publishedAt === 'string' && release.publishedAt.length > 0) {\n out.push(\n el('p', 'hqpu-release-meta', copy.release.publishedAt(new Date(release.publishedAt).toLocaleString())),\n )\n }\n if (release.requiresRestart === true) {\n out.push(el('p', 'hqpu-release-meta', copy.job.willRestartDsh))\n }\n if (typeof release.notesUrl === 'string' && release.notesUrl.length > 0) {\n const link = el('a', 'hqpu-release-link', copy.release.notesLink)\n link.href = release.notesUrl\n link.target = '_blank'\n link.rel = 'noreferrer'\n out.push(link)\n }\n return out\n}\n\nfunction hasReleaseContent(release: ReleaseInfo | null | undefined): boolean {\n if (release === null || release === undefined) return false\n return (\n (typeof release.summary === 'string' && release.summary.length > 0) ||\n (Array.isArray(release.notes) && release.notes.length > 0) ||\n (Array.isArray(release.breaking) && release.breaking.length > 0) ||\n typeof release.notesUrl === 'string' ||\n typeof release.publishedAt === 'string'\n )\n}\n\n/**\n * The job log drawer: title + outcome tag, the mono component list with a\n * JOB_ID header, the result detail, older runs, and 复制日志 ID / 关闭.\n */\nfunction buildLogDrawer(\n job: UpdateJob,\n recent: UpdateJob[],\n copy: UpdateCopy,\n onCopy: (id: string) => void,\n onClose: () => void,\n): HTMLElement[] {\n const out: HTMLElement[] = []\n\n const head = el('div', 'hqpu-log-head')\n const titleWrap = el('div', 'hqpu-log-title')\n titleWrap.append(el('span', 'hqpu-log-name', copy.ui.logTitle))\n const tag = el('span', 'hqpu-tag')\n tag.dataset.tone =\n job.phase === 'done' ? 'success' : job.phase === 'failed' ? 'danger' : 'warning'\n tag.textContent =\n job.phase === 'done'\n ? copy.job.resultDone\n : job.phase === 'failed'\n ? copy.job.resultFailed\n : job.phase === 'rolled-back'\n ? copy.job.resultRolledBack\n : copy.job.resultAbandoned\n titleWrap.append(tag)\n const closeBtn = iconButton('hqpu-icon-btn hqpu-icon-btn--sm', CLOSE_ICON(14), copy.close, onClose)\n head.append(titleWrap, closeBtn)\n out.push(head)\n\n const body = el('div', 'hqpu-log-body')\n const block = el('div', 'hqpu-log-block')\n const meta = el('div', 'hqpu-log-meta')\n const logId = job.logHint !== undefined && job.logHint.length > 0 ? job.logHint : job.id\n meta.append(el('span', '', `${copy.ui.jobIdLabel}: ${logId}`))\n meta.append(el('span', '', shortWhen(job.result?.finishedAt ?? job.updatedAt)))\n block.append(meta)\n\n const steps = Array.isArray(job.steps) ? job.steps : []\n if (steps.length > 0) {\n for (const step of steps) {\n const line = el('div', 'hqpu-log-line')\n const dot = el('span', 'hqpu-log-dot')\n dot.dataset.state = stepDotState(step.state)\n line.append(dot)\n const word = stepWord(step.state, copy)\n const text =\n step.message !== undefined && step.message.length > 0\n ? `${step.name} — ${step.message}`\n : step.name\n line.append(el('span', 'hqpu-log-line-text', word.length > 0 ? `${text} (${word})` : text))\n block.append(line)\n }\n } else {\n block.append(el('div', 'hqpu-log-line hqpu-log-line--empty', copy.job.stepsTitle))\n }\n body.append(block)\n\n // Result detail: the transition, the error code, the server's own message.\n const res = job.result\n const from = job.previousVersion ?? res?.installedVersion ?? '—'\n const to = res?.installedVersion ?? job.targetVersion\n const detail = el('div', 'hqpu-log-detail')\n detail.append(el('p', '', `${from} → ${to}`))\n if (res?.errorCode !== undefined && res.errorCode.length > 0) {\n detail.append(el('code', 'hqpu-error-code', res.errorCode))\n }\n if (res?.message !== undefined && res.message.length > 0) {\n detail.append(el('p', '', res.message))\n }\n body.append(detail)\n\n const older = recent.filter((entry) => entry.id !== job.id).slice(0, 4)\n if (older.length > 0) {\n const list = el('div', 'hqpu-log-recent')\n list.append(el('p', 'hqpu-log-recent-title', copy.job.recentTitle))\n for (const entry of older) {\n const row = el('div', 'hqpu-log-recent-row')\n row.append(el('span', '', `${jobKindLabel(entry, copy)} ${entry.targetVersion}`))\n row.append(el('span', 'hqpu-log-recent-meta', `${phaseLabel(entry, copy)} · ${shortWhen(entry.updatedAt)}`))\n list.append(row)\n }\n body.append(list)\n }\n out.push(body)\n\n const foot = el('div', 'hqpu-log-foot')\n const copyBtn = el('button', 'hqpu-link hqpu-link--icon')\n copyBtn.type = 'button'\n copyBtn.dataset.copyLog = ''\n const copyIcon = el('span')\n copyIcon.innerHTML = COPY_ICON(12)\n copyBtn.append(copyIcon, el('span', '', copy.ui.copyLogId))\n copyBtn.addEventListener('click', () => onCopy(logId))\n const closeFoot = el('button', 'hqpu-btn hqpu-btn--sm', copy.close)\n closeFoot.type = 'button'\n closeFoot.addEventListener('click', onClose)\n foot.append(copyBtn, closeFoot)\n out.push(foot)\n\n return out\n}\n\n// ── The runtime action contract ────────────────────────────────────────────\n\n/**\n * The runtime-update action row's state and callbacks.\n *\n * Installing the runtime is destructive in a way a plugin install is not:\n * hq-edge shuts itself down so the EDA host can swap the runtime directory. So\n * the row is a two-step confirm (arm → confirm), never a single click.\n */\nexport interface RuntimeActions {\n /** A request is in flight (hq-edge accepted it and is restarting). */\n busy: boolean\n /** The confirm step is armed for the version the row names. */\n armed: boolean\n /**\n * hq-edge answered `202 Accepted`: from here on the outcome belongs to the\n * EDA host, and this process is about to be replaced. Terminal — the row\n * must never re-offer the button once this is true.\n */\n accepted: boolean\n onArm(): void\n onConfirm(): void\n onCancel(): void\n}\n\n/**\n * Normalize a report's source field to the full shape.\n *\n * An older hq-edge predates the feature and sends no `source` at all — that\n * must degrade to no editor, not crash the render. A future server may add\n * fields; unknown extras are dropped here rather than trusted.\n */\nfunction normalizeSource(raw: unknown): RuntimeUpdateSource | null {\n if (raw === null || typeof raw !== 'object') return null\n const candidate = raw as Partial<RuntimeUpdateSource>\n if (typeof candidate.origin !== 'string') return null\n return {\n url: typeof candidate.url === 'string' ? candidate.url : '',\n origin: candidate.origin as RuntimeUpdateSource['origin'],\n configuredUrl: typeof candidate.configuredUrl === 'string' ? candidate.configuredUrl : '',\n override: candidate.override === true,\n }\n}\n\n// ── Toast ─────────────────────────────────────────────────────────────────\n\n/** The single live toast; a new message replaces the one on screen. */\nlet toastHost: HTMLElement | null = null\nlet toastTimer: number | null = null\n/** Copy for the toast's close affordance; set when the panel mounts. */\nlet activeCopy: UpdateCopy | null = null\n\nfunction showToast(message: string, tone: 'success' | 'error'): void {\n if (toastHost === null) return\n if (toastTimer !== null) window.clearTimeout(toastTimer)\n toastHost.textContent = ''\n const toast = el('div', 'hqpu-toast')\n // An error must interrupt; a success may wait for a pause.\n toast.setAttribute('role', tone === 'error' ? 'alert' : 'status')\n const icon = el('span', 'hqpu-toast-icon')\n icon.dataset.tone = tone\n icon.innerHTML = tone === 'success' ? CHECK_CIRCLE_ICON(16) : WARNING_ICON(16)\n toast.append(icon, el('span', 'hqpu-toast-text', message))\n const close = iconButton(\n 'hqpu-toast-close',\n CLOSE_ICON(12),\n activeCopy?.close ?? 'Close',\n () => {\n toastHost?.replaceChildren()\n if (toastTimer !== null) window.clearTimeout(toastTimer)\n toastTimer = null\n },\n )\n toast.append(close)\n toastHost.append(toast)\n toastTimer = window.setTimeout(() => {\n toastHost?.replaceChildren()\n toastTimer = null\n }, TOAST_HOLD_MS)\n}\n\n// ── Panel ─────────────────────────────────────────────────────────────────\n\n/**\n * Open (or focus) the update panel. Re-renders from the store; when the store\n * is stale the panel first refreshes the report.\n */\nexport function openUpdateView(store: UpdateStoreLike, copy: UpdateCopy): void {\n const existing = document.querySelector<HTMLElement>('[data-hq-plugin-update-view]')\n if (existing !== null) {\n existing.style.display = ''\n return\n }\n\n const previouslyFocused = document.activeElement as HTMLElement | null\n\n const overlay = el('div', 'hqpu-overlay')\n overlay.setAttribute('data-hq-plugin-update-view', '')\n overlay.addEventListener('click', (ev) => {\n if (ev.target === overlay) onClose()\n })\n\n const scrim = el('div', 'hqpu-scrim')\n const panel = el('div', 'hqpu-panel')\n panel.setAttribute('role', 'dialog')\n panel.setAttribute('aria-modal', 'true')\n panel.setAttribute('aria-label', copy.ui.title)\n panel.tabIndex = -1\n\n // ── Header ──\n const head = el('header', 'hqpu-head')\n const headMain = el('div', 'hqpu-head-main')\n const brandIcon = el('span', 'hqpu-brand-icon')\n brandIcon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M13.8 8.6A5.8 5.8 0 012.2 6.4\"/><path d=\"M13.8 4.4V8.6H9.6\"/><path d=\"M2.2 7.4A5.8 5.8 0 0113.8 9.6\"/><path d=\"M2.2 11.6V7.4H6.4\"/></svg>`\n const titleBlock = el('div', 'hqpu-title-block')\n titleBlock.append(el('h1', 'hqpu-title', copy.ui.title))\n const sub = el('p', 'hqpu-sub')\n titleBlock.append(sub)\n headMain.append(brandIcon, titleBlock)\n\n const toolbar = el('div', 'hqpu-toolbar')\n const refreshBtn = iconButton('hqpu-icon-btn', REFRESH_ICON(15), copy.refresh, () => {\n void store.check()\n })\n const gearBtn = iconButton(\n 'hqpu-icon-btn',\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"8\" cy=\"8\" r=\"2.2\"/><path d=\"M13.5 9.2v-2.4l-1.8-.6a4.4 4.4 0 00-.5-1.2l.9-1.7-1.7-1.7-1.7.9a4.4 4.4 0 00-1.2-.5L7.2 2.5H4.8l-.6 1.8a4.4 4.4 0 00-1.2.5l-1.7-.9-1.7 1.7.9 1.7a4.4 4.4 0 00-.5 1.2L2.5 7.2v2.4l1.8.6a4.4 4.4 0 00.5 1.2l-.9 1.7 1.7 1.7 1.7-.9a4.4 4.4 0 001.2.5l.6 1.8h2.4l.6-1.8a4.4 4.4 0 001.2-.5l1.7.9 1.7-1.7-.9-1.7a4.4 4.4 0 00.5-1.2l1.8-.6z\"/></svg>`,\n copy.ui.gearTooltip,\n () => {\n toggleSettings()\n },\n )\n gearBtn.dataset.settingsToggle = ''\n gearBtn.setAttribute('aria-pressed', 'false')\n const logBtn = iconButton('hqpu-icon-btn', LOG_ICON(15), copy.ui.logShow, () => {\n toggleLog()\n })\n logBtn.dataset.logToggle = ''\n logBtn.setAttribute('aria-pressed', 'false')\n const closeBtn = iconButton('hqpu-icon-btn hqpu-icon-btn--close', CLOSE_ICON(15), copy.close, () => {\n onClose()\n })\n toolbar.append(refreshBtn, gearBtn, logBtn, closeBtn)\n head.append(headMain, toolbar)\n panel.append(head)\n\n // ── Settings strip (gear-toggled, above the body so it never shrinks it) ──\n const settingsEl = el('div', 'hqpu-settings')\n settingsEl.hidden = true\n panel.append(settingsEl)\n\n // ── Body + footer + log drawer share one positioned box, so the drawer can\n // cover the body without covering the header ──\n const main = el('div', 'hqpu-main')\n const scroll = el('div', 'hqpu-scroll')\n const column = el('div', 'hqpu-column')\n const jobSection = el('div', 'hqpu-stack')\n const bannerSection = el('div', 'hqpu-stack')\n const bandSection = el('div', 'hqpu-stack')\n const listSection = el('div', 'hqpu-stack')\n const extraSection = el('div', 'hqpu-stack')\n column.append(jobSection, bannerSection, bandSection, listSection, extraSection)\n scroll.append(column)\n\n const footer = el('footer', 'hqpu-footer')\n const cta = el('button', 'hqpu-btn--block')\n cta.type = 'button'\n const ctaSpinner = el('span', 'hqpu-cta-spinner')\n ctaSpinner.hidden = true\n const ctaLabel = el('span', 'hqpu-cta-label')\n cta.append(ctaSpinner, ctaLabel)\n const footMeta = el('div', 'hqpu-foot-meta')\n const footCount = el('span', '')\n const ignoreBtn = el('button', 'hqpu-link')\n ignoreBtn.type = 'button'\n footMeta.append(footCount, ignoreBtn)\n footer.append(cta, footMeta, el('p', 'hqpu-footnote', copy.ui.footnote))\n\n const logDrawer = el('div', 'hqpu-log')\n logDrawer.dataset.logDrawer = ''\n logDrawer.hidden = true\n\n const toastSection = el('div', 'hqpu-toast-host')\n main.append(scroll, footer, logDrawer, toastSection)\n panel.append(main)\n\n overlay.append(scrim, panel)\n document.body.appendChild(overlay)\n toastHost = toastSection\n activeCopy = copy\n panel.focus()\n\n const disposers: Array<() => void> = []\n\n // ── Panel-closure UI state (survives store refreshes) ──\n let runtimeArmed = false\n let runtimeBusy = false\n let runtimeAccepted = false\n let settingsOpen = false\n let settingsBuilt = false\n let logOpen = false\n let dismissedAlert = false\n let changesAllOpen = false\n const changesOpen = new Set<string>()\n let upToDateOpen = false\n let ignoredOpen = false\n let releaseOpen = false\n let saving = false\n let pluginDraft = ''\n let runtimeDraft = ''\n\n const runtimeActions: RuntimeActions = {\n get busy() {\n return runtimeBusy\n },\n get armed() {\n return runtimeArmed\n },\n get accepted() {\n return runtimeAccepted\n },\n onArm: () => {\n runtimeArmed = true\n render()\n },\n onCancel: () => {\n runtimeArmed = false\n render()\n },\n onConfirm: () => {\n void requestRuntime()\n },\n }\n\n /** Fill both drafts from the report's URLs in effect. */\n function syncDrafts(): void {\n const report = store.report()\n pluginDraft = normalizeSource(report?.pluginSource)?.url ?? ''\n runtimeDraft = normalizeSource(report?.runtime.source)?.url ?? ''\n }\n\n function toggleSettings(): void {\n settingsOpen = !settingsOpen\n if (settingsOpen) {\n syncDrafts()\n settingsBuilt = false\n }\n render()\n }\n\n /**\n * Save both manifest sources, then re-run discovery and close the strip.\n *\n * The re-check is not optional: the report is cached, so without a fresh\n * `POST /check` the list would keep naming the OLD manifest's offer after the\n * source changed. hq-edge answers 422 with a message meant for a person on\n * refusal (bad scheme, credentials, …), which the toast shows verbatim —\n * and the strip stays open with the draft intact so it can be fixed.\n */\n async function saveSources(): Promise<void> {\n const report = store.report()\n const pluginSource = normalizeSource(report?.pluginSource)\n const runtimeSource = normalizeSource(report?.runtime.source)\n saving = true\n settingsBuilt = false\n render()\n try {\n const calls: Array<Promise<unknown>> = []\n if (pluginSource !== null && pluginDraft !== pluginSource.url) {\n calls.push(setPluginUpdateSource(pluginDraft))\n }\n if (runtimeSource !== null && runtimeDraft !== runtimeSource.url) {\n calls.push(setRuntimeUpdateSource(runtimeDraft))\n }\n await Promise.all(calls)\n settingsOpen = false\n showToast(copy.pluginSource.saved, 'success')\n try {\n await checkUpdates()\n await store.refresh()\n } catch {\n // The source IS saved; a failed re-check only leaves the previous\n // offer on screen until the next refresh — the toolbar re-runs it.\n }\n } catch (err: unknown) {\n showToast(\n `${copy.pluginSource.saveFailed}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n } finally {\n saving = false\n settingsBuilt = false\n render()\n }\n }\n\n /** Clear one user override so the configured URL applies again. */\n async function resetSource(kind: SourceKind): Promise<void> {\n saving = true\n settingsBuilt = false\n render()\n try {\n if (kind === 'runtime') await setRuntimeUpdateSource('')\n else await setPluginUpdateSource('')\n showToast(kind === 'runtime' ? copy.runtime.saved : copy.pluginSource.saved, 'success')\n try {\n await checkUpdates()\n await store.refresh()\n } catch {\n /* saved anyway */\n }\n syncDrafts()\n } catch (err: unknown) {\n showToast(\n `${copy.pluginSource.saveFailed}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n } finally {\n saving = false\n settingsBuilt = false\n render()\n }\n }\n\n async function requestRuntime(): Promise<void> {\n const version = runtimeLatestVersion(store.report()?.runtime)\n if (version === null) {\n showToast(copy.checkFailed, 'error')\n return\n }\n runtimeArmed = false\n runtimeBusy = true\n render()\n try {\n const result = await requestRuntimeUpdate(version)\n if (result?.error !== undefined) {\n showToast(`${copy.installError}: ${result.error}`, 'error')\n return\n }\n // 202 Accepted: hq-edge exits to let the EDA host swap the runtime and\n // relaunch. Latch it, so a store refresh cannot re-offer the button for\n // a server that is already gone.\n runtimeAccepted = true\n showToast(copy.runtime.updateAccepted, 'success')\n void store.fetchStatus()\n } catch (err: unknown) {\n showToast(\n `${copy.installError}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n } finally {\n runtimeBusy = false\n render()\n }\n }\n\n /** Retry a failed/abandoned job: plugin-set → a fresh install; runtime →\n * re-arm the runtime confirm flow. Always a NEW job, never re-clicking the\n * settled job id. */\n function retryJob(kind: 'plugin-set' | 'runtime'): void {\n if (kind === 'runtime') {\n runtimeArmed = true\n render()\n } else {\n void runInstall(store, copy)\n }\n }\n\n /** The newest settled job — what the log drawer is about. */\n function settledJob(): UpdateJob | null {\n const latest = store.recentJobs()[0]\n return latest !== undefined && TERMINAL_PHASES.has(latest.phase) ? latest : null\n }\n\n function toggleLog(): void {\n logOpen = !logOpen\n render()\n }\n\n async function copyLogId(id: string): Promise<void> {\n try {\n const clipboard = navigator.clipboard\n if (clipboard === undefined) throw new Error('clipboard unavailable')\n await clipboard.writeText(id)\n showToast(copy.ui.logCopied, 'success')\n } catch {\n showToast(copy.ui.logCopyFailed, 'error')\n }\n }\n\n // ── Section renderers ──\n\n function renderSettings(): void {\n if (!settingsOpen) {\n settingsEl.hidden = true\n settingsEl.textContent = ''\n return\n }\n settingsEl.hidden = false\n // Rebuilding on every store emit would blow away the caret of whoever is\n // typing, so the DOM is built once per open (or per explicit force).\n if (settingsBuilt) return\n settingsBuilt = true\n settingsEl.textContent = ''\n\n const report = store.report()\n const pluginSource = normalizeSource(report?.pluginSource)\n const runtimeSource = normalizeSource(report?.runtime.source)\n\n const row = el('div', 'hqpu-settings-row')\n row.append(el('span', 'hqpu-settings-title', copy.ui.settingsTitle))\n const overridden = (pluginSource?.override ?? false) || (runtimeSource?.override ?? false)\n const badge = el('span', 'hqpu-origin-badge', overridden ? copy.ui.originCustom : copy.ui.originDefault)\n badge.dataset.origin = overridden ? 'override' : 'config'\n row.append(badge)\n settingsEl.append(row)\n\n const field = (\n kind: SourceKind,\n source: RuntimeUpdateSource | null,\n label: string,\n ariaLabel: string,\n placeholder: string,\n draft: string,\n onDraft: (value: string) => void,\n ): void => {\n if (source === null) return\n const wrap = el('div', 'hqpu-field')\n const labelRow = el('div', 'hqpu-field-head')\n const id = `hqpu-source-${kind}`\n const labelEl = el('label', '', label)\n labelEl.htmlFor = id\n labelRow.append(labelEl)\n if (source.override) {\n const reset = linkButton('hqpu-link hqpu-link--tiny', copy.ui.resetSource, () => {\n void resetSource(kind)\n })\n reset.setAttribute(`data-${kind}-source-reset`, '')\n labelRow.append(reset)\n }\n wrap.append(labelRow)\n const input = el('input', 'hqpu-source-input')\n input.id = id\n input.type = 'url'\n input.value = draft\n input.placeholder = placeholder\n input.setAttribute('aria-label', ariaLabel)\n input.setAttribute(`data-${kind}-source-input`, '')\n input.disabled = saving\n input.spellcheck = false\n input.addEventListener('input', () => {\n // Draft-only: no re-render, so the caret and IME stay untouched.\n onDraft(input.value)\n })\n // Enter saves both; Escape closes the strip without saving.\n input.addEventListener('keydown', (ev) => {\n if (ev.key === 'Enter') {\n ev.preventDefault()\n void saveSources()\n } else if (ev.key === 'Escape') {\n ev.preventDefault()\n ev.stopPropagation()\n settingsOpen = false\n syncDrafts()\n render()\n }\n })\n wrap.append(input)\n settingsEl.append(wrap)\n }\n\n field(\n 'plugin',\n pluginSource,\n copy.ui.pluginUrlLabel,\n copy.pluginSource.inputLabel,\n copy.pluginSource.placeholder,\n pluginDraft,\n (value) => {\n pluginDraft = value\n },\n )\n field(\n 'runtime',\n runtimeSource,\n copy.ui.runtimeUrlLabel,\n copy.runtime.sourceInputLabel,\n copy.runtime.sourcePlaceholder,\n runtimeDraft,\n (value) => {\n runtimeDraft = value\n },\n )\n if (pluginSource === null && runtimeSource === null) {\n settingsEl.append(el('p', 'hqpu-status', copy.runtime.notConfigured))\n }\n\n const actions = el('div', 'hqpu-settings-actions')\n const cancel = el('button', 'hqpu-btn hqpu-btn--ghost hqpu-btn--sm', copy.cancel)\n cancel.type = 'button'\n cancel.addEventListener('click', () => {\n settingsOpen = false\n syncDrafts()\n render()\n })\n const save = el('button', 'hqpu-btn hqpu-btn--brand hqpu-btn--sm', copy.ui.saveAndRefresh)\n save.type = 'button'\n save.disabled = saving\n save.setAttribute('data-source-save', '')\n save.addEventListener('click', () => {\n void saveSources()\n })\n actions.append(cancel, save)\n settingsEl.append(actions)\n }\n\n function renderJobSection(): void {\n jobSection.textContent = ''\n const active = store.activeJob()\n if (active !== null && !TERMINAL_PHASES.has(active.phase)) {\n jobSection.append(activeJobView(active, copy))\n }\n }\n\n function renderBanners(report: PluginUpdateReport | null, jobActive: boolean): void {\n bannerSection.textContent = ''\n if (report !== null) {\n const banner = checkBanner(report, copy, () => {\n void store.check()\n })\n if (banner !== null) bannerSection.append(banner)\n }\n // A terminal non-success job leads the body; the full log is one click away\n // in the drawer, but the outcome itself must not hide there.\n if (jobActive) return\n const settled = settledJob()\n if (settled !== null && settled.phase !== 'done') {\n bannerSection.append(\n jobResultBanner(\n settled,\n copy,\n () => retryJob(settled.kind),\n () => {\n logOpen = true\n render()\n },\n ),\n )\n }\n }\n\n function renderList(report: PluginUpdateReport | null, busy: boolean): void {\n listSection.textContent = ''\n bandSection.textContent = ''\n if (report === null) {\n listSection.append(skeleton(copy))\n return\n }\n const band = decisionBand(report, copy)\n if (band !== null) bandSection.append(band)\n\n const actionable = report.items.filter(isActionable)\n if (actionable.length > 0) {\n const section = el('section', 'hqpu-group')\n section.dataset.group = copy.group.actionable\n const head = el('div', 'hqpu-sec-head')\n const titleWrap = el('div', 'hqpu-sec-title')\n titleWrap.append(el('span', '', copy.group.actionable))\n titleWrap.append(el('span', 'hqpu-count-pill', String(actionable.length)))\n head.append(titleWrap)\n const anyChanges = actionable.some((i) => Array.isArray(i.changes) && i.changes.length > 0)\n if (anyChanges) {\n head.append(\n linkButton(\n 'hqpu-link',\n changesAllOpen ? copy.ui.collapseLogs : copy.ui.expandLogs,\n () => {\n changesAllOpen = !changesAllOpen\n if (!changesAllOpen) changesOpen.clear()\n render()\n },\n ),\n )\n }\n section.append(head)\n const list = el('ul', 'hqpu-cards')\n for (const item of actionable) {\n list.append(\n pluginCard(item, copy, {\n changesOpen: changesAllOpen || changesOpen.has(item.name),\n onToggleChanges: () => {\n if (changesOpen.has(item.name)) changesOpen.delete(item.name)\n else changesOpen.add(item.name)\n render()\n },\n onInstall: () => {\n void runInstall(store, copy, [item.name])\n },\n onIgnore: () => {\n void togglePluginIgnore(item)\n },\n busy,\n }),\n )\n }\n section.append(list)\n listSection.append(section)\n }\n }\n\n function renderExtras(report: PluginUpdateReport | null): void {\n extraSection.textContent = ''\n if (report === null) return\n\n const alert = runtimeResultAlert(report, copy, dismissedAlert, () => {\n dismissedAlert = true\n render()\n })\n if (alert !== null) extraSection.append(alert)\n\n const engine = runtimeEngineCard(report, copy, runtimeActions)\n if (engine !== null) extraSection.append(engine)\n\n const byState = (state: PluginUpdateItem['state']): PluginUpdateItem[] =>\n report.items.filter((item) => item.state === state)\n\n const ignored = byState('ignored')\n if (ignored.length > 0) {\n extraSection.append(\n disclosure({\n id: 'hqpu-disc-ignored',\n title: copy.group.ignored,\n count: `(${ignored.length})`,\n note: copy.ui.ignoredNote,\n open: ignoredOpen,\n onToggle: () => {\n ignoredOpen = !ignoredOpen\n render()\n },\n build: () => {\n const ul = el('ul', 'hqpu-rows')\n for (const item of ignored) {\n ul.append(\n pluginRow(item, copy, () => {\n void togglePluginIgnore(item)\n }),\n )\n }\n return [ul]\n },\n }),\n )\n }\n\n const current = byState('current')\n if (current.length > 0) {\n extraSection.append(\n disclosure({\n id: 'hqpu-disc-current',\n title: copy.group.current,\n count: `(${current.length})`,\n note: copy.ui.upToDateNote,\n open: upToDateOpen,\n onToggle: () => {\n upToDateOpen = !upToDateOpen\n render()\n },\n build: () => {\n const ul = el('ul', 'hqpu-rows')\n for (const item of current) ul.append(pluginRow(item, copy, () => undefined))\n return [ul]\n },\n }),\n )\n }\n\n if (hasReleaseContent(report.release)) {\n extraSection.append(\n disclosure({\n id: 'hqpu-disc-release',\n title: copy.ui.releaseTitle,\n note: report.manifestVersion !== null ? `v${report.manifestVersion}` : undefined,\n open: releaseOpen,\n onToggle: () => {\n releaseOpen = !releaseOpen\n render()\n },\n build: () => releaseBody(report.release as ReleaseInfo, copy),\n }),\n )\n }\n\n if (report.items.length === 0 && typeof report.lastError !== 'string') {\n extraSection.append(el('div', 'hqpu-empty', copy.empty))\n }\n }\n\n function renderFooter(report: PluginUpdateReport | null, busy: boolean): void {\n const actionable = report === null ? 0 : actionableUpdateCount(report)\n const setVersion = report?.manifestVersion ?? null\n const failed = typeof report?.lastError === 'string' && (report?.lastError ?? '').length > 0\n const runtimePending = report !== null && runtimeUpdateOffered(report.runtime)\n const anything = actionable > 0 || runtimePending\n\n ctaSpinner.hidden = !busy\n cta.dataset.busy = busy ? 'true' : 'false'\n cta.setAttribute('aria-busy', busy ? 'true' : 'false')\n\n if (busy) {\n ctaLabel.textContent = copy.ui.ctaInstalling\n cta.disabled = true\n footCount.textContent = copy.ui.footerBusy\n } else if (report === null) {\n ctaLabel.textContent = copy.ui.ctaNoSet\n cta.disabled = true\n footCount.textContent = ''\n } else if (failed) {\n // Installing against a stale manifest is the one path that can only end\n // in a 422 — say so instead of offering a button that cannot work.\n ctaLabel.textContent = copy.ui.ctaFailed\n cta.disabled = true\n footCount.textContent = copy.ui.footerFailed\n } else if (setVersion === null) {\n ctaLabel.textContent = copy.ui.ctaNoSet\n cta.disabled = true\n footCount.textContent = copy.ui.checkedNever\n } else if (actionable === 0 && runtimePending) {\n // The plugin set is done; the only remaining offer is the runtime, which\n // needs its own confirm — the CTA must not imply it would install it.\n ctaLabel.textContent = copy.ui.ctaNoPlugins\n cta.disabled = true\n footCount.textContent = copy.ui.footerRuntimeOnly\n } else if (actionable === 0 && !runtimePending) {\n ctaLabel.textContent = copy.ui.ctaUpToDate\n cta.disabled = true\n footCount.textContent = copy.ui.footerAllGood\n } else {\n ctaLabel.textContent = copy.ui.cta(actionable)\n cta.disabled = false\n footCount.textContent = copy.ui.footerCount(actionable)\n }\n cta.onclick = () => {\n void runInstall(store, copy)\n }\n\n // Set-level mute: only with an evaluated set and something to mute.\n const canMute = setVersion !== null && report !== null && !busy\n ignoreBtn.hidden = !canMute || (!anything && !report?.ignoredSet)\n ignoreBtn.textContent = report?.ignoredSet === true ? copy.ui.unignoreBtn : copy.ui.ignoreBtn\n ignoreBtn.onclick = () => {\n if (setVersion === null) return\n const ignored = report?.ignoredSet === true\n const action = ignored ? unignoreOffer(setVersion) : ignoreOffer(setVersion)\n // `check()`, never `refresh()`: ignoring only writes the decision to\n // hq-edge's state file, and its cached report keeps every item at\n // `update` until discovery runs again.\n void action\n .then(() => store.check())\n .catch((err: unknown) => {\n showToast(\n `${copy.ignoreError}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n })\n }\n }\n\n function renderLogDrawer(jobActive: boolean): void {\n const job = settledJob()\n const available = !jobActive && job !== null\n logBtn.disabled = !available\n logBtn.setAttribute('aria-pressed', logOpen && available ? 'true' : 'false')\n const label =\n jobActive ? copy.ui.logBusy : job === null ? copy.ui.logNone : logOpen ? copy.ui.logHide : copy.ui.logShow\n logBtn.title = label\n logBtn.setAttribute('aria-label', label)\n\n // A running job (or none at all) cannot have a log; drop the open flag so\n // the drawer does not snap back open once the job settles.\n if (!available || !logOpen) {\n if (!available) logOpen = false\n logDrawer.hidden = true\n logDrawer.textContent = ''\n main.dataset.logOpen = '0'\n return\n }\n logDrawer.hidden = false\n logDrawer.textContent = ''\n // The drawer is absolutely positioned, so it cannot size its own box: on a\n // short body (nothing left to update) it would be squeezed between the\n // header and the panel edge. The flag gives `.hqpu-main` a floor while it\n // is open, and nothing at all once it closes.\n main.dataset.logOpen = '1'\n for (const node of buildLogDrawer(\n job,\n store.recentJobs(),\n copy,\n (id) => {\n void copyLogId(id)\n },\n () => {\n logOpen = false\n render()\n },\n )) {\n logDrawer.append(node)\n }\n }\n\n function render(): void {\n const report = store.report()\n const active = store.activeJob()\n const jobActive = active !== null && !TERMINAL_PHASES.has(active.phase)\n const busy = store.installing() || jobActive\n\n refreshBtn.disabled = report === null\n gearBtn.setAttribute('aria-pressed', settingsOpen ? 'true' : 'false')\n gearBtn.title = copy.ui.gearTooltip\n\n // Header sub-line: the set version and the relative check time, once.\n const ago = relativeAgo(report?.checkedAt, copy)\n const setLabel =\n report?.manifestVersion !== null && report?.manifestVersion !== undefined\n ? copy.ui.setVersion(report.manifestVersion)\n : null\n if (setLabel !== null && ago !== null) sub.textContent = copy.ui.subJoined(setLabel, ago)\n else sub.textContent = setLabel ?? ago ?? copy.ui.checkedUnknown\n\n renderSettings()\n renderJobSection()\n renderBanners(report, jobActive)\n renderList(report, busy)\n renderExtras(report)\n renderFooter(report, busy)\n renderLogDrawer(jobActive)\n }\n\n /** Flip one plugin's per-item ignore flag, then re-run discovery. */\n async function togglePluginIgnore(item: PluginUpdateItem): Promise<void> {\n const setVersion = store.report()?.manifestVersion ?? null\n if (setVersion === null) return\n const action =\n item.state === 'ignored'\n ? unignorePlugin(setVersion, item.name)\n : ignorePlugin(setVersion, item.name)\n try {\n await action\n // Discovery must re-run for the item to come back as `ignored`; a\n // refusal has to be visible, never silently swallowed.\n await store.check()\n } catch (err: unknown) {\n showToast(\n `${copy.ignoreError}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n }\n }\n\n const unsubscribe = store.subscribe(render)\n disposers.push(unsubscribe)\n\n // Refresh on open (bounded; never block the shell).\n void store.refresh()\n\n // Escape peels one layer at a time; the shell-wide contract still holds.\n const onKeyDown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape') {\n ev.stopPropagation()\n if (logOpen) {\n logOpen = false\n render()\n return\n }\n if (settingsOpen) {\n settingsOpen = false\n syncDrafts()\n render()\n return\n }\n onClose()\n return\n }\n if (ev.key === 'Tab') trapFocus(ev)\n }\n\n /** Keep Tab inside the dialog — it declares `aria-modal=\"true\"`. */\n function trapFocus(ev: KeyboardEvent): void {\n const focusable = [\n ...panel.querySelectorAll<HTMLElement>(\n 'button:not(:disabled), [href], input:not(:disabled), [tabindex]:not([tabindex=\"-1\"])',\n ),\n ].filter((node) => node.closest('[hidden]') === null)\n if (focusable.length === 0) return\n const first = focusable[0]!\n const last = focusable[focusable.length - 1]!\n const activeEl = document.activeElement\n if (ev.shiftKey && (activeEl === first || activeEl === panel)) {\n ev.preventDefault()\n last.focus()\n } else if (!ev.shiftKey && activeEl === last) {\n ev.preventDefault()\n first.focus()\n }\n }\n\n document.addEventListener('keydown', onKeyDown)\n disposers.push(() => document.removeEventListener('keydown', onKeyDown))\n\n function onClose(): void {\n for (const dispose of disposers) {\n try {\n dispose()\n } catch {\n /* already disposed */\n }\n }\n if (toastTimer !== null) window.clearTimeout(toastTimer)\n toastTimer = null\n if (toastHost === toastSection) toastHost = null\n if (activeCopy === copy) activeCopy = null\n overlay.remove()\n // Focus goes back where it came from — the sidebar row that opened this.\n if (previouslyFocused !== null && previouslyFocused.isConnected) previouslyFocused.focus()\n }\n\n render()\n}\n\n/**\n * Run the install transaction (a new durable job), then let the store poll it.\n *\n * @param pluginNames - omit for the whole set (the footer CTA), or pass one\n * name for a card's 单独更新. hq-edge honours the subset.\n */\nasync function runInstall(\n store: UpdateStoreLike,\n copy: UpdateCopy,\n pluginNames?: string[],\n): Promise<void> {\n const report = store.report()\n if (report === null) return\n // Without an evaluated set there is nothing to install: hq-edge has not read\n // a manifest yet, so the report is still its empty shell.\n if (report.manifestVersion === null) {\n showToast(copy.checkFailed, 'error')\n return\n }\n // Flip the installing flag BEFORE the request: this is what disables the\n // action buttons and shows the in-flight footer state.\n store.setInstalling(true)\n try {\n const result = await installSet(report.manifestVersion, pluginNames)\n if (result.error !== undefined) {\n store.setInstalling(false)\n showToast(`${copy.installError}: ${result.error}`, 'error')\n return\n }\n // 202 accepted: a durable job now owns the lifecycle. The store's /status\n // poller renders progress and clears the flag when the job settles.\n showToast(copy.installSuccess, 'success')\n void store.fetchStatus()\n } catch (err: unknown) {\n store.setInstalling(false)\n showToast(\n `${copy.installError}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n }\n}\n\n/** First-read placeholders, sharing the card layout. */\nfunction skeleton(copy: UpdateCopy): HTMLElement {\n const section = el('section', 'hqpu-group')\n section.setAttribute('role', 'status')\n section.dataset.loading = ''\n section.setAttribute('aria-label', copy.loading)\n const list = el('ul', 'hqpu-cards')\n list.setAttribute('aria-hidden', 'true')\n for (let index = 0; index < 3; index += 1) {\n const card = el('li', 'hqpu-card')\n const head = el('div', 'hqpu-card-head')\n const id = el('div', 'hqpu-card-id')\n id.append(el('span', 'hqpu-avatar hqpu-skeleton-fill'))\n const main = el('div', 'hqpu-card-main')\n const title = el('span', 'hqpu-card-title hqpu-skeleton-fill hqpu-skeleton-title')\n const sub = el('span', 'hqpu-card-sub hqpu-skeleton-fill hqpu-skeleton-sub')\n main.append(title, sub)\n id.append(main)\n head.append(id)\n card.append(head)\n list.append(card)\n }\n section.append(list)\n return section\n}\n","/**\n * The sidebar toolbutton's numeric badge — one pure function, no DOM, no\n * store, so the rule can be tested on its own.\n *\n * It answers one question: how many updates does this EDA install still have\n * for the user? That is every actionable plugin item plus the HQ Edge runtime\n * offer when hq-edge reports one:\n *\n * - **plugin items** — compatible `update` + `install`, minus whatever the\n * user muted ({@link actionableUpdateCount});\n * - **the HQ Edge runtime update, as one** — a different deliverable with its\n * own card and its own confirm flow, but still an available update, and\n * leaving it out of the only number on the sidebar hid a pending runtime\n * upgrade until the panel was opened. It counts as ONE (a runtime swap is a\n * single action, however many manifest entries it was picked from) and it\n * survives a muted plugin offer, because the two offers are independent —\n * ignoring this release set must not hide a newer runtime.\n *\n * What is NOT counted: the durable job history. It is permanent\n * (`listRecent(20)`, survives restarts), so turning any settled job — even a\n * successful install — into a \"1\" latched the badge on forever. That was the\n * original bug; a job is only allowed to speak while it is still RUNNING.\n *\n * @module\n */\nimport {\n TERMINAL_PHASES,\n runtimeUpdateOffered,\n type PluginUpdateReport,\n type UpdateJob,\n} from './api.js'\nimport { actionableUpdateCount } from './version.js'\n\n/**\n * The badge value for one snapshot of `(report, active job)`.\n *\n * @param report - the latest plugin-update report, or null before the first\n * read. Null contributes nothing (never a placeholder count).\n * @param activeJob - the job the server reports as active, or null when idle.\n * A RUNNING job counts as 1 only when there is nothing actionable to show,\n * so the row still signals in-flight work; a TERMINAL job contributes\n * nothing — it is history.\n * @returns the number to render; 0 means \"no badge at all\".\n */\nexport function badgeCountFor(\n report: PluginUpdateReport | null,\n activeJob: UpdateJob | null,\n): number {\n if (report !== null) {\n // Plugin items already collapse to 0 when the offer is muted; the runtime\n // is an independent offer and keeps its own count.\n const plugin = actionableUpdateCount(report)\n const runtime = runtimeUpdateOffered(report.runtime) ? 1 : 0\n const total = plugin + runtime\n if (total > 0) return total\n }\n if (activeJob !== null && !TERMINAL_PHASES.has(activeJob.phase)) return 1\n return 0\n}\n","/**\n * `@huaqiu/dsh-tool-plugin-update` — browser half.\n *\n * Mounts one sidebar toolbutton in the Huaqiu family block (\"插件更新\") with a\n * numeric badge equal to the number of updates still on offer:\n *\n * - the actionable plugin items (spec §6.3/§7.1 — compatible `update` +\n * `install` after host/HQ-Edge filtering, minus the ignored offer and any\n * muted item), PLUS\n * - one for the HQ Edge runtime update when hq-edge reports one (a separate\n * deliverable with its own card, still a pending update — see `badge.ts`).\n *\n * The durable job history never contributes (a settled job is not an update);\n * a job that is still running is the only non-offer allowed to show a count.\n * Click opens the review/confirm/ignore modal.\n *\n * The authoritative state is the hq-edge report fetched through the plugin's\n * same-origin forwarding routes. A tiny in-memory store owns the latest report\n * + installing flag; the sidebar badge and the modal subscribe to it. No\n * timers beyond the install-poll and an idle 60 s refresh.\n *\n * @module\n */\nimport {\n HUAQIU_SIDEBAR_ENTRY_FAMILY,\n detectHostLocale,\n mountSidebarEntry,\n observeHostLocale,\n type HostLocale,\n} from '@huaqiu/dsh-sidebar-entry'\nimport {\n checkUpdates,\n fetchJobStatus,\n fetchReport,\n TERMINAL_PHASES,\n type PluginUpdateReport,\n type UpdateJob,\n type UpdateJobStatus,\n} from './api.js'\nimport { COPY } from './copy.js'\nimport { PLUGIN_ICON } from './icons.js'\nimport { openUpdateView, type UpdateStoreLike } from './update-view.js'\nimport { badgeCountFor } from './badge.js'\n\n/** Stable data attributes identifying the injected entry row. */\nexport const UPDATE_ENTRY_SELECTOR = '[data-hq-plugin-update-entry]'\nexport const UPDATE_ENTRY_ATTRIBUTE = 'data-hq-plugin-update-entry'\n\n/** L2 semantic plugin id: rows carry data-dsh-plugin + data-dsh-part. */\nconst PLUGIN_ID = 'huaqiu-plugin-update'\n\n/** Row class names (defined by {@link ENTRY_CSS_TEXT}). */\nconst ENTRY_CSS: Record<string, string> = {\n entry: 'hqpu-sidebar-entry',\n entryIcon: 'hqpu-sidebar-entry__icon',\n entryLabel: 'hqpu-sidebar-entry__label',\n entryBadge: 'hqpu-sidebar-entry__badge',\n}\n\n/** Localized row copy: zh is the fallback, matching the family convention. */\nfunction entryCopy(lang: HostLocale | undefined): { label: string; tooltip: string } {\n return lang === 'en'\n ? { label: COPY.en.entryLabel, tooltip: COPY.en.entryTooltip }\n : { label: COPY.zh.entryLabel, tooltip: COPY.zh.entryTooltip }\n}\n\n/** The sidebar glyph — the same pinwheel the shell's own Plugins entry draws\n * (`IconPluginPinwheelOutlineRegular`), so both rows read as one feature. */\nexport const UPDATE_ICON = PLUGIN_ICON(16)\n\n/** Sidebar entry styles — matched to the family rows (same inset/padding/gap\n * as the shell's own Plugins entry, `SidebarRoot.module.css` `.panelRow`).\n * Every colour rides a `--dsw-alias-*` token so the row follows the shell's\n * light/dark theme; the badge foreground uses the toast label, the shell's\n * \"always light ink on a saturated surface\" token. */\nconst ENTRY_CSS_TEXT = `\n.hqpu-sidebar-entry {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n margin: 0 2px;\n min-height: 36px;\n padding: 7px 8px;\n background: transparent;\n border: none;\n border-radius: var(--dsw-radius-md);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n font: inherit;\n line-height: 22px;\n text-align: left;\n white-space: nowrap;\n}\n.hqpu-sidebar-entry:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.hqpu-sidebar-entry[data-active] {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n.hqpu-sidebar-entry:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: -2px;\n}\n.hqpu-sidebar-entry__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n}\n.hqpu-sidebar-entry__icon svg {\n display: block;\n width: 16px;\n height: 16px;\n}\n.hqpu-sidebar-entry__label {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n flex: 1;\n}\n.hqpu-sidebar-entry__badge {\n flex: none;\n min-width: 16px;\n height: 16px;\n padding: 0 5px;\n border-radius: 8px;\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-toast-label);\n font-size: 11px;\n line-height: 16px;\n font-weight: 500;\n text-align: center;\n font-variant-numeric: tabular-nums;\n}\n/* Collapsed rail: icon-only, centered — same 36px square the shell uses.\n The update-count badge moves to the button's bottom-right corner. The\n sidebar-entry helper only renders the badge element while count > 0, so a\n zero count shows a bare icon with no dot. */\n[data-sidebar-collapsed] .hqpu-sidebar-entry {\n position: relative;\n justify-content: center;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n}\n[data-sidebar-collapsed] .hqpu-sidebar-entry__icon svg {\n width: 18px;\n height: 18px;\n}\n[data-sidebar-collapsed] .hqpu-sidebar-entry__label {\n display: none;\n}\n[data-sidebar-collapsed] .hqpu-sidebar-entry__badge {\n /* Corner count badge: overlaps the icon's bottom-right like a notification\n dot. The ring uses the rail's own fill (HuaQiu override when the theme is\n mounted, shell layer-1 otherwise) so it reads on any sidebar surface. */\n position: absolute;\n right: 0;\n bottom: 0;\n min-width: 14px;\n height: 14px;\n padding: 0 3px;\n border-radius: 7px;\n font-size: 9px;\n line-height: 14px;\n box-shadow: 0 0 0 1.5px var(--dsw-specific-sidebar-fill, var(--dsw-alias-bg-layer-1));\n transform: translate(12%, 12%);\n}\n`\n\n/**\n * View styles (hqpu-*). Plain overlay + panel, drawn with the SAME design\n * tokens and geometry the shell's own plugin manager uses\n * (`ui-plugin-manager/src/client/PluginManagerPage.module.css` +\n * `ui-primitives/src/{Modal,Toast,Tag,StateDot}.module.css`), so this page\n * reads as part of the product rather than a bolt-on.\n *\n * Two rules keep it honest across themes:\n * 1. every colour is a `--dsw-alias-*` token — no literal hex/rgb, so light\n * and dark both come out right (the old sheet invented\n * `--dsw-alias-bg-surface` / `--dsw-alias-line-border` / `--dsw-alias-bg-badge`,\n * none of which exist, and so always fell back to a white panel);\n * 2. `color-mix()` tints come from the state token itself, so a palette\n * change moves fill and ink together.\n *\n * The chrome (panel, scrim, header, footer) follows `ui-primitives/Modal`,\n * because this view IS a dialog; the body follows `PluginManagerPage`, because\n * that is the content it heads. The one token the bundled shell does not\n * publish is backfilled by {@link ensureDesignTokens} — see the note there.\n */\nconst VIEW_CSS_TEXT = `\n/* ── Overlay + panel ─────────────────────────────────────────────────────\n * A 360 px card, the width the prototype fixes: the panel is a decision\n * surface inside the EDA workbench, not a page, and 360 is the width at which\n * a card's title, version row and two actions still fit on one line each. */\n.hqpu-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n /* The frame's own top inset keeps a tall card clear of the window chrome. */\n padding: max(24px, var(--dsh-frame-overlay-top, 24px)) 24px;\n}\n.hqpu-scrim {\n position: absolute;\n /* Only paint excludes the native caption; the root still blocks clicks. */\n inset: var(--dsh-frame-chrome-top, 0px) 0 0;\n background: var(--dsw-alias-bg-mask-3, var(--dsw-alias-bg-mask-1));\n animation: hqpu-fade var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease);\n}\n.hqpu-panel {\n position: relative;\n z-index: 1;\n display: flex;\n flex-direction: column;\n width: min(360px, calc(100vw - 24px));\n max-height: min(86vh, 720px);\n overflow: hidden;\n border: 0.5px solid var(--hq-border);\n border-radius: 12px;\n background: var(--hq-bg);\n color: var(--hq-text);\n box-shadow: var(--dsw-elevation-prominent, 0 16px 48px rgba(0, 0, 0, 0.24));\n animation: hqpu-panel-in var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease);\n}\n.hqpu-panel:focus {\n outline: none;\n}\n.hqpu-panel *,\n.hqpu-panel *::before,\n.hqpu-panel *::after {\n box-sizing: border-box;\n}\n/* 'hidden' is the one display switch this sheet uses for drawers, so it has to\n * beat the layout rules below it. */\n.hqpu-panel [hidden] {\n display: none !important;\n}\n\n/* The palette, expressed once. Every rule below reads these aliases, so a\n * theme switch (or the HuaQiu brand override) needs no rule to change. */\n.hqpu-panel,\n.hqpu-panel * {\n --hq-bg: var(--dsw-alias-bg-layer-1);\n --hq-card: var(--dsw-alias-bg-layer-2);\n --hq-card-soft: var(--dsw-alias-bg-overlay);\n --hq-border: var(--dsw-alias-border-l2);\n --hq-border-strong: var(--dsw-alias-border-l3);\n --hq-hover: var(--dsw-alias-interactive-bg-hover);\n --hq-text: var(--dsw-alias-label-primary);\n --hq-muted: var(--dsw-alias-label-secondary);\n --hq-faint: var(--dsw-alias-label-tertiary);\n --hq-brand: var(--dsw-alias-brand-primary);\n --hq-brand-soft: var(--dsw-alias-button-primary-hover);\n --hq-brand-dim: var(--dsw-alias-button-primary-dimmed);\n --hq-emerald: var(--dsw-alias-state-success-primary);\n --hq-amber: var(--dsw-alias-state-warn-primary);\n --hq-amber-ink: var(--dsw-alias-state-warn-label, var(--dsw-alias-state-warn-primary));\n /* Ink on an amber fill. Fixed rather than themed: warn-primary is a bright\n * amber in both themes, so the readable ink is the same warm near-black. */\n --hq-on-amber: #2b1a00;\n --hq-rose: var(--dsw-alias-state-error-primary);\n /* Ink on a saturated fill. 'toast-label' is white in both themes and is\n * backfilled by ensureDesignTokens when the shell omits it. */\n --hq-on-accent: var(--dsw-alias-brand-primary-invert, var(--dsw-alias-toast-label, #fff));\n --hq-mono: ui-monospace, SFMono-Regular, Menlo, monospace;\n /* One type scale: 9.5 / 10.5 / 11.5 / 12 / 13. Nothing inline, nothing\n * inherited from the shell's 20 px body default. */\n --hq-fs-micro: 9.5px;\n --hq-fs-meta: 10.5px;\n --hq-fs-body: 11.5px;\n --hq-fs-title: 13px;\n}\n\n@keyframes hqpu-fade {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n@keyframes hqpu-panel-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n/* ── Header ──────────────────────────────────────────────────────────────\n * Title + the set version / check time, then four icon actions in the order\n * the prototype fixes: refresh · sources · job log · close. */\n.hqpu-head {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 0.5px solid var(--hq-border);\n background: var(--hq-bg);\n}\n.hqpu-head-main {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.hqpu-brand-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 28px;\n height: 28px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--hq-brand) 12%, transparent);\n color: var(--hq-brand-soft);\n}\n.hqpu-brand-icon svg {\n display: block;\n}\n.hqpu-title-block {\n min-width: 0;\n}\n.hqpu-title {\n margin: 0;\n font-size: var(--hq-fs-title);\n font-weight: 600;\n line-height: 17px;\n color: var(--hq-text);\n}\n.hqpu-sub {\n margin: 1px 0 0;\n font-size: var(--hq-fs-meta);\n line-height: 14px;\n color: var(--hq-faint);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.hqpu-toolbar {\n display: flex;\n align-items: center;\n flex: none;\n gap: 1px;\n}\n.hqpu-icon-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 28px;\n height: 28px;\n padding: 0;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: var(--hq-muted);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.hqpu-icon-btn svg {\n display: block;\n}\n.hqpu-icon-btn:hover:not(:disabled) {\n background: var(--hq-hover);\n color: var(--hq-text);\n}\n.hqpu-icon-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n/* A toggled toolbar button (sources, job log) reads as \"on\", not just hovered —\n * the pressed state is the only hint that a drawer is open behind the body. */\n.hqpu-icon-btn[aria-pressed='true'] {\n background: var(--hq-hover);\n color: var(--hq-text);\n}\n.hqpu-icon-btn--close {\n margin-left: 4px;\n color: var(--hq-faint);\n}\n.hqpu-icon-btn--sm {\n width: 24px;\n height: 24px;\n border-radius: 6px;\n}\n\n/* ── Settings strip (gear) ───────────────────────────────────────────────\n * Above the body, never inside it: opening the source editor must not reflow\n * the list the person was reading. */\n.hqpu-settings {\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 0.5px solid var(--hq-border);\n background: var(--hq-card-soft);\n}\n.hqpu-settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-settings-title {\n font-size: var(--hq-fs-meta);\n font-weight: 600;\n color: var(--hq-muted);\n}\n.hqpu-origin-badge {\n flex: none;\n padding: 1px 6px;\n border: 0.5px solid;\n border-radius: 4px;\n font-size: var(--hq-fs-micro);\n}\n.hqpu-origin-badge[data-origin='config'] {\n color: var(--hq-emerald);\n border-color: color-mix(in srgb, var(--hq-emerald) 40%, transparent);\n background: color-mix(in srgb, var(--hq-emerald) 10%, transparent);\n}\n.hqpu-origin-badge[data-origin='override'] {\n color: var(--hq-amber-ink);\n border-color: color-mix(in srgb, var(--hq-amber) 40%, transparent);\n background: color-mix(in srgb, var(--hq-amber) 10%, transparent);\n}\n.hqpu-field {\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.hqpu-field-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-field label {\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-source-input {\n width: 100%;\n height: 26px;\n padding: 0 8px;\n border: 0.5px solid var(--hq-border-strong);\n border-radius: 6px;\n background: var(--hq-bg);\n color: var(--hq-muted);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-source-input:disabled {\n opacity: 0.6;\n}\n.hqpu-settings-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n padding-top: 2px;\n}\n\n/* ── Body: scroll + sticky footer + the log drawer over both ─────────────\n * '.hqpu-main' is the positioning box; the drawer covers the body AND the\n * footer, so an open log cannot be clicked through. */\n.hqpu-main {\n position: relative;\n display: flex;\n flex: 1;\n flex-direction: column;\n min-height: 0;\n}\n/* While the job-log drawer is open it covers this whole box; on a short body\n * the box would collapse to nothing, so give it a floor. Set/cleared from\n * update-view.ts (the drawer is absolutely positioned and cannot size it). */\n.hqpu-main[data-log-open='1'] {\n min-height: 340px;\n}\n.hqpu-scroll {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 12px;\n background: var(--hq-bg);\n /* Anything that scrolls inside the panel draws the l2 thumb. */\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\n}\n.hqpu-column {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n/* A stack of groups (or the single toast host) inside the column. */\n.hqpu-stack {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.hqpu-stack:empty {\n display: none;\n}\n\n/* ── Decision band ───────────────────────────────────────────────────────\n * The one row that answers \"is there anything for me to do\", before any list. */\n.hqpu-decision {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--hq-card-soft);\n font-size: var(--hq-fs-meta);\n}\n.hqpu-decision-main {\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-decision-dot {\n width: 3px;\n height: 3px;\n border-radius: 50%;\n background: var(--hq-faint);\n}\n.hqpu-decision-sub {\n color: var(--hq-muted);\n}\n\n/* ── Section head ────────────────────────────────────────────────────────── */\n.hqpu-sec-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 0 2px 2px;\n}\n.hqpu-sec-title {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-count-pill {\n padding: 0 5px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--hq-brand) 14%, transparent);\n color: var(--hq-brand-soft);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n font-weight: 700;\n}\n\n/* ── Text links ──────────────────────────────────────────────────────────── */\n.hqpu-link {\n padding: 0;\n border: 0;\n background: none;\n color: var(--hq-muted);\n font: inherit;\n font-size: var(--hq-fs-meta);\n cursor: pointer;\n transition: color 0.15s ease;\n}\n.hqpu-link:hover {\n color: var(--hq-text);\n}\n.hqpu-link--tiny {\n font-size: var(--hq-fs-micro);\n}\n.hqpu-link--icon {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n}\n.hqpu-link--icon svg {\n display: block;\n}\n\n/* ── Plugin cards ────────────────────────────────────────────────────────── */\n.hqpu-cards {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.hqpu-card {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px;\n border: 0.5px solid var(--hq-border);\n border-radius: 8px;\n background: var(--hq-card);\n transition: border-color 0.15s ease, background 0.15s ease;\n}\n.hqpu-card:hover {\n border-color: var(--hq-border-strong);\n}\n.hqpu-card-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-card-id {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.hqpu-avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 30px;\n height: 30px;\n border-radius: 7px;\n background: var(--hq-card-soft);\n color: var(--hq-muted);\n font-size: var(--hq-fs-micro);\n font-weight: 700;\n}\n.hqpu-card-main {\n display: flex;\n flex-direction: column;\n min-width: 0;\n gap: 1px;\n}\n.hqpu-card-title {\n font-size: var(--hq-fs-body);\n font-weight: 600;\n line-height: 15px;\n color: var(--hq-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.hqpu-card-sub {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n line-height: 12px;\n color: var(--hq-faint);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.hqpu-card-end {\n display: flex;\n align-items: center;\n flex: none;\n gap: 2px;\n}\n/* The card's own action: tinted, not filled — the filled brand fill belongs to\n * the one set-level CTA in the footer. */\n.hqpu-card-action {\n height: 24px;\n padding: 0 8px;\n border: 0;\n border-radius: 6px;\n background: color-mix(in srgb, var(--hq-brand) 12%, transparent);\n color: var(--hq-brand-soft);\n font: inherit;\n font-size: var(--hq-fs-meta);\n font-weight: 500;\n white-space: nowrap;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.hqpu-card-action:hover:not(:disabled) {\n background: var(--hq-brand);\n color: var(--hq-on-accent);\n}\n.hqpu-card-action:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.hqpu-card-foot {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-version-row {\n display: flex;\n align-items: baseline;\n gap: 4px;\n min-width: 0;\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-meta);\n color: var(--hq-muted);\n}\n.hqpu-ver-old {\n color: var(--hq-muted);\n}\n.hqpu-ver-arrow {\n color: var(--hq-border-strong);\n}\n.hqpu-ver-new {\n color: var(--hq-emerald);\n font-weight: 600;\n}\n.hqpu-ver-new--lg {\n font-size: var(--hq-fs-body);\n}\n.hqpu-ver-size {\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n.hqpu-toggle {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 0;\n border: 0;\n background: none;\n color: var(--hq-faint);\n font: inherit;\n font-size: var(--hq-fs-micro);\n white-space: nowrap;\n cursor: pointer;\n}\n.hqpu-toggle:hover {\n color: var(--hq-text);\n}\n.hqpu-toggle-chevron {\n display: inline-flex;\n transition: transform 0.15s ease;\n}\n.hqpu-toggle[data-open='true'] .hqpu-toggle-chevron {\n transform: rotate(90deg);\n}\n.hqpu-changes {\n padding-top: 8px;\n border-top: 0.5px solid var(--hq-border);\n font-size: var(--hq-fs-meta);\n color: var(--hq-muted);\n}\n.hqpu-changes-title {\n margin: 0 0 4px;\n font-size: var(--hq-fs-micro);\n font-weight: 600;\n color: var(--hq-muted);\n}\n.hqpu-changes ul {\n margin: 0;\n padding-left: 16px;\n font-size: var(--hq-fs-micro);\n line-height: 16px;\n}\n\n/* ── Collapsed groups (已忽略 / 已是最新 / 版本说明) ─────────────────────── */\n.hqpu-disc {\n padding-top: 6px;\n border-top: 0.5px solid var(--hq-border);\n}\n.hqpu-disc-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n padding: 2px 0;\n border: 0;\n background: none;\n color: var(--hq-muted);\n font: inherit;\n cursor: pointer;\n}\n.hqpu-disc-left {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.hqpu-disc-chevron {\n display: inline-flex;\n flex: none;\n color: var(--hq-faint);\n transition: transform 0.15s ease;\n}\n.hqpu-disc-head[aria-expanded='true'] .hqpu-disc-chevron {\n transform: rotate(90deg);\n}\n.hqpu-disc-title {\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-disc-count {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-meta);\n color: var(--hq-faint);\n}\n.hqpu-disc-note {\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.hqpu-disc-body {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding-top: 6px;\n}\n.hqpu-rows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.hqpu-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n background: var(--hq-card-soft);\n font-size: var(--hq-fs-meta);\n}\n.hqpu-row-name {\n font-weight: 500;\n color: var(--hq-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.hqpu-row-end {\n display: flex;\n align-items: center;\n flex: none;\n gap: 8px;\n}\n.hqpu-row-meta {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n\n/* ── Runtime card (amber: a swap is not a plugin install) ───────────────── */\n.hqpu-engine {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px;\n border: 0.5px solid color-mix(in srgb, var(--hq-amber) 32%, transparent);\n border-radius: 10px;\n background: linear-gradient(\n 135deg,\n color-mix(in srgb, var(--hq-amber) 10%, var(--hq-card)) 0%,\n var(--hq-card) 60%,\n var(--hq-card-soft) 100%\n );\n}\n.hqpu-engine-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-engine-title {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-engine-bolt {\n display: inline-flex;\n color: var(--hq-amber);\n}\n.hqpu-chip {\n flex: none;\n padding: 1px 6px;\n border: 0.5px solid var(--hq-border-strong);\n border-radius: 4px;\n background: var(--hq-card);\n color: var(--hq-muted);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-engine-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 10px;\n}\n.hqpu-engine-info {\n min-width: 0;\n}\n.hqpu-engine-new {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.hqpu-engine-size {\n margin: 2px 0 0;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n}\n.hqpu-engine-state {\n flex: none;\n max-width: 45%;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n text-align: right;\n}\n.hqpu-engine-error {\n margin: 0;\n font-size: var(--hq-fs-micro);\n color: var(--hq-rose);\n}\n.hqpu-tag--brand {\n flex: none;\n padding: 0 4px;\n border-radius: 3px;\n background: color-mix(in srgb, var(--hq-amber) 14%, transparent);\n color: var(--hq-amber-ink);\n font-size: var(--hq-fs-micro);\n font-weight: 500;\n}\n.hqpu-btn--engine {\n flex: none;\n height: 26px;\n padding: 0 10px;\n border: 0;\n border-radius: 6px;\n background: var(--hq-amber);\n color: var(--hq-on-amber);\n font: inherit;\n font-size: var(--hq-fs-meta);\n font-weight: 500;\n white-space: nowrap;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n.hqpu-btn--engine:hover:not(:disabled) {\n background: color-mix(in srgb, var(--hq-amber) 85%, #000);\n}\n.hqpu-btn--engine:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.hqpu-engine-confirm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 0.5px solid color-mix(in srgb, var(--hq-amber) 24%, transparent);\n border-radius: 8px;\n background: color-mix(in srgb, var(--hq-amber) 10%, transparent);\n}\n.hqpu-engine-confirm-text {\n margin: 0;\n font-size: var(--hq-fs-meta);\n line-height: 16px;\n color: var(--hq-amber-ink);\n}\n.hqpu-engine-confirm-actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 6px;\n}\n.hqpu-engine-note {\n display: flex;\n align-items: center;\n gap: 4px;\n margin: 0;\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n.hqpu-engine-note svg {\n flex: none;\n color: var(--hq-amber);\n}\n\n/* ── Error / result banners ──────────────────────────────────────────────── */\n.hqpu-error {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px;\n border: 0.5px solid;\n border-radius: 8px;\n}\n.hqpu-error[data-tone='danger'] {\n border-color: color-mix(in srgb, var(--hq-rose) 35%, transparent);\n background: color-mix(in srgb, var(--hq-rose) 8%, transparent);\n}\n.hqpu-error[data-tone='warning'] {\n border-color: color-mix(in srgb, var(--hq-amber) 35%, transparent);\n background: color-mix(in srgb, var(--hq-amber) 8%, transparent);\n}\n.hqpu-error-head {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n}\n.hqpu-error-icon {\n display: inline-flex;\n flex: none;\n margin-top: 1px;\n color: var(--hq-rose);\n}\n.hqpu-error[data-tone='warning'] .hqpu-error-icon {\n color: var(--hq-amber);\n}\n.hqpu-error-text {\n min-width: 0;\n}\n.hqpu-error-title {\n margin: 0;\n font-size: var(--hq-fs-body);\n font-weight: 600;\n line-height: 15px;\n color: var(--hq-rose);\n}\n.hqpu-error[data-tone='warning'] .hqpu-error-title {\n color: var(--hq-amber-ink);\n}\n.hqpu-error-body {\n margin: 2px 0 0;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n overflow-wrap: anywhere;\n}\n.hqpu-error-code {\n display: inline-block;\n margin-top: 3px;\n padding: 0 4px;\n border-radius: 3px;\n background: color-mix(in srgb, var(--hq-rose) 12%, transparent);\n color: var(--hq-rose);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-error[data-tone='warning'] .hqpu-error-code {\n background: color-mix(in srgb, var(--hq-amber) 14%, transparent);\n color: var(--hq-amber-ink);\n}\n.hqpu-error-foot {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding-top: 6px;\n border-top: 0.5px solid color-mix(in srgb, currentColor 12%, transparent);\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-error-actions {\n display: flex;\n flex: none;\n gap: 10px;\n}\n\n.hqpu-alert {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 10px;\n border-radius: 8px;\n}\n.hqpu-alert--amber {\n border: 0.5px solid color-mix(in srgb, var(--hq-amber) 32%, transparent);\n background: color-mix(in srgb, var(--hq-amber) 10%, transparent);\n}\n.hqpu-alert-icon {\n display: inline-flex;\n flex: none;\n color: var(--hq-amber);\n}\n.hqpu-alert-main {\n min-width: 0;\n}\n.hqpu-alert-title {\n font-size: var(--hq-fs-meta);\n font-weight: 600;\n color: var(--hq-amber-ink);\n}\n.hqpu-alert-body {\n margin: 2px 0 0;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n}\n.hqpu-alert-body code {\n padding: 0 3px;\n border-radius: 3px;\n background: color-mix(in srgb, var(--hq-amber) 16%, transparent);\n font-family: var(--hq-mono);\n}\n.hqpu-alert-actions {\n margin-top: 4px;\n}\n.hqpu-alert-dismiss {\n padding: 0;\n border: 0;\n background: none;\n color: var(--hq-amber-ink);\n font: inherit;\n font-size: var(--hq-fs-micro);\n text-decoration: underline;\n cursor: pointer;\n}\n.hqpu-alert-green {\n margin: 0;\n padding: 6px 10px;\n border: 0.5px solid color-mix(in srgb, var(--hq-emerald) 30%, transparent);\n border-radius: 6px;\n background: color-mix(in srgb, var(--hq-emerald) 8%, transparent);\n color: var(--hq-emerald);\n font-size: var(--hq-fs-micro);\n}\n\n/* ── Active job ──────────────────────────────────────────────────────────── */\n.hqpu-active {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 0.5px solid color-mix(in srgb, var(--hq-brand) 40%, transparent);\n border-radius: 8px;\n background: var(--hq-card);\n}\n.hqpu-active-top {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-active-title {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-active-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.hqpu-pulse-dot {\n position: relative;\n flex: none;\n width: 8px;\n height: 8px;\n}\n.hqpu-pulse-dot::before {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: 50%;\n background: var(--hq-brand);\n}\n.hqpu-pulse-ring {\n position: absolute;\n inset: 0;\n border-radius: 50%;\n background: var(--hq-brand);\n opacity: 0.6;\n animation: hqpu-ping 1.4s cubic-bezier(0, 0, 0.2, 1) infinite;\n}\n@keyframes hqpu-ping {\n 75%, 100% { transform: scale(2.4); opacity: 0; }\n}\n.hqpu-active-percent {\n flex: none;\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-body);\n font-weight: 700;\n color: var(--hq-brand-soft);\n}\n.hqpu-progress {\n height: 6px;\n border-radius: 999px;\n background: var(--hq-border);\n overflow: hidden;\n}\n.hqpu-progress-fill {\n height: 100%;\n border-radius: 999px;\n background: linear-gradient(90deg, var(--hq-brand), var(--hq-brand-soft));\n transition: width 0.3s ease;\n}\n.hqpu-active-meta {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n}\n.hqpu-num {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-active-versions {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-active-note {\n margin: 0;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-faint);\n}\n.hqpu-job-steps4 {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 2px;\n padding-top: 8px;\n border-top: 0.5px solid var(--hq-border);\n text-align: center;\n font-size: var(--hq-fs-micro);\n font-weight: 500;\n}\n.hqpu-job-step--done {\n color: var(--hq-emerald);\n}\n.hqpu-job-step--active {\n color: var(--hq-brand-soft);\n animation: hqpu-step-pulse 1.2s ease-in-out infinite;\n}\n.hqpu-job-step--todo {\n color: var(--hq-faint);\n}\n@keyframes hqpu-step-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.45; }\n}\n\n/* ── Footer ──────────────────────────────────────────────────────────────── */\n.hqpu-footer {\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 12px 12px;\n border-top: 0.5px solid var(--hq-border);\n background: var(--hq-bg);\n}\n.hqpu-btn--block {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n padding: 0 12px;\n border: 0;\n border-radius: 10px;\n background: var(--hq-brand);\n color: var(--hq-on-accent);\n font: inherit;\n font-size: var(--hq-fs-body);\n font-weight: 500;\n cursor: pointer;\n box-shadow: 0 4px 12px color-mix(in srgb, var(--hq-brand) 35%, transparent);\n transition: background 0.15s ease, box-shadow 0.15s ease;\n}\n.hqpu-btn--block:hover:not(:disabled) {\n background: var(--hq-brand-soft);\n}\n/* The disabled CTA states the reason in its own label, so it must stay legible\n * (a 40 %-opacity red button reads as \"broken\", not as \"nothing to do\"). */\n.hqpu-btn--block:disabled {\n background: var(--hq-card-soft);\n color: var(--hq-faint);\n box-shadow: none;\n cursor: not-allowed;\n}\n.hqpu-cta-spinner {\n width: 13px;\n height: 13px;\n border-radius: 50%;\n border: 1.5px solid color-mix(in srgb, currentColor 30%, transparent);\n border-top-color: currentColor;\n animation: hqpu-spin 0.9s linear infinite;\n}\n@keyframes hqpu-spin {\n to { transform: rotate(360deg); }\n}\n.hqpu-foot-meta {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-height: 14px;\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n.hqpu-footnote {\n margin: 0;\n font-size: var(--hq-fs-micro);\n line-height: 13px;\n text-align: center;\n color: var(--hq-faint);\n}\n\n/* ── Job log drawer ──────────────────────────────────────────────────────\n * Covers the body and the footer, never the header: the log button and the\n * close button stay in place while it is open. */\n.hqpu-log {\n position: absolute;\n inset: 0;\n z-index: 10;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--hq-bg);\n}\n.hqpu-log-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding-bottom: 8px;\n border-bottom: 0.5px solid var(--hq-border);\n}\n.hqpu-log-title {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.hqpu-log-name {\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n white-space: nowrap;\n}\n.hqpu-log-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 8px;\n min-height: 0;\n overflow-y: auto;\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\n}\n.hqpu-log-block {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 8px;\n border-radius: 8px;\n background: var(--hq-card-soft);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-log-meta {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding-bottom: 4px;\n border-bottom: 0.5px solid var(--hq-border);\n color: var(--hq-faint);\n overflow-wrap: anywhere;\n}\n.hqpu-log-line {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--hq-muted);\n line-height: 15px;\n}\n.hqpu-log-line--empty {\n color: var(--hq-faint);\n}\n.hqpu-log-line-text {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.hqpu-log-dot {\n flex: none;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--hq-border-strong);\n}\n.hqpu-log-dot[data-state='done'] { background: var(--hq-emerald); }\n.hqpu-log-dot[data-state='error'] { background: var(--hq-rose); }\n.hqpu-log-dot[data-state='warning'] { background: var(--hq-amber); }\n.hqpu-log-dot[data-state='idle'] { background: var(--hq-border-strong); }\n.hqpu-log-detail {\n display: flex;\n flex-direction: column;\n gap: 3px;\n padding: 8px;\n border: 0.5px solid var(--hq-border);\n border-radius: 8px;\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-log-detail p {\n margin: 0;\n overflow-wrap: anywhere;\n}\n.hqpu-log-recent {\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.hqpu-log-recent-title {\n margin: 0 0 2px;\n font-size: var(--hq-fs-micro);\n font-weight: 600;\n color: var(--hq-muted);\n}\n.hqpu-log-recent-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 6px;\n border-radius: 5px;\n background: var(--hq-card-soft);\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-log-recent-meta {\n flex: none;\n font-family: var(--hq-mono);\n color: var(--hq-faint);\n}\n.hqpu-log-foot {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding-top: 8px;\n border-top: 0.5px solid var(--hq-border);\n}\n\n/* ── Toast ──────────────────────────────────────────────────────────────── */\n.hqpu-toast-host {\n position: absolute;\n top: 8px;\n left: 12px;\n right: 12px;\n z-index: 20;\n display: flex;\n flex-direction: column;\n gap: 6px;\n pointer-events: none;\n}\n.hqpu-toast-host:empty {\n display: none;\n}\n.hqpu-toast {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-toast-bg, var(--hq-card));\n color: var(--dsw-alias-toast-label, var(--hq-text));\n font-size: var(--hq-fs-meta);\n box-shadow: var(--dsw-shadow-lv3, 0 8px 24px rgba(0, 0, 0, 0.2));\n pointer-events: auto;\n animation: hqpu-toast-in 160ms ease-out;\n}\n.hqpu-toast-icon {\n display: inline-flex;\n flex: none;\n color: var(--hq-amber);\n}\n.hqpu-toast-icon[data-tone='success'] {\n color: var(--hq-emerald);\n}\n.hqpu-toast-icon[data-tone='error'] {\n color: var(--hq-rose);\n}\n.hqpu-toast-text {\n min-width: 0;\n flex: 1;\n overflow-wrap: anywhere;\n}\n.hqpu-toast-close {\n display: inline-flex;\n flex: none;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n opacity: 0.55;\n cursor: pointer;\n}\n.hqpu-toast-close:hover {\n opacity: 1;\n}\n@keyframes hqpu-toast-in {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: none; }\n}\n\n/* ── Badges, banners, misc shared bits ──────────────────────────────────── */\n.hqpu-tag {\n display: inline-flex;\n align-items: center;\n flex: none;\n padding: 0 6px;\n border-radius: 999px;\n font-size: var(--hq-fs-micro);\n font-weight: 500;\n line-height: 16px;\n white-space: nowrap;\n}\n.hqpu-tag[data-tone='success'] {\n background: color-mix(in srgb, var(--hq-emerald) 12%, transparent);\n color: var(--hq-emerald);\n}\n.hqpu-tag[data-tone='warning'] {\n background: color-mix(in srgb, var(--hq-amber) 14%, transparent);\n color: var(--hq-amber-ink);\n}\n.hqpu-tag[data-tone='danger'] {\n background: color-mix(in srgb, var(--hq-rose) 12%, transparent);\n color: var(--hq-rose);\n}\n.hqpu-tag[data-tone='info'] {\n background: color-mix(in srgb, var(--dsw-alias-state-business-primary, var(--hq-brand)) 12%, transparent);\n color: var(--dsw-alias-state-business-primary, var(--hq-brand));\n}\n.hqpu-tag[data-tone='neutral'] {\n background: var(--hq-card-soft);\n color: var(--hq-muted);\n}\n.hqpu-banner {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 6px;\n font-size: var(--hq-fs-micro);\n line-height: 15px;\n color: var(--hq-muted);\n}\n.hqpu-banner[data-tone='danger'] {\n background: color-mix(in srgb, var(--hq-rose) 8%, transparent);\n}\n.hqpu-banner[data-tone='danger'] span {\n color: var(--hq-rose);\n}\n.hqpu-release-summary {\n margin: 0;\n font-size: var(--hq-fs-meta);\n line-height: 16px;\n color: var(--hq-muted);\n}\n.hqpu-release-notes {\n margin: 0;\n padding-left: 16px;\n font-size: var(--hq-fs-micro);\n line-height: 16px;\n color: var(--hq-muted);\n}\n.hqpu-release-meta {\n margin: 0;\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n.hqpu-release-link {\n font-size: var(--hq-fs-micro);\n color: var(--hq-brand-soft);\n text-decoration: none;\n}\n.hqpu-release-link:hover {\n text-decoration: underline;\n}\n.hqpu-status {\n margin: 0;\n font-size: var(--hq-fs-meta);\n line-height: 15px;\n color: var(--hq-muted);\n}\n.hqpu-empty {\n padding: 24px 0;\n text-align: center;\n font-size: var(--hq-fs-meta);\n color: var(--hq-faint);\n}\n\n/* ── Buttons (settings / log drawer) ─────────────────────────────────────── */\n.hqpu-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border: 0;\n border-radius: 8px;\n background: var(--hq-card-soft);\n color: var(--hq-text);\n font: inherit;\n font-size: var(--hq-fs-meta);\n cursor: pointer;\n}\n.hqpu-btn svg {\n display: block;\n}\n.hqpu-btn:hover:not(:disabled) {\n background: var(--hq-hover);\n}\n.hqpu-btn:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.hqpu-btn--sm {\n height: 26px;\n padding: 0 8px;\n font-size: var(--hq-fs-micro);\n}\n.hqpu-btn--ghost {\n background: transparent;\n color: var(--hq-muted);\n}\n.hqpu-btn--brand {\n background: var(--hq-brand);\n color: var(--hq-on-accent);\n}\n.hqpu-btn--brand:hover:not(:disabled) {\n background: var(--hq-brand-soft);\n}\n/* The runtime confirm is the same amber as the card it lives in (the\n * prototype's treatment): red is the plugin-set path, amber the runtime path.\n * Ink is a warm near-black because amber stays bright in both themes — white\n * on amber-500 sits near 2:1 and reads as a disabled button. */\n.hqpu-btn--engine-solid {\n height: 28px;\n padding: 0 12px;\n background: var(--hq-amber);\n color: var(--hq-on-amber);\n font-size: var(--hq-fs-meta);\n font-weight: 600;\n}\n.hqpu-btn--engine-solid:hover:not(:disabled) {\n background: color-mix(in srgb, var(--hq-amber) 85%, #000);\n}\n\n/* ── Skeleton ────────────────────────────────────────────────────────────── */\n.hqpu-skeleton-fill {\n background: var(--hq-border);\n animation: hqpu-skeleton 2s cubic-bezier(0.36, 0, 0.64, 1) infinite;\n}\n.hqpu-skeleton-title {\n width: 60%;\n height: 10px;\n border-radius: 3px;\n}\n.hqpu-skeleton-sub {\n width: 40%;\n height: 8px;\n border-radius: 3px;\n}\n.hqpu-card .hqpu-avatar.hqpu-skeleton-fill {\n background: var(--hq-border);\n}\n@keyframes hqpu-skeleton {\n 0% { opacity: 1; }\n 40% { opacity: 0.55; }\n 80%, 100% { opacity: 1; }\n}\n\n/* ── Focus + reduced motion ──────────────────────────────────────────────── */\n.hqpu-panel button:focus-visible,\n.hqpu-panel a:focus-visible,\n.hqpu-panel input:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: 1px;\n}\n.hqpu-panel input:focus-visible {\n outline: none;\n border-color: var(--hq-brand-soft);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .hqpu-scrim,\n .hqpu-panel,\n .hqpu-skeleton-fill,\n .hqpu-cta-spinner,\n .hqpu-toast,\n .hqpu-pulse-ring,\n .hqpu-job-step--active {\n animation: none;\n }\n}\n\n`\n\nconst STYLE_TAG_ID = 'data-plugin-css=\"huaqiu-plugin-update\"'\n\n/**\n * Design tokens the bundled DSH shell may not publish.\n *\n * hq-edge ships an older `ui-theme` build. A live probe of the running shell\n * (both themes) shows 13 of the tokens this stylesheet reads are absent:\n * `--dsw-radius-{xs,sm,md,lg,xl,panel}`, `--dsw-focus-ring-width`,\n * `--dsw-focus-ring-color`, `--dsw-alias-toast-label`,\n * `--dsw-alias-state-idle-primary`, `--dsw-font-mono`,\n * `--dsh-frame-overlay-top` and `--dsh-frame-chrome-top`.\n *\n * An unresolved `var()` is not an error — it silently degrades the property to\n * its initial value. That is how this panel once rendered with\n * `border-radius: 0px` and a 3px `auto` focus ring while every colour was\n * already correct.\n *\n * The last three (`--dsw-font-mono`, `--dsh-frame-*`) are deliberately NOT\n * backfilled: the shell's own primitives rely on the inline `var(…, fallback)`\n * form for those (`Modal.module.css`, `PluginManagerPage.module.css`), so this\n * sheet does too. Everything else is backfilled below with the canonical value\n * from `ui-theme/src/styles/{base,focus,design-platform}.css`.\n *\n * Values are feature-detected at runtime and scoped to this plugin's own roots\n * rather than `:root`, so a newer shell always wins and filling a gap can never\n * restyle the host.\n */\n\n/** The two roots this plugin styles: the view overlay (built in\n * update-view.ts) and the sidebar row ({@link ENTRY_CSS}.entry). */\nconst STYLE_ROOTS = '.hqpu-overlay, .hqpu-sidebar-entry'\n\n/** The shell's dark-theme carrier — audited live as `body[data-ds-dark-theme]`. */\nconst DARK_THEME_SELECTOR = 'body[data-ds-dark-theme]'\n\n/** Same roots, one rule per theme, for tokens whose official value flips. */\nconst DARK_STYLE_ROOTS = STYLE_ROOTS.split(',')\n .map((root) => `${DARK_THEME_SELECTOR} ${root.trim()}`)\n .join(',')\n\ntype DesignTokenFallback =\n | { token: string; value: string }\n | { token: string; value: { light: string; dark: string } }\n\nconst DESIGN_TOKEN_FALLBACKS: DesignTokenFallback[] = [\n // base.css — geometry; theme-invariant.\n { token: '--dsw-radius-xs', value: '4px' },\n { token: '--dsw-radius-sm', value: '8px' },\n { token: '--dsw-radius-md', value: '12px' },\n { token: '--dsw-radius-lg', value: '16px' },\n { token: '--dsw-radius-xl', value: '20px' },\n { token: '--dsw-radius-panel', value: '28px' },\n // focus.css — the standard ring geometry. The colour already falls back\n // inline in the sheet; publishing it too keeps the ring intact if that\n // fallback is ever edited away.\n { token: '--dsw-focus-ring-width', value: '2px' },\n { token: '--dsw-focus-ring-color', value: 'var(--dsw-alias-state-business-primary, #C7000B)' },\n // design-platform.css — `--dsw-static-neutral-bluish-00` is white in both\n // themes, which is exactly what the official alias resolves to.\n { token: '--dsw-alias-toast-label', value: 'var(--dsw-static-neutral-bluish-00, #fff)' },\n // The one token whose official value flips with the theme. `--dsw-static-*`\n // are theme-invariant, so one rule per carrier reproduces neutral-300\n // (light) / neutral-600 (dark) without knowing the current theme.\n {\n token: '--dsw-alias-state-idle-primary',\n value: {\n light: 'var(--dsw-static-neutral-300, #d4d4d4)',\n dark: 'var(--dsw-static-neutral-600, #545557)',\n },\n },\n]\n\nconst TOKEN_STYLE_TAG_ID = 'data-plugin-css=\"huaqiu-plugin-update-tokens\"'\n\n/**\n * Publish canonical values for the design tokens the shell is missing.\n *\n * Idempotent and cheap: safe to call on every open, so a shell that publishes\n * its tokens late still wins (the stale fallbacks are removed in that case).\n */\nexport function ensureDesignTokens(): void {\n if (typeof document === 'undefined' || document.body === null) return\n const probe = getComputedStyle(document.body)\n const missing = DESIGN_TOKEN_FALLBACKS.filter(\n (entry) => probe.getPropertyValue(entry.token).trim() === '',\n )\n\n const existing = document.querySelector<HTMLStyleElement>(`style[${TOKEN_STYLE_TAG_ID}]`)\n if (missing.length === 0) {\n existing?.remove()\n return\n }\n\n const flat = missing.filter(\n (entry): entry is { token: string; value: string } => typeof entry.value === 'string',\n )\n const themed = missing.filter(\n (entry): entry is { token: string; value: { light: string; dark: string } } =>\n typeof entry.value !== 'string',\n )\n\n const rules: string[] = []\n const lightDecls = [\n ...flat.map((entry) => ` ${entry.token}: ${entry.value};`),\n ...themed.map((entry) => ` ${entry.token}: ${entry.value.light};`),\n ]\n rules.push(`${STYLE_ROOTS} {\\n${lightDecls.join('\\n')}\\n}`)\n if (themed.length > 0) {\n const darkDecls = themed.map((entry) => ` ${entry.token}: ${entry.value.dark};`)\n rules.push(`${DARK_STYLE_ROOTS} {\\n${darkDecls.join('\\n')}\\n}`)\n }\n\n const tag = existing ?? document.createElement('style')\n tag.dataset.pluginCss = 'huaqiu-plugin-update-tokens'\n tag.textContent = rules.join('\\n')\n if (tag.parentNode === null) document.head.appendChild(tag)\n}\n\n/** Inject styles once (idempotent). */\nexport function injectUpdateStyles(): void {\n if (typeof document === 'undefined') return\n if (document.querySelector(`style[${STYLE_TAG_ID}]`) !== null) return\n const tag = document.createElement('style')\n tag.dataset.pluginCss = 'huaqiu-plugin-update'\n tag.textContent = `${ENTRY_CSS_TEXT}\\n${VIEW_CSS_TEXT}`\n document.head.appendChild(tag)\n}\n\n/** Minimal structural client context (dsh-client-runtime provides these). */\nexport interface ClientContext {\n get<T = unknown>(name: string): T | undefined\n effect?(fn: () => (() => void) | void, label?: string): void\n}\n\n/** In-memory update-report store shared by the badge and the modal. */\nclass UpdateStore implements UpdateStoreLike {\n private current: PluginUpdateReport | null = null\n private installingFlag = false\n /** The durable job the server currently reports as active (resumed across\n * panel close and DSH restart); null when idle. */\n private active: UpdateJob | null = null\n private recent: UpdateJob[] = []\n private readonly listeners = new Set<() => void>()\n private refreshTimer: number | null = null\n private pollTimer: number | null = null\n private pollBackoffMs = 1_000\n private pollInFlight = false\n\n report(): PluginUpdateReport | null {\n return this.current\n }\n\n installing(): boolean {\n return this.installingFlag\n }\n\n activeJob(): UpdateJob | null {\n return this.active\n }\n\n recentJobs(): UpdateJob[] {\n return [...this.recent]\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Sidebar badge: how many updates are still on offer — actionable plugin\n * items plus (once) the HQ Edge runtime update.\n *\n * The whole rule lives in {@link badgeCountFor}; in particular the job\n * history never contributes, which is what used to pin this at 1 forever.\n */\n badgeCount(): number {\n return badgeCountFor(this.current, this.active)\n }\n\n private emit(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener()\n } catch {\n /* one bad listener must not strand the rest */\n }\n }\n }\n\n /** Adopt a fresh `/status` snapshot and (re)arm the poll loop. */\n private applyStatus(status: UpdateJobStatus): void {\n const wasActive = this.active !== null\n this.active = status.activeJob\n this.recent = status.recentJobs\n if (wasActive && this.active === null) {\n // The active job settled (moved to recent/terminal) — the installing\n // footer state must clear on its own, without the panel's closure.\n this.installingFlag = false\n // …and the report is now stale: it still names the versions this job\n // just installed. hq-edge refreshes its own copy before it settles the\n // job, so one cheap read of `/report` is enough to bring the list (and\n // the badge) down to zero. Without it the badge kept counting updates\n // that no longer exist until the 60 s idle poll came round.\n void this.refresh()\n }\n this.emit()\n this.schedulePoll()\n }\n\n /**\n * Fetch the durable job status. Called on mount, on panel open (BEFORE\n * discovery) and by the poll loop. A failure (older hq-edge with no\n * `/status`, or a transient HTTP error) is swallowed: the last known job is\n * kept and the poller backs off and retries.\n */\n async fetchStatus(): Promise<void> {\n try {\n const status = await fetchJobStatus()\n this.pollBackoffMs = 1_000\n this.applyStatus(status)\n } catch {\n this.schedulePoll()\n }\n }\n\n /** ~1 s poll while a job is active; stop on terminal; back off on error\n * without discarding the last known job; never overlap requests. */\n private schedulePoll(): void {\n if (this.pollTimer !== null) {\n window.clearTimeout(this.pollTimer)\n this.pollTimer = null\n }\n const job = this.active\n if (job === null || TERMINAL_PHASES.has(job.phase)) {\n this.pollBackoffMs = 1_000\n return\n }\n this.pollTimer = window.setTimeout(() => {\n void this.pollTick()\n }, this.pollBackoffMs)\n }\n\n private async pollTick(): Promise<void> {\n if (this.pollInFlight) {\n this.schedulePoll()\n return\n }\n this.pollInFlight = true\n try {\n const status = await fetchJobStatus()\n this.pollBackoffMs = 1_000\n this.applyStatus(status)\n } catch {\n this.pollBackoffMs = Math.min(5_000, Math.max(1_000, this.pollBackoffMs * 2))\n this.schedulePoll()\n } finally {\n this.pollInFlight = false\n }\n }\n\n async refresh(): Promise<PluginUpdateReport | null> {\n try {\n const report = await fetchReport()\n this.current = report\n this.emit()\n return report\n } catch {\n return this.current\n }\n }\n\n async check(): Promise<PluginUpdateReport | null> {\n try {\n const report = await checkUpdates()\n this.current = report\n this.emit()\n return report\n } catch {\n return this.current\n }\n }\n\n /** Mark the set as installing (set by the modal before the restart). */\n setInstalling(flag: boolean): void {\n this.installingFlag = flag\n this.emit()\n }\n\n /** Idle refresh while mounted (60 s) — no overlapping poll loop. */\n startIdleRefresh(): void {\n if (this.refreshTimer !== null) return\n this.refreshTimer = window.setInterval(() => {\n void this.refresh()\n }, 60_000) as unknown as number\n }\n\n stop(): void {\n if (this.refreshTimer !== null) {\n window.clearInterval(this.refreshTimer)\n this.refreshTimer = null\n }\n if (this.pollTimer !== null) {\n window.clearTimeout(this.pollTimer)\n this.pollTimer = null\n }\n this.listeners.clear()\n }\n}\n\n/**\n * Client cordis inject: no service dependencies — the plugin reads the\n * hq-edge report purely through same-origin fetch (forwarded by its own node\n * half). `inject` stays empty so the loader never waits on another service.\n */\nexport const inject: string[] = []\n\n/**\n * Client plugin body — mount the sidebar toolbutton + review modal.\n * @param ctx - real cordis client context.\n * @returns disposer removing the row and all subscriptions.\n */\nexport function apply(ctx: ClientContext): () => void {\n if (typeof document === 'undefined') return () => {}\n injectUpdateStyles()\n ensureDesignTokens()\n\n const store = new UpdateStore()\n const copy = { zh: COPY.zh, en: COPY.en }\n const copyFor = (): typeof COPY.zh => (locale === 'en' ? copy.en : copy.zh)\n\n // Locale: re-render the row copy when <html lang> changes. The sidebar\n // helper subscribes via `refresh.subscribe`; we fan out to its listener.\n let locale: HostLocale | undefined = detectHostLocale()\n const localeSubscribers = new Set<() => void>()\n const refreshSubscribe = (listener: () => void): (() => void) => {\n localeSubscribers.add(listener)\n return () => {\n localeSubscribers.delete(listener)\n }\n }\n const unsubscribeLocale = observeHostLocale((next) => {\n locale = next\n for (const fn of [...localeSubscribers]) {\n try {\n fn()\n } catch {\n /* one bad subscriber must not strand the rest */\n }\n }\n })\n\n const openView = (): void => {\n // Re-detect the shell's design tokens on every open: a shell that\n // publishes them after this plugin mounted must win, and one that never\n // does must keep the canonical fallbacks.\n ensureDesignTokens()\n // Resume-before-discovery: read the durable job status FIRST so an in-flight\n // install/runtime update is resumed (and its view rendered) even while the\n // update check round trip is still running. The panel itself opens from\n // whatever the store already holds; check() refreshes the list behind it.\n void store.fetchStatus()\n openUpdateView(store, copyFor())\n void store.check()\n }\n\n // While a job runs the row label shows its progress (\"更新中 42%\"), and the\n // tooltip names the phase. Store changes re-apply the label (the badge\n // subscription already re-renders the count pill).\n const statusLabel = (): { label: string; tooltip: string } => {\n const base = entryCopy(locale)\n const job = store.activeJob()\n if (job !== null) {\n const cc = copyFor()\n const pct = job.progress.percent\n const phase = cc.job.phase[job.phase] ?? job.phase\n return {\n label: pct !== null ? `${base.label} · ${Math.round(pct)}%` : `${base.label} · ${phase}`,\n tooltip: `${base.tooltip} · ${phase}`,\n }\n }\n return base\n }\n\n const disposeEntry = mountSidebarEntry({\n rowAttribute: UPDATE_ENTRY_ATTRIBUTE,\n rowSelector: UPDATE_ENTRY_SELECTOR,\n plugin: PLUGIN_ID,\n icon: UPDATE_ICON,\n css: ENTRY_CSS,\n // Read the live locale on every refresh (the locale observer fires\n // `refresh.subscribe`'s listener, which re-applies these) so the row\n // re-localizes when the host UI language changes — matching the sibling\n // family rows, which read a live `currentLang` variable the same way.\n label: () => statusLabel().label,\n tooltip: () => statusLabel().tooltip,\n refresh: { subscribe: refreshSubscribe },\n badge: {\n subscribe: (listener) => store.subscribe(listener),\n count: () => store.badgeCount(),\n },\n onToggle: openView,\n position: 'after',\n familySelectors: HUAQIU_SIDEBAR_ENTRY_FAMILY,\n })\n\n // Store changes (active-job percentage, result dot) also re-apply the row\n // label, not just the badge count.\n store.subscribe(() => {\n for (const fn of [...localeSubscribers]) {\n try {\n fn()\n } catch {\n /* one bad subscriber must not strand the rest */\n }\n }\n })\n\n store.startIdleRefresh()\n void store.refresh()\n void store.fetchStatus()\n\n const dispose = (): void => {\n try {\n disposeEntry()\n } catch {\n /* already disposed */\n }\n unsubscribeLocale()\n store.stop()\n }\n if (typeof ctx.effect === 'function') {\n // `ctx.effect` takes a SETUP function and calls it immediately, keeping\n // whatever it returns as the teardown. Passing `dispose` itself would run\n // the teardown synchronously — disconnecting the sidebar-entry observers\n // and stopping the store before the shell ever renders the sidebar, so the\n // row could never be placed. Hand it a factory that returns the teardown\n // instead (the same shape the sibling tool plugins use).\n ctx.effect(() => dispose, 'plugin-update')\n }\n return dispose\n}\n"],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAm/CtB,MAAM,e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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../dsh-sidebar-entry/lib/index.mjs","../src/client/version.ts","../src/client/api.ts","../src/client/copy.ts","../src/client/icons.ts","../src/client/update-view.ts","../src/client/badge.ts","../src/client/index.ts"],"sourcesContent":["//#region src/index.ts\n/** Find the sidebar shell root element, or undefined while not yet mounted. */\nfunction sidebarRoot() {\n\tconst column = document.querySelector(\"[data-pane=\\\"sidebar\\\"], [class*=\\\"sidebarCol\\\"]\");\n\tif (column === null) return void 0;\n\treturn column.querySelector(\"[class*=\\\"logoRow\\\"]\")?.parentElement ?? column.firstElementChild;\n}\n/** The New Session button: nested in the logo row on current shells, a direct child on legacy shells. */\nfunction newSessionButton(root) {\n\tconst nested = root.querySelector(\"button[class*=\\\"newSession\\\"]\");\n\tif (nested !== null) return nested;\n\tfor (const child of root.children) if (child.tagName === \"BUTTON\") return child;\n}\n/** Build the entry row (a detached button; insert once the shell is up). */\nfunction createEntry(options) {\n\tconst entry = document.createElement(\"button\");\n\tentry.type = \"button\";\n\tentry.setAttribute(options.rowAttribute, \"\");\n\tif (options.plugin !== void 0) {\n\t\tentry.setAttribute(\"data-dsh-plugin\", options.plugin);\n\t\tentry.setAttribute(\"data-dsh-part\", \"sidebar-entry\");\n\t}\n\tentry.className = options.css[\"entry\"] ?? \"\";\n\tconst labelSpan = document.createElement(\"span\");\n\tlabelSpan.className = options.css[\"entryLabel\"] ?? \"\";\n\tconst iconSpan = document.createElement(\"span\");\n\ticonSpan.className = options.css[\"entryIcon\"] ?? \"\";\n\ticonSpan.innerHTML = options.icon;\n\tentry.append(iconSpan, labelSpan);\n\tlet badgeSpan = null;\n\tconst applyBadge = () => {\n\t\tif (options.badge === void 0) return;\n\t\tconst count = options.badge.count();\n\t\tif (count <= 0) {\n\t\t\tbadgeSpan?.remove();\n\t\t\tbadgeSpan = null;\n\t\t\tentry.removeAttribute(\"data-has-badge\");\n\t\t\treturn;\n\t\t}\n\t\tif (badgeSpan === null) {\n\t\t\tbadgeSpan = document.createElement(\"span\");\n\t\t\tbadgeSpan.className = options.css[\"entryBadge\"] ?? \"\";\n\t\t\tentry.append(badgeSpan);\n\t\t}\n\t\tbadgeSpan.textContent = String(count);\n\t\tentry.setAttribute(\"data-has-badge\", \"\");\n\t};\n\tconst applyLabel = () => {\n\t\tentry.setAttribute(\"aria-label\", options.label());\n\t\tif (options.tooltip !== void 0) entry.setAttribute(\"title\", options.tooltip());\n\t\tlabelSpan.textContent = options.label();\n\t};\n\tapplyLabel();\n\tapplyBadge();\n\tentry.addEventListener(\"click\", options.onToggle);\n\treturn {\n\t\tentry,\n\t\tapplyLabel,\n\t\tapplyBadge\n\t};\n}\n/**\n* Re-insert the entry after the New Session row, in the family's intended\n* order.\n*\n* The `familySelectors` array order is the single source of truth for visual\n* order, so the rendered order is stable regardless of which plugin mounts\n* first or of observer callback order — a re-render can never swap two rows.\n* (Earlier builds used the `position: 'before' | 'after'` convention, which\n* clustered rows by mount order and let a late-mounting `before` row steal the\n* lead. The array order supersedes it; `position` remains only as a fallback\n* for a row whose selector is absent from the family list.)\n*/\nfunction placeEntry(root, entry, options) {\n\tconst button = newSessionButton(root);\n\tif (button === void 0) return false;\n\tif (entry.parentElement !== root) {\n\t\tconst row = button.closest(\"[class*=\\\"logoRow\\\"]\");\n\t\tconst base = row !== null && row.parentElement === root ? row : button;\n\t\tconst order = options.familySelectors;\n\t\tconst present = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches(order.join(\", \"))).map((el) => ({\n\t\t\tel,\n\t\t\tidx: order.findIndex((sel) => el.matches(sel))\n\t\t}));\n\t\tconst selfIndex = order.indexOf(options.rowSelector);\n\t\tlet anchor;\n\t\tif (selfIndex === -1) anchor = options.position === \"before\" ? present.length > 0 ? present[0].el : base.nextElementSibling : present.length > 0 ? present[present.length - 1].el.nextElementSibling : base.nextElementSibling;\n\t\telse {\n\t\t\tconst later = present.find((member) => member.idx > selfIndex);\n\t\t\tif (later !== void 0) anchor = later.el;\n\t\t\telse {\n\t\t\t\tconst earlier = [...present].reverse().find((member) => member.idx < selfIndex);\n\t\t\t\tanchor = earlier !== void 0 ? earlier.el.nextElementSibling : base.nextElementSibling;\n\t\t\t}\n\t\t}\n\t\troot.insertBefore(entry, anchor ?? null);\n\t}\n\treturn true;\n}\n/**\n* Mount the sidebar entry, waiting for the shell to render and self-healing\n* on later React re-renders.\n* @param options - the row's attribute/icon/copy/action/ordering configuration.\n* @returns disposer removing the entry and its observers.\n*/\nfunction mountSidebarEntry(options) {\n\tif (typeof document !== \"undefined\" && document.querySelector(options.rowSelector) !== null) return () => {};\n\tconst { entry, applyLabel, applyBadge } = createEntry(options);\n\tlet root;\n\tlet placed = false;\n\tlet unsubscribeRefresh;\n\tif (options.refresh !== void 0) try {\n\t\tunsubscribeRefresh = options.refresh.subscribe(applyLabel);\n\t} catch {}\n\tlet unsubscribeBadge;\n\tif (options.badge !== void 0) try {\n\t\tunsubscribeBadge = options.badge.subscribe(applyBadge);\n\t} catch {}\n\tconst tryPlace = () => {\n\t\tif (root !== void 0 && !root.isConnected) {\n\t\t\trootObserver.disconnect();\n\t\t\troot = void 0;\n\t\t\tplaced = false;\n\t\t}\n\t\tif (placed) {\n\t\t\tif (document.body.contains(entry)) return;\n\t\t\trootObserver.disconnect();\n\t\t\troot = void 0;\n\t\t\tplaced = false;\n\t\t}\n\t\troot ??= sidebarRoot();\n\t\tif (root === void 0) return;\n\t\tplaced = placeEntry(root, entry, options);\n\t\tif (placed) rootObserver.observe(root, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true\n\t\t});\n\t};\n\tconst waitObserver = new MutationObserver(() => {\n\t\ttryPlace();\n\t});\n\twaitObserver.observe(document.body, {\n\t\tchildList: true,\n\t\tsubtree: true\n\t});\n\tconst rootObserver = new MutationObserver(() => {\n\t\tif (root === void 0 || !root.isConnected) {\n\t\t\tplaced = false;\n\t\t\ttryPlace();\n\t\t\treturn;\n\t\t}\n\t\tif (!root.contains(entry)) placed = placeEntry(root, entry, options);\n\t});\n\toptions.active === void 0 || (() => {\n\t\tconst syncActive = () => {\n\t\t\tif (options.active.isOpen()) entry.dataset.active = \"true\";\n\t\t\telse delete entry.dataset.active;\n\t\t};\n\t\tconst unsubscribe = options.active.subscribe(syncActive);\n\t\tsyncActive();\n\t\treturn unsubscribe;\n\t})();\n\ttryPlace();\n\treturn () => {\n\t\twaitObserver.disconnect();\n\t\trootObserver.disconnect();\n\t\tunsubscribeRefresh?.();\n\t\tunsubscribeBadge?.();\n\t\tentry.remove();\n\t};\n}\n/**\n* Every row selector in the Huaqiu sidebar family, in intended visual order.\n*\n* Family ordering is only stable when every participating plugin passes the\n* SAME list to {@link mountSidebarEntry} — the core positions a row relative to\n* the family block, never relative to transient shell geometry. Adding a new\n* family row means adding its selector here (and to no plugin-local list).\n*/\nconst HUAQIU_SIDEBAR_ENTRY_FAMILY = [\n\t\"[data-hqcg-footprint-entry]\",\n\t\"[data-hqcg-symbol-entry]\",\n\t\"[data-hqcg-schematic-entry]\",\n\t\"[data-hq-dummy-entry]\",\n\t\"[data-hq-plugin-update-entry]\"\n];\n/**\n* Read the host UI language from `<html lang>`.\n*\n* `dsh-client-locale` rewrites the attribute on every language change (dsh\n* web) and HQ Edge mirrors it, so this is the one source every family plugin\n* can read without importing a `locale` service.\n* @returns 'zh' / 'en', or undefined when the tag is absent or unsupported\n* (callers fall back to their own default).\n*/\nfunction detectHostLocale() {\n\tif (typeof document === \"undefined\") return void 0;\n\tconst tag = document.documentElement?.getAttribute(\"lang\");\n\tif (tag === null || tag === void 0 || tag === \"\") return void 0;\n\tconst primary = tag.toLowerCase().split(\"-\")[0];\n\treturn primary === \"zh\" || primary === \"en\" ? primary : void 0;\n}\n/**\n* Watch `<html lang>` and report every resolved change.\n*\n* Idempotent-safe: repeated calls share one observer and each caller gets its\n* own disposer; the observer disconnects when the last listener leaves.\n* @param listener - called with the newly resolved language.\n* @returns disposer removing this listener.\n*/\n/** Shared observer/listener set backing {@link observeHostLocale}. */\nconst localeListeners = /* @__PURE__ */ new Set();\nlet localeObserver = null;\nfunction observeHostLocale(listener) {\n\tif (typeof document === \"undefined\" || typeof MutationObserver === \"undefined\" || document.documentElement === null) return () => {};\n\tlet current = detectHostLocale();\n\tconst listeners = localeListeners;\n\tlisteners.add(listener);\n\tif (localeObserver === null) {\n\t\tlocaleObserver = new MutationObserver(() => {\n\t\t\tconst next = detectHostLocale();\n\t\t\tif (next === current) return;\n\t\t\tcurrent = next;\n\t\t\tfor (const fn of [...listeners]) try {\n\t\t\t\tfn(next);\n\t\t\t} catch {}\n\t\t});\n\t\tlocaleObserver.observe(document.documentElement, {\n\t\t\tattributes: true,\n\t\t\tattributeFilter: [\"lang\"]\n\t\t});\n\t}\n\treturn () => {\n\t\tlisteners.delete(listener);\n\t\tif (listeners.size === 0 && localeObserver !== null) {\n\t\t\tlocaleObserver.disconnect();\n\t\t\tlocaleObserver = null;\n\t\t}\n\t};\n}\n//#endregion\nexport { HUAQIU_SIDEBAR_ENTRY_FAMILY, detectHostLocale, mountSidebarEntry, observeHostLocale };\n","/**\n * SemVer direction gate for plugin-update items (browser half).\n *\n * The rule this module exists to enforce, for EVERY plugin in the report:\n * an item is only \"update available\" — and only gets an install control —\n * when the version offered by the release manifest is strictly GREATER than\n * the version installed in the active bundle. A manifest entry that merely\n * differs from the installed version offers nothing: equality is the\n * already-installed case, and a manifest that LAGS behind the installed\n * build (a stale mirror or a rollback channel publishing 0.6.2 against an\n * installed 0.6.4) must never surface as a downgrade \"update\".\n *\n * Why this lives on the client even though hq-edge owns the decision: the\n * report crosses a process boundary, and an older hq-edge still carries the\n * old \"differs ⇒ update\" rule — which rendered `analytics 可更新\n * 0.6.4 → 0.6.2` with a live 安装 button for the whole plugin set. This is\n * the same defensive second pass the badge already applies to the ignored\n * offer: one authoritative decision upstream, one guard where it is shown.\n *\n * The comparator is a strict SemVer 2.0.0 implementation (no dependency —\n * this package ships no runtime dependencies beyond the workspace log +\n * sidebar helpers). Anything that is not clean SemVer on either side makes\n * the comparison `null`, and an unprovable direction fails CLOSED: the item\n * reads as current, never as an update.\n *\n * @module\n */\nimport type { PluginUpdateItem, PluginUpdateReport } from './api.js'\n\n/** Strict SemVer 2.0.0: MAJOR.MINOR.PATCH[-prerelease][+build]. */\nconst SEMVER_RE =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/\n\ninterface ParsedVersion {\n major: number\n minor: number\n patch: number\n /** Prerelease identifiers (empty = a release, which outranks any prerelease). */\n prerelease: string[]\n}\n\n/** Parse a strict SemVer string, or null when it is not one. */\nfunction parseSemver(version: string): ParsedVersion | null {\n const match = SEMVER_RE.exec(version.trim())\n if (match === null) return null\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n prerelease: match[4] === undefined ? [] : match[4].split('.'),\n }\n}\n\n/** True when one prerelease identifier is purely numeric. */\nfunction isNumericIdentifier(id: string): boolean {\n return /^\\d+$/.test(id)\n}\n\n/**\n * Compare two prerelease identifier lists per SemVer §11:\n * numeric identifiers compare numerically and rank below alphanumeric ones;\n * a larger set of identifiers ranks higher when all shared ones are equal.\n */\nfunction comparePrerelease(a: string[], b: string[]): number {\n // No prerelease outranks any prerelease.\n if (a.length === 0 && b.length === 0) return 0\n if (a.length === 0) return 1\n if (b.length === 0) return -1\n const shared = Math.min(a.length, b.length)\n for (let index = 0; index < shared; index += 1) {\n const left = a[index]\n const right = b[index]\n if (left === undefined || right === undefined) continue\n if (left === right) continue\n const leftNumeric = isNumericIdentifier(left)\n const rightNumeric = isNumericIdentifier(right)\n if (leftNumeric && rightNumeric) {\n return Number(left) < Number(right) ? -1 : 1\n }\n if (leftNumeric !== rightNumeric) {\n // Numeric identifiers always have lower precedence than alphanumeric.\n return leftNumeric ? -1 : 1\n }\n return left < right ? -1 : 1\n }\n return a.length === b.length ? 0 : a.length < b.length ? -1 : 1\n}\n\n/**\n * Compare two strict SemVer strings: negative when `a < b`, positive when\n * `a > b`, 0 when equal, and null when either side is not SemVer (build\n * metadata is ignored in precedence, per spec).\n */\nexport function compareSemver(a: string, b: string): number | null {\n const left = parseSemver(a)\n const right = parseSemver(b)\n if (left === null || right === null) return null\n if (left.major !== right.major) return left.major < right.major ? -1 : 1\n if (left.minor !== right.minor) return left.minor < right.minor ? -1 : 1\n if (left.patch !== right.patch) return left.patch < right.patch ? -1 : 1\n return comparePrerelease(left.prerelease, right.prerelease)\n}\n\n/**\n * Whether the offered release is an actionable upgrade over the installed\n * version: an absent plugin (nothing installed) is always installable, and\n * an installed one only when the offered SemVer is strictly greater. An\n * unprovable direction (non-SemVer on either side) is NOT an upgrade.\n */\nexport function isVersionUpgrade(currentVersion: string | null, availableVersion: string): boolean {\n if (currentVersion === null) return true\n const order = compareSemver(availableVersion, currentVersion)\n return order !== null && order > 0\n}\n\n/**\n * Re-evaluate one report item against the direction rule.\n *\n * A server-reported `update` that is not a strict SemVer upgrade is demoted\n * to `current`: the plugin is at (or ahead of) the offered release, there is\n * nothing to install, and no install control may be offered for it. Every\n * other state passes through untouched — `install` (plugin absent locally)\n * has no local version to compare, and `ignored`/`current` are already\n * non-actionable. Unknown server fields are preserved verbatim: the\n * client stays a thin reader of the hq-edge report shape.\n */\nexport function normalizePluginUpdateItem<T extends PluginUpdateItem>(item: T): T {\n if (item.state !== 'update') return item\n if (isVersionUpgrade(item.currentVersion, item.availableVersion)) return item\n return { ...item, state: 'current' }\n}\n\n/** States that carry an install action (the badge/actionable definition). */\nfunction isActionableState(item: PluginUpdateItem): boolean {\n // A muted item carries no action even if an older hq-edge left its state at\n // `update` (it also flags the item, and the whole set, when it can).\n if (item.ignored === true) return false\n return item.state === 'update' || item.state === 'install'\n}\n\n/**\n * How many PLUGIN updates a report makes actionable — the ONE definition the\n * footer bar, the panel's sections and `actionableCount` share, so they can\n * never disagree about how much plugin work is left.\n *\n * Deliberately narrow (spec §6.3/§7.1):\n * - compatible `update` + `install` items only;\n * - nothing once the user has muted the offer (`ignoredSet`), and nothing\n * for an individually-ignored item;\n * - plugin items only. The HQ Edge runtime update is a separate deliverable\n * with its own card, so it is not a plugin item here — the sidebar badge\n * adds it on top, once, in `badge.ts`. Never fold it into this number:\n * that is what made the footer claim \"准备安装 N 项兼容插件\" one too many.\n *\n * Note what is absent: no durable-job input. The job history is permanent\n * (`listRecent(20)`), so any rule that counts a settled job latches on forever.\n */\nexport function actionableUpdateCount(report: PluginUpdateReport | null | undefined): number {\n if (report === null || report === undefined) return 0\n // The whole offered set is muted. An older hq-edge sets this flag but leaves\n // the item states at `update`, so it must be checked before counting.\n if (report.ignoredSet === true) return 0\n if (!Array.isArray(report.items)) return 0\n let count = 0\n for (const item of report.items) {\n if (isActionableState(item)) count += 1\n }\n return count\n}\n\n/**\n * Apply {@link normalizePluginUpdateItem} to a whole report and recompute\n * `actionableCount` from the corrected states, so the server's count can\n * never disagree with what the panel and the sidebar badge render.\n */\nexport function normalizePluginUpdateReport<T extends PluginUpdateReport>(report: T): T {\n if (!Array.isArray(report.items)) return report\n const items = report.items.map(normalizePluginUpdateItem)\n const next = { ...report, items }\n return {\n ...next,\n actionableCount: actionableUpdateCount(next),\n }\n}\n","/**\n * Plugin-update API client (browser half).\n *\n * Every call is a same-origin fetch against the DSH webServer prefix route\n * (`/api/v1/huaqiu/plugin-update/…`) which the node half forwards to hq-edge.\n * Response shapes mirror hq-edge `apps/server/src/routes/plugin-updates.ts`.\n *\n * @module\n */\nimport { normalizePluginUpdateReport } from './version.js'\n\nexport const UPDATE_BASE = '/api/v1/huaqiu/plugin-update'\n\nexport interface PluginUpdateItem {\n name: string\n /** Version installed in the active bundle; null when the plugin is absent. */\n currentVersion: string | null\n availableVersion: string\n /**\n * Server state. `update` is re-checked on arrival against the SemVer\n * direction rule (`version.ts`): an offer that is not strictly newer than\n * `currentVersion` is demoted to `current`, so the panel and the badge can\n * never offer a downgrade even against an older hq-edge.\n */\n state: 'current' | 'update' | 'install' | 'blocked' | 'ignored'\n /** The block reason, or null when the item is not blocked. The server sends a\n * single code (`\"runtime-unknown\"`), so it is normalized before use. */\n blockedBy?: string | string[] | null\n ignored?: boolean\n reasons?: string[]\n notes?: string[]\n /**\n * User-facing release metadata (schemaVersion-2 manifests). ALL optional and\n * absent for v1 manifests — the view falls back to the package short name\n * and version line when they are missing.\n */\n displayName?: string\n description?: string\n sizeBytes?: number\n changes?: string[]\n}\n\n/**\n * One downloadable HQ Edge runtime distribution, as hq-edge reports it\n * (`RuntimeUpdateEntry` in `apps/server/src/dsh/PluginUpdateService.ts`).\n *\n * Note the shape: `runtime.latest` is this ENTRY OBJECT, not a bare version\n * string. Reading it as a string is what once rendered \"[object Object]\".\n */\nexport interface RuntimeUpdateEntry {\n /** Clean SemVer of the available distribution. */\n version: string\n /** SHA-256 of the release archive, lowercase hex (64 chars). */\n sha256: string\n /** Platform the archive targets: win | darwin | linux | all. */\n platform: string\n /** Optional CPU architecture: x64 | arm64 (absent = any). */\n arch?: string\n /** Exact absolute HTTPS URL of the release archive. */\n url: string\n /**\n * Optional user-facing metadata (schemaVersion-2 runtime manifest). All\n * absent for v1 manifests; the panel degrades gracefully without them.\n */\n publishedAt?: string\n sizeBytes?: number\n summary?: string\n notesUrl?: string\n notes?: string[]\n breaking?: string[]\n channel?: string\n}\n\n/**\n * The EDA host's own report of the last runtime replacement, read from\n * `runtime-update-result.json` by hq-edge. This is the authoritative outcome\n * after HQ Edge exits and the host swaps the runtime — it must be rendered,\n * not discarded. `state` is `succeeded`, `rolled_back` (swap failed but the\n * previous runtime was restored) or `failed` (no confirmed rollback).\n */\nexport interface RuntimeLastResult {\n state: 'succeeded' | 'rolled_back' | 'failed'\n targetVersion?: string | null\n installedVersion?: string | null\n at?: string\n errorCode?: string\n message?: string\n}\n\nexport interface RuntimeUpdateInfo {\n configured: boolean\n currentVersion?: string | null\n /** The latest compatible distribution, or null when the manifest had none. */\n latest?: RuntimeUpdateEntry | null\n updateAvailable: boolean\n checkedAt?: string | null\n lastError?: string | null\n /** The host-reported outcome of the last runtime swap; null when none. */\n lastResult?: RuntimeLastResult | null\n /**\n * Where the runtime-update manifest is read from. Optional: an older hq-edge\n * does not send it, and the panel must degrade to a read-only source row.\n */\n source?: RuntimeUpdateSource\n}\n\n/**\n * The runtime-update manifest source, editable from this panel.\n *\n * `origin` says which URL is in effect: `override` is a URL the user set here\n * (persisted by hq-edge per host), `config` is the packaged/env default, and\n * `unset` means runtime updates are not configured at all.\n */\nexport interface RuntimeUpdateSource {\n /** The manifest URL currently in effect; empty when none is known. */\n url: string\n origin: 'override' | 'config' | 'unset'\n /** The URL configuration supplies, independent of any user override. */\n configuredUrl: string\n /** True when a user override is shadowing the configured URL. */\n override: boolean\n}\n\n/**\n * The plugin release-manifest source (the gitee\n * `plugins-release-manifest.json` by default) — same shape and same edit flow\n * as {@link RuntimeUpdateSource}, because hq-edge serves both from one shared\n * type.\n */\nexport type PluginUpdateSource = RuntimeUpdateSource\n\n/**\n * The latest runtime version to display, or null when there is none.\n *\n * hq-edge sends the full release entry; an older or future server could send\n * a bare string. Both are accepted here so the facts row can never again fall\n * back to `String(object)` — i.e. \"[object Object]\" — in the DOM.\n */\nexport function runtimeLatestVersion(\n runtime: RuntimeUpdateInfo | null | undefined,\n): string | null {\n const latest: unknown = runtime?.latest\n if (typeof latest === 'string') return latest.length > 0 ? latest : null\n if (latest !== null && typeof latest === 'object' && 'version' in latest) {\n const version = (latest as { version?: unknown }).version\n if (typeof version === 'string' && version.length > 0) return version\n }\n return null\n}\n\n/**\n * Whether the report offers an HQ Edge runtime update the user can act on:\n * hq-edge reports that a newer distribution exists AND it actually named one.\n *\n * Both halves matter, and sharing one predicate keeps the sidebar badge and the\n * runtime card from disagreeing: the card can only render its 升级核心 row when\n * it knows the target version, so an `updateAvailable` without a `latest` entry\n * must not add a count anywhere. hq-edge already fails closed here (it only\n * sets the flag when `semver.gt(latest, current)` holds on a comparable pair),\n * so this is a shape guard, not a second compatibility decision.\n */\nexport function runtimeUpdateOffered(\n runtime: RuntimeUpdateInfo | null | undefined,\n): boolean {\n if (runtime === null || runtime === undefined) return false\n if (runtime.updateAvailable !== true) return false\n return runtimeLatestVersion(runtime) !== null\n}\n\n/**\n * Set-level release metadata from a schemaVersion-2 plugin manifest. ALL\n * fields optional: v1 manifests send none, and the panel must fall back to\n * showing just the version then.\n */\nexport interface ReleaseInfo {\n publishedAt?: string\n channel?: string\n requiresRestart?: boolean\n summary?: string\n notesUrl?: string\n notes?: string[]\n breaking?: string[]\n}\n\nexport interface PluginUpdateReport {\n /** The evaluated release set; null while no manifest has been read. */\n manifestVersion: string | null\n pluginSetVersion?: string\n items: PluginUpdateItem[]\n actionableCount: number\n ignoredSet: boolean\n fetchedAt?: string\n /** When the last plugin discovery ran (ISO); drives the \"上次检查\" row. */\n checkedAt?: string | null\n /** Non-empty when the last plugin discovery FAILED — the panel must show\n * an error row with a Retry, never the silent \"no updates\" empty state. */\n lastError?: string | null\n /** Set-level release metadata; absent for v1 manifests. */\n release?: ReleaseInfo | null\n runtime: RuntimeUpdateInfo\n /**\n * Where the plugin release manifest is read from. Optional: an older\n * hq-edge does not send it, and the panel must degrade to no source row.\n */\n pluginSource?: PluginUpdateSource\n}\n\n// ── Durable update-job lifecycle (GET /status, GET /status/:id) ────────────\n\n/** Lifecycle phases of one durable update job. Terminal phases end the poll. */\nexport type UpdatePhase =\n | 'queued'\n | 'resolving'\n | 'downloading'\n | 'verifying'\n | 'staging'\n | 'activating'\n | 'awaiting-host'\n | 'restarting'\n | 'verifying-after-restart'\n | 'done'\n | 'failed'\n | 'rolled-back'\n | 'abandoned'\n\n/** Phases after which a job never runs again — polling stops and a result view\n * replaces the active-job view. */\nexport const TERMINAL_PHASES: ReadonlySet<UpdatePhase> = new Set<UpdatePhase>([\n 'done',\n 'failed',\n 'rolled-back',\n 'abandoned',\n])\n\nexport type UpdateKind = 'plugin-set' | 'runtime'\n\nexport interface UpdateJobProgress {\n /** 0..100 when the phase has a meaningful percentage; null otherwise. */\n percent: number | null\n bytesDone?: number\n bytesTotal?: number\n bytesPerSecond?: number\n etaSeconds?: number\n}\n\nexport interface UpdateStep {\n name: string\n state: 'pending' | 'running' | 'installed' | 'skipped' | 'failed'\n percent?: number\n message?: string\n}\n\nexport interface UpdateJobResult {\n ok: boolean\n installedVersion: string | null\n errorCode?: string\n message?: string\n rolledBack: boolean\n finishedAt: string\n}\n\n/**\n * One durable update job, as hq-edge reports it (`GET /status` and\n * `/status/:id`). The UI is a projection of this job: opening the panel\n * resumes whatever the server says is active, rather than owning the\n * lifecycle in a DOM closure.\n */\nexport interface UpdateJob {\n id: string\n kind: UpdateKind\n targetVersion: string\n previousVersion: string | null\n startedAt: string\n updatedAt: string\n phase: UpdatePhase\n message?: string\n progress: UpdateJobProgress\n steps?: UpdateStep[]\n result?: UpdateJobResult\n logHint?: string\n}\n\n/** `GET /status`: the current active job plus a bounded list of recent ones. */\nexport interface UpdateJobStatus {\n activeJob: UpdateJob | null\n recentJobs: UpdateJob[]\n}\n\n/**\n * `GET /activity`: the re-attach snapshot.\n *\n * The panel can be updating its own package, which restarts the DSH process\n * that serves this plugin — the page then has to rebuild its state from\n * scratch. `bootId` changes when that process is recreated, `stalled` says the\n * active job has stopped advancing (so the UI must stop animating a progress\n * bar that will never move), and `setVersion` is what is actually installed.\n */\nexport interface UpdateActivity {\n bootId: string\n startedAt: string\n now: string\n activeJob: UpdateJob | null\n /** The job named by `?job=`, settled or not; null when not asked/unknown. */\n job: UpdateJob | null\n phaseAgeMs: number\n stalled: boolean\n setVersion: string | null\n actionableCount: number\n}\n\nconst PHASE_VALUES: ReadonlySet<string> = new Set<string>([\n 'queued',\n 'resolving',\n 'downloading',\n 'verifying',\n 'staging',\n 'activating',\n 'awaiting-host',\n 'restarting',\n 'verifying-after-restart',\n 'done',\n 'failed',\n 'rolled-back',\n 'abandoned',\n])\n\nfunction asStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined\n const out = value.filter((entry): entry is string => typeof entry === 'string')\n return out.length > 0 ? out : undefined\n}\n\nfunction asFiniteNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined\n}\n\n/** Defensively normalize one job payload from the wire into {@link UpdateJob}.\n * Unknown/older servers may omit fields; a malformed `phase` falls back to\n * `queued` rather than crashing the view. */\nexport function normalizeJob(raw: unknown): UpdateJob | null {\n if (raw === null || typeof raw !== 'object') return null\n const r = raw as Record<string, unknown>\n if (typeof r.id !== 'string' || r.id.length === 0) return null\n const phase = typeof r.phase === 'string' && PHASE_VALUES.has(r.phase)\n ? (r.phase as UpdatePhase)\n : 'queued'\n const progressRaw = (r.progress ?? {}) as Record<string, unknown>\n const percentRaw = asFiniteNumber(progressRaw.percent)\n const progress: UpdateJobProgress = {\n percent: percentRaw === undefined ? null : Math.min(100, Math.max(0, percentRaw)),\n }\n const bytesDone = asFiniteNumber(progressRaw.bytesDone)\n const bytesTotal = asFiniteNumber(progressRaw.bytesTotal)\n const bytesPerSecond = asFiniteNumber(progressRaw.bytesPerSecond)\n const etaSeconds = asFiniteNumber(progressRaw.etaSeconds)\n if (bytesDone !== undefined) progress.bytesDone = bytesDone\n if (bytesTotal !== undefined) progress.bytesTotal = bytesTotal\n if (bytesPerSecond !== undefined) progress.bytesPerSecond = bytesPerSecond\n if (etaSeconds !== undefined) progress.etaSeconds = etaSeconds\n\n const job: UpdateJob = {\n id: r.id,\n kind: r.kind === 'runtime' ? 'runtime' : 'plugin-set',\n targetVersion: typeof r.targetVersion === 'string' ? r.targetVersion : '',\n previousVersion:\n typeof r.previousVersion === 'string' ? r.previousVersion : null,\n startedAt: typeof r.startedAt === 'string' ? r.startedAt : '',\n updatedAt: typeof r.updatedAt === 'string' ? r.updatedAt : '',\n phase,\n progress,\n }\n if (typeof r.message === 'string' && r.message.length > 0) job.message = r.message\n if (typeof r.logHint === 'string' && r.logHint.length > 0) job.logHint = r.logHint\n\n if (Array.isArray(r.steps)) {\n const steps: UpdateStep[] = []\n for (const entry of r.steps) {\n if (entry === null || typeof entry !== 'object') continue\n const s = entry as Record<string, unknown>\n if (typeof s.name !== 'string') continue\n const state =\n s.state === 'running' || s.state === 'installed' || s.state === 'skipped' || s.state === 'failed'\n ? s.state\n : 'pending'\n const step: UpdateStep = { name: s.name, state }\n const pct = asFiniteNumber(s.percent)\n if (pct !== undefined) step.percent = pct\n if (typeof s.message === 'string' && s.message.length > 0) step.message = s.message\n steps.push(step)\n }\n if (steps.length > 0) job.steps = steps\n }\n\n if (r.result !== null && typeof r.result === 'object') {\n const res = r.result as Record<string, unknown>\n const result: UpdateJobResult = {\n ok: res.ok === true,\n installedVersion:\n typeof res.installedVersion === 'string' ? res.installedVersion : null,\n rolledBack: res.rolledBack === true,\n finishedAt: typeof res.finishedAt === 'string' ? res.finishedAt : '',\n }\n if (typeof res.errorCode === 'string' && res.errorCode.length > 0) result.errorCode = res.errorCode\n if (typeof res.message === 'string' && res.message.length > 0) result.message = res.message\n job.result = result\n }\n return job\n}\n\n/** Normalize a `GET /status` payload; an empty/absent body degrades to \"no\n * active job\" rather than throwing. */\nexport function normalizeJobStatus(raw: unknown): UpdateJobStatus {\n if (raw === null || typeof raw !== 'object') return { activeJob: null, recentJobs: [] }\n const r = raw as Record<string, unknown>\n const activeJob = normalizeJob(r.activeJob)\n const recentJobs = Array.isArray(r.recentJobs)\n ? r.recentJobs.map(normalizeJob).filter((j): j is UpdateJob => j !== null)\n : []\n return { activeJob, recentJobs }\n}\n\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n const res = await fetch(`${UPDATE_BASE}${path}`, {\n cache: 'no-store',\n ...init,\n headers: { 'content-type': 'application/json; charset=utf-8', ...(init?.headers ?? {}) },\n })\n const body = (await res.json().catch(() => null)) as T | null\n if (!res.ok) {\n const err = (body as { error?: string } | null)?.error ?? `HTTP ${res.status}`\n throw new Error(err)\n }\n if (body === null) throw new Error('empty response')\n return body\n}\n\n/** GET the cached (or empty-shell) plugin-update report. */\nexport function fetchReport(): Promise<PluginUpdateReport> {\n return request<PluginUpdateReport>('/report').then(normalizePluginUpdateReport)\n}\n\n/** POST an explicit refresh; returns the freshly computed report. */\nexport function checkUpdates(): Promise<PluginUpdateReport> {\n return request<PluginUpdateReport>('/check', { method: 'POST' }).then(normalizePluginUpdateReport)\n}\n\nexport interface InstallResult {\n /**\n * New durable contract: `202 { jobId, state: 'accepted' }`. The install is\n * now an async job — poll `/status/:jobId` for progress, don't read success\n * off this response. Legacy boolean fields are kept for older hq-edge.\n */\n jobId?: string\n state?: string\n ok?: boolean\n installedSetVersion?: string\n installed?: number\n skipped?: number\n restarting?: boolean\n error?: string\n}\n\n/** POST the install transaction for the confirmed set version. */\nexport function installSet(setVersion: string, pluginNames?: string[]): Promise<InstallResult> {\n return request<InstallResult>('/install', {\n method: 'POST',\n body: JSON.stringify({ setVersion, ...(pluginNames ? { pluginNames } : {}) }),\n })\n}\n\n/** Persist a per-plugin decision for this release set. */\nexport function ignorePlugin(setVersion: string, name: string): Promise<unknown> {\n return request<unknown>('/plugin-ignore', { method: 'POST', body: JSON.stringify({ setVersion, name }) })\n}\n\nexport function unignorePlugin(setVersion: string, name: string): Promise<unknown> {\n return request<unknown>('/plugin-unignore', { method: 'POST', body: JSON.stringify({ setVersion, name }) })\n}\n\n/** POST ignore for the current offer (set-scoped, persists across restarts). */\nexport function ignoreOffer(setVersion: string): Promise<unknown> {\n return request<unknown>('/ignore', { method: 'POST', body: JSON.stringify({ setVersion }) })\n}\n\n/**\n * The outcome of a user-confirmed HQ Edge runtime update.\n *\n * hq-edge answers `202 { requestId, targetVersion, state: 'accepted' }` and\n * then shuts itself down for the EDA host to swap the runtime, so a success\n * here means \"accepted\", not \"installed\". Failures arrive as 409 (already\n * updating → `busy`), 422 (preflight/manifest/download refused) or 503.\n */\nexport interface RuntimeUpdateRequestResult {\n /** Durable job id — poll `/status/:jobId` for the accepted job's progress. */\n jobId?: string\n requestId?: string\n targetVersion?: string\n state?: string\n ok?: boolean\n error?: string\n busy?: boolean\n}\n\n/** POST the user-confirmed HQ Edge runtime update for one target version. */\nexport function requestRuntimeUpdate(\n targetVersion: string,\n): Promise<RuntimeUpdateRequestResult> {\n return request<RuntimeUpdateRequestResult>('/runtime-request', {\n method: 'POST',\n body: JSON.stringify({ targetVersion }),\n })\n}\n\n/** POST unignore for the set version (reverses an ignore). */\nexport function unignoreOffer(setVersion: string): Promise<unknown> {\n return request<unknown>('/unignore', { method: 'POST', body: JSON.stringify({ setVersion }) })\n}\n\n/**\n * GET the runtime-update manifest source: the URL in effect, its origin\n * (`override` | `config` | `unset`) and the configured URL behind it.\n *\n * Read-only upstream, so this is open like `/report` — only the mutation\n * below is loopback-guarded (and reaches it same-origin anyway).\n */\nexport function getRuntimeUpdateSource(): Promise<RuntimeUpdateSource> {\n return request<RuntimeUpdateSource>('/runtime-source')\n}\n\n/**\n * POST a new runtime-update manifest URL, or `\"\"` to clear a user override\n * and fall back to the configured URL.\n *\n * hq-edge validates the URL itself (`https` required except for loopback, no\n * embedded credentials) and answers 422 with a message meant for a person —\n * this client surfaces it verbatim rather than pre-validating, so the rule\n * lives in exactly one place. A successful answer is the NEW source; the\n * caller must then re-run discovery, because the cached report still names\n * the old manifest's offer.\n */\nexport function setRuntimeUpdateSource(url: string): Promise<RuntimeUpdateSource> {\n return request<RuntimeUpdateSource>('/runtime-source', {\n method: 'POST',\n body: JSON.stringify({ url }),\n })\n}\n\n/**\n * GET the plugin release-manifest source: the URL discovery reads, its origin\n * (`override` | `config` | `unset`) and the configured URL behind it. Same\n * open/read-only contract as the runtime source.\n */\nexport function getPluginUpdateSource(): Promise<PluginUpdateSource> {\n return request<PluginUpdateSource>('/plugin-source')\n}\n\n/**\n * POST a new plugin release-manifest URL, or `\"\"` to clear a user override.\n *\n * hq-edge persists the override per host AND re-runs discovery in the\n * background, so the next report (and the sidebar badge) already reflect the\n * new manifest. This client still re-checks explicitly after a save — the\n * background pass is a safety net for other consumers, not a substitute.\n */\nexport function setPluginUpdateSource(url: string): Promise<PluginUpdateSource> {\n return request<PluginUpdateSource>('/plugin-source', {\n method: 'POST',\n body: JSON.stringify({ url }),\n })\n}\n\n/**\n * GET the durable job status: the currently active job (if any) plus a bounded\n * list of recent jobs. This is what the panel reads BEFORE running discovery\n * on open, so an in-flight install/runtime update is resumed instead of\n * forgotten. An older hq-edge without the status route 404s — callers catch\n * that and degrade to \"no active job\".\n */\nexport function fetchJobStatus(): Promise<UpdateJobStatus> {\n return request<unknown>('/status').then(normalizeJobStatus)\n}\n\n/** GET one job by id; rejects (upstream 404 `unknown job`) for an unknown id. */\nexport function fetchJob(id: string): Promise<UpdateJob> {\n return request<unknown>(`/status/${encodeURIComponent(id)}`).then((raw) => {\n const job = normalizeJob(raw)\n if (job === null) throw new Error('malformed job payload')\n return job\n })\n}\n\n/**\n * Normalize `GET /activity`. Unknown/missing fields degrade to a usable\n * snapshot: a `bootId` empty string simply never triggers a re-attach, and an\n * unparseable age reads as 0 rather than \"stalled\".\n */\nexport function normalizeActivity(raw: unknown): UpdateActivity {\n const r = (raw !== null && typeof raw === 'object' ? raw : {}) as Record<string, unknown>\n const str = (v: unknown): string => (typeof v === 'string' ? v : '')\n const num = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0)\n return {\n bootId: str(r.bootId),\n startedAt: str(r.startedAt),\n now: str(r.now),\n activeJob: normalizeJob(r.activeJob),\n job: normalizeJob(r.job),\n phaseAgeMs: Math.max(0, num(r.phaseAgeMs)),\n stalled: r.stalled === true,\n setVersion: typeof r.setVersion === 'string' && r.setVersion.length > 0 ? r.setVersion : null,\n actionableCount: num(r.actionableCount),\n }\n}\n\n/**\n * GET the re-attach snapshot, optionally naming the job being watched.\n *\n * Used instead of `/status` whenever the panel is following a job it started:\n * it answers \"what happened to MY job\" across a DSH restart, and each read lets\n * hq-edge settle a job that its own process could not finish.\n */\nexport function fetchActivity(jobId?: string): Promise<UpdateActivity> {\n const query = jobId ? `?job=${encodeURIComponent(jobId)}` : ''\n return request<unknown>(`/activity${query}`).then(normalizeActivity)\n}\n","/**\n * Localized copy for the plugin-update UI (browser half).\n *\n * zh is the fallback, matching the rest of the Huaqiu plugin family\n * (`<html lang>` drives the resolution via the shared sidebar-entry locale\n * helpers). The wording follows the shell's own plugin manager\n * (`ui-plugin-manager/src/client/locales.ts`) so both read as one product.\n *\n * Two vocabulary rules hold throughout, and both come from the redesign brief:\n *\n * - **日志 means the JOB log** (安装日志与记录, the header's drawer). What a\n * plugin release changed is 更新内容 / 版本日志 — never 日志 — so the two\n * affordances can never be confused for one another.\n * - **Counts are plugin-set counts.** \"3 项插件可更新\" counts compatible\n * `update` + `install` items; the runtime is always named separately\n * (\"1 项运行时可升级\") because it is a different, riskier action.\n *\n * @module\n */\n\nconst zh = {\n entryLabel: '插件更新',\n entryTooltip: '查看可用的插件与运行时更新',\n viewTitle: '插件更新',\n refresh: '刷新',\n close: '关闭',\n cancel: '取消',\n loading: '正在读取插件更新…',\n empty: '没有可用的插件更新',\n checkFailed: '检查更新失败,请重试',\n checkFailedTitle: '检查更新失败',\n checkFailedBanner: (message: string) => `检查更新失败:${message}`,\n staleNote: '以下为上次成功检查的结果(可能已过期)',\n retry: '重试',\n retryNow: '重新尝试',\n ignore: '忽略此版本',\n unignore: '恢复提醒',\n installError: '安装失败',\n installSuccess: '更新已安装,正在重启 DSH…',\n ignoreError: '忽略失败',\n confirmInstall: '确认安装',\n lastError: '上次检查失败',\n // Origin tags, shared by both source editors (hq-edge serves one type).\n originOverride: '自定义',\n originConfig: '默认配置',\n originUnset: '未配置',\n group: {\n actionable: '待处理更新',\n ignored: '已忽略',\n current: '已是最新',\n },\n runtime: {\n title: 'HQ Edge 运行时',\n current: '当前版本',\n latest: '最新版本',\n set: '发布插件集',\n checkedAt: '上次检查',\n source: '更新源',\n unknown: '版本未知',\n notConfigured: '未配置更新源',\n note: '运行时更新由 EDA 安装器处理',\n sizeLabel: '包大小',\n update: '安装运行时更新',\n updateConfirm: (version: string) =>\n `确认升级 HQ Edge 运行时至 v${version}?升级将自动关闭后台服务并完成替换,随后重启。`,\n // No fixed \"takes one minute\" claim: duration depends on download speed\n // and package size (the bundle is hundreds of MB).\n updating: '正在下载并校验运行时包…',\n durationNote: '耗时取决于下载速度与包体大小;运行时包较大,替换后 HQ Edge 会自动重启。',\n updateAccepted: '运行时更新已受理,HQ Edge 正在重启…',\n size: (bytes: number) => `约 ${formatMb(bytes)}`,\n lastResult: {\n title: '上次运行时更新结果',\n succeeded: '运行时更新成功',\n failed: '运行时更新失败',\n rolledBack: '运行时更新已回滚(已恢复旧版本)',\n elevation: '系统级安装可能弹出 UAC 提权窗口;拒绝提权会导致替换失败并自动回滚到旧版本。',\n },\n // ── Update-source editor ──\n sourceHint: '运行时更新清单的下载地址,保存后立即生效',\n sourceEdit: '修改',\n sourceSave: '保存',\n sourceReset: '恢复默认',\n saved: '更新源已保存,正在重新检查更新…',\n saveFailed: '保存更新源失败',\n sourceInputLabel: '运行时更新清单地址',\n sourcePlaceholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/edge-headless-release-manifest.json',\n },\n // ── Plugin release-manifest source editor ──\n pluginSource: {\n title: '插件更新源',\n hint: '插件更新清单的下载地址,保存后立即生效',\n edit: '修改',\n save: '保存',\n reset: '恢复默认',\n saved: '更新源已保存,正在重新检查更新…',\n saveFailed: '保存更新源失败',\n inputLabel: '插件更新清单地址',\n placeholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json',\n },\n // ── Durable update job (active / result / log drawer) ──\n job: {\n kindPluginSet: '插件集更新',\n kindRuntime: 'HQ Edge 运行时更新',\n phase: {\n queued: '排队中',\n resolving: '解析版本',\n downloading: '下载中',\n verifying: '校验完整性',\n staging: '暂存文件',\n activating: '启用插件集',\n 'awaiting-host': '等待主机替换运行时',\n restarting: '正在重启',\n 'verifying-after-restart': '重启后校验',\n done: '已完成',\n failed: '失败',\n 'rolled-back': '已回滚',\n abandoned: '已中断',\n } as Record<string, string>,\n willRestartDsh: '完成后 DSH 将自动重启,更新本身会在后台继续。',\n willSwapRuntime: 'HQ Edge 会下载完整运行时包并退出,由 EDA 主机替换后重启;系统级安装可能弹出 UAC 提权窗口。',\n closeNote: '关闭此面板不会取消更新。',\n bytes: (done: string, total: string) => `${done} / ${total}`,\n speed: (v: string) => `${v}/s`,\n eta: (s: string) => `剩余约 ${s}`,\n nextDone: '正在完成,请稍候…',\n // Terminal result view\n resultDone: '更新已完成',\n resultFailed: '更新失败',\n resultRolledBack: '更新已回滚',\n resultAbandoned: '更新未完成',\n retry: '重新尝试',\n retryNote: '将发起一次新的更新',\n recentTitle: '最近更新',\n stepsTitle: '各组件进度',\n logHint: (hint: string) => `日志标识:${hint}`,\n },\n // ── Release metadata ──\n release: {\n summaryTitle: '版本说明',\n notesLink: '查看发布说明',\n breakingTitle: '破坏性变更',\n changesTitle: '更新内容',\n advanced: '高级:更新源',\n publishedAt: (when: string) => `发布于 ${when}`,\n },\n // ── Update-centre panel ──\n ui: {\n title: '更新中心',\n gearTooltip: '更新源配置',\n // Header sub-line: \"版本集 v0.6.14 · 14 分钟前\".\n setVersion: (v: string) => `版本集 v${v}`,\n subJoined: (set: string, ago: string) => `${set} · ${ago}`,\n checkedAgo: (ago: string) => `上次检查:${ago}`,\n checkedUnknown: '尚未检查更新',\n checkedNever: '尚未检查',\n agoJust: '刚刚',\n agoMinutes: (n: number) => `${n} 分钟前`,\n agoHours: (n: number) => `${n} 小时前`,\n agoDays: (n: number) => `${n} 天前`,\n // Decision band\n summaryPlugins: (n: number) => `${n} 项插件可更新`,\n summaryRuntime: '1 项运行时可升级',\n // Section disclosures\n expandLogs: '展开版本日志',\n collapseLogs: '折叠版本日志',\n upToDateNote: '全部正常运行',\n upToDateRow: (v: string) => `${v} · 已是最新`,\n ignoredNote: '已忽略,不再提醒',\n releaseTitle: '版本说明',\n // Cards\n singleUpdate: '单独更新',\n singleInstall: '单独安装',\n singleUpdateTip: '仅安装此插件并重启 DSH,不影响其他插件',\n ignorePlugin: '忽略此插件',\n restorePlugin: '恢复此插件',\n changesToggle: '更新内容',\n changesTitle: (v: string) => `v${v} 变更明细`,\n // Footer\n cta: (n: number) => `一键更新选中的 ${n} 项插件`,\n ctaInstalling: '正在更新插件集中…',\n ctaFailed: '检查更新失败,请重试',\n ctaUpToDate: '已是最新版本',\n ctaNoPlugins: '插件均已是最新',\n ctaNoSet: '暂无可安装的更新',\n footerCount: (n: number) => `准备安装 ${n} 项兼容插件`,\n footerBusy: '后台处理中,请勿关闭 DSH',\n footerConfirming: '正在确认安装结果…',\n footerFailed: '无法确认插件版本兼容性',\n footerAllGood: '所有插件与运行时均正常',\n footerRuntimeOnly: '运行时更新需在上方单独确认',\n ignoreBtn: '忽略此版本',\n unignoreBtn: '恢复版本提醒',\n footnote: '更新将自动应用并重启 DSH 视图,不影响 EDA 图纸数据',\n // Settings drawer\n settingsTitle: '更新源配置 (高级)',\n originDefault: '默认源',\n originCustom: '自定义源',\n pluginUrlLabel: '插件 Manifest URL',\n runtimeUrlLabel: '运行时 Manifest URL',\n saveAndRefresh: '保存并刷新',\n resetSource: '恢复默认',\n // Log drawer\n logShow: '查看任务日志',\n logHide: '收起任务日志',\n logNone: '暂无可查看的更新日志',\n logBusy: '更新进行中,完成后方可查看日志',\n logTitle: '安装日志与记录',\n jobIdLabel: 'JOB_ID',\n copyLogId: '复制日志 ID',\n logCopied: '日志 ID 已复制到剪贴板',\n logCopyFailed: '复制失败,请手动记录日志 ID',\n viewLog: '查看日志',\n stepSkipped: '已跳过',\n stepPending: '待处理',\n confirmRuntime: '确认升级并重启',\n // Active job\n jobPluginTitle: (v: string) => `正在升级插件集 (v${v})`,\n jobRuntimeTitle: (v: string) => `正在升级 HQ Edge 运行时 (v${v})`,\n jobSteps: ['下载', '校验', '替换', '重启'],\n activeCloseNote: '关闭此面板不会取消更新,完成后将自动重启。',\n stalledNote: '进度已停止回执,正在向 HQ Edge 确认安装结果…',\n stalledAction: '立即确认',\n // Runtime result alert\n rollbackTitle: '上次 HQ Edge 运行时更新已自动回滚',\n failedTitle: '上次 HQ Edge 运行时更新失败',\n rollbackBody: (oldVer: string, code: string) =>\n `系统已安全恢复至旧版本 (v${oldVer})。代码: ${code}`,\n failedBody: (oldVer: string, code: string) => `未能完成运行时替换 (旧版本 v${oldVer})。代码: ${code}`,\n dismiss: '忽略',\n successInline: '上次运行时更新成功',\n // Runtime engine card\n runtimeEngine: 'HQ Edge 核心运行时',\n currentChip: (v: string) => `v${v} 当前`,\n newVersion: (v: string) => `v${v} 可用`,\n breakingTag: '关键修复',\n sizeLine: (size: string, summary: string) => `体积 ${size}${summary ? ` • ${summary}` : ''}`,\n upgradeCore: '升级核心',\n runtimeRestartNote: '升级运行时需自动重启 KiCad 进程服务',\n },\n}\n\n/** Compact a byte count for display: \"12.3 MB\" / \"480 MB\". */\nexport function formatMb(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes <= 0) return ''\n const mb = bytes / (1024 * 1024)\n if (mb >= 100) return `${Math.round(mb)} MB`\n return `${mb.toFixed(1)} MB`\n}\n\nexport type UpdateCopy = typeof zh\n\nconst en: UpdateCopy = {\n entryLabel: 'Plugin Updates',\n entryTooltip: 'Review available plugin and runtime updates',\n viewTitle: 'Plugin Updates',\n refresh: 'Refresh',\n close: 'Close',\n cancel: 'Cancel',\n loading: 'Reading plugin updates…',\n empty: 'No plugin updates available',\n checkFailed: 'Failed to check for updates — please retry',\n checkFailedTitle: 'Update check failed',\n checkFailedBanner: (message: string) => `Update check failed: ${message}`,\n staleNote: 'Showing the last successful check — it may be stale',\n retry: 'Retry',\n retryNow: 'Try again',\n ignore: 'Ignore this release',\n unignore: 'Show again',\n installError: 'Install failed',\n installSuccess: 'Update installed — restarting DSH…',\n ignoreError: 'Ignore failed',\n confirmInstall: 'Confirm install',\n lastError: 'Last check failed',\n originOverride: 'Custom',\n originConfig: 'Configured',\n originUnset: 'Not configured',\n group: {\n actionable: 'Needs action',\n ignored: 'Ignored',\n current: 'Up to date',\n },\n runtime: {\n title: 'HQ Edge Runtime',\n current: 'Current version',\n latest: 'Latest version',\n set: 'Release set',\n checkedAt: 'Last checked',\n source: 'Update source',\n unknown: 'Version unknown',\n notConfigured: 'No update source configured',\n note: 'Runtime updates are handled by the EDA installer',\n sizeLabel: 'Package size',\n update: 'Install runtime update',\n updateConfirm: (version: string) =>\n `Upgrade the HQ Edge runtime to v${version}? The background service shuts down so the runtime can be swapped, then restarts.`,\n updating: 'Downloading and verifying the runtime bundle…',\n durationNote: 'Duration depends on download speed and package size; the runtime bundle is large, and HQ Edge restarts after the swap.',\n updateAccepted: 'Runtime update accepted — HQ Edge is restarting…',\n size: (bytes: number) => `~ ${formatMb(bytes)}`,\n lastResult: {\n title: 'Last runtime update result',\n succeeded: 'Runtime update succeeded',\n failed: 'Runtime update failed',\n rolledBack: 'Runtime update rolled back (previous version restored)',\n elevation: 'A system-wide install may show a UAC elevation prompt; declining it fails the swap and rolls back to the previous version.',\n },\n sourceHint: 'Where the runtime update manifest is fetched from; saved changes take effect immediately',\n sourceEdit: 'Edit',\n sourceSave: 'Save',\n sourceReset: 'Reset to default',\n saved: 'Update source saved — re-checking for updates…',\n saveFailed: 'Failed to save the update source',\n sourceInputLabel: 'Runtime update manifest URL',\n sourcePlaceholder: 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/edge-headless-release-manifest.json',\n },\n pluginSource: {\n title: 'Plugin update source',\n hint: 'Where the plugin update manifest is fetched from; saved changes take effect immediately',\n edit: 'Edit',\n save: 'Save',\n reset: 'Reset to default',\n saved: 'Update source saved — re-checking for updates…',\n saveFailed: 'Failed to save the update source',\n inputLabel: 'Plugin update manifest URL',\n placeholder:\n 'https://gitee.com/huaqiu-electronics/dsh-pcb-eda/raw/main/plugins-release-manifest.json',\n },\n job: {\n kindPluginSet: 'Plugin-set update',\n kindRuntime: 'HQ Edge runtime update',\n phase: {\n queued: 'Queued',\n resolving: 'Resolving version',\n downloading: 'Downloading',\n verifying: 'Verifying integrity',\n staging: 'Staging files',\n activating: 'Activating plugin set',\n 'awaiting-host': 'Waiting for host to swap runtime',\n restarting: 'Restarting',\n 'verifying-after-restart': 'Verifying after restart',\n done: 'Done',\n failed: 'Failed',\n 'rolled-back': 'Rolled back',\n abandoned: 'Abandoned',\n } as Record<string, string>,\n willRestartDsh: 'DSH restarts automatically once this finishes; the update continues in the background.',\n willSwapRuntime: 'HQ Edge downloads the full runtime bundle and exits; the EDA host swaps and relaunches it. A system-wide install may show a UAC prompt.',\n closeNote: 'Closing this panel does not cancel the update.',\n bytes: (done: string, total: string) => `${done} / ${total}`,\n speed: (v: string) => `${v}/s`,\n eta: (s: string) => `~${s} left`,\n nextDone: 'Finishing — please wait…',\n resultDone: 'Update completed',\n resultFailed: 'Update failed',\n resultRolledBack: 'Update rolled back',\n resultAbandoned: 'Update did not complete',\n retry: 'Retry',\n retryNote: 'Starts a new update',\n recentTitle: 'Recent updates',\n stepsTitle: 'Per-component progress',\n logHint: (hint: string) => `Log id: ${hint}`,\n },\n release: {\n summaryTitle: 'Release notes',\n notesLink: 'View release notes',\n breakingTitle: 'Breaking changes',\n changesTitle: 'What changed',\n advanced: 'Advanced: update sources',\n publishedAt: (when: string) => `Published ${when}`,\n },\n ui: {\n title: 'Update Center',\n gearTooltip: 'Update sources',\n setVersion: (v: string) => `Set v${v}`,\n subJoined: (set: string, ago: string) => `${set} · ${ago}`,\n checkedAgo: (ago: string) => `Last checked: ${ago}`,\n checkedUnknown: 'No check yet',\n checkedNever: 'never',\n agoJust: 'just now',\n agoMinutes: (n: number) => `${n} min ago`,\n agoHours: (n: number) => `${n} h ago`,\n agoDays: (n: number) => `${n} d ago`,\n summaryPlugins: (n: number) => `${n} plugin${n === 1 ? '' : 's'} can be updated`,\n summaryRuntime: '1 runtime can be upgraded',\n expandLogs: 'Expand changelogs',\n collapseLogs: 'Collapse changelogs',\n upToDateNote: 'All running normally',\n upToDateRow: (v: string) => `${v} · up to date`,\n ignoredNote: 'Ignored, no longer offered',\n releaseTitle: 'Release notes',\n singleUpdate: 'Update this one',\n singleInstall: 'Install this one',\n singleUpdateTip: 'Installs only this plugin and restarts DSH — the rest of the set is untouched',\n ignorePlugin: 'Ignore this plugin',\n restorePlugin: 'Offer this plugin again',\n changesToggle: 'What changed',\n changesTitle: (v: string) => `v${v} changes`,\n cta: (n: number) => `Update the ${n} selected plugin${n === 1 ? '' : 's'}`,\n ctaInstalling: 'Updating the plugin set…',\n ctaFailed: 'Check failed — retry first',\n ctaUpToDate: 'Everything up to date',\n ctaNoPlugins: 'All plugins are current',\n ctaNoSet: 'Nothing to install yet',\n footerCount: (n: number) => `Ready to install ${n} compatible plugin${n === 1 ? '' : 's'}`,\n footerBusy: 'Running in the background — keep DSH open',\n footerConfirming: 'Confirming the install result…',\n footerFailed: 'Cannot confirm plugin compatibility',\n footerAllGood: 'All plugins and the runtime are current',\n footerRuntimeOnly: 'Confirm the runtime upgrade on its card',\n ignoreBtn: 'Ignore this release',\n unignoreBtn: 'Offer it again',\n footnote: 'Updates apply automatically and restart the DSH view — your EDA schematic data is untouched',\n settingsTitle: 'Update source (advanced)',\n originDefault: 'Default source',\n originCustom: 'Custom source',\n pluginUrlLabel: 'Plugin manifest URL',\n runtimeUrlLabel: 'Runtime manifest URL',\n saveAndRefresh: 'Save & refresh',\n resetSource: 'Reset to default',\n logShow: 'Show the job log',\n logHide: 'Hide the job log',\n logNone: 'No update log to show',\n logBusy: 'Update in progress — the log is available when it finishes',\n logTitle: 'Install log & history',\n jobIdLabel: 'JOB_ID',\n copyLogId: 'Copy log id',\n logCopied: 'Log id copied to the clipboard',\n logCopyFailed: 'Could not copy — please note the log id',\n viewLog: 'View log',\n stepSkipped: 'skipped',\n stepPending: 'pending',\n confirmRuntime: 'Confirm & restart',\n jobPluginTitle: (v: string) => `Updating plugin set (v${v})`,\n jobRuntimeTitle: (v: string) => `Upgrading HQ Edge runtime (v${v})`,\n jobSteps: ['Download', 'Verify', 'Swap', 'Restart'],\n activeCloseNote: 'Closing this panel does not cancel the update; DSH restarts when it finishes.',\n stalledNote: 'Progress has stopped reporting — confirming the result with HQ Edge…',\n stalledAction: 'Check now',\n rollbackTitle: 'Last HQ Edge runtime update rolled back automatically',\n failedTitle: 'Last HQ Edge runtime update failed',\n rollbackBody: (oldVer: string, code: string) =>\n `Safely restored to the previous version (v${oldVer}). Code: ${code}`,\n failedBody: (oldVer: string, code: string) => `Runtime swap did not complete (old v${oldVer}). Code: ${code}`,\n dismiss: 'Dismiss',\n successInline: 'Last runtime update succeeded',\n runtimeEngine: 'HQ Core Runtime',\n currentChip: (v: string) => `v${v} current`,\n newVersion: (v: string) => `v${v} available`,\n breakingTag: 'Critical fix',\n sizeLine: (size: string, summary: string) => `Size ${size}${summary ? ` • ${summary}` : ''}`,\n upgradeCore: 'Upgrade core',\n runtimeRestartNote: 'Upgrading the runtime restarts the KiCad process service',\n },\n}\n\nexport const COPY = { zh, en } as const\n","/**\n * Official DSH icon artwork, inlined as SVG strings.\n *\n * The paths are copied verbatim from `@deepseek-ai/dsh-client-ui-primitives`\n * (deepseek-harness `packages/client/ui-primitives/src/icons/index.tsx`) so the\n * Huaqiu plugin-update view draws the same glyphs as the shell's own plugin\n * manager. Only `currentColor` is used, so every glyph inherits the label ink\n * of whatever surface it sits on — the same contract the primitives rely on.\n *\n * The plugin renders plain DOM (never a React tree), so the artwork is exposed\n * as markup strings rather than components.\n *\n * @module\n */\n\n/** Stroke width of the primitives' \"Regular\" weight. */\nconst REGULAR = 1\n\n/** Stroke width of the primitives' \"Medium\" weight. */\nconst MEDIUM = 1.3\n\n/** Wrap paths in a 16×16 stroked glyph. */\nfunction glyph(size: number, strokeWidth: number, paths: readonly string[], viewBox = '0 0 16 16'): string {\n const body = paths.map((d) => `<path d=\"${d}\" stroke=\"currentColor\" />`).join('')\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"${viewBox}\" fill=\"none\" aria-hidden=\"true\" stroke-width=\"${strokeWidth}\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${body}</svg>`\n}\n\n/** Wrap fill-only paths (the check glyph) in their own viewBox. */\nfunction fillGlyph(size: number, paths: readonly string[], viewBox: string): string {\n const body = paths.map((d) => `<path d=\"${d}\" fill=\"currentColor\" />`).join('')\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"${viewBox}\" fill=\"none\" aria-hidden=\"true\">${body}</svg>`\n}\n\n/**\n * The plugin pinwheel — the glyph the shell's own Plugins sidebar entry uses\n * (`IconPluginPinwheelOutlineRegular`). Shared by the sidebar row and the\n * package cards so both read as the same feature.\n */\nexport const PLUGIN_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n 'M7.84457 5.06199C11.6605 4.93876 14.7962 6.14848 14.8484 7.76397C14.8875 8.97461 13.1838 10.0696 10.7215 10.5942',\n 'M5.12742 8.07731C5.00419 4.26138 6.21391 1.12568 7.8294 1.07351C9.04004 1.03441 10.135 2.73808 10.6596 5.20037',\n 'M8.02457 10.6802C4.20865 10.8034 1.07294 9.5937 1.02077 7.97821C0.981678 6.76758 2.68535 5.67262 5.14763 5.14798',\n 'M10.7476 7.89535C10.8708 11.7113 9.66109 14.847 8.0456 14.8991C6.83496 14.9382 5.74 13.2346 5.21536 10.7723',\n ])\n\n/** The toolbar's refresh action (`IconRefreshOutlineRegular`). */\nexport const REFRESH_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n 'M14.5001 8C14.5 9.28552 14.1188 10.5422 13.4045 11.611C12.6903 12.6799 11.6752 13.5129 10.4875 14.0049C9.29982 14.4968 7.99295 14.6255 6.73212 14.3747C5.4713 14.124 4.31314 13.505 3.4041 12.596C2.49514 11.687 1.87614 10.5288 1.62537 9.26798C1.37459 8.00716 1.50331 6.70028 1.99525 5.51261C2.48719 4.32494 3.32025 3.30981 4.3891 2.59557C5.45795 1.88134 6.71458 1.50008 8.0001 1.5C9.9001 1.5 11.7001 2.3 13.0001 3.6L14.5001 5.1',\n 'M14.4999 1.5V5.1H10.8999',\n ])\n\n/**\n * The toolbar's log toggle: a page with ruled lines — \"the update log\".\n *\n * Not a shell primitive (the shell has no equivalent glyph), so it is drawn to\n * the same 16×16 / 1px-stroke contract as the rest of this module.\n */\nexport const LOG_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n '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',\n 'M9.5 1.5V4.5H12.5',\n 'M6 8.25H10',\n 'M6 10.75H10',\n ])\n\n/** The panel's close action (`IconCloseOutlineMedium`). */\nexport const CLOSE_ICON = (size = 16): string =>\n glyph(size, MEDIUM, ['M2.5 2.5L13.5 13.5', 'M13.5 2.5L2.5 13.5'])\n\n/** The notice glyph (`IconWarningOutlineRegular`). */\nexport const WARNING_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n 'M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5Z',\n 'M8 4.29199V9.79199',\n 'M8 10.708V11.708',\n ])\n\n/** The settled-state glyph (`IconCheckCircleFillRegular`). */\nexport const CHECK_CIRCLE_ICON = (size = 16): string =>\n fillGlyph(size, [\n 'M28.1936 14.6936L19.8066 23.0806C19.2373 23.65 18.7159 24.1742 18.24 24.5571C17.7389 24.9602 17.1365 25.3359 16.3657 25.458C15.9581 25.5225 15.5428 25.5225 15.1353 25.458C14.3645 25.3359 13.7621 24.9602 13.261 24.5571C12.7851 24.1742 12.2637 23.65 11.6943 23.0806L7.80737 19.1936L10.1936 16.8074L14.0806 20.6943C14.7033 21.317 15.0763 21.6873 15.377 21.9292C15.6523 22.1507 15.7109 22.1325 15.6626 22.1248C15.7208 22.1339 15.7802 22.1339 15.8384 22.1248C15.7901 22.1325 15.8486 22.1507 16.124 21.9292C16.4247 21.6873 16.7977 21.317 17.4204 20.6943L25.8074 12.3074L28.1936 14.6936Z',\n 'M32.8496 18.0005C32.8496 9.79906 26.2019 3.15137 18.0005 3.15137C9.79906 3.15137 3.15137 9.79906 3.15137 18.0005C3.15137 26.2019 9.79906 32.8496 18.0005 32.8496C26.2019 32.8496 32.8496 26.2019 32.8496 18.0005ZM35.7764 18.0005C35.7764 27.8173 27.8173 35.7764 18.0005 35.7764C8.18363 35.7764 0.224609 27.8173 0.224609 18.0005C0.224609 8.18363 8.18363 0.224609 18.0005 0.224609C27.8173 0.224609 35.7764 8.18363 35.7764 18.0005Z',\n ], '0 0 36 36')\n\n/** The install action (`IconDownloadOutlineRegular`). */\nexport const DOWNLOAD_ICON = (size = 16): string =>\n glyph(size, REGULAR, [\n 'M8 1.95317V10.0469',\n 'M4.25 6.29688L8 10.0469L11.75 6.29688',\n 'M1.5 10.0469V13.158C1.5 13.3937 1.60536 13.6198 1.79289 13.7865C1.98043 13.9532 2.23478 14.0469 2.5 14.0469H13.5C13.7652 14.0469 14.0196 13.9532 14.2071 13.7865C14.3946 13.6198 14.5 13.3937 14.5 13.158V10.0469',\n ])\n\n/** The head's info affordance (`IconInfoOutlineRegular`, fill-only artwork). */\nexport const INFO_ICON = (size = 11): string =>\n fillGlyph(size, [\n 'M12.5757 7.00012C12.5757 3.92085 10.0794 1.42463 7.00012 1.42456C3.9208 1.42456 1.42456 3.9208 1.42456 7.00012C1.42463 10.0794 3.92085 12.5757 7.00012 12.5757C10.0793 12.5756 12.5756 10.0793 12.5757 7.00012ZM13.8002 7.00012C13.8001 10.7559 10.7559 13.8001 7.00012 13.8002C3.2443 13.8002 0.199291 10.7559 0.199219 7.00012C0.199219 3.24426 3.24426 0.199219 7.00012 0.199219C10.7559 0.199291 13.8002 3.2443 13.8002 7.00012Z',\n 'M7.6127 3.18921V4.55986H6.38735V3.18921H7.6127Z',\n 'M7.6127 5.68921V10.8109H6.38735V5.68921H7.6127Z',\n ], '0 0 14 14')\n\n/** A folded section's chevron (`IconChevronDownOutlineRegular`). */\nexport const CHEVRON_DOWN_ICON = (size = 14): string =>\n glyph(size, REGULAR, ['M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6'])\n\n/** A right-pointing chevron — a collapsed row, rotated by CSS when it opens. */\nexport const CHEVRON_RIGHT_ICON = (size = 14): string =>\n glyph(size, REGULAR, ['M6 4L9.29289 7.29289C9.68342 7.68342 9.68342 8.31658 9.29289 8.70711L6 12'])\n\n/**\n * The per-card mute action: an eye with a slash (忽略此插件).\n *\n * Drawn here rather than borrowed: the shell's icon set has no\n * \"stop offering this\" glyph, and a bare 忽略 text button competed with the\n * card's primary action for the same 40 px.\n */\nexport const EYE_OFF_ICON = (size = 14): string =>\n glyph(size, REGULAR, [\n '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',\n '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',\n 'M2.5 2.5L13.5 13.5',\n ])\n\n/**\n * Copy-to-clipboard (the log drawer's 复制日志 ID): two offset rounded frames.\n */\nexport const COPY_ICON = (size = 14): string =>\n glyph(size, REGULAR, [\n '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',\n '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',\n ])\n\n/** The runtime card's badge: a bolt (the HQ Edge core distribution). */\nexport const BOLT_ICON = (size = 14): string =>\n glyph(size, REGULAR, ['M9.33333 2L4.66667 8H8L6.66667 14L11.33333 8H8L9.33333 2Z'])\n","/**\n * Plugin-update review view (browser half) — the \"更新中心\" panel.\n *\n * Plain-DOM (no React tree) so it can never disturb the DSH shell's\n * reconciliation — the same rule as the sidebar entry rows. The layout follows\n * `docs/prototype/plugin_update_ui_ux_prototype.html`; the reasoning behind the\n * order is in `docs/report/plugin-update-ui-ux-baseline.md`.\n *\n * Information architecture, top to bottom, and why:\n *\n * 1. **header** — brand, title, `版本集 vX · N 分钟前`, and the four icon\n * actions (refresh · sources · job log · close). The set version and the\n * relative check time live here exactly once; the body never repeats them.\n * 2. **settings strip** (gear) — the two manifest URLs, editable in place.\n * 3. **check-failed banner** — an error, never the silent empty state.\n * 4. **active job card** — pulsing title + percent + bar + telemetry +\n * 4-step pipeline, resumed on open and polled to its end.\n * 5. **failed-result banner** — error code + Retry + 查看日志. The full log\n * lives in the header's drawer; the *outcome* must not hide behind it.\n * 6. **decision band** — one line: how many plugins, and whether the runtime\n * is separately upgradable. This is the \"what do I have to decide\" row.\n * 7. **actionable cards** — 单独更新 / 忽略 promoted onto each card; the\n * collapsible drawer keeps only the changelog.\n * 8. **runtime card** — amber, because a swap is not a plugin install.\n * 9. **已忽略 / 已是最新 / 版本说明** — collapsed by default: nothing that\n * needs no action may sit above something that does.\n * 10. **footer** — the primary CTA plus its honest meta line, both reactive to\n * every state (idle / installing / failed check / up to date).\n *\n * The job log is a drawer inside the panel (`.hqpu-log`), not an inline block:\n * a full set lists one row per component, which as inline content pushed every\n * actionable card below the fold.\n *\n * @module\n */\nimport {\n checkUpdates,\n ignoreOffer,\n ignorePlugin,\n installSet,\n requestRuntimeUpdate,\n runtimeLatestVersion,\n runtimeUpdateOffered,\n setPluginUpdateSource,\n setRuntimeUpdateSource,\n unignoreOffer,\n unignorePlugin,\n TERMINAL_PHASES,\n type PluginUpdateItem,\n type PluginUpdateReport,\n type ReleaseInfo,\n type RuntimeUpdateSource,\n type UpdateJob,\n type UpdateStep,\n} from './api.js'\nimport { formatMb, type UpdateCopy } from './copy.js'\nimport { actionableUpdateCount } from './version.js'\nimport {\n BOLT_ICON,\n CHECK_CIRCLE_ICON,\n CHEVRON_RIGHT_ICON,\n CLOSE_ICON,\n COPY_ICON,\n EYE_OFF_ICON,\n LOG_ICON,\n REFRESH_ICON,\n WARNING_ICON,\n} from './icons.js'\n\nexport interface UpdateStoreLike {\n report(): PluginUpdateReport | null\n installing(): boolean\n /** Mark the installing footer state (set by the panel before the restart). */\n setInstalling(flag: boolean): void\n /** The durable job the server currently reports as active (resumed). */\n activeJob(): UpdateJob | null\n /** Bounded recent jobs (newest first), for the log drawer. */\n recentJobs(): UpdateJob[]\n refresh(): Promise<PluginUpdateReport | null>\n /** Force a fresh update check (used by the failed-check Retry action). */\n check(): Promise<PluginUpdateReport | null>\n /** Re-read `/status` (resume an active job immediately after submitting). */\n fetchStatus(): Promise<void>\n /**\n * Follow a job by id after an accept response. Optional so a minimal store\n * (tests, older hosts) still satisfies the contract; the real store uses it\n * to keep asking about THIS job across a DSH restart.\n */\n watchJob?(id: string | null): void\n /** Server-reported \"the active job stopped advancing\". */\n stalled?(): boolean\n /** Milliseconds since the active job last advanced (server clock). */\n phaseAgeMs?(): number\n subscribe(listener: () => void): () => void\n}\n\n/** How long the outcome toast stays up, in milliseconds. */\nconst TOAST_HOLD_MS = 4000\n\n/** Which manifest an editor belongs to — drives data attributes + CSS hook. */\nexport type SourceKind = 'runtime' | 'plugin'\n\n// ── Small DOM helpers ──────────────────────────────────────────────────────\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className = '',\n text?: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag)\n if (className.length > 0) node.className = className\n if (text !== undefined) node.textContent = text\n return node\n}\n\n/** A button carrying inline SVG artwork (the icon modules emit markup). */\nfunction iconButton(\n className: string,\n markup: string,\n label: string,\n onClick: () => void,\n): HTMLButtonElement {\n const button = el('button', className)\n button.type = 'button'\n button.setAttribute('aria-label', label)\n button.title = label\n button.innerHTML = markup\n button.addEventListener('click', onClick)\n return button\n}\n\n/** A text-link button (重新尝试, 展开版本日志, 忽略此版本 …). */\nfunction linkButton(className: string, label: string, onClick: () => void): HTMLButtonElement {\n const button = el('button', className, label)\n button.type = 'button'\n button.addEventListener('click', onClick)\n return button\n}\n\n/**\n * Compact a package name to what a person calls it — the shell plugin\n * manager's own rule (`presentation.ts` `shortName`), so both UIs read the\n * same plugin by the same name.\n */\nexport function shortName(name: string): string {\n const unscoped = name.startsWith('@') ? name.slice(name.indexOf('/') + 1) : name\n return unscoped.replace(/^dsh-(?:host-|client-)?/, '')\n}\n\n/** Two-letter avatar initials for a card (CJK-safe). */\nfunction avatarInitials(item: PluginUpdateItem): string {\n const src =\n typeof item.displayName === 'string' && item.displayName.trim().length > 0\n ? item.displayName.trim()\n : shortName(item.name)\n if (/[\\u2E80-\\u9FFF]/.test(src)) return src.slice(0, 2)\n const words = src.split(/[\\s_\\-./@]+/).filter(Boolean)\n if (words.length >= 2) return (words[0]![0]! + words[1]![0]!).toUpperCase()\n return src.replace(/^@/, '').slice(0, 2).toUpperCase()\n}\n\n/** The name a card shows: the manifest's displayName, else the short name. */\nfunction displayNameOf(item: PluginUpdateItem): string {\n return typeof item.displayName === 'string' && item.displayName.trim().length > 0\n ? item.displayName.trim()\n : shortName(item.name)\n}\n\nfunction isActionable(item: PluginUpdateItem): boolean {\n return item.state === 'update' || item.state === 'install'\n}\n\n// ── Time / size formatting ────────────────────────────────────────────────\n\n/** Format a byte count for the progress line, or null when unknown. */\nfunction formatBytes(n: number | undefined): string | null {\n if (n === undefined || !Number.isFinite(n) || n < 0) return null\n if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(1)} GB`\n if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`\n if (n >= 1024) return `${(n / 1024).toFixed(0)} KB`\n return `${n} B`\n}\n\n/** \"N 分钟前\" — relative time from an ISO timestamp. */\nfunction relativeAgo(iso: string | null | undefined, copy: UpdateCopy): string | null {\n if (typeof iso !== 'string' || iso.length === 0) return null\n const then = new Date(iso).getTime()\n if (!Number.isFinite(then)) return null\n const diffMin = Math.round((Date.now() - then) / 60_000)\n if (diffMin < 1) return copy.ui.agoJust\n if (diffMin < 60) return copy.ui.agoMinutes(diffMin)\n const hours = Math.round(diffMin / 60)\n if (hours < 24) return copy.ui.agoHours(hours)\n return copy.ui.agoDays(Math.round(hours / 24))\n}\n\n/** Short absolute time for log rows: \"10/29 14:34\". */\nfunction shortWhen(iso: string | null | undefined): string {\n if (typeof iso !== 'string' || iso.length === 0) return ''\n const at = new Date(iso)\n if (Number.isNaN(at.getTime())) return ''\n return at.toLocaleString(undefined, {\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n })\n}\n\nfunction jobKindLabel(job: UpdateJob, copy: UpdateCopy): string {\n return job.kind === 'runtime' ? copy.job.kindRuntime : copy.job.kindPluginSet\n}\n\n/** The phase in human words, falling back to the raw code. */\nfunction phaseLabel(job: UpdateJob, copy: UpdateCopy): string {\n return copy.job.phase[job.phase] ?? job.phase\n}\n\n/** Map a job phase onto the 4-step indicator index (下载/校验/替换/重启). */\nfunction stepIndex(phase: UpdateJob['phase']): number {\n switch (phase) {\n case 'downloading':\n case 'resolving':\n return 0\n case 'verifying':\n return 1\n case 'staging':\n case 'activating':\n case 'awaiting-host':\n return 2\n case 'restarting':\n case 'verifying-after-restart':\n return 3\n case 'queued':\n default:\n return -1\n }\n}\n\n// ── Section pieces ────────────────────────────────────────────────────────\n\n/**\n * A collapsible section: chevron + title + count, an optional right-hand note,\n * and a body that only exists in the DOM while open.\n *\n * Native `<details>` was the alternative, but it cannot express the\n * \"collapsed by default, opened by state\" rule the 已是最新 group needs without\n * a JS patch anyway, and its summary cannot host the trailing note in one row.\n */\nfunction disclosure(opts: {\n id: string\n title: string\n count?: string\n note?: string\n open: boolean\n onToggle: () => void\n build: () => HTMLElement[]\n}): HTMLElement {\n const section = el('section', 'hqpu-disc')\n // The title doubles as the group hook (`[data-group=\"已是最新\"]`), matching\n // the actionable group's convention so callers need one selector style.\n section.dataset.group = opts.title\n const head = el('button', 'hqpu-disc-head')\n head.type = 'button'\n head.setAttribute('aria-expanded', opts.open ? 'true' : 'false')\n head.setAttribute('aria-controls', opts.id)\n\n const left = el('span', 'hqpu-disc-left')\n const chevron = el('span', 'hqpu-disc-chevron')\n chevron.innerHTML = CHEVRON_RIGHT_ICON(12)\n left.append(chevron, el('span', 'hqpu-disc-title', opts.title))\n if (opts.count !== undefined) left.append(el('span', 'hqpu-disc-count', opts.count))\n head.append(left)\n if (opts.note !== undefined && opts.note.length > 0) {\n head.append(el('span', 'hqpu-disc-note', opts.note))\n }\n head.addEventListener('click', opts.onToggle)\n section.append(head)\n\n if (opts.open) {\n const body = el('div', 'hqpu-disc-body')\n body.id = opts.id\n for (const node of opts.build()) body.append(node)\n section.append(body)\n }\n return section\n}\n\n/** One muted row in a collapsed group (current or ignored plugin). */\nfunction pluginRow(\n item: PluginUpdateItem,\n copy: UpdateCopy,\n onToggleIgnore: () => void,\n): HTMLLIElement {\n const row = el('li', 'hqpu-row')\n row.dataset.pluginName = item.name\n row.dataset.state = item.state\n row.append(el('span', 'hqpu-row-name', displayNameOf(item)))\n const end = el('span', 'hqpu-row-end')\n const version = item.currentVersion ?? item.availableVersion\n end.append(el('span', 'hqpu-row-meta', copy.ui.upToDateRow(version)))\n if (item.state === 'ignored') {\n end.append(\n linkButton('hqpu-link hqpu-link--tiny', copy.unignore, onToggleIgnore),\n )\n }\n row.append(end)\n return row\n}\n\n/**\n * One actionable plugin card.\n *\n * Actions live on the card, not inside the changelog drawer: the drawer is for\n * content (what changed), and hiding 单独更新 / 忽略 behind a disclosure made\n * the set-level footer CTA the only discoverable action.\n */\nfunction pluginCard(\n item: PluginUpdateItem,\n copy: UpdateCopy,\n opts: { changesOpen: boolean; onToggleChanges: () => void; onInstall: () => void; onIgnore: () => void; busy: boolean },\n): HTMLLIElement {\n const card = el('li', 'hqpu-card')\n card.dataset.pluginName = item.name\n card.dataset.state = item.state\n\n const head = el('div', 'hqpu-card-head')\n const id = el('div', 'hqpu-card-id')\n const avatar = el('span', 'hqpu-avatar', avatarInitials(item))\n avatar.setAttribute('aria-hidden', 'true')\n const main = el('div', 'hqpu-card-main')\n main.append(el('span', 'hqpu-card-title', displayNameOf(item)))\n main.append(el('span', 'hqpu-card-sub', shortName(item.name)))\n id.append(avatar, main)\n\n const end = el('div', 'hqpu-card-end')\n const install = el(\n 'button',\n 'hqpu-card-action hqpu-card-action--install',\n item.state === 'install' ? copy.ui.singleInstall : copy.ui.singleUpdate,\n )\n install.type = 'button'\n install.title = copy.ui.singleUpdateTip\n install.disabled = opts.busy\n install.addEventListener('click', opts.onInstall)\n const ignore = iconButton(\n 'hqpu-icon-btn hqpu-icon-btn--sm',\n EYE_OFF_ICON(14),\n copy.ui.ignorePlugin,\n opts.onIgnore,\n )\n ignore.disabled = opts.busy\n end.append(install, ignore)\n head.append(id, end)\n card.append(head)\n\n // Version row + the changelog toggle, on one line.\n const foot = el('div', 'hqpu-card-foot')\n const ver = el('div', 'hqpu-version-row')\n if (item.currentVersion !== null && item.currentVersion !== undefined) {\n ver.append(el('span', 'hqpu-ver-old', item.currentVersion))\n ver.append(el('span', 'hqpu-ver-arrow', '→'))\n }\n ver.append(el('span', 'hqpu-ver-new', item.availableVersion))\n const size = formatMb(item.sizeBytes ?? 0)\n if (size.length > 0) ver.append(el('span', 'hqpu-ver-size', `(${size})`))\n foot.append(ver)\n\n const changes = Array.isArray(item.changes)\n ? item.changes.filter((c) => typeof c === 'string' && c.length > 0)\n : []\n if (changes.length > 0) {\n const toggle = el('button', 'hqpu-toggle')\n toggle.type = 'button'\n toggle.dataset.changesToggle = ''\n toggle.dataset.open = opts.changesOpen ? 'true' : 'false'\n toggle.setAttribute('aria-expanded', opts.changesOpen ? 'true' : 'false')\n toggle.append(el('span', '', copy.ui.changesToggle))\n const chevron = el('span', 'hqpu-toggle-chevron')\n chevron.innerHTML = CHEVRON_RIGHT_ICON(12)\n toggle.append(chevron)\n toggle.addEventListener('click', opts.onToggleChanges)\n foot.append(toggle)\n }\n card.append(foot)\n\n if (changes.length > 0 && opts.changesOpen) {\n const drawer = el('div', 'hqpu-changes')\n drawer.append(el('p', 'hqpu-changes-title', copy.ui.changesTitle(item.availableVersion)))\n const ul = el('ul')\n for (const change of changes) ul.append(el('li', '', change))\n drawer.append(ul)\n card.append(drawer)\n }\n return card\n}\n\n/** The \"what do I actually have to decide\" band, above every list. */\nfunction decisionBand(report: PluginUpdateReport, copy: UpdateCopy): HTMLElement | null {\n const plugins = actionableUpdateCount(report)\n const runtime = runtimeUpdateOffered(report.runtime)\n if (plugins === 0 && !runtime) return null\n const band = el('div', 'hqpu-decision')\n band.dataset.role = 'decision-band'\n if (plugins > 0) band.append(el('span', 'hqpu-decision-main', copy.ui.summaryPlugins(plugins)))\n if (plugins > 0 && runtime) {\n const dot = el('span', 'hqpu-decision-dot')\n dot.setAttribute('aria-hidden', 'true')\n band.append(dot)\n }\n if (runtime) band.append(el('span', 'hqpu-decision-sub', copy.ui.summaryRuntime))\n return band\n}\n\n/**\n * Check-status banner. A FAILED check is a red alert with its reason and a\n * retry — never the silent \"no updates\" empty state, which reads as success.\n */\nfunction checkBanner(\n report: PluginUpdateReport,\n copy: UpdateCopy,\n onRetry: () => void,\n): HTMLElement | null {\n const failed = typeof report.lastError === 'string' && report.lastError.length > 0\n if (!failed) return null\n const section = el('section', 'hqpu-group')\n section.dataset.group = 'check-status'\n const card = el('div', 'hqpu-error')\n card.dataset.tone = 'danger'\n const head = el('div', 'hqpu-error-head')\n const icon = el('span', 'hqpu-error-icon')\n icon.innerHTML = WARNING_ICON(14)\n const text = el('div', 'hqpu-error-text')\n text.append(el('p', 'hqpu-error-title', copy.checkFailedTitle))\n text.append(el('p', 'hqpu-error-body', report.lastError as string))\n head.append(icon, text)\n card.append(head)\n\n const foot = el('div', 'hqpu-error-foot')\n if (report.items.length > 0) foot.append(el('span', '', copy.staleNote))\n foot.append(linkButton('hqpu-link', copy.retryNow, onRetry))\n card.append(foot)\n section.append(card)\n return section\n}\n\n/**\n * The active job card: title (with the target version) + percent, a\n * determinate bar when the server publishes one, phase + telemetry, the honest\n * \"closing does not cancel\" note, and the 4-step pipeline.\n *\n * `stalled` marks a job the server has stopped advancing. That is the normal\n * shape of \"the set being installed contains this very plugin\": activating it\n * restarts DSH, so the process that owned the job is gone and the last thing\n * the page saw was 100 % on the final step. Instead of animating a bar that\n * will never move, the card says it is confirming the outcome and offers a\n * retry — and the next `/activity` read settles it from the installed set.\n */\nfunction activeJobView(\n job: UpdateJob,\n copy: UpdateCopy,\n opts: { stalled?: boolean; onRecheck?: () => void } = {},\n): HTMLElement {\n const section = el('section', 'hqpu-group')\n section.dataset.group = 'active-job'\n const card = el('div', 'hqpu-active')\n if (opts.stalled === true) card.dataset.stalled = '1'\n\n const top = el('div', 'hqpu-active-top')\n const titleWrap = el('div', 'hqpu-active-title')\n const pulse = el('span', 'hqpu-pulse-dot')\n pulse.append(el('span', 'hqpu-pulse-ring'))\n const title =\n job.kind === 'runtime'\n ? copy.ui.jobRuntimeTitle(job.targetVersion)\n : copy.ui.jobPluginTitle(job.targetVersion)\n titleWrap.append(pulse, el('span', 'hqpu-active-label', title))\n top.append(titleWrap)\n const pct = job.progress.percent\n if (pct !== null) top.append(el('span', 'hqpu-active-percent', `${Math.round(pct)}%`))\n card.append(top)\n\n if (pct !== null) {\n const bar = el('div', 'hqpu-progress')\n bar.setAttribute('role', 'progressbar')\n bar.setAttribute('aria-valuemin', '0')\n bar.setAttribute('aria-valuemax', '100')\n bar.setAttribute('aria-valuenow', String(Math.round(pct)))\n const fill = el('div', 'hqpu-progress-fill')\n fill.style.width = `${Math.min(100, Math.max(0, pct))}%`\n bar.append(fill)\n card.append(bar)\n }\n\n const meta = el('div', 'hqpu-active-meta')\n const phaseText =\n typeof job.message === 'string' && job.message.length > 0 ? job.message : phaseLabel(job, copy)\n meta.append(el('span', '', phaseText))\n const speed = formatBytes(job.progress.bytesPerSecond)\n const done = formatBytes(job.progress.bytesDone)\n const total = formatBytes(job.progress.bytesTotal)\n const telemetry =\n speed !== null && done !== null && total !== null\n ? `${speed}/s (${done} / ${total})`\n : speed !== null\n ? `${speed}/s`\n : done !== null && total !== null\n ? copy.job.bytes(done, total)\n : ''\n if (telemetry.length > 0) meta.append(el('span', 'hqpu-num', telemetry))\n card.append(meta)\n\n // previous → target on its own line (mono), then what happens next. A\n // plugin set keeps the \"closing does not cancel\" note; a runtime swap has a\n // stronger one of its own (HQ Edge exits, the host swaps it out).\n card.append(\n el(\n 'div',\n 'hqpu-active-versions',\n job.previousVersion !== null && job.previousVersion.length > 0\n ? `${job.previousVersion} → ${job.targetVersion}`\n : job.targetVersion,\n ),\n )\n card.append(\n el(\n 'p',\n 'hqpu-active-note',\n job.kind === 'runtime' ? copy.job.willSwapRuntime : copy.ui.activeCloseNote,\n ),\n )\n\n if (opts.stalled === true) {\n const stalled = el('div', 'hqpu-active-stalled')\n stalled.append(el('span', '', copy.ui.stalledNote))\n const recheck = linkButton('hqpu-link', copy.ui.stalledAction, () => opts.onRecheck?.())\n recheck.setAttribute('data-stalled-recheck', '')\n stalled.append(recheck)\n card.append(stalled)\n }\n\n const current = stepIndex(job.phase)\n const stepsRow = el('div', 'hqpu-job-steps4')\n copy.ui.jobSteps.forEach((label: string, index: number) => {\n const cls =\n current === -1\n ? 'hqpu-job-step--todo'\n : index < current\n ? 'hqpu-job-step--done'\n : index === current\n ? 'hqpu-job-step--active'\n : 'hqpu-job-step--todo'\n // Numbered like the prototype's pipeline (\"1. 下载\"): the count is the\n // only cue for how much is left when the active step blinks.\n stepsRow.append(el('div', cls, `${index + 1}. ${label}`))\n })\n card.append(stepsRow)\n\n section.append(card)\n return section\n}\n\n/** Phase → the word the log rows use for a component. */\nfunction stepWord(state: UpdateStep['state'], copy: UpdateCopy): string {\n switch (state) {\n case 'installed':\n return copy.job.phase.done ?? ''\n case 'skipped':\n return copy.ui.stepSkipped\n case 'failed':\n return copy.job.phase.failed ?? ''\n case 'running':\n return copy.job.phase.downloading ?? ''\n default:\n return copy.ui.stepPending\n }\n}\n\n/** Dot tone for one step row. */\nfunction stepDotState(state: UpdateStep['state']): string {\n switch (state) {\n case 'installed':\n return 'done'\n case 'failed':\n return 'error'\n case 'running':\n return 'warning'\n default:\n return 'idle'\n }\n}\n\n/**\n * The terminal-result banner for a job that did NOT succeed.\n *\n * A successful install needs no banner — the list and the footer already say\n * \"everything is current\". A failure does: the error code and the retry must be\n * on screen, not one drawer-click away.\n */\nfunction jobResultBanner(\n job: UpdateJob,\n copy: UpdateCopy,\n onRetry: () => void,\n onOpenLog: () => void,\n): HTMLElement {\n const section = el('section', 'hqpu-group')\n section.dataset.group = 'job-result'\n const tone = job.phase === 'failed' ? 'danger' : 'warning'\n const card = el('div', 'hqpu-error')\n card.dataset.tone = tone\n\n const head = el('div', 'hqpu-error-head')\n const icon = el('span', 'hqpu-error-icon')\n icon.innerHTML = WARNING_ICON(14)\n const text = el('div', 'hqpu-error-text')\n const title =\n job.phase === 'rolled-back' ? copy.job.resultRolledBack : copy.job.resultFailed\n text.append(el('p', 'hqpu-error-title', title))\n const res = job.result\n const from = job.previousVersion ?? res?.installedVersion ?? '—'\n const to = res?.installedVersion ?? job.targetVersion\n const when = res !== undefined && res.finishedAt.length > 0 ? ` · ${shortWhen(res.finishedAt)}` : ''\n text.append(el('p', 'hqpu-error-body', `${from} → ${to}${when}`))\n if (res?.message !== undefined && res.message.length > 0) {\n text.append(el('p', 'hqpu-error-body', res.message))\n }\n if (res?.errorCode !== undefined && res.errorCode.length > 0) {\n text.append(el('code', 'hqpu-error-code', res.errorCode))\n }\n head.append(icon, text)\n card.append(head)\n\n const foot = el('div', 'hqpu-error-foot')\n foot.append(el('span', '', jobKindLabel(job, copy)))\n const actions = el('span', 'hqpu-error-actions')\n actions.append(linkButton('hqpu-link', copy.ui.viewLog, onOpenLog))\n actions.append(linkButton('hqpu-link', copy.job.retry, onRetry))\n foot.append(actions)\n card.append(foot)\n section.append(card)\n return section\n}\n\n/**\n * The host's own report of the last runtime swap. Rolled-back / failed renders\n * as an AMBER alert card; succeeded renders as a quiet green line. The plugin\n * has no log-open affordance for this, so none is invented.\n */\nfunction runtimeResultAlert(\n report: PluginUpdateReport,\n copy: UpdateCopy,\n dismissed: boolean,\n onDismiss: () => void,\n): HTMLElement | null {\n const lastResult = report.runtime.lastResult\n if (lastResult === null || lastResult === undefined) return null\n\n if (lastResult.state === 'succeeded') {\n const line = el('p', 'hqpu-alert-green')\n line.dataset.role = 'runtime-success'\n line.textContent = copy.ui.successInline\n return line\n }\n if (dismissed) return null\n\n const section = el('section', 'hqpu-group')\n section.dataset.group = 'runtime-alert'\n const card = el('div', 'hqpu-alert hqpu-alert--amber')\n const icon = el('span', 'hqpu-alert-icon')\n icon.innerHTML = WARNING_ICON(14)\n card.append(icon)\n\n const body = el('div', 'hqpu-alert-main')\n body.append(\n el(\n 'div',\n 'hqpu-alert-title',\n lastResult.state === 'failed' ? copy.ui.failedTitle : copy.ui.rollbackTitle,\n ),\n )\n const oldVer = lastResult.installedVersion ?? lastResult.targetVersion ?? '—'\n const bodyP = el('p', 'hqpu-alert-body')\n bodyP.append(\n document.createTextNode(\n lastResult.state === 'failed'\n ? copy.ui.failedBody(oldVer, '')\n : copy.ui.rollbackBody(oldVer, ''),\n ),\n )\n if (typeof lastResult.errorCode === 'string' && lastResult.errorCode.length > 0) {\n bodyP.append(el('code', '', lastResult.errorCode))\n }\n if (typeof lastResult.message === 'string' && lastResult.message.length > 0) {\n bodyP.append(el('br'), document.createTextNode(lastResult.message))\n }\n body.append(bodyP)\n const actions = el('div', 'hqpu-alert-actions')\n actions.append(linkButton('hqpu-alert-dismiss', copy.ui.dismiss, onDismiss))\n body.append(actions)\n card.append(body)\n section.append(card)\n return section\n}\n\n/**\n * The HQ Edge runtime card: current chip, the offered version with its size and\n * summary, and the 升级核心 action — arm → confirm, because a runtime swap\n * shuts HQ Edge down and is not undoable from here.\n */\nfunction runtimeEngineCard(\n report: PluginUpdateReport,\n copy: UpdateCopy,\n actions: RuntimeActions,\n): HTMLElement | null {\n const runtime = report.runtime\n const latestVersion = runtimeLatestVersion(runtime)\n const card = el('section', 'hqpu-engine')\n card.dataset.group = 'runtime'\n\n const head = el('div', 'hqpu-engine-head')\n const titleWrap = el('div', 'hqpu-engine-title')\n const bolt = el('span', 'hqpu-engine-bolt')\n bolt.innerHTML = BOLT_ICON(14)\n titleWrap.append(bolt, el('span', '', copy.ui.runtimeEngine))\n head.append(titleWrap)\n if (typeof runtime.currentVersion === 'string' && runtime.currentVersion.length > 0) {\n head.append(el('span', 'hqpu-chip', copy.ui.currentChip(runtime.currentVersion)))\n }\n card.append(head)\n\n const offered = runtime.updateAvailable && latestVersion !== null\n if (!offered && typeof runtime.lastError === 'string' && runtime.lastError.length > 0) {\n card.append(el('p', 'hqpu-engine-error', `${copy.lastError}: ${runtime.lastError}`))\n return card\n }\n if (!offered) return card\n\n const latestEntry = runtime.latest\n const row = el('div', 'hqpu-engine-row')\n const info = el('div', 'hqpu-engine-info')\n const newLine = el('div', 'hqpu-engine-new')\n newLine.append(el('span', 'hqpu-ver-new hqpu-ver-new--lg', copy.ui.newVersion(latestVersion as string)))\n const hasCritical =\n (Array.isArray(latestEntry?.breaking) && (latestEntry?.breaking?.length ?? 0) > 0) ||\n (Array.isArray(latestEntry?.notes) && (latestEntry?.notes?.length ?? 0) > 0)\n if (hasCritical) newLine.append(el('span', 'hqpu-tag--brand', copy.ui.breakingTag))\n info.append(newLine)\n const size = typeof latestEntry?.sizeBytes === 'number' ? formatMb(latestEntry.sizeBytes) : ''\n const summary = typeof latestEntry?.summary === 'string' ? latestEntry.summary : ''\n if (size.length > 0 || summary.length > 0) {\n info.append(el('p', 'hqpu-engine-size', copy.ui.sizeLine(size, summary)))\n }\n row.append(info)\n\n if (actions.accepted) {\n row.append(el('span', 'hqpu-engine-state', copy.runtime.updateAccepted))\n } else if (actions.busy) {\n row.append(el('span', 'hqpu-engine-state', copy.runtime.updating))\n } else if (!actions.armed) {\n const start = el('button', 'hqpu-btn--engine', copy.ui.upgradeCore)\n start.type = 'button'\n start.dataset.runtimeUpdate = ''\n start.addEventListener('click', actions.onArm)\n row.append(start)\n }\n card.append(row)\n\n // Confirm replaces the action: a full-width block, never a 150 px column.\n if (actions.armed && !actions.busy && !actions.accepted) {\n const confirm = el('div', 'hqpu-engine-confirm')\n confirm.append(\n el('p', 'hqpu-engine-confirm-text', copy.runtime.updateConfirm(latestVersion as string)),\n )\n const buttons = el('div', 'hqpu-engine-confirm-actions')\n const no = el('button', 'hqpu-btn hqpu-btn--ghost hqpu-btn--sm', copy.cancel)\n no.type = 'button'\n no.addEventListener('click', actions.onCancel)\n const yes = el('button', 'hqpu-btn--engine hqpu-btn--engine-solid', copy.ui.confirmRuntime)\n yes.type = 'button'\n yes.dataset.runtimeConfirm = ''\n yes.addEventListener('click', actions.onConfirm)\n buttons.append(no, yes)\n confirm.append(buttons)\n card.append(confirm)\n }\n\n const note = el('p', 'hqpu-engine-note')\n note.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"10\" height=\"10\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 2L14.4 13.4H1.6L8 2Z\"/><path d=\"M8 6.4V9.2\"/><path d=\"M8 10.6V11.6\"/></svg>`\n note.append(el('span', '', copy.ui.runtimeRestartNote))\n card.append(note)\n return card\n}\n\n/** Set-level release metadata (v2 manifest): summary, notes, breaking, link. */\nfunction releaseBody(release: ReleaseInfo, copy: UpdateCopy): HTMLElement[] {\n const out: HTMLElement[] = []\n const notes = Array.isArray(release.notes)\n ? release.notes.filter((n) => typeof n === 'string' && n.length > 0)\n : []\n const breaking = Array.isArray(release.breaking)\n ? release.breaking.filter((n) => typeof n === 'string' && n.length > 0)\n : []\n if (typeof release.summary === 'string' && release.summary.length > 0) {\n out.push(el('p', 'hqpu-release-summary', release.summary))\n }\n if (notes.length > 0) {\n const ul = el('ul', 'hqpu-release-notes')\n for (const note of notes) ul.append(el('li', '', note as string))\n out.push(ul)\n }\n if (breaking.length > 0) {\n const banner = el('div', 'hqpu-banner')\n banner.dataset.tone = 'danger'\n const icon = el('span')\n icon.innerHTML = WARNING_ICON(14)\n banner.append(icon)\n const inner = el('span')\n inner.append(el('strong', '', `${copy.release.breakingTitle}: `))\n inner.append(document.createTextNode(breaking.join(' · ')))\n banner.append(inner)\n out.push(banner)\n }\n if (typeof release.publishedAt === 'string' && release.publishedAt.length > 0) {\n out.push(\n el('p', 'hqpu-release-meta', copy.release.publishedAt(new Date(release.publishedAt).toLocaleString())),\n )\n }\n if (release.requiresRestart === true) {\n out.push(el('p', 'hqpu-release-meta', copy.job.willRestartDsh))\n }\n if (typeof release.notesUrl === 'string' && release.notesUrl.length > 0) {\n const link = el('a', 'hqpu-release-link', copy.release.notesLink)\n link.href = release.notesUrl\n link.target = '_blank'\n link.rel = 'noreferrer'\n out.push(link)\n }\n return out\n}\n\nfunction hasReleaseContent(release: ReleaseInfo | null | undefined): boolean {\n if (release === null || release === undefined) return false\n return (\n (typeof release.summary === 'string' && release.summary.length > 0) ||\n (Array.isArray(release.notes) && release.notes.length > 0) ||\n (Array.isArray(release.breaking) && release.breaking.length > 0) ||\n typeof release.notesUrl === 'string' ||\n typeof release.publishedAt === 'string'\n )\n}\n\n/**\n * The job log drawer: title + outcome tag, the mono component list with a\n * JOB_ID header, the result detail, older runs, and 复制日志 ID / 关闭.\n */\nfunction buildLogDrawer(\n job: UpdateJob,\n recent: UpdateJob[],\n copy: UpdateCopy,\n onCopy: (id: string) => void,\n onClose: () => void,\n): HTMLElement[] {\n const out: HTMLElement[] = []\n\n const head = el('div', 'hqpu-log-head')\n const titleWrap = el('div', 'hqpu-log-title')\n titleWrap.append(el('span', 'hqpu-log-name', copy.ui.logTitle))\n const tag = el('span', 'hqpu-tag')\n tag.dataset.tone =\n job.phase === 'done' ? 'success' : job.phase === 'failed' ? 'danger' : 'warning'\n tag.textContent =\n job.phase === 'done'\n ? copy.job.resultDone\n : job.phase === 'failed'\n ? copy.job.resultFailed\n : job.phase === 'rolled-back'\n ? copy.job.resultRolledBack\n : copy.job.resultAbandoned\n titleWrap.append(tag)\n const closeBtn = iconButton('hqpu-icon-btn hqpu-icon-btn--sm', CLOSE_ICON(14), copy.close, onClose)\n head.append(titleWrap, closeBtn)\n out.push(head)\n\n const body = el('div', 'hqpu-log-body')\n const block = el('div', 'hqpu-log-block')\n const meta = el('div', 'hqpu-log-meta')\n const logId = job.logHint !== undefined && job.logHint.length > 0 ? job.logHint : job.id\n meta.append(el('span', '', `${copy.ui.jobIdLabel}: ${logId}`))\n meta.append(el('span', '', shortWhen(job.result?.finishedAt ?? job.updatedAt)))\n block.append(meta)\n\n const steps = Array.isArray(job.steps) ? job.steps : []\n if (steps.length > 0) {\n for (const step of steps) {\n const line = el('div', 'hqpu-log-line')\n const dot = el('span', 'hqpu-log-dot')\n dot.dataset.state = stepDotState(step.state)\n line.append(dot)\n const word = stepWord(step.state, copy)\n const text =\n step.message !== undefined && step.message.length > 0\n ? `${step.name} — ${step.message}`\n : step.name\n line.append(el('span', 'hqpu-log-line-text', word.length > 0 ? `${text} (${word})` : text))\n block.append(line)\n }\n } else {\n block.append(el('div', 'hqpu-log-line hqpu-log-line--empty', copy.job.stepsTitle))\n }\n body.append(block)\n\n // Result detail: the transition, the error code, the server's own message.\n const res = job.result\n const from = job.previousVersion ?? res?.installedVersion ?? '—'\n const to = res?.installedVersion ?? job.targetVersion\n const detail = el('div', 'hqpu-log-detail')\n detail.append(el('p', '', `${from} → ${to}`))\n if (res?.errorCode !== undefined && res.errorCode.length > 0) {\n detail.append(el('code', 'hqpu-error-code', res.errorCode))\n }\n if (res?.message !== undefined && res.message.length > 0) {\n detail.append(el('p', '', res.message))\n }\n body.append(detail)\n\n const older = recent.filter((entry) => entry.id !== job.id).slice(0, 4)\n if (older.length > 0) {\n const list = el('div', 'hqpu-log-recent')\n list.append(el('p', 'hqpu-log-recent-title', copy.job.recentTitle))\n for (const entry of older) {\n const row = el('div', 'hqpu-log-recent-row')\n row.append(el('span', '', `${jobKindLabel(entry, copy)} ${entry.targetVersion}`))\n row.append(el('span', 'hqpu-log-recent-meta', `${phaseLabel(entry, copy)} · ${shortWhen(entry.updatedAt)}`))\n list.append(row)\n }\n body.append(list)\n }\n out.push(body)\n\n const foot = el('div', 'hqpu-log-foot')\n const copyBtn = el('button', 'hqpu-link hqpu-link--icon')\n copyBtn.type = 'button'\n copyBtn.dataset.copyLog = ''\n const copyIcon = el('span')\n copyIcon.innerHTML = COPY_ICON(12)\n copyBtn.append(copyIcon, el('span', '', copy.ui.copyLogId))\n copyBtn.addEventListener('click', () => onCopy(logId))\n const closeFoot = el('button', 'hqpu-btn hqpu-btn--sm', copy.close)\n closeFoot.type = 'button'\n closeFoot.addEventListener('click', onClose)\n foot.append(copyBtn, closeFoot)\n out.push(foot)\n\n return out\n}\n\n// ── The runtime action contract ────────────────────────────────────────────\n\n/**\n * The runtime-update action row's state and callbacks.\n *\n * Installing the runtime is destructive in a way a plugin install is not:\n * hq-edge shuts itself down so the EDA host can swap the runtime directory. So\n * the row is a two-step confirm (arm → confirm), never a single click.\n */\nexport interface RuntimeActions {\n /** A request is in flight (hq-edge accepted it and is restarting). */\n busy: boolean\n /** The confirm step is armed for the version the row names. */\n armed: boolean\n /**\n * hq-edge answered `202 Accepted`: from here on the outcome belongs to the\n * EDA host, and this process is about to be replaced. Terminal — the row\n * must never re-offer the button once this is true.\n */\n accepted: boolean\n onArm(): void\n onConfirm(): void\n onCancel(): void\n}\n\n/**\n * Normalize a report's source field to the full shape.\n *\n * An older hq-edge predates the feature and sends no `source` at all — that\n * must degrade to no editor, not crash the render. A future server may add\n * fields; unknown extras are dropped here rather than trusted.\n */\nfunction normalizeSource(raw: unknown): RuntimeUpdateSource | null {\n if (raw === null || typeof raw !== 'object') return null\n const candidate = raw as Partial<RuntimeUpdateSource>\n if (typeof candidate.origin !== 'string') return null\n return {\n url: typeof candidate.url === 'string' ? candidate.url : '',\n origin: candidate.origin as RuntimeUpdateSource['origin'],\n configuredUrl: typeof candidate.configuredUrl === 'string' ? candidate.configuredUrl : '',\n override: candidate.override === true,\n }\n}\n\n// ── Toast ─────────────────────────────────────────────────────────────────\n\n/** The single live toast; a new message replaces the one on screen. */\nlet toastHost: HTMLElement | null = null\nlet toastTimer: number | null = null\n/** Copy for the toast's close affordance; set when the panel mounts. */\nlet activeCopy: UpdateCopy | null = null\n\nfunction showToast(message: string, tone: 'success' | 'error'): void {\n if (toastHost === null) return\n if (toastTimer !== null) window.clearTimeout(toastTimer)\n toastHost.textContent = ''\n const toast = el('div', 'hqpu-toast')\n // An error must interrupt; a success may wait for a pause.\n toast.setAttribute('role', tone === 'error' ? 'alert' : 'status')\n const icon = el('span', 'hqpu-toast-icon')\n icon.dataset.tone = tone\n icon.innerHTML = tone === 'success' ? CHECK_CIRCLE_ICON(16) : WARNING_ICON(16)\n toast.append(icon, el('span', 'hqpu-toast-text', message))\n const close = iconButton(\n 'hqpu-toast-close',\n CLOSE_ICON(12),\n activeCopy?.close ?? 'Close',\n () => {\n toastHost?.replaceChildren()\n if (toastTimer !== null) window.clearTimeout(toastTimer)\n toastTimer = null\n },\n )\n toast.append(close)\n toastHost.append(toast)\n toastTimer = window.setTimeout(() => {\n toastHost?.replaceChildren()\n toastTimer = null\n }, TOAST_HOLD_MS)\n}\n\n// ── Panel ─────────────────────────────────────────────────────────────────\n\n/**\n * Open (or focus) the update panel. Re-renders from the store; when the store\n * is stale the panel first refreshes the report.\n */\nexport function openUpdateView(store: UpdateStoreLike, copy: UpdateCopy): void {\n const existing = document.querySelector<HTMLElement>('[data-hq-plugin-update-view]')\n if (existing !== null) {\n existing.style.display = ''\n return\n }\n\n const previouslyFocused = document.activeElement as HTMLElement | null\n\n const overlay = el('div', 'hqpu-overlay')\n overlay.setAttribute('data-hq-plugin-update-view', '')\n overlay.addEventListener('click', (ev) => {\n if (ev.target === overlay) onClose()\n })\n\n const scrim = el('div', 'hqpu-scrim')\n const panel = el('div', 'hqpu-panel')\n panel.setAttribute('role', 'dialog')\n panel.setAttribute('aria-modal', 'true')\n panel.setAttribute('aria-label', copy.ui.title)\n panel.tabIndex = -1\n\n // ── Header ──\n const head = el('header', 'hqpu-head')\n const headMain = el('div', 'hqpu-head-main')\n const brandIcon = el('span', 'hqpu-brand-icon')\n brandIcon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M13.8 8.6A5.8 5.8 0 012.2 6.4\"/><path d=\"M13.8 4.4V8.6H9.6\"/><path d=\"M2.2 7.4A5.8 5.8 0 0113.8 9.6\"/><path d=\"M2.2 11.6V7.4H6.4\"/></svg>`\n const titleBlock = el('div', 'hqpu-title-block')\n titleBlock.append(el('h1', 'hqpu-title', copy.ui.title))\n const sub = el('p', 'hqpu-sub')\n titleBlock.append(sub)\n headMain.append(brandIcon, titleBlock)\n\n const toolbar = el('div', 'hqpu-toolbar')\n const refreshBtn = iconButton('hqpu-icon-btn', REFRESH_ICON(15), copy.refresh, () => {\n void store.check()\n })\n const gearBtn = iconButton(\n 'hqpu-icon-btn',\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"8\" cy=\"8\" r=\"2.2\"/><path d=\"M13.5 9.2v-2.4l-1.8-.6a4.4 4.4 0 00-.5-1.2l.9-1.7-1.7-1.7-1.7.9a4.4 4.4 0 00-1.2-.5L7.2 2.5H4.8l-.6 1.8a4.4 4.4 0 00-1.2.5l-1.7-.9-1.7 1.7.9 1.7a4.4 4.4 0 00-.5 1.2L2.5 7.2v2.4l1.8.6a4.4 4.4 0 00.5 1.2l-.9 1.7 1.7 1.7 1.7-.9a4.4 4.4 0 001.2.5l.6 1.8h2.4l.6-1.8a4.4 4.4 0 001.2-.5l1.7.9 1.7-1.7-.9-1.7a4.4 4.4 0 00.5-1.2l1.8-.6z\"/></svg>`,\n copy.ui.gearTooltip,\n () => {\n toggleSettings()\n },\n )\n gearBtn.dataset.settingsToggle = ''\n gearBtn.setAttribute('aria-pressed', 'false')\n const logBtn = iconButton('hqpu-icon-btn', LOG_ICON(15), copy.ui.logShow, () => {\n toggleLog()\n })\n logBtn.dataset.logToggle = ''\n logBtn.setAttribute('aria-pressed', 'false')\n const closeBtn = iconButton('hqpu-icon-btn hqpu-icon-btn--close', CLOSE_ICON(15), copy.close, () => {\n onClose()\n })\n toolbar.append(refreshBtn, gearBtn, logBtn, closeBtn)\n head.append(headMain, toolbar)\n panel.append(head)\n\n // ── Settings strip (gear-toggled, above the body so it never shrinks it) ──\n const settingsEl = el('div', 'hqpu-settings')\n settingsEl.hidden = true\n panel.append(settingsEl)\n\n // ── Body + footer + log drawer share one positioned box, so the drawer can\n // cover the body without covering the header ──\n const main = el('div', 'hqpu-main')\n const scroll = el('div', 'hqpu-scroll')\n const column = el('div', 'hqpu-column')\n const jobSection = el('div', 'hqpu-stack')\n const bannerSection = el('div', 'hqpu-stack')\n const bandSection = el('div', 'hqpu-stack')\n const listSection = el('div', 'hqpu-stack')\n const extraSection = el('div', 'hqpu-stack')\n column.append(jobSection, bannerSection, bandSection, listSection, extraSection)\n scroll.append(column)\n\n const footer = el('footer', 'hqpu-footer')\n const cta = el('button', 'hqpu-btn--block')\n cta.type = 'button'\n const ctaSpinner = el('span', 'hqpu-cta-spinner')\n ctaSpinner.hidden = true\n const ctaLabel = el('span', 'hqpu-cta-label')\n cta.append(ctaSpinner, ctaLabel)\n const footMeta = el('div', 'hqpu-foot-meta')\n const footCount = el('span', '')\n const ignoreBtn = el('button', 'hqpu-link')\n ignoreBtn.type = 'button'\n footMeta.append(footCount, ignoreBtn)\n footer.append(cta, footMeta, el('p', 'hqpu-footnote', copy.ui.footnote))\n\n const logDrawer = el('div', 'hqpu-log')\n logDrawer.dataset.logDrawer = ''\n logDrawer.hidden = true\n\n const toastSection = el('div', 'hqpu-toast-host')\n main.append(scroll, footer, logDrawer, toastSection)\n panel.append(main)\n\n overlay.append(scrim, panel)\n document.body.appendChild(overlay)\n toastHost = toastSection\n activeCopy = copy\n panel.focus()\n\n const disposers: Array<() => void> = []\n\n // ── Panel-closure UI state (survives store refreshes) ──\n let runtimeArmed = false\n let runtimeBusy = false\n let runtimeAccepted = false\n let settingsOpen = false\n let settingsBuilt = false\n let logOpen = false\n let dismissedAlert = false\n let changesAllOpen = false\n const changesOpen = new Set<string>()\n let upToDateOpen = false\n let ignoredOpen = false\n let releaseOpen = false\n let saving = false\n let pluginDraft = ''\n let runtimeDraft = ''\n\n const runtimeActions: RuntimeActions = {\n get busy() {\n return runtimeBusy\n },\n get armed() {\n return runtimeArmed\n },\n get accepted() {\n return runtimeAccepted\n },\n onArm: () => {\n runtimeArmed = true\n render()\n },\n onCancel: () => {\n runtimeArmed = false\n render()\n },\n onConfirm: () => {\n void requestRuntime()\n },\n }\n\n /** Fill both drafts from the report's URLs in effect. */\n function syncDrafts(): void {\n const report = store.report()\n pluginDraft = normalizeSource(report?.pluginSource)?.url ?? ''\n runtimeDraft = normalizeSource(report?.runtime.source)?.url ?? ''\n }\n\n function toggleSettings(): void {\n settingsOpen = !settingsOpen\n if (settingsOpen) {\n syncDrafts()\n settingsBuilt = false\n }\n render()\n }\n\n /**\n * Save both manifest sources, then re-run discovery and close the strip.\n *\n * The re-check is not optional: the report is cached, so without a fresh\n * `POST /check` the list would keep naming the OLD manifest's offer after the\n * source changed. hq-edge answers 422 with a message meant for a person on\n * refusal (bad scheme, credentials, …), which the toast shows verbatim —\n * and the strip stays open with the draft intact so it can be fixed.\n */\n async function saveSources(): Promise<void> {\n const report = store.report()\n const pluginSource = normalizeSource(report?.pluginSource)\n const runtimeSource = normalizeSource(report?.runtime.source)\n saving = true\n settingsBuilt = false\n render()\n try {\n const calls: Array<Promise<unknown>> = []\n if (pluginSource !== null && pluginDraft !== pluginSource.url) {\n calls.push(setPluginUpdateSource(pluginDraft))\n }\n if (runtimeSource !== null && runtimeDraft !== runtimeSource.url) {\n calls.push(setRuntimeUpdateSource(runtimeDraft))\n }\n await Promise.all(calls)\n settingsOpen = false\n showToast(copy.pluginSource.saved, 'success')\n try {\n await checkUpdates()\n await store.refresh()\n } catch {\n // The source IS saved; a failed re-check only leaves the previous\n // offer on screen until the next refresh — the toolbar re-runs it.\n }\n } catch (err: unknown) {\n showToast(\n `${copy.pluginSource.saveFailed}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n } finally {\n saving = false\n settingsBuilt = false\n render()\n }\n }\n\n /** Clear one user override so the configured URL applies again. */\n async function resetSource(kind: SourceKind): Promise<void> {\n saving = true\n settingsBuilt = false\n render()\n try {\n if (kind === 'runtime') await setRuntimeUpdateSource('')\n else await setPluginUpdateSource('')\n showToast(kind === 'runtime' ? copy.runtime.saved : copy.pluginSource.saved, 'success')\n try {\n await checkUpdates()\n await store.refresh()\n } catch {\n /* saved anyway */\n }\n syncDrafts()\n } catch (err: unknown) {\n showToast(\n `${copy.pluginSource.saveFailed}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n } finally {\n saving = false\n settingsBuilt = false\n render()\n }\n }\n\n async function requestRuntime(): Promise<void> {\n const version = runtimeLatestVersion(store.report()?.runtime)\n if (version === null) {\n showToast(copy.checkFailed, 'error')\n return\n }\n runtimeArmed = false\n runtimeBusy = true\n render()\n try {\n const result = await requestRuntimeUpdate(version)\n if (result?.error !== undefined) {\n showToast(`${copy.installError}: ${result.error}`, 'error')\n return\n }\n // 202 Accepted: hq-edge exits to let the EDA host swap the runtime and\n // relaunch. Latch it, so a store refresh cannot re-offer the button for\n // a server that is already gone.\n runtimeAccepted = true\n // The runtime swap restarts HQ Edge itself; follow the job it accepted so\n // the panel can report the outcome after the runtime is back.\n store.watchJob?.(result.jobId ?? null)\n showToast(copy.runtime.updateAccepted, 'success')\n void store.fetchStatus()\n } catch (err: unknown) {\n showToast(\n `${copy.installError}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n } finally {\n runtimeBusy = false\n render()\n }\n }\n\n /** Retry a failed/abandoned job: plugin-set → a fresh install; runtime →\n * re-arm the runtime confirm flow. Always a NEW job, never re-clicking the\n * settled job id. */\n function retryJob(kind: 'plugin-set' | 'runtime'): void {\n if (kind === 'runtime') {\n runtimeArmed = true\n render()\n } else {\n void runInstall(store, copy)\n }\n }\n\n /** The newest settled job — what the log drawer is about. */\n function settledJob(): UpdateJob | null {\n const latest = store.recentJobs()[0]\n return latest !== undefined && TERMINAL_PHASES.has(latest.phase) ? latest : null\n }\n\n function toggleLog(): void {\n logOpen = !logOpen\n render()\n }\n\n async function copyLogId(id: string): Promise<void> {\n try {\n const clipboard = navigator.clipboard\n if (clipboard === undefined) throw new Error('clipboard unavailable')\n await clipboard.writeText(id)\n showToast(copy.ui.logCopied, 'success')\n } catch {\n showToast(copy.ui.logCopyFailed, 'error')\n }\n }\n\n // ── Section renderers ──\n\n function renderSettings(): void {\n if (!settingsOpen) {\n settingsEl.hidden = true\n settingsEl.textContent = ''\n return\n }\n settingsEl.hidden = false\n // Rebuilding on every store emit would blow away the caret of whoever is\n // typing, so the DOM is built once per open (or per explicit force).\n if (settingsBuilt) return\n settingsBuilt = true\n settingsEl.textContent = ''\n\n const report = store.report()\n const pluginSource = normalizeSource(report?.pluginSource)\n const runtimeSource = normalizeSource(report?.runtime.source)\n\n const row = el('div', 'hqpu-settings-row')\n row.append(el('span', 'hqpu-settings-title', copy.ui.settingsTitle))\n const overridden = (pluginSource?.override ?? false) || (runtimeSource?.override ?? false)\n const badge = el('span', 'hqpu-origin-badge', overridden ? copy.ui.originCustom : copy.ui.originDefault)\n badge.dataset.origin = overridden ? 'override' : 'config'\n row.append(badge)\n settingsEl.append(row)\n\n const field = (\n kind: SourceKind,\n source: RuntimeUpdateSource | null,\n label: string,\n ariaLabel: string,\n placeholder: string,\n draft: string,\n onDraft: (value: string) => void,\n ): void => {\n if (source === null) return\n const wrap = el('div', 'hqpu-field')\n const labelRow = el('div', 'hqpu-field-head')\n const id = `hqpu-source-${kind}`\n const labelEl = el('label', '', label)\n labelEl.htmlFor = id\n labelRow.append(labelEl)\n if (source.override) {\n const reset = linkButton('hqpu-link hqpu-link--tiny', copy.ui.resetSource, () => {\n void resetSource(kind)\n })\n reset.setAttribute(`data-${kind}-source-reset`, '')\n labelRow.append(reset)\n }\n wrap.append(labelRow)\n const input = el('input', 'hqpu-source-input')\n input.id = id\n input.type = 'url'\n input.value = draft\n input.placeholder = placeholder\n input.setAttribute('aria-label', ariaLabel)\n input.setAttribute(`data-${kind}-source-input`, '')\n input.disabled = saving\n input.spellcheck = false\n input.addEventListener('input', () => {\n // Draft-only: no re-render, so the caret and IME stay untouched.\n onDraft(input.value)\n })\n // Enter saves both; Escape closes the strip without saving.\n input.addEventListener('keydown', (ev) => {\n if (ev.key === 'Enter') {\n ev.preventDefault()\n void saveSources()\n } else if (ev.key === 'Escape') {\n ev.preventDefault()\n ev.stopPropagation()\n settingsOpen = false\n syncDrafts()\n render()\n }\n })\n wrap.append(input)\n settingsEl.append(wrap)\n }\n\n field(\n 'plugin',\n pluginSource,\n copy.ui.pluginUrlLabel,\n copy.pluginSource.inputLabel,\n copy.pluginSource.placeholder,\n pluginDraft,\n (value) => {\n pluginDraft = value\n },\n )\n field(\n 'runtime',\n runtimeSource,\n copy.ui.runtimeUrlLabel,\n copy.runtime.sourceInputLabel,\n copy.runtime.sourcePlaceholder,\n runtimeDraft,\n (value) => {\n runtimeDraft = value\n },\n )\n if (pluginSource === null && runtimeSource === null) {\n settingsEl.append(el('p', 'hqpu-status', copy.runtime.notConfigured))\n }\n\n const actions = el('div', 'hqpu-settings-actions')\n const cancel = el('button', 'hqpu-btn hqpu-btn--ghost hqpu-btn--sm', copy.cancel)\n cancel.type = 'button'\n cancel.addEventListener('click', () => {\n settingsOpen = false\n syncDrafts()\n render()\n })\n const save = el('button', 'hqpu-btn hqpu-btn--brand hqpu-btn--sm', copy.ui.saveAndRefresh)\n save.type = 'button'\n save.disabled = saving\n save.setAttribute('data-source-save', '')\n save.addEventListener('click', () => {\n void saveSources()\n })\n actions.append(cancel, save)\n settingsEl.append(actions)\n }\n\n function renderJobSection(): void {\n jobSection.textContent = ''\n const active = store.activeJob()\n if (active !== null && !TERMINAL_PHASES.has(active.phase)) {\n jobSection.append(\n activeJobView(active, copy, {\n stalled: store.stalled?.() === true,\n onRecheck: () => {\n void store.fetchStatus()\n },\n }),\n )\n }\n }\n\n function renderBanners(report: PluginUpdateReport | null, jobActive: boolean): void {\n bannerSection.textContent = ''\n if (report !== null) {\n const banner = checkBanner(report, copy, () => {\n void store.check()\n })\n if (banner !== null) bannerSection.append(banner)\n }\n // A terminal non-success job leads the body; the full log is one click away\n // in the drawer, but the outcome itself must not hide there.\n if (jobActive) return\n const settled = settledJob()\n if (settled !== null && settled.phase !== 'done') {\n bannerSection.append(\n jobResultBanner(\n settled,\n copy,\n () => retryJob(settled.kind),\n () => {\n logOpen = true\n render()\n },\n ),\n )\n }\n }\n\n function renderList(report: PluginUpdateReport | null, busy: boolean): void {\n listSection.textContent = ''\n bandSection.textContent = ''\n if (report === null) {\n listSection.append(skeleton(copy))\n return\n }\n const band = decisionBand(report, copy)\n if (band !== null) bandSection.append(band)\n\n const actionable = report.items.filter(isActionable)\n if (actionable.length > 0) {\n const section = el('section', 'hqpu-group')\n section.dataset.group = copy.group.actionable\n const head = el('div', 'hqpu-sec-head')\n const titleWrap = el('div', 'hqpu-sec-title')\n titleWrap.append(el('span', '', copy.group.actionable))\n titleWrap.append(el('span', 'hqpu-count-pill', String(actionable.length)))\n head.append(titleWrap)\n const anyChanges = actionable.some((i) => Array.isArray(i.changes) && i.changes.length > 0)\n if (anyChanges) {\n head.append(\n linkButton(\n 'hqpu-link',\n changesAllOpen ? copy.ui.collapseLogs : copy.ui.expandLogs,\n () => {\n changesAllOpen = !changesAllOpen\n if (!changesAllOpen) changesOpen.clear()\n render()\n },\n ),\n )\n }\n section.append(head)\n const list = el('ul', 'hqpu-cards')\n for (const item of actionable) {\n list.append(\n pluginCard(item, copy, {\n changesOpen: changesAllOpen || changesOpen.has(item.name),\n onToggleChanges: () => {\n if (changesOpen.has(item.name)) changesOpen.delete(item.name)\n else changesOpen.add(item.name)\n render()\n },\n onInstall: () => {\n void runInstall(store, copy, [item.name])\n },\n onIgnore: () => {\n void togglePluginIgnore(item)\n },\n busy,\n }),\n )\n }\n section.append(list)\n listSection.append(section)\n }\n }\n\n function renderExtras(report: PluginUpdateReport | null): void {\n extraSection.textContent = ''\n if (report === null) return\n\n const alert = runtimeResultAlert(report, copy, dismissedAlert, () => {\n dismissedAlert = true\n render()\n })\n if (alert !== null) extraSection.append(alert)\n\n const engine = runtimeEngineCard(report, copy, runtimeActions)\n if (engine !== null) extraSection.append(engine)\n\n const byState = (state: PluginUpdateItem['state']): PluginUpdateItem[] =>\n report.items.filter((item) => item.state === state)\n\n const ignored = byState('ignored')\n if (ignored.length > 0) {\n extraSection.append(\n disclosure({\n id: 'hqpu-disc-ignored',\n title: copy.group.ignored,\n count: `(${ignored.length})`,\n note: copy.ui.ignoredNote,\n open: ignoredOpen,\n onToggle: () => {\n ignoredOpen = !ignoredOpen\n render()\n },\n build: () => {\n const ul = el('ul', 'hqpu-rows')\n for (const item of ignored) {\n ul.append(\n pluginRow(item, copy, () => {\n void togglePluginIgnore(item)\n }),\n )\n }\n return [ul]\n },\n }),\n )\n }\n\n const current = byState('current')\n if (current.length > 0) {\n extraSection.append(\n disclosure({\n id: 'hqpu-disc-current',\n title: copy.group.current,\n count: `(${current.length})`,\n note: copy.ui.upToDateNote,\n open: upToDateOpen,\n onToggle: () => {\n upToDateOpen = !upToDateOpen\n render()\n },\n build: () => {\n const ul = el('ul', 'hqpu-rows')\n for (const item of current) ul.append(pluginRow(item, copy, () => undefined))\n return [ul]\n },\n }),\n )\n }\n\n if (hasReleaseContent(report.release)) {\n extraSection.append(\n disclosure({\n id: 'hqpu-disc-release',\n title: copy.ui.releaseTitle,\n note: report.manifestVersion !== null ? `v${report.manifestVersion}` : undefined,\n open: releaseOpen,\n onToggle: () => {\n releaseOpen = !releaseOpen\n render()\n },\n build: () => releaseBody(report.release as ReleaseInfo, copy),\n }),\n )\n }\n\n if (report.items.length === 0 && typeof report.lastError !== 'string') {\n extraSection.append(el('div', 'hqpu-empty', copy.empty))\n }\n }\n\n function renderFooter(report: PluginUpdateReport | null, busy: boolean): void {\n const actionable = report === null ? 0 : actionableUpdateCount(report)\n const setVersion = report?.manifestVersion ?? null\n const failed = typeof report?.lastError === 'string' && (report?.lastError ?? '').length > 0\n const runtimePending = report !== null && runtimeUpdateOffered(report.runtime)\n const anything = actionable > 0 || runtimePending\n\n ctaSpinner.hidden = !busy\n cta.dataset.busy = busy ? 'true' : 'false'\n cta.setAttribute('aria-busy', busy ? 'true' : 'false')\n\n if (busy) {\n ctaLabel.textContent = copy.ui.ctaInstalling\n cta.disabled = true\n // A stalled job is still \"busy\" — the install may have completed and only\n // the report is missing — so the footer must not promise progress it\n // cannot see. The card above carries the retry.\n footCount.textContent =\n store.stalled?.() === true ? copy.ui.footerConfirming : copy.ui.footerBusy\n } else if (report === null) {\n ctaLabel.textContent = copy.ui.ctaNoSet\n cta.disabled = true\n footCount.textContent = ''\n } else if (failed) {\n // Installing against a stale manifest is the one path that can only end\n // in a 422 — say so instead of offering a button that cannot work.\n ctaLabel.textContent = copy.ui.ctaFailed\n cta.disabled = true\n footCount.textContent = copy.ui.footerFailed\n } else if (setVersion === null) {\n ctaLabel.textContent = copy.ui.ctaNoSet\n cta.disabled = true\n footCount.textContent = copy.ui.checkedNever\n } else if (actionable === 0 && runtimePending) {\n // The plugin set is done; the only remaining offer is the runtime, which\n // needs its own confirm — the CTA must not imply it would install it.\n ctaLabel.textContent = copy.ui.ctaNoPlugins\n cta.disabled = true\n footCount.textContent = copy.ui.footerRuntimeOnly\n } else if (actionable === 0 && !runtimePending) {\n ctaLabel.textContent = copy.ui.ctaUpToDate\n cta.disabled = true\n footCount.textContent = copy.ui.footerAllGood\n } else {\n ctaLabel.textContent = copy.ui.cta(actionable)\n cta.disabled = false\n footCount.textContent = copy.ui.footerCount(actionable)\n }\n cta.onclick = () => {\n void runInstall(store, copy)\n }\n\n // Set-level mute: only with an evaluated set and something to mute.\n const canMute = setVersion !== null && report !== null && !busy\n ignoreBtn.hidden = !canMute || (!anything && !report?.ignoredSet)\n ignoreBtn.textContent = report?.ignoredSet === true ? copy.ui.unignoreBtn : copy.ui.ignoreBtn\n ignoreBtn.onclick = () => {\n if (setVersion === null) return\n const ignored = report?.ignoredSet === true\n const action = ignored ? unignoreOffer(setVersion) : ignoreOffer(setVersion)\n // `check()`, never `refresh()`: ignoring only writes the decision to\n // hq-edge's state file, and its cached report keeps every item at\n // `update` until discovery runs again.\n void action\n .then(() => store.check())\n .catch((err: unknown) => {\n showToast(\n `${copy.ignoreError}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n })\n }\n }\n\n function renderLogDrawer(jobActive: boolean): void {\n const job = settledJob()\n const available = !jobActive && job !== null\n logBtn.disabled = !available\n logBtn.setAttribute('aria-pressed', logOpen && available ? 'true' : 'false')\n const label =\n jobActive ? copy.ui.logBusy : job === null ? copy.ui.logNone : logOpen ? copy.ui.logHide : copy.ui.logShow\n logBtn.title = label\n logBtn.setAttribute('aria-label', label)\n\n // A running job (or none at all) cannot have a log; drop the open flag so\n // the drawer does not snap back open once the job settles.\n if (!available || !logOpen) {\n if (!available) logOpen = false\n logDrawer.hidden = true\n logDrawer.textContent = ''\n main.dataset.logOpen = '0'\n return\n }\n logDrawer.hidden = false\n logDrawer.textContent = ''\n // The drawer is absolutely positioned, so it cannot size its own box: on a\n // short body (nothing left to update) it would be squeezed between the\n // header and the panel edge. The flag gives `.hqpu-main` a floor while it\n // is open, and nothing at all once it closes.\n main.dataset.logOpen = '1'\n for (const node of buildLogDrawer(\n job,\n store.recentJobs(),\n copy,\n (id) => {\n void copyLogId(id)\n },\n () => {\n logOpen = false\n render()\n },\n )) {\n logDrawer.append(node)\n }\n }\n\n function render(): void {\n const report = store.report()\n const active = store.activeJob()\n const jobActive = active !== null && !TERMINAL_PHASES.has(active.phase)\n const busy = store.installing() || jobActive\n\n refreshBtn.disabled = report === null\n gearBtn.setAttribute('aria-pressed', settingsOpen ? 'true' : 'false')\n gearBtn.title = copy.ui.gearTooltip\n\n // Header sub-line: the set version and the relative check time, once.\n const ago = relativeAgo(report?.checkedAt, copy)\n const setLabel =\n report?.manifestVersion !== null && report?.manifestVersion !== undefined\n ? copy.ui.setVersion(report.manifestVersion)\n : null\n if (setLabel !== null && ago !== null) sub.textContent = copy.ui.subJoined(setLabel, ago)\n else sub.textContent = setLabel ?? ago ?? copy.ui.checkedUnknown\n\n renderSettings()\n renderJobSection()\n renderBanners(report, jobActive)\n renderList(report, busy)\n renderExtras(report)\n renderFooter(report, busy)\n renderLogDrawer(jobActive)\n }\n\n /** Flip one plugin's per-item ignore flag, then re-run discovery. */\n async function togglePluginIgnore(item: PluginUpdateItem): Promise<void> {\n const setVersion = store.report()?.manifestVersion ?? null\n if (setVersion === null) return\n const action =\n item.state === 'ignored'\n ? unignorePlugin(setVersion, item.name)\n : ignorePlugin(setVersion, item.name)\n try {\n await action\n // Discovery must re-run for the item to come back as `ignored`; a\n // refusal has to be visible, never silently swallowed.\n await store.check()\n } catch (err: unknown) {\n showToast(\n `${copy.ignoreError}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n }\n }\n\n const unsubscribe = store.subscribe(render)\n disposers.push(unsubscribe)\n\n // Refresh on open (bounded; never block the shell).\n void store.refresh()\n\n // Escape peels one layer at a time; the shell-wide contract still holds.\n const onKeyDown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape') {\n ev.stopPropagation()\n if (logOpen) {\n logOpen = false\n render()\n return\n }\n if (settingsOpen) {\n settingsOpen = false\n syncDrafts()\n render()\n return\n }\n onClose()\n return\n }\n if (ev.key === 'Tab') trapFocus(ev)\n }\n\n /** Keep Tab inside the dialog — it declares `aria-modal=\"true\"`. */\n function trapFocus(ev: KeyboardEvent): void {\n const focusable = [\n ...panel.querySelectorAll<HTMLElement>(\n 'button:not(:disabled), [href], input:not(:disabled), [tabindex]:not([tabindex=\"-1\"])',\n ),\n ].filter((node) => node.closest('[hidden]') === null)\n if (focusable.length === 0) return\n const first = focusable[0]!\n const last = focusable[focusable.length - 1]!\n const activeEl = document.activeElement\n if (ev.shiftKey && (activeEl === first || activeEl === panel)) {\n ev.preventDefault()\n last.focus()\n } else if (!ev.shiftKey && activeEl === last) {\n ev.preventDefault()\n first.focus()\n }\n }\n\n document.addEventListener('keydown', onKeyDown)\n disposers.push(() => document.removeEventListener('keydown', onKeyDown))\n\n function onClose(): void {\n for (const dispose of disposers) {\n try {\n dispose()\n } catch {\n /* already disposed */\n }\n }\n if (toastTimer !== null) window.clearTimeout(toastTimer)\n toastTimer = null\n if (toastHost === toastSection) toastHost = null\n if (activeCopy === copy) activeCopy = null\n overlay.remove()\n // Focus goes back where it came from — the sidebar row that opened this.\n if (previouslyFocused !== null && previouslyFocused.isConnected) previouslyFocused.focus()\n }\n\n render()\n}\n\n/**\n * Run the install transaction (a new durable job), then let the store poll it.\n *\n * @param pluginNames - omit for the whole set (the footer CTA), or pass one\n * name for a card's 单独更新. hq-edge honours the subset.\n */\nasync function runInstall(\n store: UpdateStoreLike,\n copy: UpdateCopy,\n pluginNames?: string[],\n): Promise<void> {\n const report = store.report()\n if (report === null) return\n // Without an evaluated set there is nothing to install: hq-edge has not read\n // a manifest yet, so the report is still its empty shell.\n if (report.manifestVersion === null) {\n showToast(copy.checkFailed, 'error')\n return\n }\n // Flip the installing flag BEFORE the request: this is what disables the\n // action buttons and shows the in-flight footer state.\n store.setInstalling(true)\n try {\n const result = await installSet(report.manifestVersion, pluginNames)\n if (result.error !== undefined) {\n store.setInstalling(false)\n showToast(`${copy.installError}: ${result.error}`, 'error')\n return\n }\n // 202 accepted: a durable job now owns the lifecycle. Follow it BY ID —\n // that is what survives the restart when this very plugin is in the set\n // being installed (the server then has no \"active job\" to hand back, only\n // the record of the id we asked about).\n store.watchJob?.(result.jobId ?? null)\n showToast(copy.installSuccess, 'success')\n void store.fetchStatus()\n } catch (err: unknown) {\n store.setInstalling(false)\n showToast(\n `${copy.installError}: ${err instanceof Error ? err.message : String(err)}`,\n 'error',\n )\n }\n}\n\n/** First-read placeholders, sharing the card layout. */\nfunction skeleton(copy: UpdateCopy): HTMLElement {\n const section = el('section', 'hqpu-group')\n section.setAttribute('role', 'status')\n section.dataset.loading = ''\n section.setAttribute('aria-label', copy.loading)\n const list = el('ul', 'hqpu-cards')\n list.setAttribute('aria-hidden', 'true')\n for (let index = 0; index < 3; index += 1) {\n const card = el('li', 'hqpu-card')\n const head = el('div', 'hqpu-card-head')\n const id = el('div', 'hqpu-card-id')\n id.append(el('span', 'hqpu-avatar hqpu-skeleton-fill'))\n const main = el('div', 'hqpu-card-main')\n const title = el('span', 'hqpu-card-title hqpu-skeleton-fill hqpu-skeleton-title')\n const sub = el('span', 'hqpu-card-sub hqpu-skeleton-fill hqpu-skeleton-sub')\n main.append(title, sub)\n id.append(main)\n head.append(id)\n card.append(head)\n list.append(card)\n }\n section.append(list)\n return section\n}\n","/**\n * The sidebar toolbutton's numeric badge — one pure function, no DOM, no\n * store, so the rule can be tested on its own.\n *\n * It answers one question: how many updates does this EDA install still have\n * for the user? That is every actionable plugin item plus the HQ Edge runtime\n * offer when hq-edge reports one:\n *\n * - **plugin items** — compatible `update` + `install`, minus whatever the\n * user muted ({@link actionableUpdateCount});\n * - **the HQ Edge runtime update, as one** — a different deliverable with its\n * own card and its own confirm flow, but still an available update, and\n * leaving it out of the only number on the sidebar hid a pending runtime\n * upgrade until the panel was opened. It counts as ONE (a runtime swap is a\n * single action, however many manifest entries it was picked from) and it\n * survives a muted plugin offer, because the two offers are independent —\n * ignoring this release set must not hide a newer runtime.\n *\n * What is NOT counted: the durable job history. It is permanent\n * (`listRecent(20)`, survives restarts), so turning any settled job — even a\n * successful install — into a \"1\" latched the badge on forever. That was the\n * original bug; a job is only allowed to speak while it is still RUNNING.\n *\n * @module\n */\nimport {\n TERMINAL_PHASES,\n runtimeUpdateOffered,\n type PluginUpdateReport,\n type UpdateJob,\n} from './api.js'\nimport { actionableUpdateCount } from './version.js'\n\n/**\n * The badge value for one snapshot of `(report, active job)`.\n *\n * @param report - the latest plugin-update report, or null before the first\n * read. Null contributes nothing (never a placeholder count).\n * @param activeJob - the job the server reports as active, or null when idle.\n * A RUNNING job counts as 1 only when there is nothing actionable to show,\n * so the row still signals in-flight work; a TERMINAL job contributes\n * nothing — it is history.\n * @returns the number to render; 0 means \"no badge at all\".\n */\nexport function badgeCountFor(\n report: PluginUpdateReport | null,\n activeJob: UpdateJob | null,\n): number {\n if (report !== null) {\n // Plugin items already collapse to 0 when the offer is muted; the runtime\n // is an independent offer and keeps its own count.\n const plugin = actionableUpdateCount(report)\n const runtime = runtimeUpdateOffered(report.runtime) ? 1 : 0\n const total = plugin + runtime\n if (total > 0) return total\n }\n if (activeJob !== null && !TERMINAL_PHASES.has(activeJob.phase)) return 1\n return 0\n}\n","/**\n * `@huaqiu/dsh-tool-plugin-update` — browser half.\n *\n * Mounts one sidebar toolbutton in the Huaqiu family block (\"插件更新\") with a\n * numeric badge equal to the number of updates still on offer:\n *\n * - the actionable plugin items (spec §6.3/§7.1 — compatible `update` +\n * `install` after host/HQ-Edge filtering, minus the ignored offer and any\n * muted item), PLUS\n * - one for the HQ Edge runtime update when hq-edge reports one (a separate\n * deliverable with its own card, still a pending update — see `badge.ts`).\n *\n * The durable job history never contributes (a settled job is not an update);\n * a job that is still running is the only non-offer allowed to show a count.\n * Click opens the review/confirm/ignore modal.\n *\n * The authoritative state is the hq-edge report fetched through the plugin's\n * same-origin forwarding routes. A tiny in-memory store owns the latest report\n * + installing flag; the sidebar badge and the modal subscribe to it. No\n * timers beyond the install-poll and an idle 60 s refresh.\n *\n * @module\n */\nimport {\n HUAQIU_SIDEBAR_ENTRY_FAMILY,\n detectHostLocale,\n mountSidebarEntry,\n observeHostLocale,\n type HostLocale,\n} from '@huaqiu/dsh-sidebar-entry'\nimport {\n checkUpdates,\n fetchActivity,\n fetchJobStatus,\n fetchReport,\n TERMINAL_PHASES,\n type PluginUpdateReport,\n type UpdateActivity,\n type UpdateJob,\n type UpdateJobStatus,\n} from './api.js'\nimport { COPY } from './copy.js'\nimport { PLUGIN_ICON } from './icons.js'\nimport { openUpdateView, type UpdateStoreLike } from './update-view.js'\nimport { badgeCountFor } from './badge.js'\n\n/** Stable data attributes identifying the injected entry row. */\nexport const UPDATE_ENTRY_SELECTOR = '[data-hq-plugin-update-entry]'\nexport const UPDATE_ENTRY_ATTRIBUTE = 'data-hq-plugin-update-entry'\n\n/** L2 semantic plugin id: rows carry data-dsh-plugin + data-dsh-part. */\nconst PLUGIN_ID = 'huaqiu-plugin-update'\n\n/** Row class names (defined by {@link ENTRY_CSS_TEXT}). */\nconst ENTRY_CSS: Record<string, string> = {\n entry: 'hqpu-sidebar-entry',\n entryIcon: 'hqpu-sidebar-entry__icon',\n entryLabel: 'hqpu-sidebar-entry__label',\n entryBadge: 'hqpu-sidebar-entry__badge',\n}\n\n/** Localized row copy: zh is the fallback, matching the family convention. */\nfunction entryCopy(lang: HostLocale | undefined): { label: string; tooltip: string } {\n return lang === 'en'\n ? { label: COPY.en.entryLabel, tooltip: COPY.en.entryTooltip }\n : { label: COPY.zh.entryLabel, tooltip: COPY.zh.entryTooltip }\n}\n\n/** The sidebar glyph — the same pinwheel the shell's own Plugins entry draws\n * (`IconPluginPinwheelOutlineRegular`), so both rows read as one feature. */\nexport const UPDATE_ICON = PLUGIN_ICON(16)\n\n/** Sidebar entry styles — matched to the family rows (same inset/padding/gap\n * as the shell's own Plugins entry, `SidebarRoot.module.css` `.panelRow`).\n * Every colour rides a `--dsw-alias-*` token so the row follows the shell's\n * light/dark theme; the badge foreground uses the toast label, the shell's\n * \"always light ink on a saturated surface\" token. */\nconst ENTRY_CSS_TEXT = `\n.hqpu-sidebar-entry {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n margin: 0 2px;\n min-height: 36px;\n padding: 7px 8px;\n background: transparent;\n border: none;\n border-radius: var(--dsw-radius-md);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n font: inherit;\n line-height: 22px;\n text-align: left;\n white-space: nowrap;\n}\n.hqpu-sidebar-entry:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.hqpu-sidebar-entry[data-active] {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n.hqpu-sidebar-entry:focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: -2px;\n}\n.hqpu-sidebar-entry__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n}\n.hqpu-sidebar-entry__icon svg {\n display: block;\n width: 16px;\n height: 16px;\n}\n.hqpu-sidebar-entry__label {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n flex: 1;\n}\n.hqpu-sidebar-entry__badge {\n flex: none;\n min-width: 16px;\n height: 16px;\n padding: 0 5px;\n border-radius: 8px;\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-toast-label);\n font-size: 11px;\n line-height: 16px;\n font-weight: 500;\n text-align: center;\n font-variant-numeric: tabular-nums;\n}\n/* Collapsed rail: icon-only, centered — same 36px square the shell uses.\n The update-count badge moves to the button's bottom-right corner. The\n sidebar-entry helper only renders the badge element while count > 0, so a\n zero count shows a bare icon with no dot. */\n[data-sidebar-collapsed] .hqpu-sidebar-entry {\n position: relative;\n justify-content: center;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n}\n[data-sidebar-collapsed] .hqpu-sidebar-entry__icon svg {\n width: 18px;\n height: 18px;\n}\n[data-sidebar-collapsed] .hqpu-sidebar-entry__label {\n display: none;\n}\n[data-sidebar-collapsed] .hqpu-sidebar-entry__badge {\n /* Corner count badge: overlaps the icon's bottom-right like a notification\n dot. The ring uses the rail's own fill (HuaQiu override when the theme is\n mounted, shell layer-1 otherwise) so it reads on any sidebar surface. */\n position: absolute;\n right: 0;\n bottom: 0;\n min-width: 14px;\n height: 14px;\n padding: 0 3px;\n border-radius: 7px;\n font-size: 9px;\n line-height: 14px;\n box-shadow: 0 0 0 1.5px var(--dsw-specific-sidebar-fill, var(--dsw-alias-bg-layer-1));\n transform: translate(12%, 12%);\n}\n`\n\n/**\n * View styles (hqpu-*). Plain overlay + panel, drawn with the SAME design\n * tokens and geometry the shell's own plugin manager uses\n * (`ui-plugin-manager/src/client/PluginManagerPage.module.css` +\n * `ui-primitives/src/{Modal,Toast,Tag,StateDot}.module.css`), so this page\n * reads as part of the product rather than a bolt-on.\n *\n * Two rules keep it honest across themes:\n * 1. every colour is a `--dsw-alias-*` token — no literal hex/rgb, so light\n * and dark both come out right (the old sheet invented\n * `--dsw-alias-bg-surface` / `--dsw-alias-line-border` / `--dsw-alias-bg-badge`,\n * none of which exist, and so always fell back to a white panel);\n * 2. `color-mix()` tints come from the state token itself, so a palette\n * change moves fill and ink together.\n *\n * The chrome (panel, scrim, header, footer) follows `ui-primitives/Modal`,\n * because this view IS a dialog; the body follows `PluginManagerPage`, because\n * that is the content it heads. The one token the bundled shell does not\n * publish is backfilled by {@link ensureDesignTokens} — see the note there.\n */\nconst VIEW_CSS_TEXT = `\n/* ── Overlay + panel ─────────────────────────────────────────────────────\n * A 360 px card, the width the prototype fixes: the panel is a decision\n * surface inside the EDA workbench, not a page, and 360 is the width at which\n * a card's title, version row and two actions still fit on one line each. */\n.hqpu-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n /* The frame's own top inset keeps a tall card clear of the window chrome. */\n padding: max(24px, var(--dsh-frame-overlay-top, 24px)) 24px;\n}\n.hqpu-scrim {\n position: absolute;\n /* Only paint excludes the native caption; the root still blocks clicks. */\n inset: var(--dsh-frame-chrome-top, 0px) 0 0;\n background: var(--dsw-alias-bg-mask-3, var(--dsw-alias-bg-mask-1));\n animation: hqpu-fade var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease);\n}\n.hqpu-panel {\n position: relative;\n z-index: 1;\n display: flex;\n flex-direction: column;\n width: min(360px, calc(100vw - 24px));\n max-height: min(86vh, 720px);\n overflow: hidden;\n border: 0.5px solid var(--hq-border);\n border-radius: 12px;\n background: var(--hq-bg);\n color: var(--hq-text);\n box-shadow: var(--dsw-elevation-prominent, 0 16px 48px rgba(0, 0, 0, 0.24));\n animation: hqpu-panel-in var(--ds-transition-duration, 0.2s) var(--ds-ease-in-out, ease);\n}\n.hqpu-panel:focus {\n outline: none;\n}\n.hqpu-panel *,\n.hqpu-panel *::before,\n.hqpu-panel *::after {\n box-sizing: border-box;\n}\n/* 'hidden' is the one display switch this sheet uses for drawers, so it has to\n * beat the layout rules below it. */\n.hqpu-panel [hidden] {\n display: none !important;\n}\n\n/* The palette, expressed once. Every rule below reads these aliases, so a\n * theme switch (or the HuaQiu brand override) needs no rule to change. */\n.hqpu-panel,\n.hqpu-panel * {\n --hq-bg: var(--dsw-alias-bg-layer-1);\n --hq-card: var(--dsw-alias-bg-layer-2);\n --hq-card-soft: var(--dsw-alias-bg-overlay);\n --hq-border: var(--dsw-alias-border-l2);\n --hq-border-strong: var(--dsw-alias-border-l3);\n --hq-hover: var(--dsw-alias-interactive-bg-hover);\n --hq-text: var(--dsw-alias-label-primary);\n --hq-muted: var(--dsw-alias-label-secondary);\n --hq-faint: var(--dsw-alias-label-tertiary);\n --hq-brand: var(--dsw-alias-brand-primary);\n --hq-brand-soft: var(--dsw-alias-button-primary-hover);\n --hq-brand-dim: var(--dsw-alias-button-primary-dimmed);\n --hq-emerald: var(--dsw-alias-state-success-primary);\n --hq-amber: var(--dsw-alias-state-warn-primary);\n --hq-amber-ink: var(--dsw-alias-state-warn-label, var(--dsw-alias-state-warn-primary));\n /* Ink on an amber fill. Fixed rather than themed: warn-primary is a bright\n * amber in both themes, so the readable ink is the same warm near-black. */\n --hq-on-amber: #2b1a00;\n --hq-rose: var(--dsw-alias-state-error-primary);\n /* Ink on a saturated fill. 'toast-label' is white in both themes and is\n * backfilled by ensureDesignTokens when the shell omits it. */\n --hq-on-accent: var(--dsw-alias-brand-primary-invert, var(--dsw-alias-toast-label, #fff));\n --hq-mono: ui-monospace, SFMono-Regular, Menlo, monospace;\n /* One type scale: 9.5 / 10.5 / 11.5 / 12 / 13. Nothing inline, nothing\n * inherited from the shell's 20 px body default. */\n --hq-fs-micro: 9.5px;\n --hq-fs-meta: 10.5px;\n --hq-fs-body: 11.5px;\n --hq-fs-title: 13px;\n}\n\n@keyframes hqpu-fade {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n@keyframes hqpu-panel-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: none; }\n}\n\n/* ── Header ──────────────────────────────────────────────────────────────\n * Title + the set version / check time, then four icon actions in the order\n * the prototype fixes: refresh · sources · job log · close. */\n.hqpu-head {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 0.5px solid var(--hq-border);\n background: var(--hq-bg);\n}\n.hqpu-head-main {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.hqpu-brand-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 28px;\n height: 28px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--hq-brand) 12%, transparent);\n color: var(--hq-brand-soft);\n}\n.hqpu-brand-icon svg {\n display: block;\n}\n.hqpu-title-block {\n min-width: 0;\n}\n.hqpu-title {\n margin: 0;\n font-size: var(--hq-fs-title);\n font-weight: 600;\n line-height: 17px;\n color: var(--hq-text);\n}\n.hqpu-sub {\n margin: 1px 0 0;\n font-size: var(--hq-fs-meta);\n line-height: 14px;\n color: var(--hq-faint);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.hqpu-toolbar {\n display: flex;\n align-items: center;\n flex: none;\n gap: 1px;\n}\n.hqpu-icon-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 28px;\n height: 28px;\n padding: 0;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: var(--hq-muted);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.hqpu-icon-btn svg {\n display: block;\n}\n.hqpu-icon-btn:hover:not(:disabled) {\n background: var(--hq-hover);\n color: var(--hq-text);\n}\n.hqpu-icon-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n/* A toggled toolbar button (sources, job log) reads as \"on\", not just hovered —\n * the pressed state is the only hint that a drawer is open behind the body. */\n.hqpu-icon-btn[aria-pressed='true'] {\n background: var(--hq-hover);\n color: var(--hq-text);\n}\n.hqpu-icon-btn--close {\n margin-left: 4px;\n color: var(--hq-faint);\n}\n.hqpu-icon-btn--sm {\n width: 24px;\n height: 24px;\n border-radius: 6px;\n}\n\n/* ── Settings strip (gear) ───────────────────────────────────────────────\n * Above the body, never inside it: opening the source editor must not reflow\n * the list the person was reading. */\n.hqpu-settings {\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 0.5px solid var(--hq-border);\n background: var(--hq-card-soft);\n}\n.hqpu-settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-settings-title {\n font-size: var(--hq-fs-meta);\n font-weight: 600;\n color: var(--hq-muted);\n}\n.hqpu-origin-badge {\n flex: none;\n padding: 1px 6px;\n border: 0.5px solid;\n border-radius: 4px;\n font-size: var(--hq-fs-micro);\n}\n.hqpu-origin-badge[data-origin='config'] {\n color: var(--hq-emerald);\n border-color: color-mix(in srgb, var(--hq-emerald) 40%, transparent);\n background: color-mix(in srgb, var(--hq-emerald) 10%, transparent);\n}\n.hqpu-origin-badge[data-origin='override'] {\n color: var(--hq-amber-ink);\n border-color: color-mix(in srgb, var(--hq-amber) 40%, transparent);\n background: color-mix(in srgb, var(--hq-amber) 10%, transparent);\n}\n.hqpu-field {\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.hqpu-field-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-field label {\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-source-input {\n width: 100%;\n height: 26px;\n padding: 0 8px;\n border: 0.5px solid var(--hq-border-strong);\n border-radius: 6px;\n background: var(--hq-bg);\n color: var(--hq-muted);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-source-input:disabled {\n opacity: 0.6;\n}\n.hqpu-settings-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n padding-top: 2px;\n}\n\n/* ── Body: scroll + sticky footer + the log drawer over both ─────────────\n * '.hqpu-main' is the positioning box; the drawer covers the body AND the\n * footer, so an open log cannot be clicked through. */\n.hqpu-main {\n position: relative;\n display: flex;\n flex: 1;\n flex-direction: column;\n min-height: 0;\n}\n/* While the job-log drawer is open it covers this whole box; on a short body\n * the box would collapse to nothing, so give it a floor. Set/cleared from\n * update-view.ts (the drawer is absolutely positioned and cannot size it). */\n.hqpu-main[data-log-open='1'] {\n min-height: 340px;\n}\n.hqpu-scroll {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 12px;\n background: var(--hq-bg);\n /* Anything that scrolls inside the panel draws the l2 thumb. */\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\n --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);\n}\n.hqpu-column {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n/* A stack of groups (or the single toast host) inside the column. */\n.hqpu-stack {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.hqpu-stack:empty {\n display: none;\n}\n\n/* ── Decision band ───────────────────────────────────────────────────────\n * The one row that answers \"is there anything for me to do\", before any list. */\n.hqpu-decision {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--hq-card-soft);\n font-size: var(--hq-fs-meta);\n}\n.hqpu-decision-main {\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-decision-dot {\n width: 3px;\n height: 3px;\n border-radius: 50%;\n background: var(--hq-faint);\n}\n.hqpu-decision-sub {\n color: var(--hq-muted);\n}\n\n/* ── Section head ────────────────────────────────────────────────────────── */\n.hqpu-sec-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 0 2px 2px;\n}\n.hqpu-sec-title {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-count-pill {\n padding: 0 5px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--hq-brand) 14%, transparent);\n color: var(--hq-brand-soft);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n font-weight: 700;\n}\n\n/* ── Text links ──────────────────────────────────────────────────────────── */\n.hqpu-link {\n padding: 0;\n border: 0;\n background: none;\n color: var(--hq-muted);\n font: inherit;\n font-size: var(--hq-fs-meta);\n cursor: pointer;\n transition: color 0.15s ease;\n}\n.hqpu-link:hover {\n color: var(--hq-text);\n}\n.hqpu-link--tiny {\n font-size: var(--hq-fs-micro);\n}\n.hqpu-link--icon {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n}\n.hqpu-link--icon svg {\n display: block;\n}\n\n/* ── Plugin cards ────────────────────────────────────────────────────────── */\n.hqpu-cards {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.hqpu-card {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px;\n border: 0.5px solid var(--hq-border);\n border-radius: 8px;\n background: var(--hq-card);\n transition: border-color 0.15s ease, background 0.15s ease;\n}\n.hqpu-card:hover {\n border-color: var(--hq-border-strong);\n}\n.hqpu-card-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-card-id {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n.hqpu-avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 30px;\n height: 30px;\n border-radius: 7px;\n background: var(--hq-card-soft);\n color: var(--hq-muted);\n font-size: var(--hq-fs-micro);\n font-weight: 700;\n}\n.hqpu-card-main {\n display: flex;\n flex-direction: column;\n min-width: 0;\n gap: 1px;\n}\n.hqpu-card-title {\n font-size: var(--hq-fs-body);\n font-weight: 600;\n line-height: 15px;\n color: var(--hq-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.hqpu-card-sub {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n line-height: 12px;\n color: var(--hq-faint);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.hqpu-card-end {\n display: flex;\n align-items: center;\n flex: none;\n gap: 2px;\n}\n/* The card's own action: tinted, not filled — the filled brand fill belongs to\n * the one set-level CTA in the footer. */\n.hqpu-card-action {\n height: 24px;\n padding: 0 8px;\n border: 0;\n border-radius: 6px;\n background: color-mix(in srgb, var(--hq-brand) 12%, transparent);\n color: var(--hq-brand-soft);\n font: inherit;\n font-size: var(--hq-fs-meta);\n font-weight: 500;\n white-space: nowrap;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.hqpu-card-action:hover:not(:disabled) {\n background: var(--hq-brand);\n color: var(--hq-on-accent);\n}\n.hqpu-card-action:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.hqpu-card-foot {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-version-row {\n display: flex;\n align-items: baseline;\n gap: 4px;\n min-width: 0;\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-meta);\n color: var(--hq-muted);\n}\n.hqpu-ver-old {\n color: var(--hq-muted);\n}\n.hqpu-ver-arrow {\n color: var(--hq-border-strong);\n}\n.hqpu-ver-new {\n color: var(--hq-emerald);\n font-weight: 600;\n}\n.hqpu-ver-new--lg {\n font-size: var(--hq-fs-body);\n}\n.hqpu-ver-size {\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n.hqpu-toggle {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 0;\n border: 0;\n background: none;\n color: var(--hq-faint);\n font: inherit;\n font-size: var(--hq-fs-micro);\n white-space: nowrap;\n cursor: pointer;\n}\n.hqpu-toggle:hover {\n color: var(--hq-text);\n}\n.hqpu-toggle-chevron {\n display: inline-flex;\n transition: transform 0.15s ease;\n}\n.hqpu-toggle[data-open='true'] .hqpu-toggle-chevron {\n transform: rotate(90deg);\n}\n.hqpu-changes {\n padding-top: 8px;\n border-top: 0.5px solid var(--hq-border);\n font-size: var(--hq-fs-meta);\n color: var(--hq-muted);\n}\n.hqpu-changes-title {\n margin: 0 0 4px;\n font-size: var(--hq-fs-micro);\n font-weight: 600;\n color: var(--hq-muted);\n}\n.hqpu-changes ul {\n margin: 0;\n padding-left: 16px;\n font-size: var(--hq-fs-micro);\n line-height: 16px;\n}\n\n/* ── Collapsed groups (已忽略 / 已是最新 / 版本说明) ─────────────────────── */\n.hqpu-disc {\n padding-top: 6px;\n border-top: 0.5px solid var(--hq-border);\n}\n.hqpu-disc-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n padding: 2px 0;\n border: 0;\n background: none;\n color: var(--hq-muted);\n font: inherit;\n cursor: pointer;\n}\n.hqpu-disc-left {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.hqpu-disc-chevron {\n display: inline-flex;\n flex: none;\n color: var(--hq-faint);\n transition: transform 0.15s ease;\n}\n.hqpu-disc-head[aria-expanded='true'] .hqpu-disc-chevron {\n transform: rotate(90deg);\n}\n.hqpu-disc-title {\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-disc-count {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-meta);\n color: var(--hq-faint);\n}\n.hqpu-disc-note {\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.hqpu-disc-body {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding-top: 6px;\n}\n.hqpu-rows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.hqpu-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n background: var(--hq-card-soft);\n font-size: var(--hq-fs-meta);\n}\n.hqpu-row-name {\n font-weight: 500;\n color: var(--hq-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.hqpu-row-end {\n display: flex;\n align-items: center;\n flex: none;\n gap: 8px;\n}\n.hqpu-row-meta {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n\n/* ── Runtime card (amber: a swap is not a plugin install) ───────────────── */\n.hqpu-engine {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px;\n border: 0.5px solid color-mix(in srgb, var(--hq-amber) 32%, transparent);\n border-radius: 10px;\n background: linear-gradient(\n 135deg,\n color-mix(in srgb, var(--hq-amber) 10%, var(--hq-card)) 0%,\n var(--hq-card) 60%,\n var(--hq-card-soft) 100%\n );\n}\n.hqpu-engine-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-engine-title {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-engine-bolt {\n display: inline-flex;\n color: var(--hq-amber);\n}\n.hqpu-chip {\n flex: none;\n padding: 1px 6px;\n border: 0.5px solid var(--hq-border-strong);\n border-radius: 4px;\n background: var(--hq-card);\n color: var(--hq-muted);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-engine-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 10px;\n}\n.hqpu-engine-info {\n min-width: 0;\n}\n.hqpu-engine-new {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.hqpu-engine-size {\n margin: 2px 0 0;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n}\n.hqpu-engine-state {\n flex: none;\n max-width: 45%;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n text-align: right;\n}\n.hqpu-engine-error {\n margin: 0;\n font-size: var(--hq-fs-micro);\n color: var(--hq-rose);\n}\n.hqpu-tag--brand {\n flex: none;\n padding: 0 4px;\n border-radius: 3px;\n background: color-mix(in srgb, var(--hq-amber) 14%, transparent);\n color: var(--hq-amber-ink);\n font-size: var(--hq-fs-micro);\n font-weight: 500;\n}\n.hqpu-btn--engine {\n flex: none;\n height: 26px;\n padding: 0 10px;\n border: 0;\n border-radius: 6px;\n background: var(--hq-amber);\n color: var(--hq-on-amber);\n font: inherit;\n font-size: var(--hq-fs-meta);\n font-weight: 500;\n white-space: nowrap;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n.hqpu-btn--engine:hover:not(:disabled) {\n background: color-mix(in srgb, var(--hq-amber) 85%, #000);\n}\n.hqpu-btn--engine:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.hqpu-engine-confirm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 0.5px solid color-mix(in srgb, var(--hq-amber) 24%, transparent);\n border-radius: 8px;\n background: color-mix(in srgb, var(--hq-amber) 10%, transparent);\n}\n.hqpu-engine-confirm-text {\n margin: 0;\n font-size: var(--hq-fs-meta);\n line-height: 16px;\n color: var(--hq-amber-ink);\n}\n.hqpu-engine-confirm-actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 6px;\n}\n.hqpu-engine-note {\n display: flex;\n align-items: center;\n gap: 4px;\n margin: 0;\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n.hqpu-engine-note svg {\n flex: none;\n color: var(--hq-amber);\n}\n\n/* ── Error / result banners ──────────────────────────────────────────────── */\n.hqpu-error {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px;\n border: 0.5px solid;\n border-radius: 8px;\n}\n.hqpu-error[data-tone='danger'] {\n border-color: color-mix(in srgb, var(--hq-rose) 35%, transparent);\n background: color-mix(in srgb, var(--hq-rose) 8%, transparent);\n}\n.hqpu-error[data-tone='warning'] {\n border-color: color-mix(in srgb, var(--hq-amber) 35%, transparent);\n background: color-mix(in srgb, var(--hq-amber) 8%, transparent);\n}\n.hqpu-error-head {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n}\n.hqpu-error-icon {\n display: inline-flex;\n flex: none;\n margin-top: 1px;\n color: var(--hq-rose);\n}\n.hqpu-error[data-tone='warning'] .hqpu-error-icon {\n color: var(--hq-amber);\n}\n.hqpu-error-text {\n min-width: 0;\n}\n.hqpu-error-title {\n margin: 0;\n font-size: var(--hq-fs-body);\n font-weight: 600;\n line-height: 15px;\n color: var(--hq-rose);\n}\n.hqpu-error[data-tone='warning'] .hqpu-error-title {\n color: var(--hq-amber-ink);\n}\n.hqpu-error-body {\n margin: 2px 0 0;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n overflow-wrap: anywhere;\n}\n.hqpu-error-code {\n display: inline-block;\n margin-top: 3px;\n padding: 0 4px;\n border-radius: 3px;\n background: color-mix(in srgb, var(--hq-rose) 12%, transparent);\n color: var(--hq-rose);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-error[data-tone='warning'] .hqpu-error-code {\n background: color-mix(in srgb, var(--hq-amber) 14%, transparent);\n color: var(--hq-amber-ink);\n}\n.hqpu-error-foot {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding-top: 6px;\n border-top: 0.5px solid color-mix(in srgb, currentColor 12%, transparent);\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-error-actions {\n display: flex;\n flex: none;\n gap: 10px;\n}\n\n.hqpu-alert {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 10px;\n border-radius: 8px;\n}\n.hqpu-alert--amber {\n border: 0.5px solid color-mix(in srgb, var(--hq-amber) 32%, transparent);\n background: color-mix(in srgb, var(--hq-amber) 10%, transparent);\n}\n.hqpu-alert-icon {\n display: inline-flex;\n flex: none;\n color: var(--hq-amber);\n}\n.hqpu-alert-main {\n min-width: 0;\n}\n.hqpu-alert-title {\n font-size: var(--hq-fs-meta);\n font-weight: 600;\n color: var(--hq-amber-ink);\n}\n.hqpu-alert-body {\n margin: 2px 0 0;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n}\n.hqpu-alert-body code {\n padding: 0 3px;\n border-radius: 3px;\n background: color-mix(in srgb, var(--hq-amber) 16%, transparent);\n font-family: var(--hq-mono);\n}\n.hqpu-alert-actions {\n margin-top: 4px;\n}\n.hqpu-alert-dismiss {\n padding: 0;\n border: 0;\n background: none;\n color: var(--hq-amber-ink);\n font: inherit;\n font-size: var(--hq-fs-micro);\n text-decoration: underline;\n cursor: pointer;\n}\n.hqpu-alert-green {\n margin: 0;\n padding: 6px 10px;\n border: 0.5px solid color-mix(in srgb, var(--hq-emerald) 30%, transparent);\n border-radius: 6px;\n background: color-mix(in srgb, var(--hq-emerald) 8%, transparent);\n color: var(--hq-emerald);\n font-size: var(--hq-fs-micro);\n}\n\n/* ── Active job ──────────────────────────────────────────────────────────── */\n.hqpu-active {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 0.5px solid color-mix(in srgb, var(--hq-brand) 40%, transparent);\n border-radius: 8px;\n background: var(--hq-card);\n}\n.hqpu-active-top {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.hqpu-active-title {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n}\n.hqpu-active-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.hqpu-pulse-dot {\n position: relative;\n flex: none;\n width: 8px;\n height: 8px;\n}\n.hqpu-pulse-dot::before {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: 50%;\n background: var(--hq-brand);\n}\n.hqpu-pulse-ring {\n position: absolute;\n inset: 0;\n border-radius: 50%;\n background: var(--hq-brand);\n opacity: 0.6;\n animation: hqpu-ping 1.4s cubic-bezier(0, 0, 0.2, 1) infinite;\n}\n@keyframes hqpu-ping {\n 75%, 100% { transform: scale(2.4); opacity: 0; }\n}\n.hqpu-active-percent {\n flex: none;\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-body);\n font-weight: 700;\n color: var(--hq-brand-soft);\n}\n.hqpu-progress {\n height: 6px;\n border-radius: 999px;\n background: var(--hq-border);\n overflow: hidden;\n}\n.hqpu-progress-fill {\n height: 100%;\n border-radius: 999px;\n background: linear-gradient(90deg, var(--hq-brand), var(--hq-brand-soft));\n transition: width 0.3s ease;\n}\n.hqpu-active-meta {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-muted);\n}\n.hqpu-num {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-active-versions {\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-active-note {\n margin: 0;\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-faint);\n}\n/* A stalled job: the card keeps its shape but stops claiming progress. The\n * pulse stops, the bar dims, and the note line carries the retry. */\n.hqpu-active[data-stalled='1'] .hqpu-pulse-ring {\n animation: none;\n opacity: 0;\n}\n.hqpu-active[data-stalled='1'] .hqpu-pulse-dot::before {\n background: var(--hq-amber);\n}\n.hqpu-active[data-stalled='1'] .hqpu-active-percent {\n color: var(--hq-amber-ink);\n}\n.hqpu-active[data-stalled='1'] .hqpu-progress-fill {\n background: color-mix(in srgb, var(--hq-amber) 55%, var(--hq-border));\n transition: none;\n}\n/* The active step stops blinking too: nothing is advancing. */\n.hqpu-active[data-stalled='1'] .hqpu-job-step--active {\n color: var(--hq-amber-ink);\n animation: none;\n}\n.hqpu-active-stalled {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--hq-amber) 10%, transparent);\n font-size: var(--hq-fs-micro);\n line-height: 14px;\n color: var(--hq-amber-ink);\n}\n.hqpu-active-stalled .hqpu-link {\n flex: none;\n font-size: var(--hq-fs-micro);\n color: var(--hq-amber-ink);\n text-decoration: underline;\n}\n.hqpu-job-steps4 {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 2px;\n padding-top: 8px;\n border-top: 0.5px solid var(--hq-border);\n text-align: center;\n font-size: var(--hq-fs-micro);\n font-weight: 500;\n}\n.hqpu-job-step--done {\n color: var(--hq-emerald);\n}\n.hqpu-job-step--active {\n color: var(--hq-brand-soft);\n animation: hqpu-step-pulse 1.2s ease-in-out infinite;\n}\n.hqpu-job-step--todo {\n color: var(--hq-faint);\n}\n@keyframes hqpu-step-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.45; }\n}\n\n/* ── Footer ──────────────────────────────────────────────────────────────── */\n.hqpu-footer {\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 12px 12px;\n border-top: 0.5px solid var(--hq-border);\n background: var(--hq-bg);\n}\n.hqpu-btn--block {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n padding: 0 12px;\n border: 0;\n border-radius: 10px;\n background: var(--hq-brand);\n color: var(--hq-on-accent);\n font: inherit;\n font-size: var(--hq-fs-body);\n font-weight: 500;\n cursor: pointer;\n box-shadow: 0 4px 12px color-mix(in srgb, var(--hq-brand) 35%, transparent);\n transition: background 0.15s ease, box-shadow 0.15s ease;\n}\n.hqpu-btn--block:hover:not(:disabled) {\n background: var(--hq-brand-soft);\n}\n/* The disabled CTA states the reason in its own label, so it must stay legible\n * (a 40 %-opacity red button reads as \"broken\", not as \"nothing to do\"). */\n.hqpu-btn--block:disabled {\n background: var(--hq-card-soft);\n color: var(--hq-faint);\n box-shadow: none;\n cursor: not-allowed;\n}\n.hqpu-cta-spinner {\n width: 13px;\n height: 13px;\n border-radius: 50%;\n border: 1.5px solid color-mix(in srgb, currentColor 30%, transparent);\n border-top-color: currentColor;\n animation: hqpu-spin 0.9s linear infinite;\n}\n@keyframes hqpu-spin {\n to { transform: rotate(360deg); }\n}\n.hqpu-foot-meta {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-height: 14px;\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n.hqpu-footnote {\n margin: 0;\n font-size: var(--hq-fs-micro);\n line-height: 13px;\n text-align: center;\n color: var(--hq-faint);\n}\n\n/* ── Job log drawer ──────────────────────────────────────────────────────\n * Covers the body and the footer, never the header: the log button and the\n * close button stay in place while it is open. */\n.hqpu-log {\n position: absolute;\n inset: 0;\n z-index: 10;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--hq-bg);\n}\n.hqpu-log-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding-bottom: 8px;\n border-bottom: 0.5px solid var(--hq-border);\n}\n.hqpu-log-title {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.hqpu-log-name {\n font-size: var(--hq-fs-body);\n font-weight: 600;\n color: var(--hq-text);\n white-space: nowrap;\n}\n.hqpu-log-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 8px;\n min-height: 0;\n overflow-y: auto;\n --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);\n}\n.hqpu-log-block {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 8px;\n border-radius: 8px;\n background: var(--hq-card-soft);\n font-family: var(--hq-mono);\n font-size: var(--hq-fs-micro);\n}\n.hqpu-log-meta {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding-bottom: 4px;\n border-bottom: 0.5px solid var(--hq-border);\n color: var(--hq-faint);\n overflow-wrap: anywhere;\n}\n.hqpu-log-line {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--hq-muted);\n line-height: 15px;\n}\n.hqpu-log-line--empty {\n color: var(--hq-faint);\n}\n.hqpu-log-line-text {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.hqpu-log-dot {\n flex: none;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--hq-border-strong);\n}\n.hqpu-log-dot[data-state='done'] { background: var(--hq-emerald); }\n.hqpu-log-dot[data-state='error'] { background: var(--hq-rose); }\n.hqpu-log-dot[data-state='warning'] { background: var(--hq-amber); }\n.hqpu-log-dot[data-state='idle'] { background: var(--hq-border-strong); }\n.hqpu-log-detail {\n display: flex;\n flex-direction: column;\n gap: 3px;\n padding: 8px;\n border: 0.5px solid var(--hq-border);\n border-radius: 8px;\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-log-detail p {\n margin: 0;\n overflow-wrap: anywhere;\n}\n.hqpu-log-recent {\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.hqpu-log-recent-title {\n margin: 0 0 2px;\n font-size: var(--hq-fs-micro);\n font-weight: 600;\n color: var(--hq-muted);\n}\n.hqpu-log-recent-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 6px;\n border-radius: 5px;\n background: var(--hq-card-soft);\n font-size: var(--hq-fs-micro);\n color: var(--hq-muted);\n}\n.hqpu-log-recent-meta {\n flex: none;\n font-family: var(--hq-mono);\n color: var(--hq-faint);\n}\n.hqpu-log-foot {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding-top: 8px;\n border-top: 0.5px solid var(--hq-border);\n}\n\n/* ── Toast ──────────────────────────────────────────────────────────────── */\n.hqpu-toast-host {\n position: absolute;\n top: 8px;\n left: 12px;\n right: 12px;\n z-index: 20;\n display: flex;\n flex-direction: column;\n gap: 6px;\n pointer-events: none;\n}\n.hqpu-toast-host:empty {\n display: none;\n}\n.hqpu-toast {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-toast-bg, var(--hq-card));\n color: var(--dsw-alias-toast-label, var(--hq-text));\n font-size: var(--hq-fs-meta);\n box-shadow: var(--dsw-shadow-lv3, 0 8px 24px rgba(0, 0, 0, 0.2));\n pointer-events: auto;\n animation: hqpu-toast-in 160ms ease-out;\n}\n.hqpu-toast-icon {\n display: inline-flex;\n flex: none;\n color: var(--hq-amber);\n}\n.hqpu-toast-icon[data-tone='success'] {\n color: var(--hq-emerald);\n}\n.hqpu-toast-icon[data-tone='error'] {\n color: var(--hq-rose);\n}\n.hqpu-toast-text {\n min-width: 0;\n flex: 1;\n overflow-wrap: anywhere;\n}\n.hqpu-toast-close {\n display: inline-flex;\n flex: none;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n opacity: 0.55;\n cursor: pointer;\n}\n.hqpu-toast-close:hover {\n opacity: 1;\n}\n@keyframes hqpu-toast-in {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: none; }\n}\n\n/* ── Badges, banners, misc shared bits ──────────────────────────────────── */\n.hqpu-tag {\n display: inline-flex;\n align-items: center;\n flex: none;\n padding: 0 6px;\n border-radius: 999px;\n font-size: var(--hq-fs-micro);\n font-weight: 500;\n line-height: 16px;\n white-space: nowrap;\n}\n.hqpu-tag[data-tone='success'] {\n background: color-mix(in srgb, var(--hq-emerald) 12%, transparent);\n color: var(--hq-emerald);\n}\n.hqpu-tag[data-tone='warning'] {\n background: color-mix(in srgb, var(--hq-amber) 14%, transparent);\n color: var(--hq-amber-ink);\n}\n.hqpu-tag[data-tone='danger'] {\n background: color-mix(in srgb, var(--hq-rose) 12%, transparent);\n color: var(--hq-rose);\n}\n.hqpu-tag[data-tone='info'] {\n background: color-mix(in srgb, var(--dsw-alias-state-business-primary, var(--hq-brand)) 12%, transparent);\n color: var(--dsw-alias-state-business-primary, var(--hq-brand));\n}\n.hqpu-tag[data-tone='neutral'] {\n background: var(--hq-card-soft);\n color: var(--hq-muted);\n}\n.hqpu-banner {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 6px;\n font-size: var(--hq-fs-micro);\n line-height: 15px;\n color: var(--hq-muted);\n}\n.hqpu-banner[data-tone='danger'] {\n background: color-mix(in srgb, var(--hq-rose) 8%, transparent);\n}\n.hqpu-banner[data-tone='danger'] span {\n color: var(--hq-rose);\n}\n.hqpu-release-summary {\n margin: 0;\n font-size: var(--hq-fs-meta);\n line-height: 16px;\n color: var(--hq-muted);\n}\n.hqpu-release-notes {\n margin: 0;\n padding-left: 16px;\n font-size: var(--hq-fs-micro);\n line-height: 16px;\n color: var(--hq-muted);\n}\n.hqpu-release-meta {\n margin: 0;\n font-size: var(--hq-fs-micro);\n color: var(--hq-faint);\n}\n.hqpu-release-link {\n font-size: var(--hq-fs-micro);\n color: var(--hq-brand-soft);\n text-decoration: none;\n}\n.hqpu-release-link:hover {\n text-decoration: underline;\n}\n.hqpu-status {\n margin: 0;\n font-size: var(--hq-fs-meta);\n line-height: 15px;\n color: var(--hq-muted);\n}\n.hqpu-empty {\n padding: 24px 0;\n text-align: center;\n font-size: var(--hq-fs-meta);\n color: var(--hq-faint);\n}\n\n/* ── Buttons (settings / log drawer) ─────────────────────────────────────── */\n.hqpu-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border: 0;\n border-radius: 8px;\n background: var(--hq-card-soft);\n color: var(--hq-text);\n font: inherit;\n font-size: var(--hq-fs-meta);\n cursor: pointer;\n}\n.hqpu-btn svg {\n display: block;\n}\n.hqpu-btn:hover:not(:disabled) {\n background: var(--hq-hover);\n}\n.hqpu-btn:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.hqpu-btn--sm {\n height: 26px;\n padding: 0 8px;\n font-size: var(--hq-fs-micro);\n}\n.hqpu-btn--ghost {\n background: transparent;\n color: var(--hq-muted);\n}\n.hqpu-btn--brand {\n background: var(--hq-brand);\n color: var(--hq-on-accent);\n}\n.hqpu-btn--brand:hover:not(:disabled) {\n background: var(--hq-brand-soft);\n}\n/* The runtime confirm is the same amber as the card it lives in (the\n * prototype's treatment): red is the plugin-set path, amber the runtime path.\n * Ink is a warm near-black because amber stays bright in both themes — white\n * on amber-500 sits near 2:1 and reads as a disabled button. */\n.hqpu-btn--engine-solid {\n height: 28px;\n padding: 0 12px;\n background: var(--hq-amber);\n color: var(--hq-on-amber);\n font-size: var(--hq-fs-meta);\n font-weight: 600;\n}\n.hqpu-btn--engine-solid:hover:not(:disabled) {\n background: color-mix(in srgb, var(--hq-amber) 85%, #000);\n}\n\n/* ── Skeleton ────────────────────────────────────────────────────────────── */\n.hqpu-skeleton-fill {\n background: var(--hq-border);\n animation: hqpu-skeleton 2s cubic-bezier(0.36, 0, 0.64, 1) infinite;\n}\n.hqpu-skeleton-title {\n width: 60%;\n height: 10px;\n border-radius: 3px;\n}\n.hqpu-skeleton-sub {\n width: 40%;\n height: 8px;\n border-radius: 3px;\n}\n.hqpu-card .hqpu-avatar.hqpu-skeleton-fill {\n background: var(--hq-border);\n}\n@keyframes hqpu-skeleton {\n 0% { opacity: 1; }\n 40% { opacity: 0.55; }\n 80%, 100% { opacity: 1; }\n}\n\n/* ── Focus + reduced motion ──────────────────────────────────────────────── */\n.hqpu-panel button:focus-visible,\n.hqpu-panel a:focus-visible,\n.hqpu-panel input:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid\n var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: 1px;\n}\n.hqpu-panel input:focus-visible {\n outline: none;\n border-color: var(--hq-brand-soft);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .hqpu-scrim,\n .hqpu-panel,\n .hqpu-skeleton-fill,\n .hqpu-cta-spinner,\n .hqpu-toast,\n .hqpu-pulse-ring,\n .hqpu-job-step--active {\n animation: none;\n }\n}\n\n`\n\nconst STYLE_TAG_ID = 'data-plugin-css=\"huaqiu-plugin-update\"'\n\n/**\n * Design tokens the bundled DSH shell may not publish.\n *\n * hq-edge ships an older `ui-theme` build. A live probe of the running shell\n * (both themes) shows 13 of the tokens this stylesheet reads are absent:\n * `--dsw-radius-{xs,sm,md,lg,xl,panel}`, `--dsw-focus-ring-width`,\n * `--dsw-focus-ring-color`, `--dsw-alias-toast-label`,\n * `--dsw-alias-state-idle-primary`, `--dsw-font-mono`,\n * `--dsh-frame-overlay-top` and `--dsh-frame-chrome-top`.\n *\n * An unresolved `var()` is not an error — it silently degrades the property to\n * its initial value. That is how this panel once rendered with\n * `border-radius: 0px` and a 3px `auto` focus ring while every colour was\n * already correct.\n *\n * The last three (`--dsw-font-mono`, `--dsh-frame-*`) are deliberately NOT\n * backfilled: the shell's own primitives rely on the inline `var(…, fallback)`\n * form for those (`Modal.module.css`, `PluginManagerPage.module.css`), so this\n * sheet does too. Everything else is backfilled below with the canonical value\n * from `ui-theme/src/styles/{base,focus,design-platform}.css`.\n *\n * Values are feature-detected at runtime and scoped to this plugin's own roots\n * rather than `:root`, so a newer shell always wins and filling a gap can never\n * restyle the host.\n */\n\n/** The two roots this plugin styles: the view overlay (built in\n * update-view.ts) and the sidebar row ({@link ENTRY_CSS}.entry). */\nconst STYLE_ROOTS = '.hqpu-overlay, .hqpu-sidebar-entry'\n\n/** The shell's dark-theme carrier — audited live as `body[data-ds-dark-theme]`. */\nconst DARK_THEME_SELECTOR = 'body[data-ds-dark-theme]'\n\n/** Same roots, one rule per theme, for tokens whose official value flips. */\nconst DARK_STYLE_ROOTS = STYLE_ROOTS.split(',')\n .map((root) => `${DARK_THEME_SELECTOR} ${root.trim()}`)\n .join(',')\n\ntype DesignTokenFallback =\n | { token: string; value: string }\n | { token: string; value: { light: string; dark: string } }\n\nconst DESIGN_TOKEN_FALLBACKS: DesignTokenFallback[] = [\n // base.css — geometry; theme-invariant.\n { token: '--dsw-radius-xs', value: '4px' },\n { token: '--dsw-radius-sm', value: '8px' },\n { token: '--dsw-radius-md', value: '12px' },\n { token: '--dsw-radius-lg', value: '16px' },\n { token: '--dsw-radius-xl', value: '20px' },\n { token: '--dsw-radius-panel', value: '28px' },\n // focus.css — the standard ring geometry. The colour already falls back\n // inline in the sheet; publishing it too keeps the ring intact if that\n // fallback is ever edited away.\n { token: '--dsw-focus-ring-width', value: '2px' },\n { token: '--dsw-focus-ring-color', value: 'var(--dsw-alias-state-business-primary, #C7000B)' },\n // design-platform.css — `--dsw-static-neutral-bluish-00` is white in both\n // themes, which is exactly what the official alias resolves to.\n { token: '--dsw-alias-toast-label', value: 'var(--dsw-static-neutral-bluish-00, #fff)' },\n // The one token whose official value flips with the theme. `--dsw-static-*`\n // are theme-invariant, so one rule per carrier reproduces neutral-300\n // (light) / neutral-600 (dark) without knowing the current theme.\n {\n token: '--dsw-alias-state-idle-primary',\n value: {\n light: 'var(--dsw-static-neutral-300, #d4d4d4)',\n dark: 'var(--dsw-static-neutral-600, #545557)',\n },\n },\n]\n\nconst TOKEN_STYLE_TAG_ID = 'data-plugin-css=\"huaqiu-plugin-update-tokens\"'\n\n/**\n * Publish canonical values for the design tokens the shell is missing.\n *\n * Idempotent and cheap: safe to call on every open, so a shell that publishes\n * its tokens late still wins (the stale fallbacks are removed in that case).\n */\nexport function ensureDesignTokens(): void {\n if (typeof document === 'undefined' || document.body === null) return\n const probe = getComputedStyle(document.body)\n const missing = DESIGN_TOKEN_FALLBACKS.filter(\n (entry) => probe.getPropertyValue(entry.token).trim() === '',\n )\n\n const existing = document.querySelector<HTMLStyleElement>(`style[${TOKEN_STYLE_TAG_ID}]`)\n if (missing.length === 0) {\n existing?.remove()\n return\n }\n\n const flat = missing.filter(\n (entry): entry is { token: string; value: string } => typeof entry.value === 'string',\n )\n const themed = missing.filter(\n (entry): entry is { token: string; value: { light: string; dark: string } } =>\n typeof entry.value !== 'string',\n )\n\n const rules: string[] = []\n const lightDecls = [\n ...flat.map((entry) => ` ${entry.token}: ${entry.value};`),\n ...themed.map((entry) => ` ${entry.token}: ${entry.value.light};`),\n ]\n rules.push(`${STYLE_ROOTS} {\\n${lightDecls.join('\\n')}\\n}`)\n if (themed.length > 0) {\n const darkDecls = themed.map((entry) => ` ${entry.token}: ${entry.value.dark};`)\n rules.push(`${DARK_STYLE_ROOTS} {\\n${darkDecls.join('\\n')}\\n}`)\n }\n\n const tag = existing ?? document.createElement('style')\n tag.dataset.pluginCss = 'huaqiu-plugin-update-tokens'\n tag.textContent = rules.join('\\n')\n if (tag.parentNode === null) document.head.appendChild(tag)\n}\n\n/** Inject styles once (idempotent). */\nexport function injectUpdateStyles(): void {\n if (typeof document === 'undefined') return\n if (document.querySelector(`style[${STYLE_TAG_ID}]`) !== null) return\n const tag = document.createElement('style')\n tag.dataset.pluginCss = 'huaqiu-plugin-update'\n tag.textContent = `${ENTRY_CSS_TEXT}\\n${VIEW_CSS_TEXT}`\n document.head.appendChild(tag)\n}\n\n/** Minimal structural client context (dsh-client-runtime provides these). */\nexport interface ClientContext {\n get<T = unknown>(name: string): T | undefined\n effect?(fn: () => (() => void) | void, label?: string): void\n}\n\n/**\n * In-memory update-report store shared by the badge and the modal.\n *\n * Exported for tests: the re-attach logic (watch a job by id, notice that the\n * server was replaced, stop claiming progress) is the part that cannot be\n * exercised through the view alone.\n */\nexport class UpdateStore implements UpdateStoreLike {\n private current: PluginUpdateReport | null = null\n private installingFlag = false\n /** The durable job the server currently reports as active (resumed across\n * panel close and DSH restart); null when idle. */\n private active: UpdateJob | null = null\n private recent: UpdateJob[] = []\n private readonly listeners = new Set<() => void>()\n private refreshTimer: number | null = null\n private pollTimer: number | null = null\n private pollBackoffMs = 1_000\n private pollInFlight = false\n /** The job this panel started (or adopted) and is following by id. */\n private watchedJobId: string | null = null\n /** hq-edge process identity from the last `/activity` read. */\n private activityBootId: string | null = null\n private stalledFlag = false\n private phaseAgeMsValue = 0\n\n report(): PluginUpdateReport | null {\n return this.current\n }\n\n installing(): boolean {\n return this.installingFlag\n }\n\n activeJob(): UpdateJob | null {\n return this.active\n }\n\n recentJobs(): UpdateJob[] {\n return [...this.recent]\n }\n\n /** True when the server says the active job stopped advancing. */\n stalled(): boolean {\n return this.stalledFlag\n }\n\n /** Milliseconds since the active job last advanced (server clock). */\n phaseAgeMs(): number {\n return this.phaseAgeMsValue\n }\n\n /**\n * Follow a job by id. The panel calls this with the `jobId` an accept\n * response handed back, so every later read asks about THAT job — the only\n * way to learn its outcome when the install replaced this very plugin (the\n * page comes back to a server that has no \"active job\" left to report).\n */\n watchJob(id: string | null): void {\n this.watchedJobId = id\n if (id !== null) this.schedulePoll()\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Sidebar badge: how many updates are still on offer — actionable plugin\n * items plus (once) the HQ Edge runtime update.\n *\n * The whole rule lives in {@link badgeCountFor}; in particular the job\n * history never contributes, which is what used to pin this at 1 forever.\n */\n badgeCount(): number {\n return badgeCountFor(this.current, this.active)\n }\n\n private emit(): void {\n for (const listener of [...this.listeners]) {\n try {\n listener()\n } catch {\n /* one bad listener must not strand the rest */\n }\n }\n }\n\n /** Adopt a fresh `/status` snapshot and (re)arm the poll loop. */\n private applyStatus(status: UpdateJobStatus): void {\n const wasActive = this.active !== null\n this.active = status.activeJob\n this.recent = status.recentJobs\n if (this.active !== null) this.watchedJobId = this.active.id\n if (wasActive && this.active === null) this.observeSettled()\n this.emit()\n this.schedulePoll()\n }\n\n /**\n * Adopt a `/activity` snapshot.\n *\n * Two things only this read can tell us:\n *\n * - `bootId` changed → hq-edge was recreated underneath the page. That is\n * what an install of this plugin's own package looks like from here: the\n * DSH process serving this route was replaced. Nothing in memory can be\n * trusted, so the installing flag is cleared and the report re-read.\n * - `stalled` → the active job has stopped advancing. The progress bar must\n * stop pretending; the panel shows a \"confirming\" note with a retry.\n */\n private applyActivity(activity: UpdateActivity): void {\n const rebooted = this.activityBootId !== null && this.activityBootId !== activity.bootId\n this.activityBootId = activity.bootId\n this.stalledFlag = activity.stalled\n this.phaseAgeMsValue = activity.phaseAgeMs\n\n const wasActive = this.active !== null\n this.active = activity.activeJob\n if (activity.job !== null) this.upsertRecent(activity.job)\n\n if (rebooted) {\n this.installingFlag = false\n this.stalledFlag = false\n this.phaseAgeMsValue = 0\n void this.refresh()\n }\n if (wasActive && this.active === null) this.observeSettled()\n this.emit()\n this.schedulePoll()\n }\n\n /**\n * The job we were following reached a terminal phase (or vanished from the\n * active slot). Clear the transient flags and re-read the report — it still\n * names the versions that job installed.\n */\n private observeSettled(): void {\n this.installingFlag = false\n this.stalledFlag = false\n this.phaseAgeMsValue = 0\n this.watchedJobId = null\n void this.refresh()\n }\n\n /** Keep one job fresh in the local history without duplicating it. */\n private upsertRecent(job: UpdateJob): void {\n const rest = this.recent.filter((entry) => entry.id !== job.id)\n this.recent = [job, ...rest].slice(0, 20)\n }\n\n /**\n * Fetch the durable job state. Called on mount, on panel open (BEFORE\n * discovery) and by the poll loop. A failure (older hq-edge with no\n * `/status`, or a transient HTTP error) is swallowed: the last known job is\n * kept and the poller backs off and retries.\n */\n async fetchStatus(): Promise<void> {\n try {\n await this.pollOnce()\n this.pollBackoffMs = 1_000\n } catch {\n this.schedulePoll()\n }\n }\n\n /**\n * One read. While a job is being followed, ask `/activity` about THAT job\n * (survives the restart an update of this plugin causes); otherwise fall back\n * to the plain `/status` list. An older hq-edge without `/activity` 404s and\n * the status read still answers.\n */\n private async pollOnce(): Promise<void> {\n const watched = this.watchedJobId ?? this.active?.id ?? null\n if (watched !== null) {\n try {\n this.applyActivity(await fetchActivity(watched))\n return\n } catch {\n /* fall through to /status: an older hq-edge has no /activity */\n }\n }\n this.applyStatus(await fetchJobStatus())\n }\n\n /** ~1 s poll while a job is active; stop on terminal; back off on error\n * without discarding the last known job; never overlap requests. */\n private schedulePoll(): void {\n if (this.pollTimer !== null) {\n window.clearTimeout(this.pollTimer)\n this.pollTimer = null\n }\n const job = this.active\n if (job === null || TERMINAL_PHASES.has(job.phase)) {\n this.pollBackoffMs = 1_000\n return\n }\n this.pollTimer = window.setTimeout(() => {\n void this.pollTick()\n }, this.pollBackoffMs)\n }\n\n private async pollTick(): Promise<void> {\n if (this.pollInFlight) {\n this.schedulePoll()\n return\n }\n this.pollInFlight = true\n try {\n await this.pollOnce()\n this.pollBackoffMs = 1_000\n } catch {\n this.pollBackoffMs = Math.min(5_000, Math.max(1_000, this.pollBackoffMs * 2))\n this.schedulePoll()\n } finally {\n this.pollInFlight = false\n }\n }\n\n async refresh(): Promise<PluginUpdateReport | null> {\n try {\n const report = await fetchReport()\n this.current = report\n this.emit()\n return report\n } catch {\n return this.current\n }\n }\n\n async check(): Promise<PluginUpdateReport | null> {\n try {\n const report = await checkUpdates()\n this.current = report\n this.emit()\n return report\n } catch {\n return this.current\n }\n }\n\n /** Mark the set as installing (set by the modal before the restart). */\n setInstalling(flag: boolean): void {\n this.installingFlag = flag\n this.emit()\n }\n\n /** Idle refresh while mounted (60 s) — no overlapping poll loop. */\n startIdleRefresh(): void {\n if (this.refreshTimer !== null) return\n this.refreshTimer = window.setInterval(() => {\n void this.refresh()\n }, 60_000) as unknown as number\n }\n\n stop(): void {\n if (this.refreshTimer !== null) {\n window.clearInterval(this.refreshTimer)\n this.refreshTimer = null\n }\n if (this.pollTimer !== null) {\n window.clearTimeout(this.pollTimer)\n this.pollTimer = null\n }\n this.listeners.clear()\n }\n}\n\n/**\n * Client cordis inject: no service dependencies — the plugin reads the\n * hq-edge report purely through same-origin fetch (forwarded by its own node\n * half). `inject` stays empty so the loader never waits on another service.\n */\nexport const inject: string[] = []\n\n/**\n * Client plugin body — mount the sidebar toolbutton + review modal.\n * @param ctx - real cordis client context.\n * @returns disposer removing the row and all subscriptions.\n */\nexport function apply(ctx: ClientContext): () => void {\n if (typeof document === 'undefined') return () => {}\n injectUpdateStyles()\n ensureDesignTokens()\n\n const store = new UpdateStore()\n const copy = { zh: COPY.zh, en: COPY.en }\n const copyFor = (): typeof COPY.zh => (locale === 'en' ? copy.en : copy.zh)\n\n // Locale: re-render the row copy when <html lang> changes. The sidebar\n // helper subscribes via `refresh.subscribe`; we fan out to its listener.\n let locale: HostLocale | undefined = detectHostLocale()\n const localeSubscribers = new Set<() => void>()\n const refreshSubscribe = (listener: () => void): (() => void) => {\n localeSubscribers.add(listener)\n return () => {\n localeSubscribers.delete(listener)\n }\n }\n const unsubscribeLocale = observeHostLocale((next) => {\n locale = next\n for (const fn of [...localeSubscribers]) {\n try {\n fn()\n } catch {\n /* one bad subscriber must not strand the rest */\n }\n }\n })\n\n const openView = (): void => {\n // Re-detect the shell's design tokens on every open: a shell that\n // publishes them after this plugin mounted must win, and one that never\n // does must keep the canonical fallbacks.\n ensureDesignTokens()\n // Resume-before-discovery: read the durable job status FIRST so an in-flight\n // install/runtime update is resumed (and its view rendered) even while the\n // update check round trip is still running. The panel itself opens from\n // whatever the store already holds; check() refreshes the list behind it.\n void store.fetchStatus()\n openUpdateView(store, copyFor())\n void store.check()\n }\n\n // While a job runs the row label shows its progress (\"更新中 42%\"), and the\n // tooltip names the phase. Store changes re-apply the label (the badge\n // subscription already re-renders the count pill).\n const statusLabel = (): { label: string; tooltip: string } => {\n const base = entryCopy(locale)\n const job = store.activeJob()\n if (job !== null) {\n const cc = copyFor()\n const pct = job.progress.percent\n const phase = cc.job.phase[job.phase] ?? job.phase\n return {\n label: pct !== null ? `${base.label} · ${Math.round(pct)}%` : `${base.label} · ${phase}`,\n tooltip: `${base.tooltip} · ${phase}`,\n }\n }\n return base\n }\n\n const disposeEntry = mountSidebarEntry({\n rowAttribute: UPDATE_ENTRY_ATTRIBUTE,\n rowSelector: UPDATE_ENTRY_SELECTOR,\n plugin: PLUGIN_ID,\n icon: UPDATE_ICON,\n css: ENTRY_CSS,\n // Read the live locale on every refresh (the locale observer fires\n // `refresh.subscribe`'s listener, which re-applies these) so the row\n // re-localizes when the host UI language changes — matching the sibling\n // family rows, which read a live `currentLang` variable the same way.\n label: () => statusLabel().label,\n tooltip: () => statusLabel().tooltip,\n refresh: { subscribe: refreshSubscribe },\n badge: {\n subscribe: (listener) => store.subscribe(listener),\n count: () => store.badgeCount(),\n },\n onToggle: openView,\n position: 'after',\n familySelectors: HUAQIU_SIDEBAR_ENTRY_FAMILY,\n })\n\n // Store changes (active-job percentage, result dot) also re-apply the row\n // label, not just the badge count.\n store.subscribe(() => {\n for (const fn of [...localeSubscribers]) {\n try {\n fn()\n } catch {\n /* one bad subscriber must not strand the rest */\n }\n }\n })\n\n store.startIdleRefresh()\n void store.refresh()\n void store.fetchStatus()\n\n const dispose = (): void => {\n try {\n disposeEntry()\n } catch {\n /* already disposed */\n }\n unsubscribeLocale()\n store.stop()\n }\n if (typeof ctx.effect === 'function') {\n // `ctx.effect` takes a SETUP function and calls it immediately, keeping\n // whatever it returns as the teardown. Passing `dispose` itself would run\n // the teardown synchronously — disconnecting the sidebar-entry observers\n // and stopping the store before the shell ever renders the sidebar, so the\n // row could never be placed. Hand it a factory that returns the teardown\n // instead (the same shape the sibling tool plugins use).\n ctx.effect(() => dispose, 'plugin-update')\n }\n return dispose\n}\n"],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0hDtB,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BrB,MAAM,cAAc;;EAGpB,MAAM,sBAAsB;;EAG5B,MAAM,mBAAmB,YAAY,MAAM,GAAG,CAAC,CAC5C,KAAK,SAAS,GAAG,oBAAoB,GAAG,KAAK,KAAK,GAAG,CAAC,CACtD,KAAK,GAAG;EAMX,MAAM,yBAAgD;GAEpD;IAAE,OAAO;IAAmB,OAAO;GAAM;GACzC;IAAE,OAAO;IAAmB,OAAO;GAAM;GACzC;IAAE,OAAO;IAAmB,OAAO;GAAO;GAC1C;IAAE,OAAO;IAAmB,OAAO;GAAO;GAC1C;IAAE,OAAO;IAAmB,OAAO;GAAO;GAC1C;IAAE,OAAO;IAAsB,OAAO;GAAO;GAI7C;IAAE,OAAO;IAA0B,OAAO;GAAM;GAChD;IAAE,OAAO;IAA0B,OAAO;GAAmD;GAG7F;IAAE,OAAO;IAA2B,OAAO;GAA4C;GAIvF;IACE,OAAO;IACP,OAAO;KACL,OAAO;KACP,MAAM;IACR;GACF;EACF;EAEA,MAAM,qBAAqB;;;;;;;EAQ3B,SAAgB,qBAA2B;GACzC,IAAI,OAAO,aAAa,eAAe,SAAS,SAAS,MAAM;GAC/D,MAAM,QAAQ,iBAAiB,SAAS,IAAI;GAC5C,MAAM,UAAU,uBAAuB,QACpC,UAAU,MAAM,iBAAiB,MAAM,KAAK,CAAC,CAAC,KAAK,MAAM,EAC5D;GAEA,MAAM,WAAW,SAAS,cAAgC,SAAS,mBAAmB,EAAE;GACxF,IAAI,QAAQ,WAAW,GAAG;IACxB,UAAU,OAAO;IACjB;GACF;GAEA,MAAM,OAAO,QAAQ,QAClB,UAAqD,OAAO,MAAM,UAAU,QAC/E;GACA,MAAM,SAAS,QAAQ,QACpB,UACC,OAAO,MAAM,UAAU,QAC3B;GAEA,MAAM,QAAkB,CAAC;GACzB,MAAM,aAAa,CACjB,GAAG,KAAK,KAAK,UAAU,KAAK,MAAM,MAAM,IAAI,MAAM,MAAM,EAAE,GAC1D,GAAG,OAAO,KAAK,UAAU,KAAK,MAAM,MAAM,IAAI,MAAM,MAAM,MAAM,EAAE,CACpE;GACA,MAAM,KAAK,GAAG,YAAY,MAAM,WAAW,KAAK,IAAI,EAAE,IAAI;GAC1D,IAAI,OAAO,SAAS,GAAG;IACrB,MAAM,YAAY,OAAO,KAAK,UAAU,KAAK,MAAM,MAAM,IAAI,MAAM,MAAM,KAAK,EAAE;IAChF,MAAM,KAAK,GAAG,iBAAiB,MAAM,UAAU,KAAK,IAAI,EAAE,IAAI;GAChE;GAEA,MAAM,MAAM,YAAY,SAAS,cAAc,OAAO;GACtD,IAAI,QAAQ,YAAY;GACxB,IAAI,cAAc,MAAM,KAAK,IAAI;GACjC,IAAI,IAAI,eAAe,MAAM,SAAS,KAAK,YAAY,GAAG;EAC5D;;EAGA,SAAgB,qBAA2B;GACzC,IAAI,OAAO,aAAa,aAAa;GACrC,IAAI,SAAS,cAAc,SAAS,aAAa,EAAE,MAAM,MAAM;GAC/D,MAAM,MAAM,SAAS,cAAc,OAAO;GAC1C,IAAI,QAAQ,YAAY;GACxB,IAAI,cAAc,GAAG,eAAe,IAAI;GACxC,SAAS,KAAK,YAAY,GAAG;EAC/B;;;;;;;;EAeA,IAAa,cAAb,MAAoD;GAClD,UAA6C;GAC7C,iBAAyB;;;GAGzB,SAAmC;GACnC,SAA8B,CAAC;GAC/B,4BAA6B,IAAI,IAAgB;GACjD,eAAsC;GACtC,YAAmC;GACnC,gBAAwB;GACxB,eAAuB;;GAEvB,eAAsC;;GAEtC,iBAAwC;GACxC,cAAsB;GACtB,kBAA0B;GAE1B,SAAoC;IAClC,OAAO,KAAK;GACd;GAEA,aAAsB;IACpB,OAAO,KAAK;GACd;GAEA,YAA8B;IAC5B,OAAO,KAAK;GACd;GAEA,aAA0B;IACxB,OAAO,CAAC,GAAG,KAAK,MAAM;GACxB;;GAGA,UAAmB;IACjB,OAAO,KAAK;GACd;;GAGA,aAAqB;IACnB,OAAO,KAAK;GACd;;;;;;;GAQA,SAAS,IAAyB;IAChC,KAAK,eAAe;IACpB,IAAI,OAAO,MAAM,KAAK,aAAa;GACrC;GAEA,UAAU,UAAkC;IAC1C,KAAK,UAAU,IAAI,QAAQ;IAC3B,aAAa;KACX,KAAK,UAAU,OAAO,QAAQ;IAChC;GACF;;;;;;;;GASA,aAAqB;IACnB,OAAO,cAAc,KAAK,SAAS,KAAK,MAAM;GAChD;GAEA,OAAqB;IACnB,KAAK,MAAM,YAAY,CAAC,GAAG,KAAK,SAAS,GACvC,IAAI;KACF,SAAS;IACX,QAAQ,CAER;GAEJ;;GAGA,YAAoB,QAA+B;IACjD,MAAM,YAAY,KAAK,WAAW;IAClC,KAAK,SAAS,OAAO;IACrB,KAAK,SAAS,OAAO;IACrB,IAAI,KAAK,WAAW,MAAM,KAAK,eAAe,KAAK,OAAO;IAC1D,IAAI,aAAa,KAAK,WAAW,MAAM,KAAK,eAAe;IAC3D,KAAK,KAAK;IACV,KAAK,aAAa;GACpB;;;;;;;;;;;;;GAcA,cAAsB,UAAgC;IACpD,MAAM,WAAW,KAAK,mBAAmB,QAAQ,KAAK,mBAAmB,SAAS;IAClF,KAAK,iBAAiB,SAAS;IAC/B,KAAK,cAAc,SAAS;IAC5B,KAAK,kBAAkB,SAAS;IAEhC,MAAM,YAAY,KAAK,WAAW;IAClC,KAAK,SAAS,SAAS;IACvB,IAAI,SAAS,QAAQ,MAAM,KAAK,aAAa,SAAS,GAAG;IAEzD,IAAI,UAAU;KACZ,KAAK,iBAAiB;KACtB,KAAK,cAAc;KACnB,KAAK,kBAAkB;KACvB,KAAU,QAAQ;IACpB;IACA,IAAI,aAAa,KAAK,WAAW,MAAM,KAAK,eAAe;IAC3D,KAAK,KAAK;IACV,KAAK,aAAa;GACpB;;;;;;GAOA,iBAA+B;IAC7B,KAAK,iBAAiB;IACtB,KAAK,cAAc;IACnB,KAAK,kBAAkB;IACvB,KAAK,eAAe;IACpB,KAAU,QAAQ;GACpB;;GAGA,aAAqB,KAAsB;IACzC,MAAM,OAAO,KAAK,OAAO,QAAQ,UAAU,MAAM,OAAO,IAAI,EAAE;IAC9D,KAAK,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE;GAC1C;;;;;;;GAQA,MAAM,cAA6B;IACjC,IAAI;KACF,MAAM,KAAK,SAAS;KACpB,KAAK,gBAAgB;IACvB,QAAQ;KACN,KAAK,aAAa;IACpB;GACF;;;;;;;GAQA,MAAc,WAA0B;IACtC,MAAM,UAAU,KAAK,gBAAgB,KAAK,QAAQ,MAAM;IACxD,IAAI,YAAY,MACd,IAAI;KACF,KAAK,cAAc,MAAM,cAAc,OAAO,CAAC;KAC/C;IACF,QAAQ,CAER;IAEF,KAAK,YAAY,MAAM,eAAe,CAAC;GACzC;;;GAIA,eAA6B;IAC3B,IAAI,KAAK,cAAc,MAAM;KAC3B,OAAO,aAAa,KAAK,SAAS;KAClC,KAAK,YAAY;IACnB;IACA,MAAM,MAAM,KAAK;IACjB,IAAI,QAAQ,QAAQ,gBAAgB,IAAI,IAAI,KAAK,GAAG;KAClD,KAAK,gBAAgB;KACrB;IACF;IACA,KAAK,YAAY,OAAO,iBAAiB;KACvC,KAAU,SAAS;IACrB,GAAG,KAAK,aAAa;GACvB;GAEA,MAAc,WAA0B;IACtC,IAAI,KAAK,cAAc;KACrB,KAAK,aAAa;KAClB;IACF;IACA,KAAK,eAAe;IACpB,IAAI;KACF,MAAM,KAAK,SAAS;KACpB,KAAK,gBAAgB;IACvB,QAAQ;KACN,KAAK,gBAAgB,KAAK,IAAI,KAAO,KAAK,IAAI,KAAO,KAAK,gBAAgB,CAAC,CAAC;KAC5E,KAAK,aAAa;IACpB,UAAU;KACR,KAAK,eAAe;IACtB;GACF;GAEA,MAAM,UAA8C;IAClD,IAAI;KACF,MAAM,SAAS,MAAM,YAAY;KACjC,KAAK,UAAU;KACf,KAAK,KAAK;KACV,OAAO;IACT,QAAQ;KACN,OAAO,KAAK;IACd;GACF;GAEA,MAAM,QAA4C;IAChD,IAAI;KACF,MAAM,SAAS,MAAM,aAAa;KAClC,KAAK,UAAU;KACf,KAAK,KAAK;KACV,OAAO;IACT,QAAQ;KACN,OAAO,KAAK;IACd;GACF;;GAGA,cAAc,MAAqB;IACjC,KAAK,iBAAiB;IACtB,KAAK,KAAK;GACZ;;GAGA,mBAAyB;IACvB,IAAI,KAAK,iBAAiB,MAAM;IAChC,KAAK,eAAe,OAAO,kBAAkB;KAC3C,KAAU,QAAQ;IACpB,GAAG,GAAM;GACX;GAEA,OAAa;IACX,IAAI,KAAK,iBAAiB,MAAM;KAC9B,OAAO,cAAc,KAAK,YAAY;KACtC,KAAK,eAAe;IACtB;IACA,IAAI,KAAK,cAAc,MAAM;KAC3B,OAAO,aAAa,KAAK,SAAS;KAClC,KAAK,YAAY;IACnB;IACA,KAAK,UAAU,MAAM;GACvB;EACF;;;;;;EAOA,MAAa,SAAmB,CAAC;;;;;;EAOjC,SAAgB,MAAM,KAAgC;GACpD,IAAI,OAAO,aAAa,aAAa,aAAa,CAAC;GACnD,mBAAmB;GACnB,mBAAmB;GAEnB,MAAM,QAAQ,IAAI,YAAY;GAC9B,MAAM,OAAO;IAAE,IAAI,KAAK;IAAI,IAAI,KAAK;GAAG;GACxC,MAAM,gBAAiC,WAAW,OAAO,KAAK,KAAK,KAAK;GAIxE,IAAI,SAAiC,iBAAiB;GACtD,MAAM,oCAAoB,IAAI,IAAgB;GAC9C,MAAM,oBAAoB,aAAuC;IAC/D,kBAAkB,IAAI,QAAQ;IAC9B,aAAa;KACX,kBAAkB,OAAO,QAAQ;IACnC;GACF;GACA,MAAM,oBAAoB,mBAAmB,SAAS;IACpD,SAAS;IACT,KAAK,MAAM,MAAM,CAAC,GAAG,iBAAiB,GACpC,IAAI;KACF,GAAG;IACL,QAAQ,CAER;GAEJ,CAAC;GAED,MAAM,iBAAuB;IAI3B,mBAAmB;IAKnB,MAAW,YAAY;IACvB,eAAe,OAAO,QAAQ,CAAC;IAC/B,MAAW,MAAM;GACnB;GAKA,MAAM,oBAAwD;IAC5D,MAAM,OAAO,UAAU,MAAM;IAC7B,MAAM,MAAM,MAAM,UAAU;IAC5B,IAAI,QAAQ,MAAM;KAChB,MAAM,KAAK,QAAQ;KACnB,MAAM,MAAM,IAAI,SAAS;KACzB,MAAM,QAAQ,GAAG,IAAI,MAAM,IAAI,UAAU,IAAI;KAC7C,OAAO;MACL,OAAO,QAAQ,OAAO,GAAG,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,EAAE,KAAK,GAAG,KAAK,MAAM,KAAK;MACjF,SAAS,GAAG,KAAK,QAAQ,KAAK;KAChC;IACF;IACA,OAAO;GACT;GAEA,MAAM,eAAe,kBAAkB;IACrC,cAAc;IACd,aAAa;IACb,QAAQ;IACR,MAAM;IACN,KAAK;IAKL,aAAa,YAAY,CAAC,CAAC;IAC3B,eAAe,YAAY,CAAC,CAAC;IAC7B,SAAS,EAAE,WAAW,iBAAiB;IACvC,OAAO;KACL,YAAY,aAAa,MAAM,UAAU,QAAQ;KACjD,aAAa,MAAM,WAAW;IAChC;IACA,UAAU;IACV,UAAU;IACV,iBAAiB;GACnB,CAAC;GAID,MAAM,gBAAgB;IACpB,KAAK,MAAM,MAAM,CAAC,GAAG,iBAAiB,GACpC,IAAI;KACF,GAAG;IACL,QAAQ,CAER;GAEJ,CAAC;GAED,MAAM,iBAAiB;GACvB,MAAW,QAAQ;GACnB,MAAW,YAAY;GAEvB,MAAM,gBAAsB;IAC1B,IAAI;KACF,aAAa;IACf,QAAQ,CAER;IACA,kBAAkB;IAClB,MAAM,KAAK;GACb;GACA,IAAI,OAAO,IAAI,WAAW,YAOxB,IAAI,aAAa,SAAS,eAAe;GAE3C,OAAO;EACT"}