dsh-update-status 0.1.12 → 0.2.1

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 CHANGED
@@ -4,37 +4,28 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  module.exports;
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
7
- //#region src/shared/channels.ts
8
- const CHANNEL_ORDER = {
9
- latest: 0,
10
- next: 1,
11
- alpha: 2
12
- };
7
+ //#region src/shared/visual-state.ts
13
8
  /**
14
- * Reduce raw dist-tags to user-meaningful choices without losing Host facts.
15
- * The selected channel and stable return path are always present; every other
16
- * row must carry a version no earlier row already shows.
9
+ * Only a read the plugin could not complete is a chip-level problem.
10
+ *
11
+ * An advisory notice — a preview channel this bundle has not been verified against,
12
+ * say — leaves the chip surface alone and belongs in the panel. Repainting the brand
13
+ * row for a plugin-side bookkeeping fact is the surprise the breathing dot replaced:
14
+ * an operator who upgrades DSH ahead of this bundle must not watch the chip turn red.
17
15
  *
18
- * A channel is deliberately NOT hidden just because it currently points at the
19
- * release that is running. Following a channel is a statement about future
20
- * releases, and `currentVersion` still rides the input for the caller's own
21
- * rendering, but filtering on it created a deadlock: while running the version
22
- * published on `alpha`, the `alpha` row disappeared, so the one channel a user
23
- * on that line would want to follow could never be selected — the only way to
24
- * see it was to already follow it.
16
+ * A Host older than `warningKind` sends no field at all; `undefined` then keeps the
17
+ * previous behaviour (any warning is a problem) rather than silently dropping a real
18
+ * failure on the floor.
25
19
  */
