dsh-m 0.9.0 → 0.9.2

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/README.en.md CHANGED
@@ -48,6 +48,18 @@ The default registry includes DSH Skins, ModSearch, the Lark / QQ / Weixin / WeC
48
48
 
49
49
  The restart chain below applies to **Web only** (Desktop takes effect via the official app lifecycle: after installing, quit and reopen the Desktop app — see the 0.9.0 section). After any mutation, the already-open Market and Installed views refresh the profile state together, keeping badges and cards in sync without closing and reopening the marketplace; a "⚡ Restart" banner appears — under systemd, the DSH launcher's `appExit` hook hands the restart back to a unit configured with `Restart=on-failure` or `Restart=always`, avoiding a `systemctl` helper inside the unit cgroup that is about to stop; if `appExit` is unavailable, the fallback uses a manager-owned transient `systemd-run` service and only then a detached helper. The client confirms the replacement by boot id and dismisses the banner, leaving DSH Web's own background connection recovery in control; it does not force a full-page reload during the auth/route handoff. The restart chain has been live-verified on the current DSH Web `0.2.0-rc.2` (2026-10-01): dsh-m probes the unit's `Restart=` policy, hands the stop back via `appExit`, the `status=75/TEMPFAIL` exit is caught by the Restart policy and auto-started, and after recovery the page reconnects in the background with the panel fully functional. Historical scope: the `0.1.5-rc.1` era covered live `/dshm` ping and authenticated `303 → 200` checks; `0.1.2-rc.1` is covered by contract checks plus runtime shape-detection fallback (see the verified array) — with the deployment moved on, no live E2E is chased on retired generations; the transient `systemd-run` fallback only applies to hosts without `appExit` (all supported generations provide it) and stays a documented fallback rather than a release gate; repeated install/uninstall is covered by the profile-transaction test surface (compensation, post-install guard, 813 cases) plus the 0.4.0→0.4.2 consecutive-release evidence. Installs stream live pnpm progress (resolve → download → link → build).
50
50
 
51
+ ### Fixed in 0.9.2 — Header chip stuck on the old version after a service restart
52
+
53
+ - **In-place refresh once the restart is confirmed**: the moment the boot id confirms DSH Web is back, the panel re-fetches ping — the `dsh-m vX.Y.Z` chip and the profile chip sync to the new process's data instead of lingering on the old version (observed on 0.9.1: chip upgrade + restart still showed v0.9.0).
54
+ - **Refetch when the page becomes visible again**: with the panel left open while the service is restarted externally, returning to the tab refetches ping via `visibilitychange` (zero polling cost).
55
+ - **Chip version guard**: a self-check verdict whose version doesn't match the current process (stale verdict from the old process) is treated as silent, preventing "v0.9.1 ⬆ v0.9.1"-style misrenders.
56
+
57
+ ### New in 0.9.1 — Header version chip upgrade notice (lights up only when an update exists)
58
+
59
+ - **Silent by default**: the panel-header `dsh-m vX.Y.Z` chip keeps the static look introduced in 0.7.5 — up to date, failed check, or a local dev build ahead of npm (ahead) never interrupt (ahead only shows a "local dev build" hint in the hover title).
60
+ - **Lights up only when outdated**: when `self-check` (npm latest vs the installed version, read-only) finds a newer version, the chip turns into a warn state showing `⬆ v<latest>`; clicking it runs `self-upgrade` (same mutation session + post-install guard) and a "⚡ Restart" banner follows on success. On Desktop the click is rejected structurally by the capability table (409, with official lifecycle guidance).
61
+ - **TTL cache + version guard**: check results are cached in browser localStorage for 30 minutes so opening the panel doesn't hit npm every time; after an upgrade + restart the cache auto-invalidates by version. Failed checks stay silent — no error is shown.
62
+
51
63
  ### New in 0.9.0 — Official Desktop (dual profile) support
52
64
 
53
65
  - **One package, two profiles**: dsh-m now installs into the official Desktop's `desktop` profile (`~/.dsh/profiles/desktop`) alongside the Web `web` profile; the catalog, panel, and agent tools are shared, and everything manages the host's current profile (official `profileContext` as the single source of truth).