26
- function visibleChannelReleases(status) {
27
- const ordered = [...status.channels].sort((left, right) => CHANNEL_ORDER[left.channel] - CHANNEL_ORDER[right.channel]);
28
- const mandatory = /* @__PURE__ */ new Set(["latest", status.channel]);
29
- const visible = ordered.filter((release) => mandatory.has(release.channel));
30
- const seenVersions = new Set(visible.map((release) => release.version).filter((version) => version !== null));
31
- for (const release of ordered) {
32
- if (mandatory.has(release.channel) || release.version === null) continue;
33
- if (seenVersions.has(release.version)) continue;
34
- visible.push(release);
35
- seenVersions.add(release.version);
36
- }
37
- return visible.sort((left, right) => CHANNEL_ORDER[left.channel] - CHANNEL_ORDER[right.channel]);
20
+ function isChipProblem(status) {
21
+ if (status === null) return false;
22
+ if (status.warningKind === void 0) return status.warning !== null;
23
+ return status.warningKind === "failure";
24
+ }
25
+ function visualState(status, loading, error) {
26
+ if (loading) return "loading";
27
+ if (error !== null || isChipProblem(status)) return status?.hasUpdate === true ? "update" : "problem";
28
+ return status?.hasUpdate === true ? "update" : "current";
38
29
  }
39
30
  //#endregion
40
31
  //#region src/shared/types.ts
@@ -45,9 +36,13 @@ window.__ModuleLoader__.load({
45
36
  * `harness.handle` / `host.call` are dynamic-Cordis-only closure APIs. The
46
37
  * endpoint vocabulary remains deliberately small and private to this plugin
47
38
  * channel.
39
+ *
40
+ * There is ONE release line, not a channel catalogue: the Host reports the
41
+ * newest version the npm registry publishes under any dist-tag, and the client
42
+ * compares it with the running version. `latest`, `next` and `alpha` are npm's
43
+ * bookkeeping, not a user choice this plugin asks anyone to make.
48
44
  */
49
45
  const PLUGIN_ID$1 = "dsh-update-status";
50
- const PACKAGE_NAME = "@deepseek-ai/dsh";
51
46
  /** Shared Connection RPC channel. Custom prefixes 405 on the SPA fallback. */
52
47
  const UPDATE_STATUS_CHANNEL = "/api";
53
48
  const MAX_CACHE_TTL_MINUTES = 1440;
@@ -58,54 +53,6 @@ window.__ModuleLoader__.load({
58
53
  getStatus: "dsh-update-status.get-status",
59
54
  checkUpdate: "dsh-update-status.check-update"
60
55
  };
61
- function isReleaseChannel(value) {
62
- return value === "latest" || value === "next" || value === "alpha";
63
- }
64
- //#endregion
65
- //#region src/shared/semver.ts
66
- const SEMVER = /^(?:v)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
67
- function parseSemver(value) {
68
- const match = SEMVER.exec(value.trim());
69
- if (match === null) return void 0;
70
- const major = Number(match[1]);
71
- const minor = Number(match[2]);
72
- const patch = Number(match[3]);
73
- if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) return void 0;
74
- return {
75
- major,
76
- minor,
77
- patch,
78
- prerelease: match[4] === void 0 ? [] : match[4].split(".")
79
- };
80
- }
81
- //#endregion
82
- //#region src/shared/preview-guidance.ts
83
- /** Pinned guidance only: never execute a command or mutate the followed channel. */
84
- function previewCommand(status, version) {
85
- if (version === null || version.trim() !== version || parseSemver(version) === void 0) return null;
86
- if (status.installKind === "npm-global") return `npm install -g ${PACKAGE_NAME}@${version}`;
87
- if (status.installKind === "pnpm-global") return `pnpm add -g ${PACKAGE_NAME}@${version}`;
88
- return null;
89
- }
90
- //#endregion
91
- //#region src/shared/visual-state.ts
92
- /**
93
- * Only a read the plugin could not complete is a chip-level problem.
94
- *
95
- * An advisory notice — a preview channel this bundle has not been verified against,
96
- * say — leaves the chip surface alone and belongs in the panel. Repainting the brand
97
- * row for a plugin-side bookkeeping fact is the surprise the breathing dot replaced:
98
- * an operator who upgrades DSH ahead of this bundle must not watch the chip turn red.
99
- *
100
- * A Host older than `warningKind` sends no field at all; `undefined` then keeps the
101
- * previous behaviour (any warning is a problem) rather than silently dropping a real
102
- * failure on the floor.
103
- */
104
- function isChipProblem(status) {
105
- if (status === null) return false;
106
- if (status.warningKind === void 0) return status.warning !== null;
107
- return status.warningKind === "failure";
108
- }
109
56
  //#endregion
110
57
  //#region src/client/i18n.ts
111
58
  const DICTIONARY = {
@@ -157,13 +104,9 @@ window.__ModuleLoader__.load({
157
104
  zh: "当前运行版本",
158
105
  en: "Current running version"
159
106
  },
160
- "panel.latest": {
161
- zh: "所选通道版本",
162
- en: "Selected channel version"
163
- },
164
- "panel.channels": {
165
- zh: "可用发布通道",
166
- en: "Available release channels"
107
+ "panel.newer": {
108
+ zh: "可更新至",
109
+ en: "Newer version available"
167
110
  },
168
111
  "panel.compatVerified": {
169
112
  zh: "已验证兼容",
@@ -177,14 +120,6 @@ window.__ModuleLoader__.load({
177
120
  zh: "已知不兼容",
178
121
  en: "Known incompatible"
179
122
  },
180
- "panel.selectChannel": {
181
- zh: "选择此通道",
182
- en: "Select channel"
183
- },
184
- "panel.selectedChannel": {
185
- zh: "已选择",
186
- en: "Selected"
187
- },
188
123
  "panel.notChecked": {
189
124
  zh: "尚未取得",
190
125
  en: "Not available yet"
@@ -194,8 +129,8 @@ window.__ModuleLoader__.load({
194
129
  en: "Update available"
195
130
  },
196
131
  "panel.currentState": {
197
- zh: "未发现可用更新",
198
- en: "No update found"
132
+ zh: "已是最新",
133
+ en: "Up to date"
199
134
  },
200
135
  "panel.cached": {
201
136
  zh: "来自 Host 缓存",
@@ -221,10 +156,6 @@ window.__ModuleLoader__.load({
221
156
  zh: "检查中…",
222
157
  en: "Checking…"
223
158
  },
224
- "panel.switchingChannel": {
225
- zh: "正在切换通道…",
226
- en: "Switching channel…"
227
- },
228
159
  "panel.releaseNotes": {
229
160
  zh: "发布说明",
230
161
  en: "Release notes"
@@ -261,17 +192,13 @@ window.__ModuleLoader__.load({
261
192
  zh: "无法检查 npm registry:{detail}",
262
193
  en: "Unable to check the npm registry: {detail}"
263
194
  },
264
- "warning.channelUnavailable": {
265
- zh: "npm registry 未发布 {channel} 通道。",
266
- en: "The npm registry does not publish a {channel} channel."
267
- },
268
195
  "warning.versionIncomparable": {
269
- zh: "无法按 SemVer 比较当前版本 {currentVersion} 与 {channel} 通道版本 {selectedVersion}。",
270
- en: "Unable to compare the current version {currentVersion} with {channel} channel version {selectedVersion} using SemVer."
196
+ zh: "无法按 SemVer 比较当前版本 {currentVersion} 与线上版本 {latestVersion}。",
197
+ en: "Unable to compare the current version {currentVersion} with the published version {latestVersion} using SemVer."
271
198
  },
272
- "warning.previewUnverified": {
273
- zh: "{channel} 是预览通道,版本 {version} 尚未验证与本插件兼容。",
274
- en: "{channel} is a preview channel; version {version} has not been verified as compatible with this plugin."
199
+ "warning.versionUnverified": {
200
+ zh: "线上版本 {version} 尚未验证与本插件兼容。",
201
+ en: "Version {version} has not been verified as compatible with this plugin."
275
202
  },
276
203
  "warning.staleSchemastery": {
277
204
  zh: "本插件解析到的 @deepseek-ai/schemastery(版本 {version})来自 {path},不是 DSH 自带的那份,偏好字段无法标记为 volatile。请删除该残留目录并重启 DSH:rm -rf {nodeModulesDir}",
@@ -289,42 +216,6 @@ window.__ModuleLoader__.load({
289
216
  zh: "升级前请先确认 DSH 的安装方式;本插件无法代为执行升级。",
290
217
  en: "Confirm how DSH was installed before upgrading; this plugin cannot perform the upgrade for you."
291
218
  },
292
- "panel.static": {
293
- zh: "此连接只显示静态版本;请在运行 DSH 的本机打开侧栏查看完整更新信息。",
294
- en: "This connection shows only the static version. Open the sidebar on the computer running DSH for full update details."
295
- },
296
- "preview.open": {
297
- zh: "查看预览版升级方式",
298
- en: "View preview upgrade instructions"
299
- },
300
- "preview.close": {
301
- zh: "收起升级说明",
302
- en: "Hide upgrade instructions"
303
- },
304
- "preview.follow": {
305
- zh: "关注通道只影响检查结果,不代表已安装或已切换版本。",
306
- en: "Following a channel only changes update checks, not the installed version."
307
- },
308
- "preview.target": {
309
- zh: "目标版本",
310
- en: "Target version"
311
- },
312
- "preview.risk": {
313
- zh: "预览版本可能不稳定,插件兼容性尚需确认。执行前请保存工作、备份配置并结束运行中的任务;安装后需手动重启 DSH。此页面不会安装或重启。",
314
- en: "Preview builds may be unstable and plugin compatibility needs checking. Save work, back up configuration and finish active tasks before executing. Restart DSH manually afterwards. This page never installs or restarts."
315
- },
316
- "preview.unavailable": {
317
- zh: "暂无可确认的预览目标,请先检查更新。",
318
- en: "No confirmed preview target. Check for updates first."
319
- },
320
- "preview.manual": {
321
- zh: "当前安装方式无法安全生成命令,请先确认安装来源;源码安装需按其构建说明操作。",
322
- en: "Cannot safely generate a command for this installation. Confirm its source; source checkouts require their build instructions."
323
- },
324
- "preview.same": {
325
- zh: "此目标与当前运行版本相同,无需重复安装。",
326
- en: "This target is already running; no reinstall is needed."
327
- },
328
219
  "settings.title": {
329
220
  zh: "版本与更新",
330
221
  en: "Version & updates"
@@ -334,28 +225,8 @@ window.__ModuleLoader__.load({
334
225
  en: "Show the version-status entry in the sidebar"
335
226
  },
336
227
  "settings.sidebarHint": {
337
- zh: "关闭后不再渲染品牌行里的版本芯片。不会执行或安排升级。",
338
- en: "When off, the version chip in the brand row is not rendered. No update is run or scheduled."
339
- },
340
- "settings.channel": {
341
- zh: "关注发布通道",
342
- en: "Release channel to follow"
343
- },
344
- "settings.channelLatest": {
345
- zh: "稳定版(latest,推荐)",
346
- en: "Stable (latest, recommended)"
347
- },
348
- "settings.channelNext": {
349
- zh: "候选版(next)",
350
- en: "Release candidate (next)"
351
- },
352
- "settings.channelAlpha": {
353
- zh: "预览版(alpha)",
354
- en: "Preview (alpha)"
355
- },
356
- "settings.channelHint": {
357
- zh: "预览通道可能包含未稳定接口或插件兼容性变化。这里只检查并生成命令,不会安装。",
358
- en: "Preview channels may contain unstable APIs or plugin compatibility changes. This only checks and generates a command; it never installs."
228
+ zh: "关闭后不再渲染品牌行里的版本芯片,品牌行恢复为 DSH 官方内容。不会执行或安排升级。",
229
+ en: "When off, the version chip in the brand row is not rendered and the brand row falls back to DSH’s own content. No update is run or scheduled."
359
230
  },
360
231
  "settings.readonly": {
361
232
  zh: "此连接的设置为只读;显示状态不受影响。",
@@ -377,16 +248,11 @@ window.__ModuleLoader__.load({
377
248
  function warningText(warning, language = languageOf()) {
378
249
  switch (warning.code) {
379
250
  case "registry-unavailable": return t("warning.registryUnavailable", { detail: warning.detail }, language);
380
- case "channel-unavailable": return t("warning.channelUnavailable", { channel: warning.channel }, language);
381
251
  case "version-incomparable": return t("warning.versionIncomparable", {
382
252
  currentVersion: warning.currentVersion,
383
- channel: warning.channel,
384
- selectedVersion: warning.selectedVersion
385
- }, language);
386
- case "preview-unverified": return t("warning.previewUnverified", {
387
- channel: warning.channel,
388
- version: warning.version
253
+ latestVersion: warning.latestVersion
389
254
  }, language);
255
+ case "version-unverified": return t("warning.versionUnverified", { version: warning.version }, language);
390
256
  case "stale-schemastery": {
391
257
  const params = {
392
258
  version: warning.version ?? "—",
@@ -433,11 +299,6 @@ window.__ModuleLoader__.load({
433
299
  if (value.length <= 16) return value;
434
300
  return value.slice(0, 15) + "…";
435
301
  }
436
- function channelLabel(channel) {
437
- if (channel === "latest") return t("settings.channelLatest");
438
- if (channel === "next") return t("settings.channelNext");
439
- return t("settings.channelAlpha");
440
- }
441
302
  function compatibilityLabel(value) {
442
303
  if (value === "verified") return t("panel.compatVerified");
443
304
  if (value === "incompatible") return t("panel.compatIncompatible");
@@ -498,11 +359,6 @@ window.__ModuleLoader__.load({
498
359
  ]
499
360
  });
500
361
  }
501
- function visualState(status, loading, error) {
502
- if (loading) return "loading";
503
- if (error !== null || isChipProblem(status)) return status?.hasUpdate === true ? "update" : "problem";
504
- return status?.hasUpdate === true ? "update" : "current";
505
- }
506
362
  function badgeLabel(status, loading, error) {
507
363
  const state = visualState(status, loading, error);
508
364
  if (state === "loading") return t("brand.checking");
@@ -567,7 +423,7 @@ window.__ModuleLoader__.load({
567
423
  };
568
424
  if (status?.hasUpdate === true) return {
569
425
  kind: "update",
570
- text: t("panel.available")
426
+ text: `${t("panel.available")} · ${status.latestVersion ?? ""}`.trim()
571
427
  };
572
428
  if (status !== null) {
573
429
  const warning = localizedWarning(status);
@@ -593,14 +449,63 @@ window.__ModuleLoader__.load({
593
449
  element.focus();
594
450
  } catch {}
595
451
  }
452
+ /**
453
+ * The one upgrade command plus its copy affordance, shared by the panel and the
454
+ * settings section so both surfaces can never disagree about what to run.
455
+ */
456
+ function CommandBlock({ command, disabled = false }) {
457
+ const commandRef = React.useRef(null);
458
+ const [copyMessage, setCopyMessage] = React.useState(null);
459
+ const copy = async () => {
460
+ setCopyMessage(null);
461
+ try {
462
+ if (!navigator.clipboard?.writeText) throw new Error("Clipboard API unavailable");
463
+ await navigator.clipboard.writeText(command);
464
+ setCopyMessage(t("panel.copied"));
465
+ } catch {
466
+ selectCommand(commandRef.current);
467
+ setCopyMessage(t("panel.copyFallback"));
468
+ }
469
+ };
470
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
471
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
472
+ className: "dus-command-label",
473
+ children: t("panel.command")
474
+ }),
475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
476
+ ref: commandRef,
477
+ className: "dus-command",
478
+ tabIndex: 0,
479
+ children: command
480
+ }),
481
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
482
+ className: "dus-actions dus-command-actions",
483
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
484
+ className: "dus-action",
485
+ type: "button",
486
+ disabled,
487
+ onClick: () => {
488
+ copy();
489
+ },
490
+ children: t("panel.copy")
491
+ }), copyMessage !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
492
+ className: "dus-copy-message",
493
+ role: "status",
494
+ children: copyMessage
495
+ })]
496
+ }),
497
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
498
+ className: "dus-note",
499
+ children: t("panel.commandNote")
500
+ })
501
+ ] });
502
+ }
596
503
  /** Full detail from whichever page the operator is on (the transport is authenticated). */
597
504
  function UpdatePanel({ ui }) {
598
505
  const panel = useObservable(ui.panel);
599
506
  const preferences = useObservable(ui.preferences);
600
507
  const snapshot = useObservable(ui.status);
601
- const commandRef = React.useRef(null);
602
508
  const dialogRef = React.useRef(null);
603
- const [copyMessage, setCopyMessage] = React.useState(null);
604
509
  const visible = panel.open && preferences.sidebarEnabled;
605
510
  React.useLayoutEffect(() => {
606
511
  if (!visible) return void 0;
@@ -629,19 +534,8 @@ window.__ModuleLoader__.load({
629
534
  const status = snapshot.status;
630
535
  const warning = status === null ? null : localizedWarning(status);
631
536
  const summary = statusSummary(status, snapshot.loading, snapshot.error);
632
- const switchingChannel = status !== null && status.channel !== preferences.channel;
633
- const command = switchingChannel ? t("panel.switchingChannel") : status === null ? "—" : localizedUpgradeGuidance(status);
634
- const copy = async () => {
635
- setCopyMessage(null);
636
- try {
637
- if (!navigator.clipboard?.writeText) throw new Error("Clipboard API unavailable");
638
- await navigator.clipboard.writeText(command);
639
- setCopyMessage(t("panel.copied"));
640
- } catch {
641
- selectCommand(commandRef.current);
642
- setCopyMessage(t("panel.copyFallback"));
643
- }
644
- };
537
+ const hasUpdate = status?.hasUpdate === true && status.latestVersion !== null;
538
+ const command = status === null || !hasUpdate ? "" : localizedUpgradeGuidance(status);
645
539
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
646
540
  ref: dialogRef,
647
541
  className: "dus-overlay-root",
@@ -698,28 +592,29 @@ window.__ModuleLoader__.load({
698
592
  children: status?.currentVersion ?? "…"
699
593
  })]
700
594
  }),
701
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
595
+ hasUpdate && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
702
596
  className: "dus-metadata-row",
703
597
  children: [
704
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
705
599
  className: "dus-meta-label",
706
- children: [
707
- t("panel.latest"),
708
- " · ",
709
- status === null ? "latest" : channelLabel(status.channel)
710
- ]
600
+ children: t("panel.newer")
711
601
  }),
712
602
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
713
603
  className: "dus-meta-value",
714
- children: status?.latestVersion ?? t("panel.notChecked")
604
+ children: status.latestVersion
715
605
  }),
716
- status?.publishedAt !== null && status?.publishedAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
606
+ status.publishedAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
717
607
  className: "dus-meta-sub",
718
608
  children: [
719
609
  t("panel.publishedAt"),
720
610
  ": ",
721
611
  timeText(status.publishedAt)
722
612
  ]
613
+ }),
614
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
615
+ className: "dus-meta-sub dus-compat",
616
+ "data-compatibility": status.compatibility,
617
+ children: compatibilityLabel(status.compatibility)
723
618
  })
724
619
  ]
725
620
  }),
@@ -742,39 +637,6 @@ window.__ModuleLoader__.load({
742
637
  })
743
638
  ]
744
639
  }),
745
- status !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
746
- className: "dus-channel-list",
747
- "aria-label": t("panel.channels"),
748
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
749
- className: "dus-command-label",
750
- children: t("panel.channels")
751
- }), visibleChannelReleases(status).map((release) => {
752
- const selected = release.channel === preferences.channel;
753
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
754
- className: "dus-channel-row",
755
- "data-selected": selected || void 0,
756
- children: [
757
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: channelLabel(release.channel) }),
758
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: release.version ?? "—" }),
759
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
760
- className: "dus-channel-compat",
761
- "data-compatibility": release.compatibility,
762
- children: compatibilityLabel(release.compatibility)
763
- }),
764
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
765
- className: "dus-channel-action",
766
- type: "button",
767
- disabled: selected || !preferences.writable || release.version === null,
768
- "aria-label": `${selected ? t("panel.selectedChannel") : t("panel.selectChannel")}: ${channelLabel(release.channel)}`,
769
- onClick: () => {
770
- ui.preferences.setChannel(release.channel);
771
- },
772
- children: selected ? t("panel.selectedChannel") : t("panel.selectChannel")
773
- })
774
- ]
775
- }, release.channel);
776
- })]
777
- }),
778
640
  warning !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
779
641
  className: "dus-warning",
780
642
  children: [
@@ -793,52 +655,25 @@ window.__ModuleLoader__.load({
793
655
  }),
794
656
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
795
657
  className: "dus-actions",
796
- children: [
797
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
798
- className: "dus-action",
799
- type: "button",
800
- disabled: snapshot.loading,
801
- onClick: () => {
802
- ui.status.refresh(void 0, preferences.cacheTtlMinutes);
803
- },
804
- children: snapshot.loading ? t("panel.checking") : t("panel.check")
805
- }),
806
- status !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
807
- className: "dus-action",
808
- href: status.changelogUrl,
809
- target: "_blank",
810
- rel: "noreferrer",
811
- children: t("panel.releaseNotes")
812
- }),
813
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
814
- className: "dus-action",
815
- type: "button",
816
- disabled: switchingChannel || status === null,
817
- onClick: () => {
818
- copy();
819
- },
820
- children: t("panel.copy")
821
- })
822
- ]
823
- }),
824
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
825
- className: "dus-command-label",
826
- children: t("panel.command")
827
- }),
828
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
829
- ref: commandRef,
830
- className: "dus-command",
831
- tabIndex: 0,
832
- children: command
833
- }),
834
- copyMessage !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
835
- className: "dus-copy-message",
836
- role: "status",
837
- children: copyMessage
658
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
659
+ className: "dus-action",
660
+ type: "button",
661
+ disabled: snapshot.loading,
662
+ onClick: () => {
663
+ ui.status.refresh(preferences.cacheTtlMinutes);
664
+ },
665
+ children: snapshot.loading ? t("panel.checking") : t("panel.check")
666
+ }), status !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
667
+ className: "dus-action",
668
+ href: status.changelogUrl,
669
+ target: "_blank",
670
+ rel: "noreferrer",
671
+ children: t("panel.releaseNotes")
672
+ })]
838
673
  }),