package/README.md CHANGED
@@ -48,6 +48,18 @@
48
48
 
49
49
  以下重启链路仅适用于 **Web**(Desktop 的生效走官方应用生命周期:安装完成后退出并重新打开 Desktop 应用即可,见 0.9.0 节)。安装 / 卸载 / 升级完成后,当前已打开的市场页与已装页会一起重新读取 profile 状态并同步徽标/卡片,不需要关闭后重新打开插件市场;随后出现「⚡ 一键重启」横幅——受 systemd 管理时通过 DSH launcher 的 `appExit` 交给服务的 `Restart` 策略,避免在待停止 unit 的 cgroup 内启动 `systemctl` helper;无 `appExit` 的 systemd 兜底改用 manager-owned transient `systemd-run`,最后才退回 detached-helper。客户端按 boot id 确认新进程已恢复后关闭横幅,交由 DSH Web 自身的后台连接重试恢复页面,不强制整页刷新,避免认证/路由切换期间白屏。重启链路已在当前 DSH Web `0.2.0-rc.2` 实机核验(2026-10-01):dsh-m 探测 unit `Restart=` 策略后经 `appExit` 交还 systemd,`status=75/TEMPFAIL` 退出由 Restart 策略接住自动拉起,服务恢复后页面后台重连、面板全功能可用。历史口径:`0.1.5-rc.1` 时代已核验 `/dshm` ping 与带认证 `303 → 200`;`0.1.2-rc.1` 按契约核对 + 形态探测兜底收录(verified 数组),部署面演进后不再追旧代 live E2E;transient `systemd-run` 兜底仅适用于无 `appExit` 的宿主(受支持代际均提供 `appExit`),保持设计兜底而非发布门槛;连续安装/卸载由 profile 事务测试面(补偿事务、装后守卫等 813 用例)与 0.4.0→0.4.2 连续发布实证覆盖。安装过程实时显示 pnpm 进度(解析 → 下载 → 链接 → 构建)。
50
50
 
51
+ ### 0.9.2 修复:跨服务重启后头部角标停留旧版本
52
+
53
+ - **一键重启确认新进程后就地刷新**:boot id 确认 DSH Web 已恢复的瞬间,面板立即重取 ping——`dsh-m vX.Y.Z` 角标与 profile chip 同步到新进程数据,不再停留旧版本(0.9.1 实测:芯片升级 + 重启后仍显 v0.9.0)。
54
+ - **页面回前台时重取**:面板常开、服务在后台被外部重启的场景,`visibilitychange` 回前台即重取 ping(零轮询成本)。
55
+ - **角标版本护栏**:self-check 判定携带的版本与当前进程不符(旧进程残留判定)时一律静默,杜绝「v0.9.1 ⬆ v0.9.1」式误渲染。
56
+
57
+ ### 0.9.1 新增:头部版本角标升级提示(有更新才点亮)
58
+
59
+ - **静默口径**:面板头部 `dsh-m vX.Y.Z` 角标常态维持 0.7.5 起的静态展示——已是最新、检查失败、本地 dev 版领先 npm(ahead)一律不打扰(ahead 仅在悬停 title 里提示「本地开发版」)。
60
+ - **仅 outdated 点亮**:`self-check`(npm latest vs 装机版本,只读)判定有新版本时,角标点亮为 warn 态并显示 `⬆ v<最新>`;点击即触发 `self-upgrade`(同一 mutation session + 装后守卫),成功后出「⚡ 一键重启」横幅。Desktop 下点击按能力表结构化拒绝(409,带官方生命周期指引)。
61
+ - **TTL 缓存 + 版本护栏**:检查结果在浏览器 localStorage 缓存 30 分钟,开面板不重复打 npm registry;升级重启后缓存按版本号自动作废。检查失败静默,不弹任何错误。
62
+
51
63
  ### 0.9.0 新增:官方 Desktop(双 profile)支持
52
64
 
53
65
  - **同一包、两个 profile**:dsh-m 现在可装入官方 Desktop 的 `desktop` profile(`~/.dsh/profiles/desktop`),与 Web 的 `web` profile 并列;市场目录、面板与 agent 工具同一套,管理对象始终是宿主当前 profile(官方 `profileContext` 单一事实源)。