839
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
840
- className: "dus-note",
841
- children: t("panel.commandNote")
674
+ hasUpdate && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandBlock, {
675
+ command,
676
+ disabled: snapshot.loading
842
677
  }),
843
678
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
844
679
  className: "dus-note",
@@ -852,9 +687,10 @@ window.__ModuleLoader__.load({
852
687
  function UpdateSettings({ ui }) {
853
688
  const preferences = useObservable(ui.preferences);
854
689
  const snapshot = useObservable(ui.status);
855
- const [showGuidance, setShowGuidance] = React.useState(false);
856
- const previews = snapshot.status?.channels.filter((release) => release.channel !== "latest") ?? [];
857
- const channelOptions = snapshot.status === null ? [{ channel: preferences.channel }] : visibleChannelReleases(snapshot.status);
690
+ const status = snapshot.status;
691
+ const summary = statusSummary(status, snapshot.loading, snapshot.error);
692
+ const hasUpdate = status?.hasUpdate === true && status.latestVersion !== null;
693
+ const command = status === null || !hasUpdate ? "" : localizedUpgradeGuidance(status);
858
694
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
859
695
  className: "dus-settings",
860
696
  children: [
@@ -889,94 +725,58 @@ window.__ModuleLoader__.load({
889
725
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
890
726
  className: "dus-settings-card",
891
727
  children: [
892
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
893
- className: "dus-settings-field",
894
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.channel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
895
- className: "dus-channel-select",
896
- value: preferences.channel,
897
- disabled: !preferences.writable,
898
- onChange: (event) => {
899
- ui.preferences.setChannel(event.currentTarget.value);
900
- },
901
- children: channelOptions.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
902
- value: option.channel,
903
- children: channelLabel(option.channel)
904
- }, option.channel))
905
- })]
906
- }),
907
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
908
- className: "dus-settings-hint",
909
- children: t("settings.channelHint")
910
- }),
911
728
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
912
729
  className: "dus-settings-hint",
913
730
  children: [
914
731
  t("panel.current"),
915
732
  ": ",
916
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: snapshot.status?.currentVersion ?? "…" })
733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: status?.currentVersion ?? "…" })
917
734
  ]
918
735
  }),
919
736
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
920
- className: "dus-settings-hint",
921
- children: t("preview.follow")
737
+ className: "dus-state",
738
+ "data-kind": summary.kind,
739
+ children: summary.text
922
740
  }),
923
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
924
- className: "dus-action",
925
- type: "button",
926
- "aria-expanded": showGuidance,
927
- onClick: () => {
928
- setShowGuidance(!showGuidance);
929
- },
930
- children: t(showGuidance ? "preview.close" : "preview.open")
931
- }),
932
- showGuidance && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
933
- "aria-label": t("preview.open"),
741
+ hasUpdate && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
742
+ className: "dus-settings-hint",
934
743
  children: [
935
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
936
- className: "dus-warning",
937
- children: t("preview.risk")
938
- }),
939
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
940
- className: "dus-action",
941
- type: "button",
942
- disabled: snapshot.loading,
943
- onClick: () => {
944
- ui.status.refresh(void 0, preferences.cacheTtlMinutes);
945
- },
946
- children: t(snapshot.loading ? "panel.checking" : "panel.check")
947
- }),
948
- snapshot.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
949
- role: "alert",
950
- children: snapshot.error
951
- }),
952
- previews.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("preview.unavailable") }),
953
- previews.map((release) => {
954
- const status = snapshot.status;
955
- const command = previewCommand(status, release.version);
956
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
957
- className: "dus-settings-card",
958
- children: [
959
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
960
- channelLabel(release.channel),
961
- " · ",
962
- t("preview.target"),
963
- ": ",
964
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: release.version ?? t("panel.notChecked") })
965
- ] }),
966
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: compatibilityLabel(release.compatibility) }),
967
- release.version === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("preview.unavailable") }) : release.version === status.currentVersion ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("preview.same") }) : command !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("panel.command") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
968
- className: "dus-command",
969
- tabIndex: 0,
970
- children: command
971
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("preview.manual") })
972
- ]
973
- }, release.channel);
974
- }),
975
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
976
- className: "dus-note",
977
- children: t("panel.commandNote")
744
+ t("panel.newer"),
745
+ ": ",
746
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: status.latestVersion }),
747
+ " · ",
748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
749
+ className: "dus-compat",
750
+ "data-compatibility": status.compatibility,
751
+ children: compatibilityLabel(status.compatibility)
978
752
  })
979
753
  ]