package/lib/client.js CHANGED
@@ -965,6 +965,74 @@ var init_favorites = __esm({
965
965
  }
966
966
  });
967
967
 
968
+ // src/client/self-check.js
969
+ var self_check_exports = {};
970
+ __export(self_check_exports, {
971
+ SELF_CHECK_STORAGE_KEY: () => SELF_CHECK_STORAGE_KEY,
972
+ SELF_CHECK_TTL_MS: () => SELF_CHECK_TTL_MS,
973
+ clearSelfCheckCache: () => clearSelfCheckCache,
974
+ deriveChipState: () => deriveChipState,
975
+ readSelfCheckCache: () => readSelfCheckCache,
976
+ writeSelfCheckCache: () => writeSelfCheckCache
977
+ });
978
+ function readSelfCheckCache(storage, opts = {}) {
979
+ if (!storage || typeof storage.getItem !== "function") return null;
980
+ const now = typeof opts.now === "number" ? opts.now : Date.now();
981
+ const ttlMs = typeof opts.ttlMs === "number" ? opts.ttlMs : SELF_CHECK_TTL_MS;
982
+ let raw = null;
983
+ try {
984
+ raw = storage.getItem(SELF_CHECK_STORAGE_KEY);
985
+ } catch {
986
+ return null;
987
+ }
988
+ if (typeof raw !== "string" || raw === "") return null;
989
+ let box;
990
+ try {
991
+ box = JSON.parse(raw);
992
+ } catch {
993
+ return null;
994
+ }
995
+ if (!box || typeof box !== "object") return null;
996
+ if (typeof box.ts !== "number" || !Number.isFinite(box.ts)) return null;
997
+ if (now - box.ts > ttlMs) return null;
998
+ const data = box.data;
999
+ if (!data || typeof data !== "object" || typeof data.current !== "string") return null;
1000
+ if (typeof opts.version === "string" && opts.version !== "" && data.current !== opts.version) return null;
1001
+ return data;
1002
+ }
1003
+ function writeSelfCheckCache(storage, data, opts = {}) {
1004
+ if (!storage || typeof storage.setItem !== "function") return;
1005
+ if (!data || typeof data !== "object" || typeof data.current !== "string") return;
1006
+ const now = typeof opts.now === "number" ? opts.now : Date.now();
1007
+ try {
1008
+ storage.setItem(SELF_CHECK_STORAGE_KEY, JSON.stringify({ ts: now, data }));
1009
+ } catch {
1010
+ }
1011
+ }
1012
+ function clearSelfCheckCache(storage) {
1013
+ try {
1014
+ if (storage && typeof storage.removeItem === "function") storage.removeItem(SELF_CHECK_STORAGE_KEY);
1015
+ } catch {
1016
+ }
1017
+ }
1018
+ function deriveChipState(check, version) {
1019
+ if (!check || typeof check !== "object") return { kind: "idle" };
1020
+ if (typeof version === "string" && version !== "" && check.current !== version) return { kind: "idle" };
1021
+ if (check.outdated === true && typeof check.latest === "string" && check.latest !== "") {
1022
+ return { kind: "outdated", latest: check.latest };
1023
+ }
1024
+ if (check.ahead === true) return { kind: "ahead", latest: typeof check.latest === "string" ? check.latest : null };
1025
+ return { kind: "idle" };
1026
+ }
1027
+ var SELF_CHECK_STORAGE_KEY, SELF_CHECK_TTL_MS;
1028
+ var init_self_check = __esm({
1029
+ "src/client/self-check.js"() {
1030
+ "use strict";
1031
+ SELF_CHECK_STORAGE_KEY = "dshm-selfcheck";
1032
+ SELF_CHECK_TTL_MS = 30 * 60 * 1e3;
1033
+ }
1034
+ });
1035
+
968
1036
  // src/client/main.jsx
969
1037
  var React = require("react");
970
1038
  var rd = require("react-dom");