754
+ }),
755
+ snapshot.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
756
+ role: "alert",
757
+ children: snapshot.error
758
+ }),
759
+ hasUpdate && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandBlock, {
760
+ command,
761
+ disabled: snapshot.loading
762
+ }),
763
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
764
+ className: "dus-actions",
765
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
766
+ className: "dus-action",
767
+ type: "button",
768
+ disabled: snapshot.loading,
769
+ onClick: () => {
770
+ ui.status.refresh(preferences.cacheTtlMinutes);
771
+ },
772
+ children: snapshot.loading ? t("panel.checking") : t("panel.check")
773
+ }), status !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
774
+ className: "dus-action",
775
+ href: status.changelogUrl,
776
+ target: "_blank",
777
+ rel: "noreferrer",
778
+ children: t("panel.releaseNotes")
779
+ })]
980
780
  })
981
781
  ]
982
782
  })
@@ -985,7 +785,6 @@ window.__ModuleLoader__.load({
985
785
  }
986
786
  //#endregion
987
787
  //#region src/client/contract.ts
988
- /** Minimal structural client faces — runtime services stay owned by DSH. */
989
788
  function errorMessage(error) {
990
789
  return (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim().slice(0, 220) || "unknown error";
991
790
  }
@@ -999,19 +798,13 @@ window.__ModuleLoader__.load({
999
798
  code: record.code,
1000
799
  detail: record.detail
1001
800
  };
1002
- if (record.code === "channel-unavailable" && isReleaseChannel(record.channel)) return {
1003
- code: record.code,
1004
- channel: record.channel
1005
- };
1006
- if (record.code === "version-incomparable" && typeof record.currentVersion === "string" && isReleaseChannel(record.channel) && typeof record.selectedVersion === "string") return {
801
+ if (record.code === "version-incomparable" && typeof record.currentVersion === "string" && typeof record.latestVersion === "string") return {
1007
802
  code: record.code,
1008
803
  currentVersion: record.currentVersion,
1009
- channel: record.channel,
1010
- selectedVersion: record.selectedVersion
804
+ latestVersion: record.latestVersion
1011
805
  };
1012
- if (record.code === "preview-unverified" && isReleaseChannel(record.channel) && typeof record.version === "string") return {
806
+ if (record.code === "version-unverified" && typeof record.version === "string") return {
1013
807
  code: record.code,
1014
- channel: record.channel,
1015
808
  version: record.version
1016
809
  };
1017
810
  if (record.code === "stale-schemastery") {
@@ -1030,8 +823,9 @@ window.__ModuleLoader__.load({
1030
823
  function updateStatusOf(value) {
1031
824
  if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
1032
825
  const record = value;
1033
- if (typeof record.currentVersion !== "string" || typeof record.hasUpdate !== "boolean" || typeof record.cached !== "boolean" || typeof record.installKind !== "string" || typeof record.upgradeCommand !== "string" || typeof record.releaseUrl !== "string" || typeof record.changelogUrl !== "string" || typeof record.packageName !== "string" || !isReleaseChannel(record.channel) || !Array.isArray(record.channels) || record.canApplyInPlace !== false) return void 0;
826
+ if (typeof record.currentVersion !== "string" || typeof record.hasUpdate !== "boolean" || typeof record.cached !== "boolean" || typeof record.installKind !== "string" || typeof record.upgradeCommand !== "string" || typeof record.releaseUrl !== "string" || typeof record.changelogUrl !== "string" || typeof record.packageName !== "string" || record.canApplyInPlace !== false) return void 0;
1034
827
  if (!(record.installKind === "npm-global" || record.installKind === "pnpm-global" || record.installKind === "source-checkout" || record.installKind === "unknown")) return void 0;
828
+ if (record.compatibility !== "verified" && record.compatibility !== "unverified" && record.compatibility !== "incompatible") return void 0;
1035
829
  const latestVersion = record.latestVersion === null ? null : stringOrNull(record.latestVersion);
1036
830
  const checkedAt = record.checkedAt === null ? null : stringOrNull(record.checkedAt);
1037
831
  const warning = record.warning === null ? null : stringOrNull(record.warning);
@@ -1047,24 +841,11 @@ window.__ModuleLoader__.load({
1047
841
  warnings.push(parsed);
1048
842
  }
1049
843
  }
1050
- const channels = [];
1051
- for (const raw of record.channels) {
1052
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
1053
- const item = raw;
1054
- const version = item.version === null ? null : stringOrNull(item.version);
1055
- const channelPublishedAt = item.publishedAt === null ? null : stringOrNull(item.publishedAt);
1056
- if (!isReleaseChannel(item.channel) || item.version !== null && version === null || item.publishedAt !== null && channelPublishedAt === null || item.compatibility !== "verified" && item.compatibility !== "unverified" && item.compatibility !== "incompatible") return void 0;
1057
- channels.push({
1058
- channel: item.channel,
1059
- version,
1060
- publishedAt: channelPublishedAt,
1061
- compatibility: item.compatibility
1062
- });
1063
- }
1064
844
  return {
1065
845
  currentVersion: record.currentVersion,
1066
846
  latestVersion,
1067
847
  hasUpdate: record.hasUpdate,
848
+ compatibility: record.compatibility,
1068
849
  cached: record.cached,
1069
850
  checkedAt,
1070
851
  warning,
@@ -1076,8 +857,6 @@ window.__ModuleLoader__.load({
1076
857
  changelogUrl: record.changelogUrl,
1077
858
  publishedAt,
1078
859
  packageName: record.packageName,
1079
- channel: record.channel,
1080
- channels,
1081
860
  canApplyInPlace: false
1082
861
  };
1083
862
  }
@@ -1107,20 +886,24 @@ window.__ModuleLoader__.load({
1107
886
  };
1108
887
  /** First mount reads the Host's cached status; it never starts a browser timer. */
1109
888
  async load(cacheTtlMinutes = 360) {
1110
- await this.request(false, this.snapshot.status?.channel ?? "latest", cacheTtlMinutes);
889
+ await this.request(false, cacheTtlMinutes);
1111
890
  }
1112
891
  /** User gesture only: force the Host to bypass TTL (while retaining single-flight). */
1113
- async refresh(channel = this.snapshot.status?.channel ?? "latest", cacheTtlMinutes = 360) {
1114
- await this.request(true, channel, cacheTtlMinutes);
892
+ async refresh(cacheTtlMinutes = 360) {
893
+ await this.request(true, cacheTtlMinutes);
1115
894
  }
1116
- /** Channel selection re-projects the Host cache; it is not a forced refresh. */
1117
- async selectChannel(channel, cacheTtlMinutes = 360) {
1118
- for (let attempts = 0; attempts < 3 && !this.stopped && this.snapshot.status?.channel !== channel; attempts += 1) {
1119
- const pending = this.inFlight;
1120
- if (pending !== void 0 && !await pending) return;
1121
- if (this.stopped || this.snapshot.status?.channel === channel) return;
1122
- if (!await this.request(false, channel, cacheTtlMinutes)) return;
1123
- }
895
+ /**
896
+ * Ordinary read that waits out an in-flight request first.
897
+ *
898
+ * Used once per mount, when the stored cache policy arrives after the first
899
+ * read already started: joining that request would keep the default policy, so
900
+ * the follow-up read has to come after it settles.
901
+ */
902
+ async reload(cacheTtlMinutes) {
903
+ const pending = this.inFlight;
904
+ if (pending !== void 0) await pending;
905
+ if (this.stopped) return;
906
+ await this.request(false, cacheTtlMinutes);
1124
907
  }
1125
908
  stop() {
1126
909
  this.stopped = true;
@@ -1131,7 +914,7 @@ window.__ModuleLoader__.load({
1131
914
  this.snapshot = next;
1132
915
  for (const listener of this.listeners) listener();
1133
916
  }
1134
- request(force, channel, cacheTtlMinutes) {
917
+ request(force, cacheTtlMinutes) {
1135
918
  if (this.inFlight !== void 0) return this.inFlight;
1136
919
  const rpc = this.connection.rpc;
1137
920
  if (rpc === void 0 || typeof rpc.call !== "function") {
@@ -1153,12 +936,8 @@ window.__ModuleLoader__.load({
1153
936
  const ttl = isCacheTtlMinutes(cacheTtlMinutes) ? cacheTtlMinutes : 360;
1154
937
  const raw = await rpc.call(UPDATE_STATUS_CHANNEL, endpoint, force ? {
1155
938
  force: true,
1156
- channel,
1157
- cacheTtlMinutes: ttl
1158
- } : {
1159
- channel,
1160
939
  cacheTtlMinutes: ttl
1161
- });
940
+ } : { cacheTtlMinutes: ttl });
1162
941
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("invalid update-status RPC response");
1163
942
  const envelope = raw;
1164
943
  if (envelope.ok !== true) throw new Error(typeof envelope.error?.message === "string" ? envelope.error.message : "update-status RPC failed");
@@ -1188,7 +967,6 @@ window.__ModuleLoader__.load({
1188
967
  };
1189
968
  const INITIAL_PREFERENCES = {
1190
969
  sidebarEnabled: true,
1191
- channel: "latest",
1192
970
  cacheTtlMinutes: 360,
1193
971
  writable: false,
1194
972
  status: "loading"
@@ -1212,7 +990,6 @@ window.__ModuleLoader__.load({
1212
990
  const status = raw.status === "ready" || raw.status === "unavailable" ? raw.status : "loading";
1213
991
  this.publish({
1214
992
  sidebarEnabled: value.sidebarEnabled !== false,
1215
- channel: isReleaseChannel(value.channel) ? value.channel : "latest",
1216
993
  cacheTtlMinutes: isCacheTtlMinutes(value.cacheTtlMinutes) ? value.cacheTtlMinutes : 360,
1217
994
  writable: raw.writable === true,
1218
995
  status
@@ -1235,7 +1012,6 @@ window.__ModuleLoader__.load({
1235
1012
  const value = raw.value !== null && typeof raw.value === "object" && !Array.isArray(raw.value) ? raw.value : {};
1236
1013
  this.publish({
1237
1014
  sidebarEnabled: value.sidebarEnabled !== false,
1238
- channel: isReleaseChannel(value.channel) ? value.channel : "latest",
1239
1015
  cacheTtlMinutes: isCacheTtlMinutes(value.cacheTtlMinutes) ? value.cacheTtlMinutes : 360,
1240
1016
  writable: raw.writable === true,
1241
1017
  status: raw.status === "ready" || raw.status === "unavailable" ? raw.status : "loading"
@@ -1245,18 +1021,6 @@ window.__ModuleLoader__.load({
1245
1021
  }
1246
1022
  });
1247
1023
  }
1248
- setChannel(channel) {
1249
- const previous = this.snapshot;
1250
- this.publish({
1251
- ...previous,
1252
- channel
1253
- });
1254
- const scope = this.scope;
1255
- if (scope === void 0 || !previous.writable) return;
1256
- scope.set("channel", channel).catch(() => {
1257
- this.publish(previous);
1258
- });
1259
- }
1260
1024
  setCacheTtlMinutes(cacheTtlMinutes) {
1261
1025
  if (!isCacheTtlMinutes(cacheTtlMinutes)) return;
1262
1026
  const previous = this.snapshot;
@@ -1272,7 +1036,7 @@ window.__ModuleLoader__.load({
1272
1036
  }
1273
1037
  publish(next) {
1274
1038
  const previous = this.snapshot;
1275
- if (previous.sidebarEnabled === next.sidebarEnabled && previous.channel === next.channel && previous.cacheTtlMinutes === next.cacheTtlMinutes && previous.writable === next.writable && previous.status === next.status) return;
1039
+ if (previous.sidebarEnabled === next.sidebarEnabled && previous.cacheTtlMinutes === next.cacheTtlMinutes && previous.writable === next.writable && previous.status === next.status) return;
1276
1040
  this.snapshot = next;
1277
1041
  for (const listener of this.listeners) listener();
1278
1042
  }
@@ -1789,19 +1553,11 @@ window.__ModuleLoader__.load({
1789
1553
  .dus-settings-card{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:12px}
1790
1554
  .dus-settings-toggle{align-items:flex-start;cursor:pointer;display:flex;gap:10px;font-size:13px;line-height:1.4}
1791
1555
  .dus-settings-toggle input{accent-color:var(--dsw-alias-state-business-primary);height:18px;margin:0;min-height:18px;min-width:18px;width:18px}
1792
- .dus-settings-field{display:grid;font-size:13px;font-weight:600;gap:8px}
1793
- .dus-channel-select{background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:inherit;font:inherit;min-height:40px;padding:0 10px;width:100%}
1794
- .dus-channel-list{border-top:1px solid var(--dsw-alias-border-l2);margin:12px 0 0;padding-top:2px}
1795
- .dus-channel-row{align-items:center;display:grid;font-size:11px;gap:8px;grid-template-columns:minmax(0,1fr) auto;line-height:1.4;padding:6px 0}
1796
- .dus-channel-row[data-selected=true]{color:var(--dsw-alias-state-business-primary)}
1797
- .dus-channel-row code{font-size:11px}
1798
- .dus-channel-compat{color:var(--dsw-alias-label-secondary)}
1799
- .dus-channel-action{background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;color:inherit;cursor:pointer;font-size:11px;justify-self:end;min-height:32px;padding:0 9px}
1800
- .dus-channel-action:disabled{cursor:default;opacity:.55}
1801
- .dus-channel-action:not(:disabled):hover{background:var(--dsw-alias-button-floating-hover)}
1802
- .dus-channel-compat[data-compatibility=verified]{color:var(--dsw-alias-state-success-primary)}
1803
- .dus-channel-compat[data-compatibility=incompatible]{color:var(--dsw-alias-state-error-primary)}
1804
1556
  .dus-settings-hint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.45;margin:8px 0 0}
1557
+ .dus-compat{color:var(--dsw-alias-label-secondary)}
1558
+ .dus-compat[data-compatibility=verified]{color:var(--dsw-alias-state-success-primary)}
1559
+ .dus-compat[data-compatibility=incompatible]{color:var(--dsw-alias-state-error-primary)}
1560
+ .dus-command-actions{align-items:center;margin:8px 0 0}
1805
1561
  @media (max-width:640px),(hover:none) and (pointer:coarse){.dus-panel{border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0;bottom:0;left:0;right:0;max-height:min(78dvh,calc(100dvh - env(safe-area-inset-top)));padding:16px max(16px,env(safe-area-inset-right)) max(16px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));position:fixed;top:auto;width:100vw}.dus-action{min-height:40px;line-height:38px}.dus-close{height:40px;min-height:40px;min-width:40px;width:40px}}
1806
1562
  `;
1807
1563
  //#endregion
@@ -1866,16 +1622,19 @@ window.__ModuleLoader__.load({
1866
1622
  ctx.effect(() => {
1867
1623
  const disposeStyles = installStyles();
1868
1624
  const detach = preferences.attach(channel);
1869
- let selected = status.getSnapshot().status?.channel ?? "latest";
1870
- const syncPreferences = () => {
1871
- const next = preferences.getSnapshot();
1872
- if (next.channel !== selected) {
1873
- selected = next.channel;
1874
- status.selectChannel(selected, next.cacheTtlMinutes);
1875
- }
1625
+ let usedCacheTtl = preferences.getSnapshot().cacheTtlMinutes;
1626
+ let storedPolicyApplied = false;
1627
+ const applyStoredPolicyOnce = () => {
1628
+ if (storedPolicyApplied) return;
1629
+ const snapshot = preferences.getSnapshot();
1630
+ if (snapshot.status === "loading") return;
1631
+ storedPolicyApplied = true;
1632
+ if (snapshot.cacheTtlMinutes === usedCacheTtl) return;
1633
+ usedCacheTtl = snapshot.cacheTtlMinutes;
1634
+ status.reload(snapshot.cacheTtlMinutes);
1876
1635
  };
1877
- syncPreferences();
1878
- const unsubscribe = preferences.subscribe(syncPreferences);
1636
+ applyStoredPolicyOnce();
1637
+ const unsubscribe = preferences.subscribe(applyStoredPolicyOnce);
1879
1638
  status.load(preferences.getSnapshot().cacheTtlMinutes);
1880
1639
  return () => {
1881
1640
  stopped = true;
@@ -1911,10 +1670,27 @@ window.__ModuleLoader__.load({
1911
1670
  if (typeof ctx.on === "function") try {
1912
1671
  own(ctx.on("connection/reset", () => channel.reload()));
1913
1672
  } catch {}
1914
- ctx.slots.inject("sidebar.brand.name", () => ctx.slots.register({
1915
- name: "sidebar.brand.name",
1916
- priority: -10
1917
- }, () => BrandName({ ui })));
1673
+ ctx.slots.inject("sidebar.brand.name", () => {
1674
+ let registration;
1675
+ const syncRegistration = () => {
1676
+ const enabled = preferences.getSnapshot().sidebarEnabled;
1677
+ if (enabled && registration === void 0) registration = ctx.slots.register({
1678
+ name: "sidebar.brand.name",
1679
+ priority: -10
1680
+ }, () => BrandName({ ui }));
1681
+ else if (!enabled && registration !== void 0) {
1682
+ registration();
1683
+ registration = void 0;
1684
+ }
1685
+ };
1686
+ syncRegistration();
1687
+ const unsubscribe = preferences.subscribe(syncRegistration);
1688
+ return () => {
1689
+ unsubscribe();
1690
+ registration?.();
1691
+ registration = void 0;
1692
+ };
1693
+ });
1918
1694
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
1919
1695
  name: "shell.overlay",
1920
1696
  id: "dsh-update-status",