@@ -982,6 +1050,7 @@ var { RESTART_POLL_MS: RESTART_POLL_MS2, RESTART_DEADLINE_MS: RESTART_DEADLINE_M
982
1050
  var { refreshAfterMutation: refreshAfterMutation2 } = (init_view_refresh(), __toCommonJS(view_refresh_exports));
983
1051
  var { createOperationsStore: createOperationsStore2, restoreRecords: restoreRecords2, createOpsPump: createOpsPump2, opAppliesTo: opAppliesTo2 } = (init_operations(), __toCommonJS(operations_exports));
984
1052
  var { createFavoritesStore: createFavoritesStore2, partitionStale: partitionStale2 } = (init_favorites(), __toCommonJS(favorites_exports));
1053
+ var { readSelfCheckCache: readSelfCheckCache2, writeSelfCheckCache: writeSelfCheckCache2, clearSelfCheckCache: clearSelfCheckCache2, deriveChipState: deriveChipState2 } = (init_self_check(), __toCommonJS(self_check_exports));
985
1054
  var ZH = {
986
1055
  "market.title": "\u63D2\u4EF6\u5E02\u573A",
987
1056
  "tab.market": "\u5E02\u573A",
@@ -1206,6 +1275,8 @@ var ZH = {
1206
1275
  "notify.leftovers": "\uFF1B\u68C0\u6D4B\u5230\u7591\u4F3C\u6B8B\u7559\u6570\u636E\uFF1A{paths}",
1207
1276
  "notify.upgraded": "\u5DF2\u5347\u7EA7 {pkg}\uFF08{from} \u2192 {to}\uFF09",
1208
1277
  "notify.upgradehint": "\uFF08\u6CE8\u610F\uFF1A\u8BE5\u63D2\u4EF6\u6267\u884C\u4E86\u6784\u5EFA\u811A\u672C\uFF09",
1278
+ "notify.selfupgraded": "dsh-m \u5DF2\u5347\u7EA7\uFF08v{from} \u2192 v{to}\uFF09",
1279
+ "notify.selfupgraded.failed": "dsh-m \u5347\u7EA7\u5931\u8D25\uFF1A{err}",
1209
1280
  "failed.install": "\u5B89\u88C5\u5931\u8D25\uFF1A{err}",
1210
1281
  "failed.uninstall": "\u5378\u8F7D\u5931\u8D25\uFF1A{err}",
1211
1282
  "failed.upgrade": "\u5347\u7EA7\u5931\u8D25\uFF1A{err}",
@@ -1221,6 +1292,9 @@ var ZH = {
1221
1292
  "restart.hint.done": "\u5DF2\u8BF7\u6C42\u91CD\u542F DSH web\uFF08via {via}\uFF09\u3002\u670D\u52A1\u6062\u590D\u540E DSH Web \u4F1A\u5728\u540E\u53F0\u81EA\u52A8\u91CD\u8FDE\u3002",
1222
1293
  "profile.restartHint": "\u53D8\u66F4\u5B8C\u6210\u3002Desktop \u63D2\u4EF6\u7531\u5B98\u65B9\u5E94\u7528\u7BA1\u7406\uFF1A\u8BF7\u9000\u51FA\u5E76\u91CD\u65B0\u6253\u5F00 Desktop \u5E94\u7528\uFF08\u5173\u95ED\u7A97\u53E3\u53EF\u80FD\u53EA\u662F\u9690\u85CF\uFF09\u4EE5\u52A0\u8F7D\u65B0\u72B6\u6001\u3002",
1223
1294
  "profile.chipTitle": "\u5F53\u524D DSH profile\uFF1A{name}\uFF08Desktop \u9996\u53D1\u4EC5\u652F\u6301\u53EA\u8BFB\u5E02\u573A\u3001\u5B89\u88C5\u65B0\u5305\u4E0E\u5F00\u5173\uFF09",
1295
+ "selfupdate.available": "\u53D1\u73B0\u65B0\u7248\u672C v{v}\uFF0C\u70B9\u51FB\u5347\u7EA7",
1296
+ "selfupdate.ahead": "\u672C\u5730\u5F00\u53D1\u7248\uFF08\u9886\u5148 npm\uFF1Av{v}\uFF09",
1297
+ "selfupdate.checking": "\u68C0\u67E5\u66F4\u65B0\u4E2D\u2026",
1224
1298
  "phase.resolving": "\u89E3\u6790\u4F9D\u8D56",
1225
1299
  "phase.downloading": "\u4E0B\u8F7D",
1226
1300
  "phase.linking": "\u94FE\u63A5\u5B89\u88C5",
@@ -1461,6 +1535,8 @@ var EN = {
1461
1535
  "notify.leftovers": "; possible leftover data: {paths}",
1462
1536
  "notify.upgraded": "Upgraded {pkg} ({from} \u2192 {to})",
1463
1537
  "notify.upgradehint": " (note: this plugin ran build scripts)",
1538
+ "notify.selfupgraded": "dsh-m upgraded (v{from} \u2192 v{to})",
1539
+ "notify.selfupgraded.failed": "dsh-m upgrade failed: {err}",
1464
1540
  "failed.install": "Install failed: {err}",
1465
1541
  "failed.uninstall": "Uninstall failed: {err}",
1466
1542
  "failed.upgrade": "Upgrade failed: {err}",
@@ -1476,6 +1552,9 @@ var EN = {
1476
1552
  "restart.hint.done": "Restart requested (via {via}). DSH Web will reconnect in the background after the service returns.",
1477
1553
  "profile.restartHint": "Changes applied. Desktop plugins are managed by the official app: quit and reopen the Desktop app (closing the window may only hide it) to load the new state.",
1478
1554
  "profile.chipTitle": "Current DSH profile: {name} (Desktop first release supports read-only market, installing new packages, and toggles)",
1555
+ "selfupdate.available": "New version v{v} available \u2014 click to upgrade",
1556
+ "selfupdate.ahead": "Local dev build (ahead of npm: v{v})",
1557
+ "selfupdate.checking": "Checking for updates\u2026",
1479
1558
  "phase.resolving": "Resolving",
1480
1559
  "phase.downloading": "Downloading",
1481
1560
  "phase.linking": "Linking",
@@ -1700,6 +1779,12 @@ button.dshm-badge:hover{filter:brightness(.95)}
1700
1779
  /* \u5934\u90E8 dsh-m \u7248\u672C\u89D2\u6807\uFF1A\u7B49\u5BBD\u5C0F\u5B57\u5706\u89D2\uFF0C\u9759\u6001\u5C55\u793A\u4E0D\u52A0\u7C97\u4E0D\u53EF\u70B9\uFF080.7.5 \u8D77\u6539\u663E dsh-m \u7248\u672C\uFF09 */
1701
1780
  .dshm-dshchip{display:inline-flex;align-items:center;gap:4px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#f5f6f8);color:var(--dsw-alias-label-secondary,#4b5563);border-radius:999px;padding:5px 10px;font:11px/1 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:nowrap;flex:none}
1702
1781
  .dshm-dshchip-v{font-weight:400;color:inherit}
1782
+ /* 0.9.1\uFF1A\u4EC5 self-check \u5224 outdated \u624D\u70B9\u4EAE\u2014\u2014warn \u6001\u4E3A\u53EF\u70B9\u6309\u94AE\uFF08\u70B9\u51FB\u5373\u81EA\u5347\u7EA7\uFF09\uFF1B\u5176\u4F59\u9759\u9ED8\u590D\u7528\u9759\u6001\u6837\u5F0F */
1783
+ button.dshm-dshchip{cursor:pointer}
1784
+ button.dshm-dshchip:disabled{cursor:default;opacity:.75}
1785
+ .dshm-dshchip.warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#b45309) 45%,transparent);background:var(--dsw-alias-state-warn-tertiary,#fffbeb);color:var(--dsw-alias-state-warn-primary,#b45309)}
1786
+ .dshm-dshchip.warn:hover:not(:disabled){filter:brightness(.96)}
1787
+ .dshm-dshchip .dshm-up{font-weight:600}
1703
1788
 
1704
1789
  /* 0.4.0\uFF1A\u5F00\u5173 / \u76F8\u4F4D\u70B9 / \u517C\u5BB9\u786E\u8BA4\u5F39\u7A97 */
1705
1790
  .dshm-dot{font-size:9px;line-height:1;vertical-align:middle;margin-right:2px}
@@ -1917,7 +2002,7 @@ function TwoStepButton({ label, confirmLabel, className, onConfirm, disabled })
1917
2002
  arm ? confirmLabel : label
1918
2003
  );
1919
2004
  }
1920
- function RestartBanner({ note, onDone, desktop }) {
2005
+ function RestartBanner({ note, onDone, desktop, onRestarted }) {
1921
2006
  const [phase, setPhase] = useState("idle");
1922
2007
  const [err, setErr] = useState(null);
1923
2008
  const restart = useCallback(async () => {
@@ -1946,12 +2031,18 @@ function RestartBanner({ note, onDone, desktop }) {
1946
2031
  if (nextRestartWait2({ phase: "after-ping", bootChanged }) === "done") break;
1947
2032
  }
1948
2033
  setPhase("idle");
2034
+ if (onRestarted) {
2035
+ try {
2036
+ onRestarted();
2037
+ } catch {
2038
+ }
2039
+ }
1949
2040
  onDone(true);
1950
2041
  } catch (e) {
1951
2042
  setPhase("idle");
1952
2043
  setErr(String(e && e.message || e));
1953
2044
  }
1954
- }, [onDone]);
2045
+ }, [onDone, onRestarted]);
1955
2046
  return h(
1956
2047
  "div",
1957
2048
  { className: "dshm-banner" },
@@ -3258,10 +3349,64 @@ var TABS = [
3258
3349
  ["installed", "tab.installed", "installed"],
3259
3350
  ["settings", "tab.settings", null]
3260
3351
  ];
3261
- function DshmVersionChip({ version }) {
3352
+ function DshmVersionChip({ version, notify, initialCheck }) {
3353
+ const [check, setCheck] = useState(
3354
+ () => initialCheck !== void 0 ? initialCheck : typeof window !== "undefined" && window.localStorage ? readSelfCheckCache2(window.localStorage, { version }) : null
3355
+ );
3356
+ const [busy, setBusy] = useState(false);
3357
+ useEffect(() => {
3358
+ if (initialCheck !== void 0) return;
3359
+ let live = true;
3360
+ api("self-check").then((data) => {
3361
+ if (!live) return;
3362
+ if (data && typeof data === "object" && typeof data.current === "string") {
3363
+ writeSelfCheckCache2(typeof window !== "undefined" && window.localStorage ? window.localStorage : null, data);
3364
+ setCheck(data);
3365
+ }
3366
+ }).catch(() => {
3367
+ });
3368
+ return () => {
3369
+ live = false;
3370
+ };
3371
+ }, [initialCheck]);
3372
+ const doUpgrade = async () => {
3373
+ if (busy) return;
3374
+ setBusy(true);
3375
+ try {
3376
+ const res = await api("self-upgrade");
3377
+ clearSelfCheckCache2(typeof window !== "undefined" && window.localStorage ? window.localStorage : null);
3378
+ setCheck({ current: version, latest: null, outdated: false, ahead: false });
3379
+ notify({
3380
+ kind: "ok",
3381
+ needsRestart: true,
3382
+ text: lookup("notify.selfupgraded", { from: version, to: res.version || "latest" }) + (res.buildApprovals && res.buildApprovals.length ? lookup("notify.builds", { names: res.buildApprovals.join(", ") }) : res.fallbackAllBuilds ? lookup("notify.builds.fallback") : "")
3383
+ });
3384
+ } catch (e) {
3385
+ notify({ kind: "err", text: lookup("notify.selfupgraded.failed", { err: e && e.message || e }) });
3386
+ } finally {
3387
+ setBusy(false);
3388
+ }
3389
+ };
3390
+ const state = deriveChipState2(check, version);
3391
+ if (state.kind === "outdated") {
3392
+ return h(
3393
+ "button",
3394
+ {
3395
+ type: "button",
3396
+ className: "dshm-dshchip warn",
3397
+ title: lookup("selfupdate.available", { v: state.latest }),
3398
+ onClick: doUpgrade,
3399
+ disabled: busy
3400
+ },
3401
+ "dsh-m ",
3402
+ h("span", { className: "dshm-dshchip-v" }, `v${version}`),
3403
+ busy ? h(Spin) : h("span", { className: "dshm-up" }, `\u2B06 v${state.latest}`)
3404
+ );
3405
+ }
3406
+ const title = state.kind === "ahead" && state.latest ? lookup("selfupdate.ahead", { v: state.latest }) : `dsh-m v${version}`;
3262
3407
  return h(
3263
3408
  "span",
3264
- { className: "dshm-dshchip", title: `dsh-m v${version}` },
3409
+ { className: "dshm-dshchip", title },
3265
3410
  "dsh-m ",
3266
3411
  h("span", { className: "dshm-dshchip-v" }, `v${version}`)
3267
3412
  );
@@ -3337,7 +3482,7 @@ function MarketPanel({ onClose }) {
3337
3482
  };
3338
3483
  const [pluginVersion, setPluginVersion] = useState(null);
3339
3484
  const [profile, setProfile] = useState(null);
3340
- useEffect(() => {
3485
+ const reloadPing = useCallback(() => {
3341
3486
  let live = true;
3342
3487
  api("ping").then((r) => {
3343
3488
  if (!live) return;
@@ -3349,6 +3494,14 @@ function MarketPanel({ onClose }) {
3349
3494
  live = false;
3350
3495
  };
3351
3496
  }, []);
3497
+ useEffect(() => reloadPing(), [reloadPing]);
3498
+ useEffect(() => {
3499
+ const onVis = () => {
3500
+ if (typeof document !== "undefined" && !document.hidden) reloadPing();
3501
+ };
3502
+ document.addEventListener("visibilitychange", onVis);
3503
+ return () => document.removeEventListener("visibilitychange", onVis);
3504
+ }, [reloadPing]);
3352
3505
  const marketReloadAll = useCallback(
3353
3506
  (force) => {
3354
3507
  marketCommunity.reload(force);
@@ -3466,7 +3619,7 @@ function MarketPanel({ onClose }) {
3466
3619
  )
3467
3620
  ),
3468
3621
  h("span", { className: "dshm-spacer" }),
3469
- pluginVersion ? h(DshmVersionChip, { version: pluginVersion }) : null,
3622
+ pluginVersion ? h(DshmVersionChip, { version: pluginVersion, notify }) : null,
3470
3623
  profile && profile.kind !== "web" ? h("span", { className: "dshm-dshchip", title: lookup("profile.chipTitle", { name: profile.name }) }, profile.name) : null,
3471
3624
  // 窗口控制组(0.7.7):最大化/还原 + 关闭,连体设计(系统化窗口按钮)
3472
3625
  h(
@@ -3510,7 +3663,7 @@ function MarketPanel({ onClose }) {
3510
3663
  { className: toast.kind === "err" ? "dshm-banner err" : "dshm-banner" },
3511
3664
  h("span", { className: "dshm-banner-text" }, toast.text)
3512
3665
  ) : null,
3513
- banner ? h(RestartBanner, { note: banner.text, onDone: () => setBanner(null), desktop: banner.desktop === true }) : null
3666
+ banner ? h(RestartBanner, { note: banner.text, onDone: () => setBanner(null), desktop: banner.desktop === true, onRestarted: reloadPing }) : null
3514
3667
  )
3515
3668
  );
3516
3669
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/registry.json CHANGED
@@ -27,7 +27,7 @@
27
27
  {
28
28
  "id": "dsh-skins",
29
29
  "name": "DSH Skins",
30
- "description": "DSH Web 皮肤插件:多套品牌皮肤侧栏热切换,一键回到官方界面。已适配 0.1.7-rc.2 与 0.2.0-rc.1,详见仓库。",
30
+ "description": "DSH Web 皮肤插件:多套品牌皮肤侧栏热切换,一键回到官方界面。已适配 0.1.7-rc.2 与 0.2.0-rc.1/rc.2,详见仓库。",
31
31
  "category": "ui",
32
32
  "tags": [
33
33
  "皮肤",
@@ -37,7 +37,8 @@
37
37
  "verified": [
38
38
  "0.1.2-rc.1",
39
39
  "0.1.7-rc.2",
40
- "0.2.0-rc.1"
40
+ "0.2.0-rc.1",
41
+ "0.2.0-rc.2"
41
42
  ],
42
43
  "source": "npm",
43
44
  "npm": "@iasiv5/dsh-skins",