dsh-update-status 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +39 -7
- package/README.zh.md +39 -7
- package/assets/sidebar-chip.png +0 -0
- package/assets/update-panel.png +0 -0
- package/docs/RELEASING.md +1 -1
- package/lib/client.js +67 -6
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +20 -0
- package/lib/index.js +11 -2
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.1.5 — 2026-09-18
|
|
6
|
+
|
|
7
|
+
Verified DeepSeek Harness: `0.1.6-alpha.2` (also `0.1.6-alpha.1` and `0.1.5-rc.1`). Full bilingual release notes: [`docs/releases/v0.1.5.md`](docs/releases/v0.1.5.md).
|
|
8
|
+
|
|
9
|
+
- **The up-to-date state is now a green circle.** The dot beside the version used the chip's `currentColor`, which on the light shell is near-black and on the dark shell near-white: a dot that reads as "off" rather than "you are current". It now uses the theme's success token (`--dsw-alias-state-success-primary`, `#22c55e` in both themes) and is a flat 5px circle — no glow, no animation, so "nothing to do" can never be confused with the update signal. The dot carries a new `data-state` (the value `visualState` already computes), and the precedence is explicit: up-to-date is declared first, then the neutral grey **checking** pulse, then the amber **update** halo, so an in-flight check and a pending update are untouched by this change. The failed-read state keeps the chip's own label colour on the red fill.
|
|
10
|
+
- **An advisory notice no longer repaints the chip — the red chip was a classification bug.** DSH `0.1.6-alpha.2` shipped after this bundle's verified list, so the Host honestly reported *"alpha 是预览通道,版本 0.1.6-alpha.2 尚未验证与本插件兼容。"* and the client treated **any** `warning` as a failure: the whole version chip turned solid red (`#ec1313`) on a perfectly healthy read — exactly the "the background colour changed again" surprise the breathing dot was meant to remove. The Host now labels each warning with a `warningKind`: `failure` when it could not produce a usable answer (registry read failed, channel unpublished, versions not SemVer-comparable), `notice` when the answer is complete and the text is merely advisory (an unverified preview). Only `failure` can repaint the chip. A Host older than the field sends none, and the client then keeps the previous "any warning is a problem" behaviour rather than dropping a real failure. The classification moved out of the TSX component into `src/shared/visual-state.ts` so it is unit-tested: advisory → `current`, failure → `problem`, legacy payload → `problem`.
|
|
11
|
+
- **Verified against DSH `0.1.6-alpha.2`**, so the running release is recognised instead of flagged: `0.1.6-alpha.2` joins `VERIFIED_DSH_VERSIONS` and `package.json`'s `dsh.compatibility.dshReleases`. A new spec compares the two lists, because their drifting apart is silent.
|
|
12
|
+
- **The version chip is now a neutral second-level surface.** It used `label-primary` as its fill with `label-primary-inverted` text, which on the dark shell is a near-white pill with dark text — a white frame that swallowed the amber halo and fought the warn colour. The chip now uses `button-floating-hover`, the one palette surface that is grey in **both** themes (`#f1f3f5` light / `#353638` dark), with `label-primary` text (`#0f1115` light / `#f9fafb` dark): a light grey chip on the light shell, a dark grey chip with white text on the dark one.
|
|
13
|
+
- **Light, dark and system are followed automatically, and hover no longer inverts in light mode.** Every rendered colour is a DSH semantic token, so the chip, dot, halo, footer ring and panel follow whichever appearance DSH resolves — including `system`, which tracks the OS — with no plugin-side theme detection and no media query to keep in sync. The old hover used `brightness(1.08)`, which only ever brightens: on the light shell it drove the grey chip to pure white, i.e. made it vanish. Hover now mixes the theme's own label colour into the surface (darkens in light mode, lightens in dark mode) inside an `@supports (background:color-mix(…))` guard, keeping the brightness filter as the fallback for an engine without `color-mix`. Measured: `#f1f3f5` → `#dfe1e3` light, `#353638` → `#454648` dark.
|
|
14
|
+
- **The error chip label is theme-correct.** It still repaints red, but now uses the shell's own badge pattern (`bg-layer-3` label) instead of hard-coded white: white on the dark red of the light theme, dark on the light red of the dark theme — both readable, unlike the fixed white text it replaces.
|
|
15
|
+
- **A pending update no longer repaints the version chip.** The chip used to flip to a solid amber fill (`warn-primary`) the moment a newer release existed, which recoloured the whole brand row for a state that is not a chip state. The chip now keeps its normal fill in every state, and the only signal is the amber dot beside the version: a 6px dot with a soft halo whose scale and glow breathe together on a 1.6s cycle. The halo is mixed from the theme's own warn colour (`color-mix`) with a literal amber declaration in front of it, so the glow always matches the dot and still renders on an engine without `color-mix`. `prefers-reduced-motion` holds the glow still instead of animating it.
|
|
16
|
+
- Styling invariants are locked by `tests/client/styles.spec.ts`: no `data-update` fill rule on the chip, the chip's grey surface and theme label, the theme-mixed hover with its brightness fallback, the error label, the dot's own animation and halo (with its fallback), the update state outranking the loading state, and the reduced-motion escape hatch.
|
|
17
|
+
|
|
5
18
|
## 0.1.4 — 2026-09-16
|
|
6
19
|
|
|
7
20
|
Verified DeepSeek Harness: `0.1.6-alpha.1` (also `0.1.5-rc.1`). Full bilingual release notes: [`docs/releases/v0.1.4.md`](docs/releases/v0.1.4.md).
|
package/README.md
CHANGED
|
@@ -26,15 +26,19 @@
|
|
|
26
26
|
|
|
27
27
|
> DSH Update Status is a community plugin for DeepSeek Harness. It does not modify DSH core and it never installs, restarts, rolls back, downloads, or replaces DSH files.
|
|
28
28
|
|
|
29
|
-
It shadows only the expanded sidebar brand name with `DeepSeek` plus a compact version chip that fits the 24px brand row, leaving the official fish mark untouched. Tap the chip to inspect npm release channels, compatibility status, and a copy-only command for the selected channel.
|
|
29
|
+
It shadows only the expanded sidebar brand name with `DeepSeek` plus a compact version chip that fits the 24px brand row, leaving the official fish mark untouched. A green dot next to the version means there is nothing to do, and a breathing amber dot means a newer release is available. Tap the chip to inspect npm release channels, compatibility status, and a copy-only command for the selected channel.
|
|
30
30
|
|
|
31
31
|
<p align="center">
|
|
32
|
-
<img src="./assets/update-panel.png" width="
|
|
32
|
+
<img src="./assets/update-panel.png" width="400" alt="DSH Update Status panel: cache duration, the running 0.1.6-alpha.2 on the alpha channel, a latest row that does not match the followed channel, compatibility labels, and a copy-only upgrade command">
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
35
|
## Features
|
|
36
36
|
|
|
37
37
|
- **Visible version status** — shows the running DSH version in the expanded sidebar and a fallback action in the collapsed rail.
|
|
38
|
+
- **Quiet update signal** — a pending update never repaints the version chip. Only an amber dot with a soft halo next to the version breathes (scale + glow; the animation is disabled under `prefers-reduced-motion`), so a new release reads as one small light instead of a recoloured brand row.
|
|
39
|
+
- **Three dot states, one glance** — with nothing to do the dot beside the version is a plain **green circle** (the theme's success colour, not the text colour, so a dark shell no longer paints a dot that reads as "off"). While a check is running it is the neutral grey pulse; a pending update is the amber halo. Only the update state animates or glows, and only a failed status read repaints the chip, in red.
|
|
40
|
+
- **Neutral chip surface** — the chip is a grey second-level surface in both themes (`#f1f3f5` light / `#353638` dark) with the theme's normal label colour, so the dark shell gets a dark grey chip with white text rather than a white pill that hides the amber halo.
|
|
41
|
+
- **Light, dark, or system — followed automatically** — every colour the plugin renders is a DSH semantic token, so the chip, the dot, its halo, the footer ring and the panel all resolve through whatever appearance DSH is using. Set DSH to light, to dark, or to system and the plugin switches with the shell: no plugin-side theme setting, no media query to keep in sync. Hover lifts the chip in both directions (darkened in light mode, lightened in dark mode) by mixing the theme's label colour into the surface.
|
|
38
42
|
- **Stable and preview discovery** — reads npm dist-tags `latest`, `next`, and `alpha` in one registry request; `latest` is the default.
|
|
39
43
|
- **Useful choices only** — de-duplicates rows by version, keeps `latest` and the channel you follow, and never hides the channel that matches the release you are running.
|
|
40
44
|
- **In-panel channel selection** — select a meaningful stable, candidate, or preview release directly in the panel; the preference is stored by the DSH Host.
|
|
@@ -49,7 +53,7 @@ Requirements:
|
|
|
49
53
|
|
|
50
54
|
- DeepSeek Harness with the Web profile
|
|
51
55
|
- Node.js 20 or newer
|
|
52
|
-
- Verified DSH release: `0.1.6-alpha.
|
|
56
|
+
- Verified DSH release: `0.1.6-alpha.2` (also verified on `0.1.6-alpha.1` and `0.1.5-rc.1`)
|
|
53
57
|
|
|
54
58
|
With an installed `dsh` command:
|
|
55
59
|
|
|
@@ -94,6 +98,23 @@ npm install -g @deepseek-ai/dsh@alpha
|
|
|
94
98
|
|
|
95
99
|
The plugin displays one command that matches the detected installation kind and selected channel. It does not run these commands.
|
|
96
100
|
|
|
101
|
+
### What the dot means
|
|
102
|
+
|
|
103
|
+
The dot beside the version carries the whole status, and only the update state animates:
|
|
104
|
+
|
|
105
|
+
| Dot | State | Meaning |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| green circle | up to date | The followed channel points at the running release. The chip keeps its normal fill. |
|
|
108
|
+
| grey pulse | checking | A status read is in flight (first mount, a channel change, or **Check for updates**). |
|
|
109
|
+
| amber dot with a halo | update | The followed channel points at a newer release. Copy the command, run it yourself, restart DSH. |
|
|
110
|
+
| chip turns red | failed read | The plugin could not determine the state: the registry read failed, the channel is unpublished, or the versions are not SemVer-comparable. An *unverified* release is not this case — it is an advisory shown only in the panel. |
|
|
111
|
+
|
|
112
|
+
<p align="center">
|
|
113
|
+
<img src="./assets/sidebar-chip.png" width="300" alt="The version chip in a light shell (grey chip, dark text) and a dark shell (dark grey chip, white text), each showing the green up-to-date dot">
|
|
114
|
+
</p>
|
|
115
|
+
|
|
116
|
+
The chip itself is a neutral second-level surface — grey in both themes, with the theme's own label colour — so the amber halo always reads against it, and it never changes colour to announce an update.
|
|
117
|
+
|
|
97
118
|
## Release channels
|
|
98
119
|
|
|
99
120
|
| Channel | Purpose | Compatibility treatment |
|
|
@@ -127,28 +148,30 @@ If you want DSH's stock policy instead (a non-loopback page never persists setti
|
|
|
127
148
|
|
|
128
149
|
## Compatibility
|
|
129
150
|
|
|
130
|
-
Current release: plugin **`0.1.
|
|
151
|
+
Current release: plugin **`0.1.5`** is verified against DeepSeek Harness **`0.1.6-alpha.2`**.
|
|
131
152
|
|
|
132
153
|
### Which plugin version goes with which DeepSeek Harness version
|
|
133
154
|
|
|
134
155
|
| Plugin | Verified DeepSeek Harness | On npm | What that version is |
|
|
135
156
|
| --- | --- | --- | --- |
|
|
136
|
-
| **`0.1.
|
|
157
|
+
| **`0.1.5`** | `0.1.6-alpha.2`, `0.1.6-alpha.1`, `0.1.5-rc.1` | `latest` | One dot carries every state (green up to date, grey checking, amber breathing update), neutral grey chip surface in both themes, and advisory notices no longer repaint the chip |
|
|
158
|
+
| `0.1.4` | `0.1.6-alpha.1`, `0.1.5-rc.1` | published | Fixes the unselectable "channel you are running"; preference writes locked by tests |
|
|
137
159
|
| `0.1.3` | `0.1.6-alpha.1`, `0.1.5-rc.1` | published | Carries the `0.1.2` LAN (non-loopback) fix, re-verified on 0.1.6 and locked by tests |
|
|
138
160
|
| `0.1.2` | `0.1.5-rc.1` | **never published** | Removed the `connection.isLoopback` gate, so LAN pages work |
|
|
139
161
|
| `0.1.1` | `0.1.5-rc.1` | published | The previous npm `latest`; the plugin is inert on LAN/non-loopback pages |
|
|
140
162
|
| `0.1.0` | `0.1.2-rc.1` | published | First release |
|
|
141
163
|
|
|
142
|
-
- **Verified DeepSeek Harness** is the exact DSH release that plugin build was tested against. A
|
|
164
|
+
- **Verified DeepSeek Harness** is the exact DSH release that plugin build was tested against. The list has one source of truth in two places — `VERIFIED_DSH_VERSIONS` in [`src/shared/types.ts`](src/shared/types.ts) and `dsh.compatibility.dshReleases` in [`package.json`](package.json) — and a test keeps them identical. A release that is not on the list is not declared compatible: verify it manually first, and if it turns out incompatible, disable or uninstall the plugin rather than patching DSH core. A release that is merely *not listed yet* is reported as **unverified**: that is an advisory in the panel only, and it never repaints the chip — an operator who upgrades DSH ahead of this plugin keeps a normal chip.
|
|
143
165
|
- **On npm** is what `dsh plugin --profile web add dsh-update-status@latest` actually installs. A version that exists in this repository but not on npm is a development state, not a release.
|
|
144
166
|
- Match them explicitly when it matters:
|
|
145
167
|
|
|
146
168
|
```sh
|
|
169
|
+
dsh plugin --profile web add dsh-update-status@0.1.5 # DSH 0.1.6-alpha.2, 0.1.6-alpha.1 or 0.1.5-rc.1
|
|
147
170
|
dsh plugin --profile web add dsh-update-status@0.1.4 # DSH 0.1.6-alpha.1 or 0.1.5-rc.1
|
|
148
171
|
dsh plugin --profile web add dsh-update-status@0.1.0 # DSH 0.1.2-rc.1 only
|
|
149
172
|
```
|
|
150
173
|
|
|
151
|
-
- Per-release notes — what changed, who is affected, what to do — are hand-written in Chinese and English and become the GitHub Release body: [`v0.1.4`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.4.md) · [`v0.1.3`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.3.md) (covers the never-published `0.1.2`).
|
|
174
|
+
- Per-release notes — what changed, who is affected, what to do — are hand-written in Chinese and English and become the GitHub Release body: [`v0.1.5`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.5.md) · [`v0.1.4`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.4.md) · [`v0.1.3`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.3.md) (covers the never-published `0.1.2`).
|
|
152
175
|
|
|
153
176
|
## Configuration
|
|
154
177
|
|
|
@@ -172,6 +195,15 @@ node -p "require(process.env.HOME + '/.dsh/profiles/web/node_modules/dsh-update-
|
|
|
172
195
|
dsh plugin --profile web add dsh-update-status@latest
|
|
173
196
|
```
|
|
174
197
|
|
|
198
|
+
**The chip turned red right after I upgraded DSH.**
|
|
199
|
+
On `0.1.5` and newer a red chip means one thing: the plugin could not determine the update state — the registry read failed, the channel is unpublished, or the versions are not SemVer-comparable. Up to `0.1.4` the rule was broader: the client treated **any** warning as a problem, and *"this preview has not been verified against the plugin"* is exactly the warning a DSH release newer than the plugin's list produces. Upgrading DSH ahead of the plugin therefore painted the chip red even though the read had succeeded. `0.1.5` splits the two — `failure` still repaints, an advisory stays in the panel — so upgrade if you keep DSH ahead of this plugin:
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
dsh plugin --profile web add dsh-update-status@latest
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Until then, following a channel whose release is on the verified list avoids the warning.
|
|
206
|
+
|
|
175
207
|
**The channel I want is missing from the list.**
|
|
176
208
|
Rows are de-duplicated by version: when two tags point at the same release only the first is rendered. Selecting `latest` first re-projects the cache and can surface a preview row that was collapsed behind it. `0.1.4` also stopped hiding the channel that matches the running release.
|
|
177
209
|
|
package/README.zh.md
CHANGED
|
@@ -26,15 +26,19 @@
|
|
|
26
26
|
|
|
27
27
|
> DSH Update Status 是 DeepSeek Harness 社区插件。它不修改 DSH 核心,也绝不会安装、重启、回滚、下载或替换 DSH 文件。
|
|
28
28
|
|
|
29
|
-
插件只把展开侧栏中的品牌名称换成适配 24px 品牌行的 `DeepSeek +
|
|
29
|
+
插件只把展开侧栏中的品牌名称换成适配 24px 品牌行的 `DeepSeek + 版本芯片`,官方鱼标保持不变。版本号旁的**绿点**表示无事可做,**橙色呼吸圆点**表示线上有新版本。点击芯片可查看 npm 发布通道、兼容性状态,以及与所选通道对应的“仅复制”命令。
|
|
30
30
|
|
|
31
31
|
<p align="center">
|
|
32
|
-
<img src="./assets/update-panel.png" width="
|
|
32
|
+
<img src="./assets/update-panel.png" width="400" alt="DSH Update Status 面板:缓存时长、正在运行的 0.1.6-alpha.2(alpha 通道)、与所选通道不一致的 latest 行、兼容性标签和仅复制的升级命令">
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
35
|
## 功能
|
|
36
36
|
|
|
37
37
|
- **侧栏版本状态**:展开侧栏显示当前 DSH 版本,收起轨道提供状态入口。
|
|
38
|
+
- **安静的更新提示**:有新版本时**不改变版本 Badge 的背景**,只在版本号旁保留一个带小光晕的橙色圆点做放大缩小的呼吸动画(`prefers-reduced-motion` 下停用动画),让新版本表现为一盏小灯,而不是整条品牌行换色。
|
|
39
|
+
- **一眼三态**:没有可做的事时,版本号旁是**纯绿色小圆圈**(取主题 success 色,不再跟随文字色——暗色外壳下不会再出现一个看起来像「熄灭」的深色点);检查中是中性灰呼吸点;有新版本才是橙色光晕呼吸点。只有更新态会动、会发光,也只有状态读取失败才会把 Badge 重绘成红色。
|
|
40
|
+
- **中性版本底框**:Badge 在明暗两套主题下都用灰色二级面(浅色 `#f1f3f5` / 深色 `#353638`)配主题常规文字色,所以暗色下是「深灰底 + 白字」,不再是会把橙色光晕吃掉的白框;只有状态读取失败时才会重绘成红色。
|
|
41
|
+
- **浅色 / 深色 / 跟随系统,自动适配**:插件渲染的每个颜色都是 DSH 语义令牌,Badge、圆点、光晕、收起轨道圆环与面板都按 DSH 当前外观解析。DSH 设为浅色、深色或跟随系统,插件就跟着切换——插件侧没有自己的主题开关,也没有需要同步的媒体查询。hover 在两个方向上都是「抬起」:浅色下压深、深色下提亮(把主题文字色按比例混进底色实现)。
|
|
38
42
|
- **稳定版与预览版发现**:一次 registry 请求读取 npm dist-tags `latest`、`next`、`alpha`,默认选择 `latest`。
|
|
39
43
|
- **只展示有意义的选择**:按版本号去重;`latest` 与你当前跟随的通道始终保留,并且**不会隐藏与你正在运行的版本相符的那条通道**。
|
|
40
44
|
- **弹窗内直接选择通道**:可直接选择有价值的稳定版、候选版或预览版;偏好由 DSH Host 持久化。
|
|
@@ -49,7 +53,7 @@
|
|
|
49
53
|
|
|
50
54
|
- 带 Web profile 的 DeepSeek Harness
|
|
51
55
|
- Node.js 20 或更新版本
|
|
52
|
-
- 已验证的 DSH 版本:`0.1.6-alpha.
|
|
56
|
+
- 已验证的 DSH 版本:`0.1.6-alpha.2`(同时验证了 `0.1.6-alpha.1` 与 `0.1.5-rc.1`)
|
|
53
57
|
|
|
54
58
|
已经安装 `dsh` 命令:
|
|
55
59
|
|
|
@@ -94,6 +98,23 @@ npm install -g @deepseek-ai/dsh@alpha
|
|
|
94
98
|
|
|
95
99
|
插件只显示与检测到的安装方式和所选通道匹配的一条命令,不会运行这些命令。
|
|
96
100
|
|
|
101
|
+
### 圆点含义
|
|
102
|
+
|
|
103
|
+
版本号旁那颗圆点承载全部状态,且只有更新态会动:
|
|
104
|
+
|
|
105
|
+
| 圆点 | 状态 | 含义 |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| 绿色小圆圈 | 已是最新 | 你跟随的通道指向正在运行的版本,芯片保持原底色。 |
|
|
108
|
+
| 灰点呼吸 | 检查中 | 状态读取正在进行(首次挂载、切换通道,或点了**检查更新**)。 |
|
|
109
|
+
| 橙色圆点 + 光晕 | 有更新 | 你跟随的通道指向更新的版本。自行复制命令、执行,然后重启 DSH。 |
|
|
110
|
+
| 芯片变红 | 读取失败 | 插件无法判定状态:registry 读取失败、该通道未发布,或两个版本无法按 SemVer 比较。**未验证兼容**不属于这一档——它只是面板里的提示。 |
|
|
111
|
+
|
|
112
|
+
<p align="center">
|
|
113
|
+
<img src="./assets/sidebar-chip.png" width="300" alt="版本芯片在浅色外壳(浅灰底、深色字)与深色外壳(深灰底、白字)下的样子,右侧都是表示已是最新的绿点">
|
|
114
|
+
</p>
|
|
115
|
+
|
|
116
|
+
芯片本身是中性二级面——两套主题都是灰色,文字用主题常规色——所以橙色光晕始终看得清,而且它绝不会为了宣布更新而改变颜色。
|
|
117
|
+
|
|
97
118
|
## 发布通道
|
|
98
119
|
|
|
99
120
|
| 通道 | 用途 | 兼容性处理 |
|
|
@@ -127,28 +148,30 @@ DSH 对来源不是 loopback(`localhost` / `127.0.0.1`)的页面会关闭 Ho
|
|
|
127
148
|
|
|
128
149
|
## 兼容性
|
|
129
150
|
|
|
130
|
-
当前发布:插件 **`0.1.
|
|
151
|
+
当前发布:插件 **`0.1.5`** 已针对 DeepSeek Harness **`0.1.6-alpha.2`** 验证。
|
|
131
152
|
|
|
132
153
|
### 插件版本与 DeepSeek Harness 版本的对应关系
|
|
133
154
|
|
|
134
155
|
| 插件版本 | 已验证的 DeepSeek Harness | npm 发布状态 | 该版本是什么 |
|
|
135
156
|
| --- | --- | --- | --- |
|
|
136
|
-
| **`0.1.
|
|
157
|
+
| **`0.1.5`** | `0.1.6-alpha.2`、`0.1.6-alpha.1`、`0.1.5-rc.1` | `latest` | 一颗圆点承载全部状态(绿=已是最新、灰=检查中、橙=有新版本)、两套主题都用中性灰底框、提示性告警不再重绘芯片 |
|
|
158
|
+
| `0.1.4` | `0.1.6-alpha.1`、`0.1.5-rc.1` | 已发布 | 修复「正在运行的通道不可选」;偏好写入路径补齐回归测试 |
|
|
137
159
|
| `0.1.3` | `0.1.6-alpha.1`、`0.1.5-rc.1` | 已发布 | 含 `0.1.2` 的局域网(非回环)修复,并在 0.1.6 上复验、补上回归测试 |
|
|
138
160
|
| `0.1.2` | `0.1.5-rc.1` | **未发布** | 移除 `connection.isLoopback` 门控,局域网页面可用 |
|
|
139
161
|
| `0.1.1` | `0.1.5-rc.1` | 已发布 | 此前 npm 上的 `latest`;局域网/非回环页面下插件整体不可用 |
|
|
140
162
|
| `0.1.0` | `0.1.2-rc.1` | 已发布 | 首个版本 |
|
|
141
163
|
|
|
142
|
-
- **已验证的 DeepSeek Harness** 是该插件构建实际测试过的确切 DSH
|
|
164
|
+
- **已验证的 DeepSeek Harness** 是该插件构建实际测试过的确切 DSH 版本。这份清单只有两个存放处——[`src/shared/types.ts`](src/shared/types.ts) 的 `VERIFIED_DSH_VERSIONS` 与 [`package.json`](package.json) 的 `dsh.compatibility.dshReleases`——并有测试保证两者一致。未列出的 DSH 版本不会被宣称为兼容:请先人工验证;确认不兼容时请禁用或卸载插件,不要修改 DSH 核心。若只是**尚未列入**,插件会标为「尚未验证兼容」:这是**只出现在面板里**的提示,绝不会重绘芯片——DSH 升级快于插件时,芯片依然保持正常外观。
|
|
143
165
|
- **npm 发布状态** 是 `dsh plugin --profile web add dsh-update-status@latest` 实际会装到的版本。只存在于本仓库、尚未发布到 npm 的版本属于开发状态,不是发布版本。
|
|
144
166
|
- 需要精确对应时显式指定版本:
|
|
145
167
|
|
|
146
168
|
```sh
|
|
169
|
+
dsh plugin --profile web add dsh-update-status@0.1.5 # DSH 0.1.6-alpha.2、0.1.6-alpha.1 或 0.1.5-rc.1
|
|
147
170
|
dsh plugin --profile web add dsh-update-status@0.1.4 # DSH 0.1.6-alpha.1 或 0.1.5-rc.1
|
|
148
171
|
dsh plugin --profile web add dsh-update-status@0.1.0 # 仅 DSH 0.1.2-rc.1
|
|
149
172
|
```
|
|
150
173
|
|
|
151
|
-
- 每个版本的中英文详细说明(改了什么、影响谁、需要做什么)手写后直接作为 GitHub Release 正文:[`v0.1.4`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.4.md) · [`v0.1.3`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.3.md)(含从未发布的 `0.1.2`)。
|
|
174
|
+
- 每个版本的中英文详细说明(改了什么、影响谁、需要做什么)手写后直接作为 GitHub Release 正文:[`v0.1.5`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.5.md) · [`v0.1.4`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.4.md) · [`v0.1.3`](https://github.com/idoall/dsh-update-status/blob/main/docs/releases/v0.1.3.md)(含从未发布的 `0.1.2`)。
|
|
152
175
|
|
|
153
176
|
## 配置
|
|
154
177
|
|
|
@@ -172,6 +195,15 @@ node -p "require(process.env.HOME + '/.dsh/profiles/web/node_modules/dsh-update-
|
|
|
172
195
|
dsh plugin --profile web add dsh-update-status@latest
|
|
173
196
|
```
|
|
174
197
|
|
|
198
|
+
**升级 DSH 之后芯片变红了。**
|
|
199
|
+
在 `0.1.5` 及更新版本上,红色只代表一件事:插件无法判定状态——registry 读取失败、该通道未发布,或两个版本无法按 SemVer 比较。`0.1.4` 及更早版本的判定更宽:客户端把**任何**警告都当成故障,而「这个预览版尚未验证与本插件兼容」正是 DSH 版本比插件清单更新时必然产生的警告。所以「DSH 先升级、插件还没跟上」就会把芯片涂红,哪怕读取其实成功了。`0.1.5` 把两者分开——`failure` 仍然重绘,提示只留在面板——因此长期让 DSH 领先的用法请升级:
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
dsh plugin --profile web add dsh-update-status@latest
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
在那之前,跟随一个发布版在已验证清单里的通道即可避免该警告。
|
|
206
|
+
|
|
175
207
|
**想要的通道不在列表里。**
|
|
176
208
|
列表按版本号去重:两个 dist-tag 指向同一版本时只渲染第一个。先选一次 `latest` 会重新投影缓存,可能让被折叠在后面的预览行出现。另外 `0.1.4` 起,与运行版本相符的那条通道不再被隐藏。
|
|
177
209
|
|
|
Binary file
|
package/assets/update-panel.png
CHANGED
|
Binary file
|
package/docs/RELEASING.md
CHANGED
|
@@ -133,7 +133,7 @@ The final command must print:
|
|
|
133
133
|
dsh-update-status X.Y.Z web
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
Then install the package in a disposable DSH profile and manually verify the sidebar badge, panel, release-channel selection, cache-duration setting, and mobile drawer behavior:
|
|
136
|
+
Then install the package in a disposable DSH profile and manually verify the sidebar badge, its three dot states (green when up to date, grey pulse while checking, amber halo when a newer release exists — the chip fill itself must not change), the panel, release-channel selection, cache-duration setting, and mobile drawer behavior:
|
|
137
137
|
|
|
138
138
|
```sh
|
|
139
139
|
TEST_HOME="$(mktemp -d)"
|
package/lib/client.js
CHANGED
|
@@ -88,6 +88,25 @@ window.__ModuleLoader__.load({
|
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
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
|
+
//#endregion
|
|
91
110
|
//#region src/client/i18n.ts
|
|
92
111
|
const DICTIONARY = {
|
|
93
112
|
"brand.status": {
|
|
@@ -418,7 +437,7 @@ window.__ModuleLoader__.load({
|
|
|
418
437
|
}
|
|
419
438
|
function visualState(status, loading, error) {
|
|
420
439
|
if (loading) return "loading";
|
|
421
|
-
if (error !== null || status
|
|
440
|
+
if (error !== null || isChipProblem(status)) return status?.hasUpdate === true ? "update" : "problem";
|
|
422
441
|
return status?.hasUpdate === true ? "update" : "current";
|
|
423
442
|
}
|
|
424
443
|
function badgeLabel(status, loading, error) {
|
|
@@ -466,6 +485,7 @@ window.__ModuleLoader__.load({
|
|
|
466
485
|
children: shortVersion(version)
|
|
467
486
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
468
487
|
className: "dus-dot",
|
|
488
|
+
"data-state": state,
|
|
469
489
|
"data-update": snapshot.status?.hasUpdate === true || void 0,
|
|
470
490
|
"data-loading": snapshot.loading || void 0,
|
|
471
491
|
"aria-hidden": "true"
|
|
@@ -494,6 +514,7 @@ window.__ModuleLoader__.load({
|
|
|
494
514
|
children: "↟"
|
|
495
515
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
496
516
|
className: "dus-dot dus-footer-dot",
|
|
517
|
+
"data-state": state,
|
|
497
518
|
"data-update": snapshot.status?.hasUpdate === true || void 0,
|
|
498
519
|
"data-loading": state === "loading" || void 0
|
|
499
520
|
})]
|
|
@@ -941,6 +962,7 @@ window.__ModuleLoader__.load({
|
|
|
941
962
|
const latestVersion = record.latestVersion === null ? null : stringOrNull(record.latestVersion);
|
|
942
963
|
const checkedAt = record.checkedAt === null ? null : stringOrNull(record.checkedAt);
|
|
943
964
|
const warning = record.warning === null ? null : stringOrNull(record.warning);
|
|
965
|
+
const warningKind = record.warningKind === "failure" || record.warningKind === "notice" ? record.warningKind : void 0;
|
|
944
966
|
const publishedAt = record.publishedAt === null ? null : stringOrNull(record.publishedAt);
|
|
945
967
|
if (record.latestVersion !== null && latestVersion === null || record.checkedAt !== null && checkedAt === null || record.warning !== null && warning === null || record.publishedAt !== null && publishedAt === null) return void 0;
|
|
946
968
|
const channels = [];
|
|
@@ -964,6 +986,7 @@ window.__ModuleLoader__.load({
|
|
|
964
986
|
cached: record.cached,
|
|
965
987
|
checkedAt,
|
|
966
988
|
warning,
|
|
989
|
+
warningKind,
|
|
967
990
|
installKind: record.installKind,
|
|
968
991
|
upgradeCommand: record.upgradeCommand,
|
|
969
992
|
releaseUrl: record.releaseUrl,
|
|
@@ -1561,17 +1584,55 @@ window.__ModuleLoader__.load({
|
|
|
1561
1584
|
const UPDATE_STATUS_CSS = `
|
|
1562
1585
|
.dus-brand-name{align-items:center;gap:6px;height:24px;max-width:100%;min-width:0;display:inline-flex}
|
|
1563
1586
|
.dus-brand-deepseek{font-size:14px;font-weight:650;letter-spacing:-.015em;line-height:24px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1564
|
-
|
|
1587
|
+
/* The chip is a NEUTRAL SECOND-LEVEL SURFACE, never a high-contrast pill. It used
|
|
1588
|
+
to be label-primary + label-primary-inverted, which is a near-white box with dark
|
|
1589
|
+
text on the dark shell — a white frame that swallows the amber halo and fights the
|
|
1590
|
+
warn colour. --dsw-alias-button-floating-hover is the one palette surface that is
|
|
1591
|
+
grey in BOTH themes (#f1f3f5 light / #353638 dark), matching the reference: a light
|
|
1592
|
+
grey chip on the light shell, a dark grey chip with white text on the dark one. The
|
|
1593
|
+
sibling button-floating-fill is pure white in light mode and changes nothing there.
|
|
1594
|
+
Text stays label-primary (#0f1115 light / #f9fafb dark), so it is never low-contrast.
|
|
1595
|
+
Every colour here is a theme token, so the chip follows whatever appearance DSH
|
|
1596
|
+
resolves — light, dark, or system-follows-the-OS — with no plugin-side detection,
|
|
1597
|
+
no media query and no per-theme branch to keep in sync. */
|
|
1598
|
+
.dus-badge{--dus-badge-surface:var(--dsw-alias-button-floating-hover,#f1f3f5);align-items:center;background:var(--dus-badge-surface);border:0;border-radius:4px;color:var(--dsw-alias-label-primary,#0f1115);cursor:pointer;display:inline-flex;flex:none;font-family:var(--ds-font-family-code,var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace));font-size:10px;font-variant-numeric:tabular-nums;font-weight:600;gap:4px;height:16px;line-height:16px;max-width:140px;outline:none;padding:0 6px;touch-action:manipulation;user-select:none;white-space:nowrap}
|
|
1565
1599
|
.dus-badge:focus-visible,.dus-footer-button:focus-visible,.dus-action:focus-visible,.dus-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
1600
|
+
/* Hover has to read as "raised" in BOTH themes. brightness() only ever brightens, and on
|
|
1601
|
+
the light shell it turns the grey chip pure white — the chip all but disappears. Mixing
|
|
1602
|
+
a little of the theme's own label colour into the surface darkens it in light mode and
|
|
1603
|
+
lightens it in dark mode, so one declaration stays correct in both. The brightness
|
|
1604
|
+
filter remains the fallback: an engine without color-mix drops only the second
|
|
1605
|
+
declaration, never the whole hover rule. */
|
|
1566
1606
|
.dus-badge:hover{filter:brightness(1.08)}
|
|
1567
|
-
.dus-badge
|
|
1568
|
-
|
|
1607
|
+
@supports (background:color-mix(in srgb,red 50%,transparent)){.dus-badge:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 8%,var(--dus-badge-surface));filter:none}}
|
|
1608
|
+
/* The failure state is the one deliberate repaint, and it follows the shell's own
|
|
1609
|
+
badge pattern (brokenBadge): error fill with a bg-layer-3 label, which is white
|
|
1610
|
+
text on the dark red of the light theme and dark text on the light red of the dark
|
|
1611
|
+
theme — readable in both, unlike the fixed white text it replaces. */
|
|
1612
|
+
.dus-badge[data-error=true]{background:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-bg-layer-3,#fff)}
|
|
1569
1613
|
.dus-badge-version{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1570
1614
|
.dus-dot{background:currentColor;border-radius:50%;display:inline-block;flex:0 0 auto;height:5px;opacity:.9;width:5px}
|
|
1571
|
-
|
|
1615
|
+
/* An available update repaints NOTHING: the chip keeps its normal fill and only
|
|
1616
|
+
the amber dot breathes. The halo is animated with the dot so the glow grows
|
|
1617
|
+
and shrinks as one light, never as a separate ripple. The update state is
|
|
1618
|
+
declared AFTER the loading state, so a re-check on a known update keeps the
|
|
1619
|
+
amber glow instead of falling back to the neutral loading pulse.
|
|
1620
|
+
Each box-shadow is declared twice: a literal amber first for any engine
|
|
1621
|
+
without color-mix, then the halo mixed from the theme's own warn colour so the
|
|
1622
|
+
glow always matches the dot in both light and dark themes. */
|
|
1623
|
+
/* The dot carries the whole status. Three states, in precedence order:
|
|
1624
|
+
up to date = a plain green circle (success token, never the text colour, so a
|
|
1625
|
+
dark theme no longer paints a black dot that reads as "off"); checking = the
|
|
1626
|
+
neutral grey pulse; a pending update = the amber dot below, which is the only
|
|
1627
|
+
state allowed to breathe or glow.
|
|
1628
|
+
The up-to-date rule is declared BEFORE loading and update: equal specificity, so
|
|
1629
|
+
the later, louder states win if two ever land on the same dot. */
|
|
1630
|
+
.dus-dot[data-state=current]{background:var(--dsw-alias-state-success-primary,#22c55e);opacity:1}
|
|
1572
1631
|
.dus-dot[data-loading=true]{animation:dus-pulse .9s ease-in-out infinite}
|
|
1632
|
+
.dus-dot[data-update=true]{--dus-update-color:var(--dsw-alias-state-warn-primary,#f59e0b);animation:dus-update-pulse 1.6s ease-in-out infinite;background:var(--dus-update-color);height:6px;opacity:1;width:6px}
|
|
1573
1633
|
@keyframes dus-pulse{0%,100%{opacity:.45;transform:scale(.82)}50%{opacity:1;transform:scale(1.15)}}
|
|
1574
|
-
@
|
|
1634
|
+
@keyframes dus-update-pulse{0%,100%{box-shadow:0 0 2px 0 rgba(245,158,11,.4);box-shadow:0 0 2px 0 color-mix(in srgb,var(--dus-update-color) 45%,transparent);transform:scale(.8)}50%{box-shadow:0 0 7px 2px rgba(245,158,11,.72);box-shadow:0 0 7px 2px color-mix(in srgb,var(--dus-update-color) 72%,transparent);transform:scale(1.15)}}
|
|
1635
|
+
@media (prefers-reduced-motion:reduce){.dus-dot[data-update=true],.dus-dot[data-loading=true]{animation:none}.dus-dot[data-update=true]{box-shadow:0 0 5px 1px rgba(245,158,11,.6);box-shadow:0 0 5px 1px color-mix(in srgb,var(--dus-update-color) 62%,transparent)}}
|
|
1575
1636
|
.dus-footer-button{align-items:center;background:transparent;border:0;border-radius:10px;color:var(--dsw-alias-label-secondary);cursor:pointer;display:flex;height:44px;justify-content:center;min-height:44px;min-width:44px;padding:0;position:relative;touch-action:manipulation;width:44px}
|
|
1576
1637
|
.dus-footer-button:hover{background:var(--dsw-alias-button-floating-hover);color:var(--dsw-alias-label-primary)}
|
|
1577
1638
|
.dus-footer-icon{font-size:18px;line-height:1}
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["PLUGIN_ID","PLUGIN_ID","candidate","sameSnapshot"],"sources":["../src/shared/channels.ts","../src/shared/types.ts","../src/shared/semver.ts","../src/shared/preview-guidance.ts","../src/client/i18n.ts","../src/client/react.ts","../src/client/components.tsx","../src/client/contract.ts","../src/client/stores.ts","../src/client/settings/scopeFaces.ts","../src/client/settings/hostDirectScope.ts","../src/client/settings/settingsChannel.ts","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["import type { ChannelRelease, UpdateStatus } from './types.ts'\n\nconst CHANNEL_ORDER = { latest: 0, next: 1, alpha: 2 } as const\n\n/**\n * Reduce raw dist-tags to user-meaningful choices without losing Host facts.\n * The selected channel and stable return path are always present; every other\n * row must carry a version no earlier row already shows.\n *\n * A channel is deliberately NOT hidden just because it currently points at the\n * release that is running. Following a channel is a statement about future\n * releases, and `currentVersion` still rides the input for the caller's own\n * rendering, but filtering on it created a deadlock: while running the version\n * published on `alpha`, the `alpha` row disappeared, so the one channel a user\n * on that line would want to follow could never be selected — the only way to\n * see it was to already follow it.\n */\nexport function visibleChannelReleases(status: Pick<UpdateStatus, 'currentVersion' | 'channel' | 'channels'>): ChannelRelease[] {\n const ordered = [...status.channels].sort((left, right) => CHANNEL_ORDER[left.channel] - CHANNEL_ORDER[right.channel])\n const mandatory = new Set(['latest', status.channel])\n const visible = ordered.filter(release => mandatory.has(release.channel))\n const seenVersions = new Set(visible.map(release => release.version).filter((version): version is string => version !== null))\n\n for (const release of ordered) {\n if (mandatory.has(release.channel) || release.version === null) continue\n if (seenVersions.has(release.version)) continue\n visible.push(release)\n seenVersions.add(release.version)\n }\n\n return visible.sort((left, right) => CHANNEL_ORDER[left.channel] - CHANNEL_ORDER[right.channel])\n}\n","/**\n * JSON-only contract shared by the Host and Web halves.\n *\n * The static package uses the authenticated Connection RPC channel because\n * `harness.handle` / `host.call` are dynamic-Cordis-only closure APIs in DSH\n * 0.1.5-rc.1. The endpoint vocabulary remains deliberately small and private\n * to this plugin channel.\n */\n\nexport const PLUGIN_ID = 'dsh-update-status'\nexport const PACKAGE_NAME = '@deepseek-ai/dsh'\n/** Shared Connection RPC channel. Custom prefixes 405 on the SPA fallback. */\nexport const UPDATE_STATUS_CHANNEL = '/api'\nexport const RELEASES_URL = 'https://github.com/deepseek-ai/deepseek-harness/releases'\n/**\n * DSH releases this bundle has actually been verified against. The same list is\n * declared in package.json's `dsh.compatibility.dshReleases`, and every other\n * release the registry reports stays `unverified` — the plugin never claims a\n * compatibility nobody checked.\n */\nexport const VERIFIED_DSH_VERSIONS: readonly string[] = ['0.1.5-rc.1', '0.1.6-alpha.1']\nexport const RELEASE_CHANNELS = ['latest', 'next', 'alpha'] as const\nexport const DEFAULT_CACHE_TTL_MINUTES = 360\nexport const MIN_CACHE_TTL_MINUTES = 30\nexport const MAX_CACHE_TTL_MINUTES = 1_440\n\nexport function isCacheTtlMinutes(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value)\n && value >= MIN_CACHE_TTL_MINUTES && value <= MAX_CACHE_TTL_MINUTES\n}\n\nexport const UPDATE_ENDPOINTS = {\n getStatus: 'dsh-update-status.get-status',\n checkUpdate: 'dsh-update-status.check-update',\n} as const\n\nexport type UpdateEndpoint = (typeof UPDATE_ENDPOINTS)[keyof typeof UPDATE_ENDPOINTS]\nexport type ReleaseChannel = (typeof RELEASE_CHANNELS)[number]\nexport type ReleaseCompatibility = 'verified' | 'unverified' | 'incompatible'\nexport type InstallKind = 'npm-global' | 'pnpm-global' | 'source-checkout' | 'unknown'\n\nexport function isReleaseChannel(value: unknown): value is ReleaseChannel {\n return value === 'latest' || value === 'next' || value === 'alpha'\n}\n\nexport interface ChannelRelease {\n channel: ReleaseChannel\n version: string | null\n publishedAt: string | null\n compatibility: ReleaseCompatibility\n}\n\n/** Arguments accepted by either read/check endpoint. */\nexport interface CheckUpdateRequest {\n force?: boolean\n channel?: ReleaseChannel\n /** User preference, bounded by the Host before it affects cache expiry. */\n cacheTtlMinutes?: number\n}\n\n/**\n * A lossless, JSON-serializable snapshot used by both browser surfaces.\n * Nulls are intentional: a failed first check must still render a truthful\n * current-version card instead of an empty or malformed UI.\n */\nexport interface UpdateStatus {\n currentVersion: string\n latestVersion: string | null\n hasUpdate: boolean\n cached: boolean\n checkedAt: string | null\n warning: string | null\n installKind: InstallKind\n upgradeCommand: string\n releaseUrl: string\n changelogUrl: string\n publishedAt: string | null\n packageName: string\n /** Selected npm dist-tag used for comparison and command generation. */\n channel: ReleaseChannel\n /** All supported dist-tags returned by the same cached registry request. */\n channels: ChannelRelease[]\n /** Phase 1 is informational only; the GUI must never apply an update. */\n canApplyInPlace: false\n}\n\n/** Persisted browser preference served through the ordinary DSH settings seam. */\nexport interface UpdateStatusSettings {\n sidebarEnabled: boolean\n channel: ReleaseChannel\n /** On-demand npm-registry cache duration. This never starts a browser timer. */\n cacheTtlMinutes: number\n}\n","/**\n * Small, dependency-free SemVer 2.0 comparator for the host check path.\n * It intentionally accepts the common leading `v` used by release tags while\n * preserving prerelease precedence (`0.1.2-rc.1 < 0.1.2`).\n */\n\nexport interface ParsedSemver {\n major: number\n minor: number\n patch: number\n prerelease: readonly string[]\n}\n\nconst 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-]+)*)?$/\nconst NUMERIC_IDENTIFIER = /^(0|[1-9]\\d*)$/\n\nexport function parseSemver(value: string): ParsedSemver | undefined {\n const match = SEMVER.exec(value.trim())\n if (match === null) return undefined\n const major = Number(match[1])\n const minor = Number(match[2])\n const patch = Number(match[3])\n if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) return undefined\n const prerelease = match[4] === undefined ? [] : match[4].split('.')\n return { major, minor, patch, prerelease }\n}\n\nfunction compareIdentifier(left: string, right: string): number {\n const leftNumeric = NUMERIC_IDENTIFIER.test(left)\n const rightNumeric = NUMERIC_IDENTIFIER.test(right)\n if (leftNumeric && rightNumeric) return Number(left) - Number(right)\n if (leftNumeric) return -1\n if (rightNumeric) return 1\n return left < right ? -1 : left > right ? 1 : 0\n}\n\n/** Returns a negative number when left is older; undefined means unparsable. */\nexport function compareSemver(leftValue: string, rightValue: string): number | undefined {\n const left = parseSemver(leftValue)\n const right = parseSemver(rightValue)\n if (left === undefined || right === undefined) return undefined\n\n for (const key of ['major', 'minor', 'patch'] as const) {\n if (left[key] !== right[key]) return left[key] - right[key]\n }\n\n const leftStable = left.prerelease.length === 0\n const rightStable = right.prerelease.length === 0\n if (leftStable && rightStable) return 0\n if (leftStable) return 1\n if (rightStable) return -1\n\n const length = Math.max(left.prerelease.length, right.prerelease.length)\n for (let index = 0; index < length; index += 1) {\n const leftPart = left.prerelease[index]\n const rightPart = right.prerelease[index]\n if (leftPart === undefined) return -1\n if (rightPart === undefined) return 1\n const comparison = compareIdentifier(leftPart, rightPart)\n if (comparison !== 0) return comparison\n }\n return 0\n}\n\n/** False rather than a guess when either version is not valid SemVer. */\nexport function hasSemverUpdate(currentVersion: string, latestVersion: string): boolean {\n const comparison = compareSemver(currentVersion, latestVersion)\n return comparison !== undefined && comparison < 0\n}\n","import type { UpdateStatus } from './types.ts'\nimport { PACKAGE_NAME } from './types.ts'\nimport { parseSemver } from './semver.ts'\n\n/** Pinned guidance only: never execute a command or mutate the followed channel. */\nexport function previewCommand(status: UpdateStatus, version: string | null): string | null {\n if (version === null || version.trim() !== version || parseSemver(version) === undefined) return null\n if (status.installKind === 'npm-global') return `npm install -g ${PACKAGE_NAME}@${version}`\n if (status.installKind === 'pnpm-global') return `pnpm add -g ${PACKAGE_NAME}@${version}`\n return null\n}\n","/** Tiny bilingual dictionary kept local to avoid changing the host locale tree. */\n\nexport type Language = 'zh' | 'en'\n\ntype Entry = { zh: string; en: string }\n\nconst DICTIONARY: Record<string, Entry> = {\n 'brand.status': { zh: 'DSH 版本状态', en: 'DSH version status' },\n 'brand.checking': { zh: '正在检查版本', en: 'Checking version' },\n 'brand.update': { zh: '有可用更新', en: 'Update available' },\n 'brand.current': { zh: '已是最新或尚未发现更新', en: 'Up to date or no update found' },\n 'brand.problem': { zh: '检查遇到问题', en: 'Check encountered a problem' },\n 'footer.status': { zh: '打开 DSH 版本状态', en: 'Open DSH version status' },\n 'panel.title': { zh: 'DSH 更新状态', en: 'DSH update status' },\n 'panel.close': { zh: '关闭', en: 'Close' },\n 'panel.cacheDuration': { zh: '缓存时长', en: 'Cache duration' },\n 'panel.minutes': { zh: '分钟', en: 'minutes' },\n 'panel.cacheHint': { zh: '到期后在下次打开或读取状态时检查;不会后台轮询。点击“检查更新”会立即检查。', en: 'After expiry, DSH checks only on the next status read; there is no background polling. “Check for updates” checks immediately.' },\n 'panel.cacheReadonly': { zh: '此连接的缓存设置为只读。', en: 'This connection’s cache setting is read-only.' },\n 'panel.current': { zh: '当前运行版本', en: 'Current running version' },\n 'panel.latest': { zh: '所选通道版本', en: 'Selected channel version' },\n 'panel.channels': { zh: '可用发布通道', en: 'Available release channels' },\n 'panel.compatVerified': { zh: '已验证兼容', en: 'Verified compatible' },\n 'panel.compatUnverified': { zh: '尚未验证兼容', en: 'Compatibility unverified' },\n 'panel.compatIncompatible': { zh: '已知不兼容', en: 'Known incompatible' },\n 'panel.selectChannel': { zh: '选择此通道', en: 'Select channel' },\n 'panel.selectedChannel': { zh: '已选择', en: 'Selected' },\n 'panel.notChecked': { zh: '尚未取得', en: 'Not available yet' },\n 'panel.available': { zh: '发现新版本', en: 'Update available' },\n 'panel.currentState': { zh: '未发现可用更新', en: 'No update found' },\n 'panel.cached': { zh: '来自 Host 缓存', en: 'From Host cache' },\n 'panel.live': { zh: '刚从 npm registry 检查', en: 'Checked npm registry now' },\n 'panel.checkedAt': { zh: '上次检查', en: 'Last checked' },\n 'panel.publishedAt': { zh: '发布时间', en: 'Published' },\n 'panel.check': { zh: '检查更新', en: 'Check for updates' },\n 'panel.checking': { zh: '检查中…', en: 'Checking…' },\n 'panel.switchingChannel': { zh: '正在切换通道…', en: 'Switching channel…' },\n 'panel.releaseNotes': { zh: '发布说明', en: 'Release notes' },\n 'panel.command': { zh: '升级命令', en: 'Upgrade command' },\n 'panel.copy': { zh: '复制命令', en: 'Copy command' },\n 'panel.copied': { zh: '已复制', en: 'Copied' },\n 'panel.copyFallback': { zh: '浏览器未允许复制;已选中文本,请长按或复制。', en: 'Clipboard unavailable; the command is selected for long-press or copy.' },\n 'panel.commandNote': { zh: '仅复制,不会执行。请在运行 DSH 的那台电脑的终端执行;完成后由你自行重启 DSH。', en: 'Copy only — nothing runs here. Execute it in a terminal on the computer running DSH, then restart DSH yourself.' },\n 'panel.readOnly': { zh: '阶段 1 仅提示:本插件不会安装、重启、回滚或替换任何文件。', en: 'Phase 1 is advisory only: this plugin never installs, restarts, rolls back, or replaces files.' },\n 'panel.error': { zh: '检查提示', en: 'Check notice' },\n 'panel.static': { zh: '此连接只显示静态版本;请在运行 DSH 的本机打开侧栏查看完整更新信息。', en: 'This connection shows only the static version. Open the sidebar on the computer running DSH for full update details.' },\n 'preview.open': { zh: '查看预览版升级方式', en: 'View preview upgrade instructions' },\n 'preview.close': { zh: '收起升级说明', en: 'Hide upgrade instructions' },\n 'preview.follow': { zh: '关注通道只影响检查结果,不代表已安装或已切换版本。', en: 'Following a channel only changes update checks, not the installed version.' },\n 'preview.target': { zh: '目标版本', en: 'Target version' },\n 'preview.risk': { zh: '预览版本可能不稳定,插件兼容性尚需确认。执行前请保存工作、备份配置并结束运行中的任务;安装后需手动重启 DSH。此页面不会安装或重启。', 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.' },\n 'preview.unavailable': { zh: '暂无可确认的预览目标,请先检查更新。', en: 'No confirmed preview target. Check for updates first.' },\n 'preview.manual': { zh: '当前安装方式无法安全生成命令,请先确认安装来源;源码安装需按其构建说明操作。', en: 'Cannot safely generate a command for this installation. Confirm its source; source checkouts require their build instructions.' },\n 'preview.same': { zh: '此目标与当前运行版本相同,无需重复安装。', en: 'This target is already running; no reinstall is needed.' },\n 'settings.title': { zh: '版本与更新', en: 'Version & updates' },\n 'settings.sidebar': { zh: '在侧栏显示版本状态入口', en: 'Show the version-status entry in the sidebar' },\n 'settings.sidebarHint': { zh: '关闭后不再渲染品牌 Badge 和收起轨道兜底入口。不会执行或安排升级。', en: 'When off, the brand badge and collapsed-rail fallback are not rendered. No update is run or scheduled.' },\n 'settings.channel': { zh: '关注发布通道', en: 'Release channel to follow' },\n 'settings.channelLatest': { zh: '稳定版(latest,推荐)', en: 'Stable (latest, recommended)' },\n 'settings.channelNext': { zh: '候选版(next)', en: 'Release candidate (next)' },\n 'settings.channelAlpha': { zh: '预览版(alpha)', en: 'Preview (alpha)' },\n 'settings.channelHint': { zh: '预览通道可能包含未稳定接口或插件兼容性变化。这里只检查并生成命令,不会安装。', en: 'Preview channels may contain unstable APIs or plugin compatibility changes. This only checks and generates a command; it never installs.' },\n 'settings.readonly': { zh: '此连接的设置为只读;显示状态不受影响。', en: 'Settings are read-only on this connection; status display is unchanged.' },\n}\n\nexport function languageOf(): Language {\n try {\n return navigator.language.toLowerCase().startsWith('zh') ? 'zh' : 'en'\n } catch {\n return 'en'\n }\n}\n\nexport function t(key: keyof typeof DICTIONARY): string {\n const entry = DICTIONARY[key]\n if (entry === undefined) return key\n return entry[languageOf()] ?? entry.en\n}\n","/** React arrives from DSH's frozen browser module table at client materialization. */\nimport type * as ReactNS from 'react'\n\n// eslint/oxlint would normally discourage require; DSH's closure factory makes\n// this the supported runtime import path for a third-party client bundle.\nexport const React: typeof ReactNS = require('react') as typeof ReactNS\nexport const h = React.createElement\n","/** Sidebar replacement, collapsed-rail fallback, overlay detail panel, and settings page. */\n\nimport type * as ReactNS from 'react'\nimport { visibleChannelReleases } from '../shared/channels.ts'\nimport { previewCommand } from '../shared/preview-guidance.ts'\nimport { MAX_CACHE_TTL_MINUTES, MIN_CACHE_TTL_MINUTES, isCacheTtlMinutes, type ReleaseChannel, type ReleaseCompatibility, type UpdateStatus } from '../shared/types.ts'\nimport type { PreferencesStore, PanelStore, StatusStore } from './stores.ts'\nimport { t } from './i18n.ts'\nimport { React, h } from './react.ts'\n\nfunction useObservable<T>(store: { subscribe(listener: () => void): () => void; getSnapshot(): T }): T {\n return React.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\n}\n\nfunction timeText(value: string | null): string | null {\n if (value === null) return null\n try {\n return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(value))\n } catch {\n return value\n }\n}\n\nfunction shortVersion(value: string): string {\n if (value.length <= 16) return value\n return value.slice(0, 15) + '…'\n}\n\nfunction channelLabel(channel: ReleaseChannel): string {\n if (channel === 'latest') return t('settings.channelLatest')\n if (channel === 'next') return t('settings.channelNext')\n return t('settings.channelAlpha')\n}\n\nfunction compatibilityLabel(value: ReleaseCompatibility): string {\n if (value === 'verified') return t('panel.compatVerified')\n if (value === 'incompatible') return t('panel.compatIncompatible')\n return t('panel.compatUnverified')\n}\n\n/** Persists a cache policy only; it never schedules a browser or Host timer. */\nfunction CacheTtlControl({ preferences }: { preferences: PreferencesStore }): ReactNS.ReactElement {\n const snapshot = useObservable(preferences)\n const [draft, setDraft] = React.useState(String(snapshot.cacheTtlMinutes))\n React.useEffect(() => { setDraft(String(snapshot.cacheTtlMinutes)) }, [snapshot.cacheTtlMinutes])\n const save = (): void => {\n const value = Number(draft)\n if (isCacheTtlMinutes(value)) preferences.setCacheTtlMinutes(value)\n else setDraft(String(snapshot.cacheTtlMinutes))\n }\n return (\n <div className=\"dus-cache-setting\">\n <label className=\"dus-cache-field\">\n <span>{t('panel.cacheDuration')}</span>\n <span className=\"dus-cache-input-wrap\">\n <input\n className=\"dus-cache-input\"\n type=\"number\"\n inputMode=\"numeric\"\n min={MIN_CACHE_TTL_MINUTES}\n max={MAX_CACHE_TTL_MINUTES}\n step={1}\n value={draft}\n disabled={!snapshot.writable}\n aria-describedby=\"dus-cache-hint\"\n onChange={(event) => { setDraft(event.currentTarget.value) }}\n onBlur={save}\n onKeyDown={(event) => {\n if (event.key === 'Enter') { event.currentTarget.blur() }\n if (event.key === 'Escape') { setDraft(String(snapshot.cacheTtlMinutes)); event.currentTarget.blur() }\n }}\n />\n <span>{t('panel.minutes')}</span>\n </span>\n </label>\n <p className=\"dus-cache-hint\" id=\"dus-cache-hint\">{t('panel.cacheHint')}</p>\n {!snapshot.writable && <p className=\"dus-cache-hint\">{t('panel.cacheReadonly')}</p>}\n </div>\n )\n}\n\ntype VisualState = 'loading' | 'update' | 'current' | 'problem'\n\nfunction visualState(status: UpdateStatus | null, loading: boolean, error: string | null): VisualState {\n if (loading) return 'loading'\n if (error !== null || status?.warning !== null) return status?.hasUpdate === true ? 'update' : 'problem'\n return status?.hasUpdate === true ? 'update' : 'current'\n}\n\nfunction badgeLabel(status: UpdateStatus | null, loading: boolean, error: string | null): string {\n const state = visualState(status, loading, error)\n if (state === 'loading') return t('brand.checking')\n if (state === 'update') return t('brand.update')\n if (state === 'problem') return t('brand.problem')\n return t('brand.current')\n}\n\nexport interface SharedUi {\n status: StatusStore\n preferences: PreferencesStore\n panel: PanelStore\n}\n\n/** Occupies ONLY sidebar.brand.name; the official fish mark stays untouched. */\nexport function BrandName({ ui }: { ui: SharedUi }): ReactNS.ReactElement | null {\n const preferences = useObservable(ui.preferences)\n const snapshot = useObservable(ui.status)\n if (!preferences.sidebarEnabled) return null\n\n const state = visualState(snapshot.status, snapshot.loading, snapshot.error)\n const version = snapshot.status?.currentVersion ?? '—'\n const activate = (event: ReactNS.SyntheticEvent): void => {\n // The surrounding sidebar brand is an existing New Session <button>. This\n // non-button interaction prevents a nested button and stops its click.\n event.preventDefault()\n event.stopPropagation()\n // The official brand identity ancestor is aria-hidden. Do not leave focus\n // inside it while the modal dialog is open (Chrome otherwise warns).\n const target = event.currentTarget\n if (target instanceof HTMLElement) target.blur()\n ui.panel.toggle('brand')\n }\n const onKeyDown = (event: ReactNS.KeyboardEvent<HTMLSpanElement>): void => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n activate(event)\n }\n\n return (\n <span className=\"dus-brand-name\">\n {/* No handler: clicking this still bubbles to the shell's New Session button. */}\n <span className=\"dus-brand-deepseek\">DeepSeek</span>\n <span\n className=\"dus-badge\"\n data-update={snapshot.status?.hasUpdate === true || undefined}\n data-error={state === 'problem' || undefined}\n role=\"button\"\n tabIndex={0}\n aria-label={badgeLabel(snapshot.status, snapshot.loading, snapshot.error)}\n title={badgeLabel(snapshot.status, snapshot.loading, snapshot.error)}\n onClick={activate}\n onKeyDown={onKeyDown}\n >\n <span className=\"dus-badge-version\">{shortVersion(version)}</span>\n <span className=\"dus-dot\" data-update={snapshot.status?.hasUpdate === true || undefined} data-loading={snapshot.loading || undefined} aria-hidden=\"true\" />\n </span>\n </span>\n )\n}\n\n/** List-slot fallback: it deliberately disappears while the name badge is wide. */\nexport function FooterAction({ wide, ui }: { wide?: unknown; ui: SharedUi }): ReactNS.ReactElement | null {\n const preferences = useObservable(ui.preferences)\n const snapshot = useObservable(ui.status)\n if (wide === true || !preferences.sidebarEnabled) return null\n const state = visualState(snapshot.status, snapshot.loading, snapshot.error)\n const label = badgeLabel(snapshot.status, snapshot.loading, snapshot.error)\n return (\n <button\n className=\"dus-footer-button\"\n type=\"button\"\n aria-label={t('footer.status')}\n title={label}\n onClick={() => { ui.panel.toggle('rail') }}\n >\n <span className=\"dus-footer-icon\" aria-hidden=\"true\">↟</span>\n <span className=\"dus-dot dus-footer-dot\" data-update={snapshot.status?.hasUpdate === true || undefined} data-loading={state === 'loading' || undefined} />\n </button>\n )\n}\n\nfunction statusSummary(status: UpdateStatus | null, loading: boolean, error: string | null): { kind: 'update' | 'ok' | 'warning' | 'error'; text: string } {\n if (loading && status === null) return { kind: 'warning', text: t('brand.checking') }\n if (error !== null) return { kind: 'error', text: error }\n if (status?.hasUpdate === true) return { kind: 'update', text: t('panel.available') }\n if (status?.warning !== null && status?.warning !== undefined) return { kind: 'warning', text: status.warning }\n return { kind: 'ok', text: t('panel.currentState') }\n}\n\nfunction selectCommand(element: HTMLElement | null): void {\n if (element === null) return\n try {\n const selection = window.getSelection()\n if (selection === null) return\n const range = document.createRange()\n range.selectNodeContents(element)\n selection.removeAllRanges()\n selection.addRange(range)\n element.focus()\n } catch {\n // The command remains ordinary selectable text even when selection fails.\n }\n}\n\n/** Full detail from whichever page the operator is on (the transport is authenticated). */\nexport function UpdatePanel({ ui }: { ui: SharedUi }): ReactNS.ReactElement | null {\n const panel = useObservable(ui.panel)\n const preferences = useObservable(ui.preferences)\n const snapshot = useObservable(ui.status)\n const commandRef = React.useRef<HTMLElement | null>(null)\n const dialogRef = React.useRef<HTMLDialogElement | null>(null)\n const [copyMessage, setCopyMessage] = React.useState<string | null>(null)\n const visible = panel.open && preferences.sidebarEnabled\n\n React.useLayoutEffect(() => {\n if (!visible) return undefined\n const dialog = dialogRef.current\n if (dialog === null) return undefined\n try {\n if (!dialog.open) dialog.showModal()\n } catch {\n // Older embedded Chromium still receives a visible non-top-layer dialog.\n dialog.setAttribute('open', '')\n }\n return () => {\n if (dialog.open) dialog.close()\n }\n }, [visible])\n\n React.useEffect(() => {\n if (!panel.open) return undefined\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') ui.panel.close()\n }\n window.addEventListener('keydown', onKeyDown)\n return () => { window.removeEventListener('keydown', onKeyDown) }\n }, [panel.open, ui.panel])\n\n if (!visible) return null\n const status = snapshot.status\n const summary = statusSummary(status, snapshot.loading, snapshot.error)\n const switchingChannel = status !== null && status.channel !== preferences.channel\n const command = switchingChannel ? t('panel.switchingChannel') : status?.upgradeCommand ?? '—'\n\n const copy = async (): Promise<void> => {\n setCopyMessage(null)\n try {\n if (!navigator.clipboard?.writeText) throw new Error('Clipboard API unavailable')\n await navigator.clipboard.writeText(command)\n setCopyMessage(t('panel.copied'))\n } catch {\n selectCommand(commandRef.current)\n setCopyMessage(t('panel.copyFallback'))\n }\n }\n\n return (\n <dialog\n ref={dialogRef}\n className=\"dus-overlay-root\"\n aria-label={t('panel.title')}\n onCancel={(event) => {\n event.preventDefault()\n ui.panel.close()\n }}\n >\n <div className=\"dus-backdrop\" onClick={() => { ui.panel.close() }} />\n <section className=\"dus-panel\" data-origin={panel.origin} role=\"dialog\" aria-modal=\"true\" aria-label={t('panel.title')} onClick={(event) => { event.stopPropagation() }}>\n <div className=\"dus-panel-head\">\n <span className=\"dus-panel-title\">{t('panel.title')}</span>\n <button className=\"dus-close\" type=\"button\" aria-label={t('panel.close')} onClick={() => { ui.panel.close() }}>×</button>\n </div>\n\n <CacheTtlControl preferences={ui.preferences} />\n <p className=\"dus-state\" data-kind={summary.kind}>{summary.text}</p>\n\n <div className=\"dus-metadata\">\n <div className=\"dus-metadata-row\">\n <span className=\"dus-meta-label\">{t('panel.current')}</span>\n <code className=\"dus-meta-value\">{status?.currentVersion ?? '…'}</code>\n </div>\n <div className=\"dus-metadata-row\">\n <span className=\"dus-meta-label\">{t('panel.latest')} · {status === null ? 'latest' : channelLabel(status.channel)}</span>\n <code className=\"dus-meta-value\">{status?.latestVersion ?? t('panel.notChecked')}</code>\n {status?.publishedAt !== null && status?.publishedAt !== undefined && <span className=\"dus-meta-sub\">{t('panel.publishedAt')}: {timeText(status.publishedAt)}</span>}\n </div>\n {status?.checkedAt !== null && status?.checkedAt !== undefined && <div className=\"dus-metadata-row\">\n <span className=\"dus-meta-label\">{t('panel.checkedAt')}</span>\n <span className=\"dus-meta-value\">{timeText(status.checkedAt)}</span>\n <span className=\"dus-meta-sub\">{status.cached ? t('panel.cached') : t('panel.live')}</span>\n </div>}\n </div>\n\n {status !== null && <div className=\"dus-channel-list\" aria-label={t('panel.channels')}>\n <p className=\"dus-command-label\">{t('panel.channels')}</p>\n {visibleChannelReleases(status).map(release => {\n const selected = release.channel === preferences.channel\n return <div className=\"dus-channel-row\" data-selected={selected || undefined} key={release.channel}>\n <span>{channelLabel(release.channel)}</span>\n <code>{release.version ?? '—'}</code>\n <span className=\"dus-channel-compat\" data-compatibility={release.compatibility}>{compatibilityLabel(release.compatibility)}</span>\n <button\n className=\"dus-channel-action\"\n type=\"button\"\n disabled={selected || !preferences.writable || release.version === null}\n aria-label={`${selected ? t('panel.selectedChannel') : t('panel.selectChannel')}: ${channelLabel(release.channel)}`}\n onClick={() => { ui.preferences.setChannel(release.channel) }}\n >{selected ? t('panel.selectedChannel') : t('panel.selectChannel')}</button>\n </div>\n })}\n </div>}\n\n {status?.warning !== null && status?.warning !== undefined && <p className=\"dus-warning\">{t('panel.error')}: {status.warning}</p>}\n {snapshot.error !== null && <p className=\"dus-warning\">{t('panel.error')}: {snapshot.error}</p>}\n\n <div className=\"dus-actions\">\n <button className=\"dus-action\" type=\"button\" disabled={snapshot.loading} onClick={() => { void ui.status.refresh(undefined, preferences.cacheTtlMinutes) }}>\n {snapshot.loading ? t('panel.checking') : t('panel.check')}\n </button>\n {status !== null && <a className=\"dus-action\" href={status.changelogUrl} target=\"_blank\" rel=\"noreferrer\">{t('panel.releaseNotes')}</a>}\n <button className=\"dus-action\" type=\"button\" disabled={switchingChannel || status === null} onClick={() => { void copy() }}>{t('panel.copy')}</button>\n </div>\n\n <p className=\"dus-command-label\">{t('panel.command')}</p>\n <code ref={commandRef} className=\"dus-command\" tabIndex={0}>{command}</code>\n {copyMessage !== null && <p className=\"dus-copy-message\" role=\"status\">{copyMessage}</p>}\n <p className=\"dus-note\">{t('panel.commandNote')}</p>\n <p className=\"dus-note\">{t('panel.readOnly')}</p>\n </section>\n </dialog>\n )\n}\n\n/** Separate setting page: disables only this plugin's own rendering. */\nexport function UpdateSettings({ ui }: { ui: SharedUi }): ReactNS.ReactElement {\n const preferences = useObservable(ui.preferences)\n const snapshot = useObservable(ui.status)\n const [showGuidance, setShowGuidance] = React.useState(false)\n const previews = snapshot.status?.channels.filter(release => release.channel !== 'latest') ?? []\n const channelOptions = snapshot.status === null\n ? [{ channel: preferences.channel }]\n : visibleChannelReleases(snapshot.status)\n return (\n <section className=\"dus-settings\">\n <h2 className=\"dus-settings-heading\">{t('settings.title')}</h2>\n <div className=\"dus-settings-card\">\n <label className=\"dus-settings-toggle\">\n <input\n type=\"checkbox\"\n checked={preferences.sidebarEnabled}\n disabled={!preferences.writable}\n onChange={(event) => { ui.preferences.setSidebarEnabled(event.currentTarget.checked) }}\n />\n <span>{t('settings.sidebar')}</span>\n </label>\n <p className=\"dus-settings-hint\">{t('settings.sidebarHint')}</p>\n {!preferences.writable && <p className=\"dus-settings-hint\">{t('settings.readonly')}</p>}\n </div>\n <div className=\"dus-settings-card\">\n <label className=\"dus-settings-field\">\n <span>{t('settings.channel')}</span>\n <select\n className=\"dus-channel-select\"\n value={preferences.channel}\n disabled={!preferences.writable}\n onChange={(event) => { ui.preferences.setChannel(event.currentTarget.value as ReleaseChannel) }}\n >\n {channelOptions.map(option => <option value={option.channel} key={option.channel}>{channelLabel(option.channel)}</option>)}\n </select>\n </label>\n <p className=\"dus-settings-hint\">{t('settings.channelHint')}</p>\n <p className=\"dus-settings-hint\">{t('panel.current')}: <code>{snapshot.status?.currentVersion ?? '…'}</code></p>\n <p className=\"dus-settings-hint\">{t('preview.follow')}</p>\n <button className=\"dus-action\" type=\"button\" aria-expanded={showGuidance} onClick={() => { setShowGuidance(!showGuidance) }}>{t(showGuidance ? 'preview.close' : 'preview.open')}</button>\n {showGuidance && <section aria-label={t('preview.open')}>\n <p className=\"dus-warning\">{t('preview.risk')}</p>\n <button className=\"dus-action\" type=\"button\" disabled={snapshot.loading} onClick={() => { void ui.status.refresh(undefined, preferences.cacheTtlMinutes) }}>{t(snapshot.loading ? 'panel.checking' : 'panel.check')}</button>\n {snapshot.error !== null && <p role=\"alert\">{snapshot.error}</p>}\n {previews.length === 0 && <p>{t('preview.unavailable')}</p>}\n {previews.map(release => {\n const status = snapshot.status!\n const command = previewCommand(status, release.version)\n return <div className=\"dus-settings-card\" key={release.channel}>\n <p>{channelLabel(release.channel)} · {t('preview.target')}: <code>{release.version ?? t('panel.notChecked')}</code></p>\n <p>{compatibilityLabel(release.compatibility)}</p>\n {release.version === null ? <p>{t('preview.unavailable')}</p> : release.version === status.currentVersion ? <p>{t('preview.same')}</p> : command !== null ? <><p>{t('panel.command')}</p><code className=\"dus-command\" tabIndex={0}>{command}</code></> : <p>{t('preview.manual')}</p>}\n </div>\n })}\n <p className=\"dus-note\">{t('panel.commandNote')}</p>\n </section>}\n </div>\n </section>\n )\n}\n","/** Minimal structural client faces — runtime services stay owned by DSH. */\n\nimport { isReleaseChannel, type ChannelRelease, type UpdateStatus } from '../shared/types.ts'\n\nexport interface Observable<T> {\n getSnapshot(): T\n subscribe(listener: () => void): () => void\n}\n\nexport interface ConnectionRpc {\n call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<unknown>\n}\n\n/**\n * The authenticated transport face. There is deliberately NO `isLoopback` gate\n * here: DSH's settings Client treats a non-loopback page as process-local, but\n * that is a settings *persistence* policy — the Connection RPC itself stays\n * authenticated and reachable over a LAN bridge, so gating this plugin's own\n * read-only status on it only made the panel inert on exactly the page it is\n * used from. See settings/settingsChannel.ts for the one place a loopback\n * distinction still matters (choosing the Host settings channel).\n */\nexport interface ConnectionClient {\n rpc?: ConnectionRpc\n}\n\nexport interface SlotRegistration {\n name: string\n id?: string\n order?: number\n /** Single-slot arbitration: the lowest registered priority renders. */\n priority?: number\n label?: () => string\n locale?: string\n inject?: () => unknown\n}\n\nexport interface Slots {\n inject(name: string, callback: () => unknown): () => void\n register(registration: SlotRegistration, component: (props: Record<string, unknown>) => unknown): () => void\n}\n\nexport interface ClientContext {\n slots: Slots\n get(name: string): unknown\n inject(names: string[], callback: (ctx: unknown) => void): () => void\n effect(setup: () => (() => void) | void, label?: string): () => void\n /** Optional lifecycle event seam (Cordis contexts expose it; guarded here). */\n on?(name: string, listener: () => void): unknown\n}\n\nexport function errorMessage(error: unknown): string {\n const text = error instanceof Error ? error.message : String(error)\n return text.replace(/\\s+/g, ' ').trim().slice(0, 220) || 'unknown error'\n}\n\nfunction stringOrNull(value: unknown): string | null {\n return typeof value === 'string' ? value : null\n}\n\n/** Reject malformed RPC output before it reaches a slot component. */\nexport function updateStatusOf(value: unknown): UpdateStatus | undefined {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined\n const record = value as Record<string, unknown>\n if (typeof record.currentVersion !== 'string' || typeof record.hasUpdate !== 'boolean'\n || typeof record.cached !== 'boolean' || typeof record.installKind !== 'string'\n || typeof record.upgradeCommand !== 'string' || typeof record.releaseUrl !== 'string'\n || typeof record.changelogUrl !== 'string' || typeof record.packageName !== 'string'\n || !isReleaseChannel(record.channel) || !Array.isArray(record.channels) || record.canApplyInPlace !== false) return undefined\n const validKind = record.installKind === 'npm-global' || record.installKind === 'pnpm-global'\n || record.installKind === 'source-checkout' || record.installKind === 'unknown'\n if (!validKind) return undefined\n const latestVersion = record.latestVersion === null ? null : stringOrNull(record.latestVersion)\n const checkedAt = record.checkedAt === null ? null : stringOrNull(record.checkedAt)\n const warning = record.warning === null ? null : stringOrNull(record.warning)\n const publishedAt = record.publishedAt === null ? null : stringOrNull(record.publishedAt)\n if ((record.latestVersion !== null && latestVersion === null) || (record.checkedAt !== null && checkedAt === null)\n || (record.warning !== null && warning === null) || (record.publishedAt !== null && publishedAt === null)) return undefined\n const channels: ChannelRelease[] = []\n for (const raw of record.channels) {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return undefined\n const item = raw as Record<string, unknown>\n const version = item.version === null ? null : stringOrNull(item.version)\n const channelPublishedAt = item.publishedAt === null ? null : stringOrNull(item.publishedAt)\n if (!isReleaseChannel(item.channel) || (item.version !== null && version === null)\n || (item.publishedAt !== null && channelPublishedAt === null)\n || (item.compatibility !== 'verified' && item.compatibility !== 'unverified' && item.compatibility !== 'incompatible')) return undefined\n channels.push({ channel: item.channel, version, publishedAt: channelPublishedAt, compatibility: item.compatibility })\n }\n return {\n currentVersion: record.currentVersion,\n latestVersion,\n hasUpdate: record.hasUpdate,\n cached: record.cached,\n checkedAt,\n warning,\n installKind: record.installKind as UpdateStatus['installKind'],\n upgradeCommand: record.upgradeCommand,\n releaseUrl: record.releaseUrl,\n changelogUrl: record.changelogUrl,\n publishedAt,\n packageName: record.packageName,\n channel: record.channel,\n channels,\n canApplyInPlace: false,\n }\n}\n","/** Small observable stores shared by the independent sidebar and overlay slots. */\n\nimport { DEFAULT_CACHE_TTL_MINUTES, isCacheTtlMinutes, isReleaseChannel, PLUGIN_ID, UPDATE_ENDPOINTS, UPDATE_STATUS_CHANNEL, type ReleaseChannel, type UpdateStatus } from '../shared/types.ts'\nimport type { ConnectionClient, Observable } from './contract.ts'\nimport { errorMessage, updateStatusOf } from './contract.ts'\nimport type { SettingsScopeLike } from './settings/scopeFaces.ts'\n\nexport interface StatusSnapshot {\n status: UpdateStatus | null\n loading: boolean\n error: string | null\n}\n\nconst INITIAL_STATUS: StatusSnapshot = { status: null, loading: true, error: null }\n\nexport class StatusStore implements Observable<StatusSnapshot> {\n private snapshot: StatusSnapshot = INITIAL_STATUS\n private readonly listeners = new Set<() => void>()\n private inFlight: Promise<boolean> | undefined\n private stopped = false\n\n constructor(private readonly connection: ConnectionClient) {}\n\n getSnapshot = (): StatusSnapshot => this.snapshot\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n /** First mount reads the Host's cached status; it never starts a browser timer. */\n async load(cacheTtlMinutes: number = DEFAULT_CACHE_TTL_MINUTES): Promise<void> {\n await this.request(false, this.snapshot.status?.channel ?? 'latest', cacheTtlMinutes)\n }\n\n /** User gesture only: force the Host to bypass TTL (while retaining single-flight). */\n async refresh(channel: ReleaseChannel = this.snapshot.status?.channel ?? 'latest', cacheTtlMinutes: number = DEFAULT_CACHE_TTL_MINUTES): Promise<void> {\n await this.request(true, channel, cacheTtlMinutes)\n }\n\n /** Channel selection re-projects the Host cache; it is not a forced refresh. */\n async selectChannel(channel: ReleaseChannel, cacheTtlMinutes: number = DEFAULT_CACHE_TTL_MINUTES): Promise<void> {\n // A persisted preference or a newer selection can arrive while another\n // channel is in flight. Keep checking after every joined request until the\n // requested projection is actually the published snapshot.\n for (let attempts = 0; attempts < 3 && !this.stopped && this.snapshot.status?.channel !== channel; attempts += 1) {\n const pending = this.inFlight\n if (pending !== undefined && !await pending) return\n if (this.stopped || this.snapshot.status?.channel === channel) return\n if (!await this.request(false, channel, cacheTtlMinutes)) return\n }\n }\n\n stop(): void {\n this.stopped = true\n this.listeners.clear()\n }\n\n private publish(next: StatusSnapshot): void {\n if (this.stopped) return\n this.snapshot = next\n for (const listener of this.listeners) listener()\n }\n\n private request(force: boolean, channel: ReleaseChannel, cacheTtlMinutes: number): Promise<boolean> {\n if (this.inFlight !== undefined) return this.inFlight\n const rpc = this.connection.rpc\n if (rpc === undefined || typeof rpc.call !== 'function') {\n this.publish({ ...this.snapshot, loading: false, error: 'DSH connection RPC is unavailable' })\n return Promise.resolve(false)\n }\n\n this.publish({ ...this.snapshot, loading: true, error: null })\n const run = (async () => {\n try {\n const endpoint = force ? UPDATE_ENDPOINTS.checkUpdate : UPDATE_ENDPOINTS.getStatus\n const ttl = isCacheTtlMinutes(cacheTtlMinutes) ? cacheTtlMinutes : DEFAULT_CACHE_TTL_MINUTES\n const raw = await rpc.call(UPDATE_STATUS_CHANNEL, endpoint, force\n ? { force: true, channel, cacheTtlMinutes: ttl }\n : { channel, cacheTtlMinutes: ttl })\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('invalid update-status RPC response')\n const envelope = raw as { ok?: unknown; value?: unknown; error?: { message?: unknown } }\n if (envelope.ok !== true) throw new Error(typeof envelope.error?.message === 'string' ? envelope.error.message : 'update-status RPC failed')\n const status = updateStatusOf(envelope.value)\n if (status === undefined) throw new Error('invalid update-status payload')\n this.publish({ status, loading: false, error: null })\n return true\n } catch (error) {\n this.publish({ ...this.snapshot, loading: false, error: errorMessage(error) })\n return false\n }\n })()\n this.inFlight = run\n void run.finally(() => {\n if (this.inFlight === run) this.inFlight = undefined\n })\n return run\n }\n}\n\nexport interface PreferencesSnapshot {\n sidebarEnabled: boolean\n channel: ReleaseChannel\n cacheTtlMinutes: number\n writable: boolean\n status: 'loading' | 'ready' | 'unavailable'\n}\n\nconst INITIAL_PREFERENCES: PreferencesSnapshot = {\n sidebarEnabled: true,\n channel: 'latest',\n cacheTtlMinutes: DEFAULT_CACHE_TTL_MINUTES,\n writable: false,\n status: 'loading',\n}\n\nexport class PreferencesStore implements Observable<PreferencesSnapshot> {\n private snapshot: PreferencesSnapshot = INITIAL_PREFERENCES\n private readonly listeners = new Set<() => void>()\n private scope: SettingsScopeLike | undefined\n\n getSnapshot = (): PreferencesSnapshot => this.snapshot\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n attach(scope: SettingsScopeLike): () => void {\n this.scope = scope\n const sync = () => {\n const raw = scope.getSnapshot()\n const value = raw.value !== null && typeof raw.value === 'object' && !Array.isArray(raw.value)\n ? raw.value as Record<string, unknown>\n : {}\n const status = raw.status === 'ready' || raw.status === 'unavailable' ? raw.status : 'loading'\n this.publish({\n sidebarEnabled: value.sidebarEnabled !== false,\n channel: isReleaseChannel(value.channel) ? value.channel : 'latest',\n cacheTtlMinutes: isCacheTtlMinutes(value.cacheTtlMinutes) ? value.cacheTtlMinutes : DEFAULT_CACHE_TTL_MINUTES,\n writable: raw.writable === true,\n status,\n })\n }\n sync()\n return scope.subscribe(sync)\n }\n\n setSidebarEnabled(enabled: boolean): void {\n const previous = this.snapshot\n this.publish({ ...previous, sidebarEnabled: enabled })\n const scope = this.scope\n if (scope === undefined || !previous.writable) return\n void scope.set('sidebarEnabled', enabled).catch(() => {\n // A rejected Host-backed write restores the authoritative scope snapshot.\n try {\n const raw = scope.getSnapshot()\n const value = raw.value !== null && typeof raw.value === 'object' && !Array.isArray(raw.value)\n ? raw.value as Record<string, unknown>\n : {}\n this.publish({\n sidebarEnabled: value.sidebarEnabled !== false,\n channel: isReleaseChannel(value.channel) ? value.channel : 'latest',\n cacheTtlMinutes: isCacheTtlMinutes(value.cacheTtlMinutes) ? value.cacheTtlMinutes : DEFAULT_CACHE_TTL_MINUTES,\n writable: raw.writable === true,\n status: raw.status === 'ready' || raw.status === 'unavailable' ? raw.status : 'loading',\n })\n } catch {\n this.publish(previous)\n }\n })\n }\n\n setChannel(channel: ReleaseChannel): void {\n const previous = this.snapshot\n this.publish({ ...previous, channel })\n const scope = this.scope\n if (scope === undefined || !previous.writable) return\n void scope.set('channel', channel).catch(() => { this.publish(previous) })\n }\n\n setCacheTtlMinutes(cacheTtlMinutes: number): void {\n if (!isCacheTtlMinutes(cacheTtlMinutes)) return\n const previous = this.snapshot\n this.publish({ ...previous, cacheTtlMinutes })\n const scope = this.scope\n if (scope === undefined || !previous.writable) return\n void scope.set('cacheTtlMinutes', cacheTtlMinutes).catch(() => { this.publish(previous) })\n }\n\n private publish(next: PreferencesSnapshot): void {\n const previous = this.snapshot\n if (previous.sidebarEnabled === next.sidebarEnabled && previous.channel === next.channel\n && previous.cacheTtlMinutes === next.cacheTtlMinutes\n && previous.writable === next.writable && previous.status === next.status) return\n this.snapshot = next\n for (const listener of this.listeners) listener()\n }\n}\n\nexport type PanelOrigin = 'brand' | 'rail'\n\nexport interface PanelSnapshot {\n open: boolean\n origin: PanelOrigin\n}\n\n/** Open state also retains its trigger, so the desktop card sits beside it. */\nexport class PanelStore implements Observable<PanelSnapshot> {\n private snapshot: PanelSnapshot = { open: false, origin: 'brand' }\n private readonly listeners = new Set<() => void>()\n\n getSnapshot = (): PanelSnapshot => this.snapshot\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n toggle(origin: PanelOrigin): void {\n const open = !(this.snapshot.open && this.snapshot.origin === origin)\n this.set({ open, origin })\n }\n\n close(): void { this.set({ ...this.snapshot, open: false }) }\n\n private set(next: PanelSnapshot): void {\n if (this.snapshot.open === next.open && this.snapshot.origin === next.origin) return\n this.snapshot = next\n for (const listener of this.listeners) listener()\n }\n}\n\n/** The one namespace name used by Host registration and browser binding. */\nexport const SETTINGS_NAMESPACE = PLUGIN_ID\n","/**\n * dsh-update-status — client settings-scope faces (structural).\n *\n * The runtime scope object comes from `ctx.settingsScope.bind({ namespace })`\n * (dsh-client-ui-settings). The plugin never imports that package at runtime, so\n * the consumed members are re-typed here against the published SettingsScope\n * contract and re-proved at the boundary.\n *\n * `set(field, value)` is the member this plugin actually calls; the official\n * scope implements it as `mutate([{ op: 'set', path: [field], value }])`, so the\n * direct Host channel (hostDirectScope.ts) mirrors that exact semantic.\n */\n\n/** Mirrors dsh-client-ui-settings' SettingsScopeSnapshot<T>. */\nexport interface SettingsScopeSnapshotLike {\n status: 'loading' | 'ready' | 'unavailable'\n /** Raw namespace section; `undefined` before the first accepted answer. */\n value: unknown\n /** Composition layer (the built-in base), when the owner declared one. */\n base: unknown\n /** Raw user layer as stored, when one exists; presence marks user overrides. */\n user: unknown\n /** Namespace revision fencing the next write. */\n revision: number | undefined\n /** Whether the Host document accepts writes; memory mode never does. */\n writable: boolean\n mode: 'host' | 'memory'\n}\n\n/** One path-addressed settings edit (the wire op shape). */\nexport type SettingsPathOpLike =\n | { op: 'set'; path: string[]; value: unknown }\n | { op: 'unset'; path: string[] }\n\n/** The bound settings scope (ctx.settingsScope.bind result), reduced to what is used here. */\nexport interface SettingsScopeLike {\n getSnapshot(): SettingsScopeSnapshotLike\n subscribe(listener: () => void): () => void\n /** Queue one scalar field write inside the namespace section. */\n set(field: string, value: unknown): Promise<void>\n}\n\n/** The ctx.settingsScope binder face. */\nexport interface SettingsScopeBinderFace {\n bind(spec: { namespace: string }): SettingsScopeLike\n}\n\n/** Guard: does an object look like a binder with bind()? */\nexport function binderOf(raw: unknown): SettingsScopeBinderFace | undefined {\n if (raw === null || typeof raw !== 'object') return undefined\n const candidate = raw as { settingsScope?: unknown }\n const binder = candidate.settingsScope\n if (binder === null || typeof binder !== 'object') return undefined\n if (typeof (binder as { bind?: unknown }).bind !== 'function') return undefined\n return binder as SettingsScopeBinderFace\n}\n\n/** Guard: does an object look like a bound scope? */\nexport function scopeOf(value: unknown): SettingsScopeLike | undefined {\n if (value === null || typeof value !== 'object') return undefined\n const scope = value as { getSnapshot?: unknown; subscribe?: unknown; set?: unknown }\n if (typeof scope.getSnapshot !== 'function') return undefined\n if (typeof scope.subscribe !== 'function') return undefined\n if (typeof scope.set !== 'function') return undefined\n return scope as unknown as SettingsScopeLike\n}\n","/**\n * dsh-update-status — direct Host settings channel (non-loopback pages).\n *\n * WHY THIS EXISTS\n *\n * DSH's settings Client keeps Host persistence disabled on any page whose\n * location is not a loopback authority. `settingsScope.bind()` then answers\n * `status: 'unavailable'` immediately and NEVER sends `settings.describe`\n * (dsh-client-ui-settings README, \"Known Limitations\": \"Non-loopback pages get\n * no durable settings — this Client keeps Host persistence disabled there, so a\n * scope starts `unavailable` and never crosses the wire; every row it backs is\n * inert even though Connection authentication covers the API\").\n *\n * The decision itself is one line in the official client:\n *\n * const persistence = ctx.remote.$host.isLoopback ? \"host\" : \"memory\"\n *\n * and `isLoopback` is true only for `localhost` / `[::1]` / `127.0.0.0/8` pages\n * (or a transport that declares `ownsHost`). A LAN page reached through a\n * forwarding plugin is therefore non-loopback even though the API answers and\n * the page is authenticated.\n *\n * This plugin's settings — the sidebar toggle, the followed release channel and\n * the cache policy — live in the Host namespace `dsh-update-status`, so without a\n * channel they all silently fall back to defaults on a LAN page (the host's\n * configured `channel` would be ignored, silently switching the followed release\n * line). This module speaks the SAME public Remote the official scope speaks\n * (`settings.describe` / `settings.mutate`, the generated Typert face mounted by\n * `@deepseek-ai/dsh-api-remotes`) and derives the same per-namespace snapshot\n * shape, so the ONE shared Host document keeps serving every device.\n *\n * This channel is opened ONLY when the official scope reports `unavailable`\n * (see `settingsChannel.ts`), so loopback pages keep the official path and pay\n * no extra wire read. Everything here is guarded: an absent Remote, a refused\n * read, or a malformed answer leaves the preferences exactly as unavailable as\n * they were before this channel existed.\n */\nimport type { SettingsPathOpLike, SettingsScopeLike, SettingsScopeSnapshotLike } from './scopeFaces.ts'\n\n/** One namespace row of a `settings.describe` answer (the fields consumed here). */\nexport interface SettingsNamespaceViewLike {\n readonly ns: string\n readonly value: unknown\n readonly base?: unknown\n readonly user?: unknown\n readonly revision: number\n}\n\n/** The `settings.describe` answer body. */\nexport interface SettingsDescribeViewLike {\n readonly writable: boolean\n readonly hasDocument?: boolean\n readonly namespaces: readonly SettingsNamespaceViewLike[]\n}\n\n/** The Remote envelope every generated client call answers with. */\nexport type SettingsRemoteOutcome<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly error?: { readonly code?: string; readonly message?: string } }\n\n/** The `remote.settings` namespace face (structural; never imported at runtime). */\nexport interface SettingsRemoteLike {\n describe(): Promise<SettingsRemoteOutcome<SettingsDescribeViewLike>>\n mutate(\n namespace: string,\n ops: readonly SettingsPathOpLike[],\n expectedRevision?: number,\n ): Promise<SettingsRemoteOutcome<SettingsNamespaceViewLike>>\n}\n\n/** A refused namespace write; `code` keeps the Host's own diagnosis. */\nexport class SettingsWriteFailure extends Error {\n readonly code: string\n\n constructor(code: string, message?: string) {\n super(message === undefined || message === '' ? code : message)\n this.name = 'SettingsWriteFailure'\n this.code = code\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\n/**\n * Guard: does this value look like the `remote.settings` namespace face?\n * Applied to a bare service object (`ctx.get('remote.settings')` or a payload\n * member), and it never calls anything.\n */\nexport function settingsRemoteFace(value: unknown): SettingsRemoteLike | undefined {\n if (!isRecord(value)) return undefined\n if (typeof value.describe !== 'function' || typeof value.mutate !== 'function') return undefined\n return value as unknown as SettingsRemoteLike\n}\n\n/**\n * Guard: read the `remote.settings` face off an injection payload.\n *\n * The payload a `ctx.inject(['remote.settings'], …)` callback receives exposes\n * the LITERAL service key. Reading the dotted PARENT off that injected context\n * (`payload.remote`) throws `cannot get property \"remote\" without inject` — a\n * throw that used to abort a whole wiring callback on a LAN page. So the literal\n * key is read FIRST and every read is contained; the nested\n * (`payload.remote.settings`) shape is only a fallback for a plain object\n * payload. Both absence and refusal answer `undefined`.\n */\nexport function settingsRemoteOf(raw: unknown): SettingsRemoteLike | undefined {\n if (!isRecord(raw)) return undefined\n try {\n const direct = settingsRemoteFace((raw as Record<string, unknown>)['remote.settings'])\n if (direct !== undefined) return direct\n } catch {\n // Injected-context proxy refusal: fall through to the nested shape.\n }\n try {\n const remote = (raw as { remote?: unknown }).remote\n if (isRecord(remote)) return settingsRemoteFace(remote.settings)\n } catch {\n // Same refusal: no face is readable from this payload.\n }\n return undefined\n}\n\n/**\n * Guard: subscribe to the Host's settings-document invalidation on the Remote\n * service object (`remote.$on`). Returns undefined when the service exposes no\n * event seam — the preferences then refresh on reconnect only.\n */\nexport function settingsInvalidationsOf(service: unknown): ((listener: () => void) => () => void) | undefined {\n if (!isRecord(service)) return undefined\n const on = service.$on\n if (typeof on !== 'function') return undefined\n return (listener) => {\n const dispose = (on as (event: string, callback: () => void) => unknown)\n .call(service, 'settings/document-updated', listener)\n return typeof dispose === 'function' ? dispose as () => void : () => {}\n }\n}\n\n/** The channel consumers subscribe to, plus the refresh/dispose seams the client wires. */\nexport interface HostDirectScope extends SettingsScopeLike {\n /** Re-read the Host document; resolves after the snapshot reflects the answer. */\n load(): Promise<void>\n /** Drop every listener and stop accepting work. */\n dispose(): void\n /** Last Host error message (diagnostics only; never rendered as plugin state). */\n lastError(): string | undefined\n}\n\nfunction cloneOps(ops: readonly SettingsPathOpLike[]): SettingsPathOpLike[] {\n try {\n return structuredClone(ops) as SettingsPathOpLike[]\n } catch {\n // structuredClone absent/refusing the shape: copy by hand (the op payload is\n // plain JSON by contract, so a shallow structural copy is equivalent).\n return ops.map(op => (op.op === 'set'\n ? { op: 'set' as const, path: [...op.path], value: op.value }\n : { op: 'unset' as const, path: [...op.path] }))\n }\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction sameSnapshot(a: SettingsScopeSnapshotLike, b: SettingsScopeSnapshotLike): boolean {\n return a.status === b.status\n && a.revision === b.revision\n && a.writable === b.writable\n && a.mode === b.mode\n && a.value === b.value\n && a.base === b.base\n && a.user === b.user\n}\n\nfunction readySnapshot(\n row: SettingsNamespaceViewLike,\n writable: boolean,\n): SettingsScopeSnapshotLike {\n return {\n status: 'ready',\n // The raw section crosses this boundary untouched: PreferencesStore is the\n // plugin's own strict judge and reports a hand-edited or future-schema\n // document truthfully instead of silently dropping it.\n value: row.value,\n base: row.base,\n user: row.user,\n revision: row.revision,\n writable,\n mode: 'host',\n }\n}\n\nfunction unavailableSnapshot(writable: boolean): SettingsScopeSnapshotLike {\n return {\n status: 'unavailable',\n value: undefined,\n base: undefined,\n user: undefined,\n revision: undefined,\n writable,\n mode: 'host',\n }\n}\n\n/**\n * Build the direct Host settings scope for one namespace.\n *\n * Snapshot semantics mirror the official per-namespace derivation: a namespace\n * row answers `ready` with the resolved section, its `base`/`user` layers and its\n * revision fence, and the document's `writable` decides editability. A row the\n * Host does not serve answers `unavailable`; a refused read keeps the last\n * confirmed document and reports `unavailable` only while it has never held one.\n *\n * Writes are serialized in issue order, fenced by the latest known revision and\n * REJECT with {@link SettingsWriteFailure} on a Host refusal — that rejection is\n * what makes the settings page surface a conflict instead of pretending the edit\n * landed (the official scope resolves on `!ok` after its recovery read).\n *\n * @param remote - the `remote.settings` face of the page's Remote service.\n * @param namespace - the settings namespace this scope derives from.\n * @returns the scope plus its load/dispose seams.\n */\nexport function createHostDirectScope(remote: SettingsRemoteLike, namespace: string): HostDirectScope {\n let snapshot: SettingsScopeSnapshotLike = {\n status: 'loading',\n value: undefined,\n base: undefined,\n user: undefined,\n revision: undefined,\n writable: false,\n mode: 'host',\n }\n let held: SettingsScopeSnapshotLike | undefined\n let error: string | undefined\n let disposed = false\n let tail: Promise<void> = Promise.resolve()\n const listeners = new Set<() => void>()\n\n const publish = (next: SettingsScopeSnapshotLike): void => {\n if (disposed || sameSnapshot(next, snapshot)) return\n snapshot = next\n for (const listener of [...listeners]) {\n try {\n listener()\n } catch {\n // A broken listener must never break the channel.\n }\n }\n }\n\n const adopt = (next: SettingsScopeSnapshotLike): void => {\n if (next.status === 'ready') held = next\n publish(next)\n }\n\n async function load(): Promise<void> {\n if (disposed) return\n let outcome: SettingsRemoteOutcome<SettingsDescribeViewLike>\n try {\n outcome = await remote.describe()\n } catch (caught) {\n outcome = { ok: false, error: { message: messageOf(caught) } }\n }\n if (disposed) return\n if (!outcome.ok) {\n error = outcome.error?.message ?? outcome.error?.code ?? 'settings/describe-failed'\n // A refused read never invents state: the last confirmed document stays\n // authoritative; without one the channel is unavailable.\n publish(held ?? unavailableSnapshot(snapshot.writable))\n return\n }\n error = undefined\n const view = outcome.value\n const writable = view.writable === true\n const row = Array.isArray(view.namespaces)\n ? view.namespaces.find(candidate => candidate.ns === namespace)\n : undefined\n if (row === undefined || typeof row.revision !== 'number') {\n publish(unavailableSnapshot(writable))\n return\n }\n adopt(readySnapshot(row, writable))\n }\n\n /** Fold one write answer's namespace row in without a second wire read. */\n function fold(row: SettingsNamespaceViewLike, writable: boolean): void {\n if (typeof row.revision !== 'number') return\n adopt(readySnapshot(row, writable))\n }\n\n function mutate(ops: readonly SettingsPathOpLike[], expectedRevision?: number): Promise<void> {\n const owned = cloneOps(ops)\n const run = tail.then(async () => {\n if (disposed) throw new SettingsWriteFailure('settings/unavailable')\n const revision = expectedRevision ?? snapshot.revision\n let outcome: SettingsRemoteOutcome<SettingsNamespaceViewLike>\n try {\n outcome = revision === undefined\n ? await remote.mutate(namespace, owned)\n : await remote.mutate(namespace, owned, revision)\n } catch (caught) {\n await load()\n throw new SettingsWriteFailure('settings/unreachable', messageOf(caught))\n }\n if (outcome.ok) {\n // A successful write proves the document accepts writes on this page.\n fold(outcome.value, true)\n return\n }\n await load()\n throw new SettingsWriteFailure(\n outcome.error?.code ?? 'settings/unknown',\n outcome.error?.message,\n )\n })\n // The queue never poisons itself: the caller sees the rejection, the next\n // queued write still runs.\n tail = run.then(() => undefined, () => undefined)\n return run\n }\n\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n // The official scope implements `set` exactly this way; the revision fence\n // falls back to the latest known one, same as the official pendingRevision.\n set: (field, value) => mutate([{ op: 'set', path: [field], value }]),\n load,\n dispose() {\n disposed = true\n listeners.clear()\n },\n lastError: () => error,\n }\n}\n","/**\n * dsh-update-status — settings source selection.\n *\n * The plugin's ONE storage contract is the Host settings namespace\n * `dsh-update-status`. Two client channels can serve it:\n *\n * 1. the official `settingsScope` service (loopback pages), and\n * 2. the direct Host channel (`hostDirectScope.ts`), which exists exactly\n * because the official scope is deliberately inert on a non-loopback page —\n * the LAN page a phone uses.\n *\n * This module picks between them without ever guessing:\n *\n * - The official scope wins whenever it is not `unavailable`, so a loopback page\n * keeps the official semantics (schema decode, revision fences, document\n * invalidation) and pays NO extra wire read.\n * - Only when the official scope reports `unavailable` (the documented\n * non-loopback degradation) is the direct channel opened, lazily and once.\n * - With neither source the channel stays `loading`, i.e. exactly the state this\n * plugin had before the direct channel existed.\n */\nimport type { SettingsScopeLike, SettingsScopeSnapshotLike } from './scopeFaces.ts'\n\n/** Snapshot used while no channel has answered yet. */\nconst LOADING_SNAPSHOT: SettingsScopeSnapshotLike = Object.freeze({\n status: 'loading',\n value: undefined,\n base: undefined,\n user: undefined,\n revision: undefined,\n writable: false,\n mode: 'host',\n}) as SettingsScopeSnapshotLike\n\nexport interface SettingsChannelOptions {\n /**\n * Build the direct Host channel. Called at most once, and only when the\n * official scope cannot serve this page. Returning `undefined` (the Remote\n * service is not composed) leaves the channel on the official/unavailable\n * path; a later {@link SettingsChannel.refresh} retries.\n */\n openDirect(): SettingsScopeLike | undefined\n}\n\nexport interface SettingsChannel extends SettingsScopeLike {\n /** Point the channel at the official scope once the service answers. */\n setOfficial(scope: SettingsScopeLike | undefined): void\n /** Re-evaluate the selection (e.g. the Remote service just arrived). */\n refresh(): void\n /** Ask the active source for a fresh read (no-op for the official scope). */\n reload(): void\n dispose(): void\n}\n\nfunction sameSnapshot(a: SettingsScopeSnapshotLike, b: SettingsScopeSnapshotLike): boolean {\n return a.status === b.status\n && a.revision === b.revision\n && a.writable === b.writable\n && a.mode === b.mode\n && a.value === b.value\n && a.base === b.base\n && a.user === b.user\n}\n\n/** The load seam a direct channel exposes (the official scope refreshes itself). */\ntype LoadableScope = SettingsScopeLike & { load?: () => Promise<void> }\n\n/** Ask a channel for its first/next read; a refusal surfaces as a snapshot, never a throw. */\nfunction loadSource(source: SettingsScopeLike | undefined): Promise<void> {\n const loadable = source as LoadableScope | undefined\n if (loadable === undefined || typeof loadable.load !== 'function') return Promise.resolve()\n return Promise.resolve(loadable.load()).then(() => undefined, () => undefined)\n}\n\nexport function createSettingsChannel(options: SettingsChannelOptions): SettingsChannel {\n let official: SettingsScopeLike | undefined\n let direct: SettingsScopeLike | undefined\n let active: SettingsScopeLike | undefined\n let snapshot: SettingsScopeSnapshotLike = LOADING_SNAPSHOT\n let disposed = false\n const disposers: Array<() => void> = []\n const listeners = new Set<() => void>()\n\n const notify = (): void => {\n for (const listener of [...listeners]) {\n try {\n listener()\n } catch {\n // A broken listener must never break the channel.\n }\n }\n }\n\n const publish = (next: SettingsScopeSnapshotLike): void => {\n if (disposed || sameSnapshot(next, snapshot)) return\n snapshot = next\n notify()\n }\n\n const select = (): SettingsScopeLike | undefined => {\n // The official scope is authoritative unless it reports the documented\n // non-loopback degradation.\n if (official !== undefined && official.getSnapshot().status !== 'unavailable') return official\n if (direct === undefined) {\n try {\n direct = options.openDirect()\n } catch {\n direct = undefined\n }\n if (direct !== undefined) {\n try {\n disposers.push(direct.subscribe(() => {\n if (active === direct) publish(direct!.getSnapshot())\n }))\n } catch {\n // An unsubscribable direct channel is still readable synchronously.\n }\n // The direct channel has no owner to `ensure()` it (the official scope is\n // ensured by its own plugin), so the initial read is issued here.\n void loadSource(direct)\n }\n }\n return direct ?? official\n }\n\n const reselect = (): void => {\n if (disposed) return\n const next = select()\n if (next !== active) {\n active = next\n publish(next?.getSnapshot() ?? LOADING_SNAPSHOT)\n return\n }\n // Same source: forward its current snapshot (a change must have come from it).\n publish(next?.getSnapshot() ?? LOADING_SNAPSHOT)\n }\n\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n set(field, value) {\n const source = active\n if (source === undefined) {\n return Promise.reject(new Error('dsh-update-status: settings channel is unavailable'))\n }\n return source.set(field, value)\n },\n setOfficial(scope) {\n if (disposed) return\n official = scope\n if (scope !== undefined) {\n try {\n disposers.push(scope.subscribe(() => {\n reselect()\n }))\n } catch {\n // Guarded read path: selection still happens synchronously below.\n }\n }\n reselect()\n },\n refresh: reselect,\n reload() {\n if (active === undefined || active === official) return\n void loadSource(active)\n },\n dispose() {\n disposed = true\n listeners.clear()\n for (const dispose of disposers.splice(0)) {\n try {\n dispose()\n } catch {\n // Contained.\n }\n }\n const disposable = active as (SettingsScopeLike & { dispose?: () => void }) | undefined\n if (disposable !== undefined && typeof disposable.dispose === 'function') {\n try {\n disposable.dispose()\n } catch {\n // Contained.\n }\n }\n },\n }\n}\n","/** Plugin-owned CSS only; no shell DOM selection or official SVG manipulation. */\n\nexport const UPDATE_STATUS_CSS = `\n.dus-brand-name{align-items:center;gap:6px;height:24px;max-width:100%;min-width:0;display:inline-flex}\n.dus-brand-deepseek{font-size:14px;font-weight:650;letter-spacing:-.015em;line-height:24px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dus-badge{align-items:center;background:var(--dsw-alias-label-primary,#111);border:0;border-radius:4px;color:var(--dsw-alias-label-primary-inverted,#fff);cursor:pointer;display:inline-flex;flex:none;font-family:var(--ds-font-family-code,var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace));font-size:10px;font-variant-numeric:tabular-nums;font-weight:600;gap:4px;height:16px;line-height:16px;max-width:140px;outline:none;padding:0 6px;touch-action:manipulation;user-select:none;white-space:nowrap}\n.dus-badge:focus-visible,.dus-footer-button:focus-visible,.dus-action:focus-visible,.dus-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n.dus-badge:hover{filter:brightness(1.08)}\n.dus-badge[data-update=true]{background:var(--dsw-alias-state-warn-primary,#d97706);color:#111}\n.dus-badge[data-error=true]{background:var(--dsw-alias-state-error-primary,#dc2626);color:#fff}\n.dus-badge-version{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dus-dot{background:currentColor;border-radius:50%;display:inline-block;flex:0 0 auto;height:5px;opacity:.9;width:5px}\n.dus-dot[data-update=true]{animation:dus-pulse 1.5s ease-in-out infinite;background:var(--dsw-alias-state-warn-primary)}\n.dus-dot[data-loading=true]{animation:dus-pulse .9s ease-in-out infinite}\n@keyframes dus-pulse{0%,100%{opacity:.45;transform:scale(.82)}50%{opacity:1;transform:scale(1.15)}}\n@media (prefers-reduced-motion:reduce){.dus-dot[data-update=true],.dus-dot[data-loading=true]{animation:none}}\n.dus-footer-button{align-items:center;background:transparent;border:0;border-radius:10px;color:var(--dsw-alias-label-secondary);cursor:pointer;display:flex;height:44px;justify-content:center;min-height:44px;min-width:44px;padding:0;position:relative;touch-action:manipulation;width:44px}\n.dus-footer-button:hover{background:var(--dsw-alias-button-floating-hover);color:var(--dsw-alias-label-primary)}\n.dus-footer-icon{font-size:18px;line-height:1}\n.dus-footer-dot{border:1.5px solid var(--dsw-specific-sidebar-fill);position:absolute;right:9px;top:9px}\n.dus-overlay-root{background:transparent;border:0;color:inherit;height:100dvh;inset:0;margin:0;max-height:none;max-width:none;padding:0;pointer-events:none;position:fixed;width:100vw}\n.dus-overlay-root::backdrop{background:transparent}\n.dus-backdrop{background:transparent;inset:0;pointer-events:auto;position:absolute}\n.dus-panel{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:14px;box-shadow:var(--dsw-elevation-panel);box-sizing:border-box;color:var(--dsw-alias-label-primary);max-height:min(640px,calc(100dvh - 32px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));overflow:auto;padding:14px;pointer-events:auto;position:absolute;top:12px;width:min(390px,calc(100vw - 24px));z-index:1}\n.dus-panel[data-origin=brand]{left:min(292px,calc(100vw - 402px))}\n.dus-panel[data-origin=rail]{left:min(68px,calc(100vw - 402px))}\n.dus-panel-head{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-bottom:12px}\n.dus-panel-title{font-size:14px;font-weight:650}\n.dus-close{align-items:center;background:transparent;border:0;border-radius:8px;color:inherit;cursor:pointer;display:inline-flex;font-size:20px;height:32px;justify-content:center;line-height:1;min-height:32px;min-width:32px;padding:0;touch-action:manipulation;width:32px}\n.dus-close:hover,.dus-action:hover{background:var(--dsw-alias-button-floating-hover)}\n.dus-cache-setting{border:1px solid var(--dsw-alias-border-l2);border-radius:9px;margin:0 0 12px;padding:9px}\n.dus-cache-field{align-items:center;display:flex;font-size:12px;font-weight:600;gap:10px;justify-content:space-between}\n.dus-cache-input-wrap{align-items:center;display:flex;gap:6px;font-size:11px;font-weight:400}\n.dus-cache-input{background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:7px;color:inherit;font:inherit;min-height:30px;padding:0 7px;text-align:right;width:72px}\n.dus-cache-input:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n.dus-cache-input:disabled{cursor:not-allowed;opacity:.55}\n.dus-cache-hint{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.4;margin:7px 0 0}\n.dus-state{border-radius:9px;color:var(--dsw-alias-label-primary);font-size:12px;line-height:1.45;margin:0 0 12px;padding:8px 9px}\n.dus-state[data-kind=update],.dus-state[data-kind=warning]{background:var(--dsw-alias-state-warn-tertiary)}\n.dus-state[data-kind=ok]{background:var(--dsw-alias-state-success-tertiary)}\n.dus-state[data-kind=error]{background:var(--dsw-alias-bg-layer-3)}\n.dus-metadata{display:grid;gap:9px;margin:0 0 12px}\n.dus-metadata-row{align-items:baseline;display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto}\n.dus-meta-label{color:var(--dsw-alias-label-secondary);font-size:12px}\n.dus-meta-value{font-family:var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:12px;max-width:210px;overflow-wrap:anywhere;text-align:right}\n.dus-meta-sub{color:var(--dsw-alias-label-secondary);font-size:11px;grid-column:1 / -1}\n.dus-warning{border-left:2px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.45;margin:0 0 12px;padding-left:8px}\n.dus-actions{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 12px}\n.dus-action{background:var(--dsw-alias-button-floating-fill);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:inherit;cursor:pointer;font-size:12px;line-height:30px;min-height:32px;padding:0 10px;touch-action:manipulation}\n.dus-action:disabled{cursor:wait;opacity:.65}\n.dus-command-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;margin:0 0 6px}\n.dus-command{background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;display:block;font-family:var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:11px;line-height:1.5;margin:0;overflow-wrap:anywhere;padding:9px;tab-size:2;user-select:text;white-space:pre-wrap}\n.dus-command:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n.dus-note{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.45;margin:8px 0 0}\n.dus-copy-message{color:var(--dsw-alias-state-business-primary);font-size:11px;margin:7px 0 0}\n.dus-settings{display:grid;gap:14px;max-width:640px;padding:4px 0}\n.dus-settings-heading{font-size:16px;font-weight:650;margin:0}\n.dus-settings-card{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:12px}\n.dus-settings-toggle{align-items:flex-start;cursor:pointer;display:flex;gap:10px;font-size:13px;line-height:1.4}\n.dus-settings-toggle input{accent-color:var(--dsw-alias-state-business-primary);height:18px;margin:0;min-height:18px;min-width:18px;width:18px}\n.dus-settings-field{display:grid;font-size:13px;font-weight:600;gap:8px}\n.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%}\n.dus-channel-list{border-top:1px solid var(--dsw-alias-border-l2);margin:12px 0 0;padding-top:2px}\n.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}\n.dus-channel-row[data-selected=true]{color:var(--dsw-alias-state-business-primary)}\n.dus-channel-row code{font-size:11px}\n.dus-channel-compat{color:var(--dsw-alias-label-secondary)}\n.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}\n.dus-channel-action:disabled{cursor:default;opacity:.55}\n.dus-channel-action:not(:disabled):hover{background:var(--dsw-alias-button-floating-hover)}\n.dus-channel-compat[data-compatibility=verified]{color:var(--dsw-alias-state-success-primary)}\n.dus-channel-compat[data-compatibility=incompatible]{color:var(--dsw-alias-state-error-primary)}\n.dus-settings-hint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.45;margin:8px 0 0}\n@media (max-width:640px),(hover:none) and (pointer:coarse){.dus-panel[data-origin]{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-footer-button{height:48px;min-height:48px;min-width:48px;width:48px}.dus-action{min-height:40px;line-height:38px}.dus-close{height:40px;min-height:40px;min-width:40px;width:40px}}\n`\n","/**\n * DSH Web client half. It shadows only `sidebar.brand.name` with a compact\n * name + version chip that fits the 24px brand row, leaves the official fish\n * (`sidebar.brand.mark`) untouched, and adds a collapsed-rail fallback at\n * `sidebar.footer.action`. The detail panel is an additive `shell.overlay`,\n * never a chat-area floating widget.\n *\n * The status read and the detail panel are NOT restricted to a loopback page.\n * DSH disables Host settings *persistence* on a non-loopback page, but the\n * Connection RPC stays authenticated and reachable there — see\n * settings/settingsChannel.ts for the one place the loopback distinction still\n * matters.\n */\n\nimport type { ClientContext, ConnectionClient } from './contract.ts'\nimport { BrandName, FooterAction, type SharedUi, UpdatePanel, UpdateSettings } from './components.tsx'\nimport { SETTINGS_NAMESPACE, PanelStore, PreferencesStore, StatusStore } from './stores.ts'\nimport { binderOf, scopeOf } from './settings/scopeFaces.ts'\nimport {\n createHostDirectScope,\n settingsInvalidationsOf,\n settingsRemoteFace,\n settingsRemoteOf,\n type SettingsRemoteLike,\n} from './settings/hostDirectScope.ts'\nimport { createSettingsChannel } from './settings/settingsChannel.ts'\nimport { t } from './i18n.ts'\nimport { UPDATE_STATUS_CSS } from './styles.ts'\n\nconst PLUGIN_ID = 'dsh-update-status'\nconst CSS_TAG_ID = `${PLUGIN_ID}/styles`\n\nfunction installStyles(): () => void {\n if (typeof document === 'undefined') return () => {}\n const existing = document.querySelector(`style[data-plugin-css=\"${CSS_TAG_ID}\"]`)\n if (existing !== null) return () => {}\n const tag = document.createElement('style')\n tag.dataset.plugin = PLUGIN_ID\n tag.dataset.pluginCss = CSS_TAG_ID\n tag.textContent = UPDATE_STATUS_CSS\n document.head.appendChild(tag)\n return () => {\n const live = document.querySelector(`style[data-plugin-css=\"${CSS_TAG_ID}\"]`)\n if (live?.parentNode !== undefined && live?.parentNode !== null) live.parentNode.removeChild(live)\n }\n}\n\nfunction connectionOf(ctx: ClientContext): ConnectionClient {\n try {\n const candidate = ctx.get('connection')\n return candidate !== null && typeof candidate === 'object' ? candidate as ConnectionClient : {}\n } catch {\n return {}\n }\n}\n\nfunction apply(ctx: ClientContext): void {\n const connection = connectionOf(ctx)\n const status = new StatusStore(connection)\n const preferences = new PreferencesStore()\n const panel = new PanelStore()\n const ui: SharedUi = { status, preferences, panel }\n\n const disposers: Array<() => void> = []\n let stopped = false\n /** Own a subscription raised from an injection callback; release after teardown at once. */\n const own = (dispose: unknown): void => {\n if (typeof dispose !== 'function') return\n if (stopped) {\n try {\n (dispose as () => void)()\n } catch {\n // Contained.\n }\n return\n }\n disposers.push(dispose as () => void)\n }\n\n // The plugin's ONE storage contract is the Host namespace `dsh-update-status`,\n // but two client channels can serve it: the official `settingsScope` (loopback\n // pages) and — only when that scope reports the documented non-loopback\n // degradation — a direct Host channel over the same public Remote. A LAN page\n // is exactly that non-loopback case; without the direct channel every\n // preference silently falls back to its default there. See settingsChannel.ts.\n let remoteSettings: SettingsRemoteLike | undefined\n /** Resolve the direct channel's Remote face; `ctx.get` covers a payload we could not read. */\n const directRemote = (): SettingsRemoteLike | undefined => {\n if (remoteSettings !== undefined) return remoteSettings\n try {\n remoteSettings = settingsRemoteFace(ctx.get('remote.settings'))\n } catch {\n // The dotted service key is not readable on this context.\n }\n return remoteSettings\n }\n const channel = createSettingsChannel({\n openDirect: () => {\n const remote = directRemote()\n return remote === undefined ? undefined : createHostDirectScope(remote, SETTINGS_NAMESPACE)\n },\n })\n\n ctx.effect(() => {\n const disposeStyles = installStyles()\n const detach = preferences.attach(channel)\n // Changing cache policy deliberately does not issue a network request. Its\n // value is sent on the next ordinary status read or manual check.\n let selected = status.getSnapshot().status?.channel ?? 'latest'\n const syncPreferences = () => {\n const next = preferences.getSnapshot()\n if (next.channel !== selected) {\n selected = next.channel\n void status.selectChannel(selected, next.cacheTtlMinutes)\n }\n }\n syncPreferences()\n const unsubscribe = preferences.subscribe(syncPreferences)\n // Every mount reads the Host's cached status, whichever page it is on; the\n // transport is authenticated and the Host route is the plugin's own.\n void status.load(preferences.getSnapshot().cacheTtlMinutes)\n return () => {\n stopped = true\n unsubscribe()\n detach()\n for (const dispose of disposers.splice(0)) {\n try {\n dispose()\n } catch {\n // Contained.\n }\n }\n channel.dispose()\n status.stop()\n disposeStyles()\n }\n }, 'dsh-update-status: styles, settings channel and first status read')\n\n // Official seam first: it stays authoritative whenever it is not `unavailable`,\n // so a loopback page keeps the official semantics and pays no extra wire read.\n try {\n ctx.inject(['settingsScope'], (raw: unknown) => {\n try {\n const binder = binderOf(raw)\n if (binder === undefined) return\n channel.setOfficial(scopeOf(binder.bind({ namespace: SETTINGS_NAMESPACE })))\n } catch {\n // Unreadable settings seam: the preferences stay on their defaults.\n }\n })\n } catch {\n // No settingsScope seam on this host: the direct channel is the only hope.\n }\n\n // Direct Host channel. The Remote service can arrive before or after the\n // official scope, so `refresh()` re-evaluates the selection either way; the\n // document invalidation keeps this page in step with edits made elsewhere,\n // and a reconnect retries a read that was refused.\n //\n // Every read here is contained: the injected payload intentionally refuses the\n // dotted PARENT (`payload.remote` throws \"cannot get property … without\n // inject\"), so one unreadable member must never abort the whole wiring.\n try {\n ctx.inject(['remote.settings'], (raw: unknown) => {\n try {\n remoteSettings = settingsRemoteOf(raw) ?? remoteSettings\n channel.refresh()\n const invalidations = settingsInvalidationsOf(ctx.get('remote'))\n if (invalidations !== undefined) own(invalidations(() => channel.reload()))\n } catch {\n // No usable Remote seam: the direct channel stays closed and the official\n // scope keeps its verdict.\n }\n })\n } catch {\n // No Remote seam: non-loopback pages keep the honest unavailable state.\n }\n if (typeof ctx.on === 'function') {\n try {\n own(ctx.on('connection/reset', () => channel.reload()))\n } catch {\n // No lifecycle event seam on this host.\n }\n }\n\n // Single slot: a compact name + version chip shadows the official wordmark\n // (priority 0). Lowest priority renders; never touch sidebar.brand.mark.\n ctx.slots.inject('sidebar.brand.name', () => ctx.slots.register(\n { name: 'sidebar.brand.name', priority: -10 },\n () => BrandName({ ui }),\n ))\n\n // additive fallback; returns null in wide mode to avoid two visible badges.\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register(\n { name: 'sidebar.footer.action', id: 'dsh-update-status', order: 40 },\n props => FooterAction({ wide: props.wide, ui }),\n ))\n\n // additive frame overlay for click/tap panel and narrow-view bottom sheet.\n ctx.slots.inject('shell.overlay', () => ctx.slots.register(\n { name: 'shell.overlay', id: 'dsh-update-status', order: 40 },\n () => UpdatePanel({ ui }),\n ))\n\n // Independent settings section provides the required way to hide our entry.\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n { name: 'settings.section', id: 'dsh-update-status', order: 80, label: () => t('settings.title') },\n () => UpdateSettings({ ui }),\n ))\n}\n\nmodule.exports = {\n name: PLUGIN_ID,\n inject: ['slots', 'connection'],\n apply,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["PLUGIN_ID","PLUGIN_ID","candidate","sameSnapshot"],"sources":["../src/shared/channels.ts","../src/shared/types.ts","../src/shared/semver.ts","../src/shared/preview-guidance.ts","../src/shared/visual-state.ts","../src/client/i18n.ts","../src/client/react.ts","../src/client/components.tsx","../src/client/contract.ts","../src/client/stores.ts","../src/client/settings/scopeFaces.ts","../src/client/settings/hostDirectScope.ts","../src/client/settings/settingsChannel.ts","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["import type { ChannelRelease, UpdateStatus } from './types.ts'\n\nconst CHANNEL_ORDER = { latest: 0, next: 1, alpha: 2 } as const\n\n/**\n * Reduce raw dist-tags to user-meaningful choices without losing Host facts.\n * The selected channel and stable return path are always present; every other\n * row must carry a version no earlier row already shows.\n *\n * A channel is deliberately NOT hidden just because it currently points at the\n * release that is running. Following a channel is a statement about future\n * releases, and `currentVersion` still rides the input for the caller's own\n * rendering, but filtering on it created a deadlock: while running the version\n * published on `alpha`, the `alpha` row disappeared, so the one channel a user\n * on that line would want to follow could never be selected — the only way to\n * see it was to already follow it.\n */\nexport function visibleChannelReleases(status: Pick<UpdateStatus, 'currentVersion' | 'channel' | 'channels'>): ChannelRelease[] {\n const ordered = [...status.channels].sort((left, right) => CHANNEL_ORDER[left.channel] - CHANNEL_ORDER[right.channel])\n const mandatory = new Set(['latest', status.channel])\n const visible = ordered.filter(release => mandatory.has(release.channel))\n const seenVersions = new Set(visible.map(release => release.version).filter((version): version is string => version !== null))\n\n for (const release of ordered) {\n if (mandatory.has(release.channel) || release.version === null) continue\n if (seenVersions.has(release.version)) continue\n visible.push(release)\n seenVersions.add(release.version)\n }\n\n return visible.sort((left, right) => CHANNEL_ORDER[left.channel] - CHANNEL_ORDER[right.channel])\n}\n","/**\n * JSON-only contract shared by the Host and Web halves.\n *\n * The static package uses the authenticated Connection RPC channel because\n * `harness.handle` / `host.call` are dynamic-Cordis-only closure APIs in DSH\n * 0.1.5-rc.1. The endpoint vocabulary remains deliberately small and private\n * to this plugin channel.\n */\n\nexport const PLUGIN_ID = 'dsh-update-status'\nexport const PACKAGE_NAME = '@deepseek-ai/dsh'\n/** Shared Connection RPC channel. Custom prefixes 405 on the SPA fallback. */\nexport const UPDATE_STATUS_CHANNEL = '/api'\nexport const RELEASES_URL = 'https://github.com/deepseek-ai/deepseek-harness/releases'\n/**\n * DSH releases this bundle has actually been verified against. The same list is\n * declared in package.json's `dsh.compatibility.dshReleases`, and every other\n * release the registry reports stays `unverified` — the plugin never claims a\n * compatibility nobody checked.\n */\nexport const VERIFIED_DSH_VERSIONS: readonly string[] = ['0.1.5-rc.1', '0.1.6-alpha.1', '0.1.6-alpha.2']\nexport const RELEASE_CHANNELS = ['latest', 'next', 'alpha'] as const\nexport const DEFAULT_CACHE_TTL_MINUTES = 360\nexport const MIN_CACHE_TTL_MINUTES = 30\nexport const MAX_CACHE_TTL_MINUTES = 1_440\n\nexport function isCacheTtlMinutes(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value)\n && value >= MIN_CACHE_TTL_MINUTES && value <= MAX_CACHE_TTL_MINUTES\n}\n\nexport const UPDATE_ENDPOINTS = {\n getStatus: 'dsh-update-status.get-status',\n checkUpdate: 'dsh-update-status.check-update',\n} as const\n\nexport type UpdateEndpoint = (typeof UPDATE_ENDPOINTS)[keyof typeof UPDATE_ENDPOINTS]\nexport type ReleaseChannel = (typeof RELEASE_CHANNELS)[number]\nexport type ReleaseCompatibility = 'verified' | 'unverified' | 'incompatible'\n/**\n * Severity of `warning`, so a surface can tell \"the plugin could not determine\n * the update state\" from \"here is something worth knowing\".\n *\n * - `failure`: no usable answer — a failed registry read, a channel the registry\n * does not publish, or a version SemVer cannot compare. This is what justifies\n * repainting the sidebar chip.\n * - `notice`: the answer is complete and usable; the text is advisory, e.g. a\n * preview channel whose release this bundle has not been verified against.\n * The chip must NOT repaint for this — an operator upgrading DSH ahead of the\n * plugin would otherwise see the chip turn red for a plugin-side bookkeeping\n * fact.\n *\n * Optional on purpose: a Host older than this field leaves it `undefined`, and\n * the client then falls back to treating any warning as a failure.\n */\nexport type UpdateWarningKind = 'failure' | 'notice'\nexport type InstallKind = 'npm-global' | 'pnpm-global' | 'source-checkout' | 'unknown'\n\nexport function isReleaseChannel(value: unknown): value is ReleaseChannel {\n return value === 'latest' || value === 'next' || value === 'alpha'\n}\n\nexport interface ChannelRelease {\n channel: ReleaseChannel\n version: string | null\n publishedAt: string | null\n compatibility: ReleaseCompatibility\n}\n\n/** Arguments accepted by either read/check endpoint. */\nexport interface CheckUpdateRequest {\n force?: boolean\n channel?: ReleaseChannel\n /** User preference, bounded by the Host before it affects cache expiry. */\n cacheTtlMinutes?: number\n}\n\n/**\n * A lossless, JSON-serializable snapshot used by both browser surfaces.\n * Nulls are intentional: a failed first check must still render a truthful\n * current-version card instead of an empty or malformed UI.\n */\nexport interface UpdateStatus {\n currentVersion: string\n latestVersion: string | null\n hasUpdate: boolean\n cached: boolean\n checkedAt: string | null\n warning: string | null\n /** Severity of `warning`; absent from a Host older than the field. */\n warningKind?: UpdateWarningKind | null\n installKind: InstallKind\n upgradeCommand: string\n releaseUrl: string\n changelogUrl: string\n publishedAt: string | null\n packageName: string\n /** Selected npm dist-tag used for comparison and command generation. */\n channel: ReleaseChannel\n /** All supported dist-tags returned by the same cached registry request. */\n channels: ChannelRelease[]\n /** Phase 1 is informational only; the GUI must never apply an update. */\n canApplyInPlace: false\n}\n\n/** Persisted browser preference served through the ordinary DSH settings seam. */\nexport interface UpdateStatusSettings {\n sidebarEnabled: boolean\n channel: ReleaseChannel\n /** On-demand npm-registry cache duration. This never starts a browser timer. */\n cacheTtlMinutes: number\n}\n","/**\n * Small, dependency-free SemVer 2.0 comparator for the host check path.\n * It intentionally accepts the common leading `v` used by release tags while\n * preserving prerelease precedence (`0.1.2-rc.1 < 0.1.2`).\n */\n\nexport interface ParsedSemver {\n major: number\n minor: number\n patch: number\n prerelease: readonly string[]\n}\n\nconst 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-]+)*)?$/\nconst NUMERIC_IDENTIFIER = /^(0|[1-9]\\d*)$/\n\nexport function parseSemver(value: string): ParsedSemver | undefined {\n const match = SEMVER.exec(value.trim())\n if (match === null) return undefined\n const major = Number(match[1])\n const minor = Number(match[2])\n const patch = Number(match[3])\n if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) return undefined\n const prerelease = match[4] === undefined ? [] : match[4].split('.')\n return { major, minor, patch, prerelease }\n}\n\nfunction compareIdentifier(left: string, right: string): number {\n const leftNumeric = NUMERIC_IDENTIFIER.test(left)\n const rightNumeric = NUMERIC_IDENTIFIER.test(right)\n if (leftNumeric && rightNumeric) return Number(left) - Number(right)\n if (leftNumeric) return -1\n if (rightNumeric) return 1\n return left < right ? -1 : left > right ? 1 : 0\n}\n\n/** Returns a negative number when left is older; undefined means unparsable. */\nexport function compareSemver(leftValue: string, rightValue: string): number | undefined {\n const left = parseSemver(leftValue)\n const right = parseSemver(rightValue)\n if (left === undefined || right === undefined) return undefined\n\n for (const key of ['major', 'minor', 'patch'] as const) {\n if (left[key] !== right[key]) return left[key] - right[key]\n }\n\n const leftStable = left.prerelease.length === 0\n const rightStable = right.prerelease.length === 0\n if (leftStable && rightStable) return 0\n if (leftStable) return 1\n if (rightStable) return -1\n\n const length = Math.max(left.prerelease.length, right.prerelease.length)\n for (let index = 0; index < length; index += 1) {\n const leftPart = left.prerelease[index]\n const rightPart = right.prerelease[index]\n if (leftPart === undefined) return -1\n if (rightPart === undefined) return 1\n const comparison = compareIdentifier(leftPart, rightPart)\n if (comparison !== 0) return comparison\n }\n return 0\n}\n\n/** False rather than a guess when either version is not valid SemVer. */\nexport function hasSemverUpdate(currentVersion: string, latestVersion: string): boolean {\n const comparison = compareSemver(currentVersion, latestVersion)\n return comparison !== undefined && comparison < 0\n}\n","import type { UpdateStatus } from './types.ts'\nimport { PACKAGE_NAME } from './types.ts'\nimport { parseSemver } from './semver.ts'\n\n/** Pinned guidance only: never execute a command or mutate the followed channel. */\nexport function previewCommand(status: UpdateStatus, version: string | null): string | null {\n if (version === null || version.trim() !== version || parseSemver(version) === undefined) return null\n if (status.installKind === 'npm-global') return `npm install -g ${PACKAGE_NAME}@${version}`\n if (status.installKind === 'pnpm-global') return `pnpm add -g ${PACKAGE_NAME}@${version}`\n return null\n}\n","/**\n * The single place that decides which visual state the sidebar chip is in.\n *\n * It lives in `shared` rather than inside the component so the decision is a pure\n * function that a test can drive directly: the regression this guards against is\n * exactly a state-classification mistake, and it was invisible to every test while\n * it sat inside TSX.\n */\nimport type { UpdateStatus } from './types.ts'\n\nexport type VisualState = 'loading' | 'update' | 'current' | 'problem'\n\n/**\n * Only a read the plugin could not complete is a chip-level problem.\n *\n * An advisory notice — a preview channel this bundle has not been verified against,\n * say — leaves the chip surface alone and belongs in the panel. Repainting the brand\n * row for a plugin-side bookkeeping fact is the surprise the breathing dot replaced:\n * an operator who upgrades DSH ahead of this bundle must not watch the chip turn red.\n *\n * A Host older than `warningKind` sends no field at all; `undefined` then keeps the\n * previous behaviour (any warning is a problem) rather than silently dropping a real\n * failure on the floor.\n */\nexport function isChipProblem(status: UpdateStatus | null): boolean {\n if (status === null) return false\n if (status.warningKind === undefined) return status.warning !== null\n return status.warningKind === 'failure'\n}\n\nexport function visualState(status: UpdateStatus | null, loading: boolean, error: string | null): VisualState {\n if (loading) return 'loading'\n if (error !== null || isChipProblem(status)) return status?.hasUpdate === true ? 'update' : 'problem'\n return status?.hasUpdate === true ? 'update' : 'current'\n}\n","/** Tiny bilingual dictionary kept local to avoid changing the host locale tree. */\n\nexport type Language = 'zh' | 'en'\n\ntype Entry = { zh: string; en: string }\n\nconst DICTIONARY: Record<string, Entry> = {\n 'brand.status': { zh: 'DSH 版本状态', en: 'DSH version status' },\n 'brand.checking': { zh: '正在检查版本', en: 'Checking version' },\n 'brand.update': { zh: '有可用更新', en: 'Update available' },\n 'brand.current': { zh: '已是最新或尚未发现更新', en: 'Up to date or no update found' },\n 'brand.problem': { zh: '检查遇到问题', en: 'Check encountered a problem' },\n 'footer.status': { zh: '打开 DSH 版本状态', en: 'Open DSH version status' },\n 'panel.title': { zh: 'DSH 更新状态', en: 'DSH update status' },\n 'panel.close': { zh: '关闭', en: 'Close' },\n 'panel.cacheDuration': { zh: '缓存时长', en: 'Cache duration' },\n 'panel.minutes': { zh: '分钟', en: 'minutes' },\n 'panel.cacheHint': { zh: '到期后在下次打开或读取状态时检查;不会后台轮询。点击“检查更新”会立即检查。', en: 'After expiry, DSH checks only on the next status read; there is no background polling. “Check for updates” checks immediately.' },\n 'panel.cacheReadonly': { zh: '此连接的缓存设置为只读。', en: 'This connection’s cache setting is read-only.' },\n 'panel.current': { zh: '当前运行版本', en: 'Current running version' },\n 'panel.latest': { zh: '所选通道版本', en: 'Selected channel version' },\n 'panel.channels': { zh: '可用发布通道', en: 'Available release channels' },\n 'panel.compatVerified': { zh: '已验证兼容', en: 'Verified compatible' },\n 'panel.compatUnverified': { zh: '尚未验证兼容', en: 'Compatibility unverified' },\n 'panel.compatIncompatible': { zh: '已知不兼容', en: 'Known incompatible' },\n 'panel.selectChannel': { zh: '选择此通道', en: 'Select channel' },\n 'panel.selectedChannel': { zh: '已选择', en: 'Selected' },\n 'panel.notChecked': { zh: '尚未取得', en: 'Not available yet' },\n 'panel.available': { zh: '发现新版本', en: 'Update available' },\n 'panel.currentState': { zh: '未发现可用更新', en: 'No update found' },\n 'panel.cached': { zh: '来自 Host 缓存', en: 'From Host cache' },\n 'panel.live': { zh: '刚从 npm registry 检查', en: 'Checked npm registry now' },\n 'panel.checkedAt': { zh: '上次检查', en: 'Last checked' },\n 'panel.publishedAt': { zh: '发布时间', en: 'Published' },\n 'panel.check': { zh: '检查更新', en: 'Check for updates' },\n 'panel.checking': { zh: '检查中…', en: 'Checking…' },\n 'panel.switchingChannel': { zh: '正在切换通道…', en: 'Switching channel…' },\n 'panel.releaseNotes': { zh: '发布说明', en: 'Release notes' },\n 'panel.command': { zh: '升级命令', en: 'Upgrade command' },\n 'panel.copy': { zh: '复制命令', en: 'Copy command' },\n 'panel.copied': { zh: '已复制', en: 'Copied' },\n 'panel.copyFallback': { zh: '浏览器未允许复制;已选中文本,请长按或复制。', en: 'Clipboard unavailable; the command is selected for long-press or copy.' },\n 'panel.commandNote': { zh: '仅复制,不会执行。请在运行 DSH 的那台电脑的终端执行;完成后由你自行重启 DSH。', en: 'Copy only — nothing runs here. Execute it in a terminal on the computer running DSH, then restart DSH yourself.' },\n 'panel.readOnly': { zh: '阶段 1 仅提示:本插件不会安装、重启、回滚或替换任何文件。', en: 'Phase 1 is advisory only: this plugin never installs, restarts, rolls back, or replaces files.' },\n 'panel.error': { zh: '检查提示', en: 'Check notice' },\n 'panel.static': { zh: '此连接只显示静态版本;请在运行 DSH 的本机打开侧栏查看完整更新信息。', en: 'This connection shows only the static version. Open the sidebar on the computer running DSH for full update details.' },\n 'preview.open': { zh: '查看预览版升级方式', en: 'View preview upgrade instructions' },\n 'preview.close': { zh: '收起升级说明', en: 'Hide upgrade instructions' },\n 'preview.follow': { zh: '关注通道只影响检查结果,不代表已安装或已切换版本。', en: 'Following a channel only changes update checks, not the installed version.' },\n 'preview.target': { zh: '目标版本', en: 'Target version' },\n 'preview.risk': { zh: '预览版本可能不稳定,插件兼容性尚需确认。执行前请保存工作、备份配置并结束运行中的任务;安装后需手动重启 DSH。此页面不会安装或重启。', 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.' },\n 'preview.unavailable': { zh: '暂无可确认的预览目标,请先检查更新。', en: 'No confirmed preview target. Check for updates first.' },\n 'preview.manual': { zh: '当前安装方式无法安全生成命令,请先确认安装来源;源码安装需按其构建说明操作。', en: 'Cannot safely generate a command for this installation. Confirm its source; source checkouts require their build instructions.' },\n 'preview.same': { zh: '此目标与当前运行版本相同,无需重复安装。', en: 'This target is already running; no reinstall is needed.' },\n 'settings.title': { zh: '版本与更新', en: 'Version & updates' },\n 'settings.sidebar': { zh: '在侧栏显示版本状态入口', en: 'Show the version-status entry in the sidebar' },\n 'settings.sidebarHint': { zh: '关闭后不再渲染品牌 Badge 和收起轨道兜底入口。不会执行或安排升级。', en: 'When off, the brand badge and collapsed-rail fallback are not rendered. No update is run or scheduled.' },\n 'settings.channel': { zh: '关注发布通道', en: 'Release channel to follow' },\n 'settings.channelLatest': { zh: '稳定版(latest,推荐)', en: 'Stable (latest, recommended)' },\n 'settings.channelNext': { zh: '候选版(next)', en: 'Release candidate (next)' },\n 'settings.channelAlpha': { zh: '预览版(alpha)', en: 'Preview (alpha)' },\n 'settings.channelHint': { zh: '预览通道可能包含未稳定接口或插件兼容性变化。这里只检查并生成命令,不会安装。', en: 'Preview channels may contain unstable APIs or plugin compatibility changes. This only checks and generates a command; it never installs.' },\n 'settings.readonly': { zh: '此连接的设置为只读;显示状态不受影响。', en: 'Settings are read-only on this connection; status display is unchanged.' },\n}\n\nexport function languageOf(): Language {\n try {\n return navigator.language.toLowerCase().startsWith('zh') ? 'zh' : 'en'\n } catch {\n return 'en'\n }\n}\n\nexport function t(key: keyof typeof DICTIONARY): string {\n const entry = DICTIONARY[key]\n if (entry === undefined) return key\n return entry[languageOf()] ?? entry.en\n}\n","/** React arrives from DSH's frozen browser module table at client materialization. */\nimport type * as ReactNS from 'react'\n\n// eslint/oxlint would normally discourage require; DSH's closure factory makes\n// this the supported runtime import path for a third-party client bundle.\nexport const React: typeof ReactNS = require('react') as typeof ReactNS\nexport const h = React.createElement\n","/** Sidebar replacement, collapsed-rail fallback, overlay detail panel, and settings page. */\n\nimport type * as ReactNS from 'react'\nimport { visibleChannelReleases } from '../shared/channels.ts'\nimport { previewCommand } from '../shared/preview-guidance.ts'\nimport { isChipProblem } from '../shared/visual-state.ts'\nimport { MAX_CACHE_TTL_MINUTES, MIN_CACHE_TTL_MINUTES, isCacheTtlMinutes, type ReleaseChannel, type ReleaseCompatibility, type UpdateStatus } from '../shared/types.ts'\nimport type { PreferencesStore, PanelStore, StatusStore } from './stores.ts'\nimport { t } from './i18n.ts'\nimport { React, h } from './react.ts'\n\nfunction useObservable<T>(store: { subscribe(listener: () => void): () => void; getSnapshot(): T }): T {\n return React.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\n}\n\nfunction timeText(value: string | null): string | null {\n if (value === null) return null\n try {\n return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(value))\n } catch {\n return value\n }\n}\n\nfunction shortVersion(value: string): string {\n if (value.length <= 16) return value\n return value.slice(0, 15) + '…'\n}\n\nfunction channelLabel(channel: ReleaseChannel): string {\n if (channel === 'latest') return t('settings.channelLatest')\n if (channel === 'next') return t('settings.channelNext')\n return t('settings.channelAlpha')\n}\n\nfunction compatibilityLabel(value: ReleaseCompatibility): string {\n if (value === 'verified') return t('panel.compatVerified')\n if (value === 'incompatible') return t('panel.compatIncompatible')\n return t('panel.compatUnverified')\n}\n\n/** Persists a cache policy only; it never schedules a browser or Host timer. */\nfunction CacheTtlControl({ preferences }: { preferences: PreferencesStore }): ReactNS.ReactElement {\n const snapshot = useObservable(preferences)\n const [draft, setDraft] = React.useState(String(snapshot.cacheTtlMinutes))\n React.useEffect(() => { setDraft(String(snapshot.cacheTtlMinutes)) }, [snapshot.cacheTtlMinutes])\n const save = (): void => {\n const value = Number(draft)\n if (isCacheTtlMinutes(value)) preferences.setCacheTtlMinutes(value)\n else setDraft(String(snapshot.cacheTtlMinutes))\n }\n return (\n <div className=\"dus-cache-setting\">\n <label className=\"dus-cache-field\">\n <span>{t('panel.cacheDuration')}</span>\n <span className=\"dus-cache-input-wrap\">\n <input\n className=\"dus-cache-input\"\n type=\"number\"\n inputMode=\"numeric\"\n min={MIN_CACHE_TTL_MINUTES}\n max={MAX_CACHE_TTL_MINUTES}\n step={1}\n value={draft}\n disabled={!snapshot.writable}\n aria-describedby=\"dus-cache-hint\"\n onChange={(event) => { setDraft(event.currentTarget.value) }}\n onBlur={save}\n onKeyDown={(event) => {\n if (event.key === 'Enter') { event.currentTarget.blur() }\n if (event.key === 'Escape') { setDraft(String(snapshot.cacheTtlMinutes)); event.currentTarget.blur() }\n }}\n />\n <span>{t('panel.minutes')}</span>\n </span>\n </label>\n <p className=\"dus-cache-hint\" id=\"dus-cache-hint\">{t('panel.cacheHint')}</p>\n {!snapshot.writable && <p className=\"dus-cache-hint\">{t('panel.cacheReadonly')}</p>}\n </div>\n )\n}\n\ntype VisualState = 'loading' | 'update' | 'current' | 'problem'\n\nfunction visualState(status: UpdateStatus | null, loading: boolean, error: string | null): VisualState {\n if (loading) return 'loading'\n if (error !== null || isChipProblem(status)) return status?.hasUpdate === true ? 'update' : 'problem'\n return status?.hasUpdate === true ? 'update' : 'current'\n}\n\nfunction badgeLabel(status: UpdateStatus | null, loading: boolean, error: string | null): string {\n const state = visualState(status, loading, error)\n if (state === 'loading') return t('brand.checking')\n if (state === 'update') return t('brand.update')\n if (state === 'problem') return t('brand.problem')\n return t('brand.current')\n}\n\nexport interface SharedUi {\n status: StatusStore\n preferences: PreferencesStore\n panel: PanelStore\n}\n\n/** Occupies ONLY sidebar.brand.name; the official fish mark stays untouched. */\nexport function BrandName({ ui }: { ui: SharedUi }): ReactNS.ReactElement | null {\n const preferences = useObservable(ui.preferences)\n const snapshot = useObservable(ui.status)\n if (!preferences.sidebarEnabled) return null\n\n const state = visualState(snapshot.status, snapshot.loading, snapshot.error)\n const version = snapshot.status?.currentVersion ?? '—'\n const activate = (event: ReactNS.SyntheticEvent): void => {\n // The surrounding sidebar brand is an existing New Session <button>. This\n // non-button interaction prevents a nested button and stops its click.\n event.preventDefault()\n event.stopPropagation()\n // The official brand identity ancestor is aria-hidden. Do not leave focus\n // inside it while the modal dialog is open (Chrome otherwise warns).\n const target = event.currentTarget\n if (target instanceof HTMLElement) target.blur()\n ui.panel.toggle('brand')\n }\n const onKeyDown = (event: ReactNS.KeyboardEvent<HTMLSpanElement>): void => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n activate(event)\n }\n\n return (\n <span className=\"dus-brand-name\">\n {/* No handler: clicking this still bubbles to the shell's New Session button. */}\n <span className=\"dus-brand-deepseek\">DeepSeek</span>\n <span\n className=\"dus-badge\"\n data-update={snapshot.status?.hasUpdate === true || undefined}\n data-error={state === 'problem' || undefined}\n role=\"button\"\n tabIndex={0}\n aria-label={badgeLabel(snapshot.status, snapshot.loading, snapshot.error)}\n title={badgeLabel(snapshot.status, snapshot.loading, snapshot.error)}\n onClick={activate}\n onKeyDown={onKeyDown}\n >\n <span className=\"dus-badge-version\">{shortVersion(version)}</span>\n {/* `data-state` drives the palette (green when up to date); `data-update`\n and `data-loading` remain the louder, state-specific rules. */}\n <span className=\"dus-dot\" data-state={state} data-update={snapshot.status?.hasUpdate === true || undefined} data-loading={snapshot.loading || undefined} aria-hidden=\"true\" />\n </span>\n </span>\n )\n}\n\n/** List-slot fallback: it deliberately disappears while the name badge is wide. */\nexport function FooterAction({ wide, ui }: { wide?: unknown; ui: SharedUi }): ReactNS.ReactElement | null {\n const preferences = useObservable(ui.preferences)\n const snapshot = useObservable(ui.status)\n if (wide === true || !preferences.sidebarEnabled) return null\n const state = visualState(snapshot.status, snapshot.loading, snapshot.error)\n const label = badgeLabel(snapshot.status, snapshot.loading, snapshot.error)\n return (\n <button\n className=\"dus-footer-button\"\n type=\"button\"\n aria-label={t('footer.status')}\n title={label}\n onClick={() => { ui.panel.toggle('rail') }}\n >\n <span className=\"dus-footer-icon\" aria-hidden=\"true\">↟</span>\n <span className=\"dus-dot dus-footer-dot\" data-state={state} data-update={snapshot.status?.hasUpdate === true || undefined} data-loading={state === 'loading' || undefined} />\n </button>\n )\n}\n\nfunction statusSummary(status: UpdateStatus | null, loading: boolean, error: string | null): { kind: 'update' | 'ok' | 'warning' | 'error'; text: string } {\n if (loading && status === null) return { kind: 'warning', text: t('brand.checking') }\n if (error !== null) return { kind: 'error', text: error }\n if (status?.hasUpdate === true) return { kind: 'update', text: t('panel.available') }\n if (status?.warning !== null && status?.warning !== undefined) return { kind: 'warning', text: status.warning }\n return { kind: 'ok', text: t('panel.currentState') }\n}\n\nfunction selectCommand(element: HTMLElement | null): void {\n if (element === null) return\n try {\n const selection = window.getSelection()\n if (selection === null) return\n const range = document.createRange()\n range.selectNodeContents(element)\n selection.removeAllRanges()\n selection.addRange(range)\n element.focus()\n } catch {\n // The command remains ordinary selectable text even when selection fails.\n }\n}\n\n/** Full detail from whichever page the operator is on (the transport is authenticated). */\nexport function UpdatePanel({ ui }: { ui: SharedUi }): ReactNS.ReactElement | null {\n const panel = useObservable(ui.panel)\n const preferences = useObservable(ui.preferences)\n const snapshot = useObservable(ui.status)\n const commandRef = React.useRef<HTMLElement | null>(null)\n const dialogRef = React.useRef<HTMLDialogElement | null>(null)\n const [copyMessage, setCopyMessage] = React.useState<string | null>(null)\n const visible = panel.open && preferences.sidebarEnabled\n\n React.useLayoutEffect(() => {\n if (!visible) return undefined\n const dialog = dialogRef.current\n if (dialog === null) return undefined\n try {\n if (!dialog.open) dialog.showModal()\n } catch {\n // Older embedded Chromium still receives a visible non-top-layer dialog.\n dialog.setAttribute('open', '')\n }\n return () => {\n if (dialog.open) dialog.close()\n }\n }, [visible])\n\n React.useEffect(() => {\n if (!panel.open) return undefined\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') ui.panel.close()\n }\n window.addEventListener('keydown', onKeyDown)\n return () => { window.removeEventListener('keydown', onKeyDown) }\n }, [panel.open, ui.panel])\n\n if (!visible) return null\n const status = snapshot.status\n const summary = statusSummary(status, snapshot.loading, snapshot.error)\n const switchingChannel = status !== null && status.channel !== preferences.channel\n const command = switchingChannel ? t('panel.switchingChannel') : status?.upgradeCommand ?? '—'\n\n const copy = async (): Promise<void> => {\n setCopyMessage(null)\n try {\n if (!navigator.clipboard?.writeText) throw new Error('Clipboard API unavailable')\n await navigator.clipboard.writeText(command)\n setCopyMessage(t('panel.copied'))\n } catch {\n selectCommand(commandRef.current)\n setCopyMessage(t('panel.copyFallback'))\n }\n }\n\n return (\n <dialog\n ref={dialogRef}\n className=\"dus-overlay-root\"\n aria-label={t('panel.title')}\n onCancel={(event) => {\n event.preventDefault()\n ui.panel.close()\n }}\n >\n <div className=\"dus-backdrop\" onClick={() => { ui.panel.close() }} />\n <section className=\"dus-panel\" data-origin={panel.origin} role=\"dialog\" aria-modal=\"true\" aria-label={t('panel.title')} onClick={(event) => { event.stopPropagation() }}>\n <div className=\"dus-panel-head\">\n <span className=\"dus-panel-title\">{t('panel.title')}</span>\n <button className=\"dus-close\" type=\"button\" aria-label={t('panel.close')} onClick={() => { ui.panel.close() }}>×</button>\n </div>\n\n <CacheTtlControl preferences={ui.preferences} />\n <p className=\"dus-state\" data-kind={summary.kind}>{summary.text}</p>\n\n <div className=\"dus-metadata\">\n <div className=\"dus-metadata-row\">\n <span className=\"dus-meta-label\">{t('panel.current')}</span>\n <code className=\"dus-meta-value\">{status?.currentVersion ?? '…'}</code>\n </div>\n <div className=\"dus-metadata-row\">\n <span className=\"dus-meta-label\">{t('panel.latest')} · {status === null ? 'latest' : channelLabel(status.channel)}</span>\n <code className=\"dus-meta-value\">{status?.latestVersion ?? t('panel.notChecked')}</code>\n {status?.publishedAt !== null && status?.publishedAt !== undefined && <span className=\"dus-meta-sub\">{t('panel.publishedAt')}: {timeText(status.publishedAt)}</span>}\n </div>\n {status?.checkedAt !== null && status?.checkedAt !== undefined && <div className=\"dus-metadata-row\">\n <span className=\"dus-meta-label\">{t('panel.checkedAt')}</span>\n <span className=\"dus-meta-value\">{timeText(status.checkedAt)}</span>\n <span className=\"dus-meta-sub\">{status.cached ? t('panel.cached') : t('panel.live')}</span>\n </div>}\n </div>\n\n {status !== null && <div className=\"dus-channel-list\" aria-label={t('panel.channels')}>\n <p className=\"dus-command-label\">{t('panel.channels')}</p>\n {visibleChannelReleases(status).map(release => {\n const selected = release.channel === preferences.channel\n return <div className=\"dus-channel-row\" data-selected={selected || undefined} key={release.channel}>\n <span>{channelLabel(release.channel)}</span>\n <code>{release.version ?? '—'}</code>\n <span className=\"dus-channel-compat\" data-compatibility={release.compatibility}>{compatibilityLabel(release.compatibility)}</span>\n <button\n className=\"dus-channel-action\"\n type=\"button\"\n disabled={selected || !preferences.writable || release.version === null}\n aria-label={`${selected ? t('panel.selectedChannel') : t('panel.selectChannel')}: ${channelLabel(release.channel)}`}\n onClick={() => { ui.preferences.setChannel(release.channel) }}\n >{selected ? t('panel.selectedChannel') : t('panel.selectChannel')}</button>\n </div>\n })}\n </div>}\n\n {status?.warning !== null && status?.warning !== undefined && <p className=\"dus-warning\">{t('panel.error')}: {status.warning}</p>}\n {snapshot.error !== null && <p className=\"dus-warning\">{t('panel.error')}: {snapshot.error}</p>}\n\n <div className=\"dus-actions\">\n <button className=\"dus-action\" type=\"button\" disabled={snapshot.loading} onClick={() => { void ui.status.refresh(undefined, preferences.cacheTtlMinutes) }}>\n {snapshot.loading ? t('panel.checking') : t('panel.check')}\n </button>\n {status !== null && <a className=\"dus-action\" href={status.changelogUrl} target=\"_blank\" rel=\"noreferrer\">{t('panel.releaseNotes')}</a>}\n <button className=\"dus-action\" type=\"button\" disabled={switchingChannel || status === null} onClick={() => { void copy() }}>{t('panel.copy')}</button>\n </div>\n\n <p className=\"dus-command-label\">{t('panel.command')}</p>\n <code ref={commandRef} className=\"dus-command\" tabIndex={0}>{command}</code>\n {copyMessage !== null && <p className=\"dus-copy-message\" role=\"status\">{copyMessage}</p>}\n <p className=\"dus-note\">{t('panel.commandNote')}</p>\n <p className=\"dus-note\">{t('panel.readOnly')}</p>\n </section>\n </dialog>\n )\n}\n\n/** Separate setting page: disables only this plugin's own rendering. */\nexport function UpdateSettings({ ui }: { ui: SharedUi }): ReactNS.ReactElement {\n const preferences = useObservable(ui.preferences)\n const snapshot = useObservable(ui.status)\n const [showGuidance, setShowGuidance] = React.useState(false)\n const previews = snapshot.status?.channels.filter(release => release.channel !== 'latest') ?? []\n const channelOptions = snapshot.status === null\n ? [{ channel: preferences.channel }]\n : visibleChannelReleases(snapshot.status)\n return (\n <section className=\"dus-settings\">\n <h2 className=\"dus-settings-heading\">{t('settings.title')}</h2>\n <div className=\"dus-settings-card\">\n <label className=\"dus-settings-toggle\">\n <input\n type=\"checkbox\"\n checked={preferences.sidebarEnabled}\n disabled={!preferences.writable}\n onChange={(event) => { ui.preferences.setSidebarEnabled(event.currentTarget.checked) }}\n />\n <span>{t('settings.sidebar')}</span>\n </label>\n <p className=\"dus-settings-hint\">{t('settings.sidebarHint')}</p>\n {!preferences.writable && <p className=\"dus-settings-hint\">{t('settings.readonly')}</p>}\n </div>\n <div className=\"dus-settings-card\">\n <label className=\"dus-settings-field\">\n <span>{t('settings.channel')}</span>\n <select\n className=\"dus-channel-select\"\n value={preferences.channel}\n disabled={!preferences.writable}\n onChange={(event) => { ui.preferences.setChannel(event.currentTarget.value as ReleaseChannel) }}\n >\n {channelOptions.map(option => <option value={option.channel} key={option.channel}>{channelLabel(option.channel)}</option>)}\n </select>\n </label>\n <p className=\"dus-settings-hint\">{t('settings.channelHint')}</p>\n <p className=\"dus-settings-hint\">{t('panel.current')}: <code>{snapshot.status?.currentVersion ?? '…'}</code></p>\n <p className=\"dus-settings-hint\">{t('preview.follow')}</p>\n <button className=\"dus-action\" type=\"button\" aria-expanded={showGuidance} onClick={() => { setShowGuidance(!showGuidance) }}>{t(showGuidance ? 'preview.close' : 'preview.open')}</button>\n {showGuidance && <section aria-label={t('preview.open')}>\n <p className=\"dus-warning\">{t('preview.risk')}</p>\n <button className=\"dus-action\" type=\"button\" disabled={snapshot.loading} onClick={() => { void ui.status.refresh(undefined, preferences.cacheTtlMinutes) }}>{t(snapshot.loading ? 'panel.checking' : 'panel.check')}</button>\n {snapshot.error !== null && <p role=\"alert\">{snapshot.error}</p>}\n {previews.length === 0 && <p>{t('preview.unavailable')}</p>}\n {previews.map(release => {\n const status = snapshot.status!\n const command = previewCommand(status, release.version)\n return <div className=\"dus-settings-card\" key={release.channel}>\n <p>{channelLabel(release.channel)} · {t('preview.target')}: <code>{release.version ?? t('panel.notChecked')}</code></p>\n <p>{compatibilityLabel(release.compatibility)}</p>\n {release.version === null ? <p>{t('preview.unavailable')}</p> : release.version === status.currentVersion ? <p>{t('preview.same')}</p> : command !== null ? <><p>{t('panel.command')}</p><code className=\"dus-command\" tabIndex={0}>{command}</code></> : <p>{t('preview.manual')}</p>}\n </div>\n })}\n <p className=\"dus-note\">{t('panel.commandNote')}</p>\n </section>}\n </div>\n </section>\n )\n}\n","/** Minimal structural client faces — runtime services stay owned by DSH. */\n\nimport { isReleaseChannel, type ChannelRelease, type UpdateStatus } from '../shared/types.ts'\n\nexport interface Observable<T> {\n getSnapshot(): T\n subscribe(listener: () => void): () => void\n}\n\nexport interface ConnectionRpc {\n call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<unknown>\n}\n\n/**\n * The authenticated transport face. There is deliberately NO `isLoopback` gate\n * here: DSH's settings Client treats a non-loopback page as process-local, but\n * that is a settings *persistence* policy — the Connection RPC itself stays\n * authenticated and reachable over a LAN bridge, so gating this plugin's own\n * read-only status on it only made the panel inert on exactly the page it is\n * used from. See settings/settingsChannel.ts for the one place a loopback\n * distinction still matters (choosing the Host settings channel).\n */\nexport interface ConnectionClient {\n rpc?: ConnectionRpc\n}\n\nexport interface SlotRegistration {\n name: string\n id?: string\n order?: number\n /** Single-slot arbitration: the lowest registered priority renders. */\n priority?: number\n label?: () => string\n locale?: string\n inject?: () => unknown\n}\n\nexport interface Slots {\n inject(name: string, callback: () => unknown): () => void\n register(registration: SlotRegistration, component: (props: Record<string, unknown>) => unknown): () => void\n}\n\nexport interface ClientContext {\n slots: Slots\n get(name: string): unknown\n inject(names: string[], callback: (ctx: unknown) => void): () => void\n effect(setup: () => (() => void) | void, label?: string): () => void\n /** Optional lifecycle event seam (Cordis contexts expose it; guarded here). */\n on?(name: string, listener: () => void): unknown\n}\n\nexport function errorMessage(error: unknown): string {\n const text = error instanceof Error ? error.message : String(error)\n return text.replace(/\\s+/g, ' ').trim().slice(0, 220) || 'unknown error'\n}\n\nfunction stringOrNull(value: unknown): string | null {\n return typeof value === 'string' ? value : null\n}\n\n/** Reject malformed RPC output before it reaches a slot component. */\nexport function updateStatusOf(value: unknown): UpdateStatus | undefined {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined\n const record = value as Record<string, unknown>\n if (typeof record.currentVersion !== 'string' || typeof record.hasUpdate !== 'boolean'\n || typeof record.cached !== 'boolean' || typeof record.installKind !== 'string'\n || typeof record.upgradeCommand !== 'string' || typeof record.releaseUrl !== 'string'\n || typeof record.changelogUrl !== 'string' || typeof record.packageName !== 'string'\n || !isReleaseChannel(record.channel) || !Array.isArray(record.channels) || record.canApplyInPlace !== false) return undefined\n const validKind = record.installKind === 'npm-global' || record.installKind === 'pnpm-global'\n || record.installKind === 'source-checkout' || record.installKind === 'unknown'\n if (!validKind) return undefined\n const latestVersion = record.latestVersion === null ? null : stringOrNull(record.latestVersion)\n const checkedAt = record.checkedAt === null ? null : stringOrNull(record.checkedAt)\n const warning = record.warning === null ? null : stringOrNull(record.warning)\n // Absent or unrecognised means a Host older than the field: leave it undefined and\n // let the visual state fall back to \"any warning is a failure\", never to \"no warning\".\n const warningKind = record.warningKind === 'failure' || record.warningKind === 'notice' ? record.warningKind : undefined\n const publishedAt = record.publishedAt === null ? null : stringOrNull(record.publishedAt)\n if ((record.latestVersion !== null && latestVersion === null) || (record.checkedAt !== null && checkedAt === null)\n || (record.warning !== null && warning === null) || (record.publishedAt !== null && publishedAt === null)) return undefined\n const channels: ChannelRelease[] = []\n for (const raw of record.channels) {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return undefined\n const item = raw as Record<string, unknown>\n const version = item.version === null ? null : stringOrNull(item.version)\n const channelPublishedAt = item.publishedAt === null ? null : stringOrNull(item.publishedAt)\n if (!isReleaseChannel(item.channel) || (item.version !== null && version === null)\n || (item.publishedAt !== null && channelPublishedAt === null)\n || (item.compatibility !== 'verified' && item.compatibility !== 'unverified' && item.compatibility !== 'incompatible')) return undefined\n channels.push({ channel: item.channel, version, publishedAt: channelPublishedAt, compatibility: item.compatibility })\n }\n return {\n currentVersion: record.currentVersion,\n latestVersion,\n hasUpdate: record.hasUpdate,\n cached: record.cached,\n checkedAt,\n warning,\n warningKind,\n installKind: record.installKind as UpdateStatus['installKind'],\n upgradeCommand: record.upgradeCommand,\n releaseUrl: record.releaseUrl,\n changelogUrl: record.changelogUrl,\n publishedAt,\n packageName: record.packageName,\n channel: record.channel,\n channels,\n canApplyInPlace: false,\n }\n}\n","/** Small observable stores shared by the independent sidebar and overlay slots. */\n\nimport { DEFAULT_CACHE_TTL_MINUTES, isCacheTtlMinutes, isReleaseChannel, PLUGIN_ID, UPDATE_ENDPOINTS, UPDATE_STATUS_CHANNEL, type ReleaseChannel, type UpdateStatus } from '../shared/types.ts'\nimport type { ConnectionClient, Observable } from './contract.ts'\nimport { errorMessage, updateStatusOf } from './contract.ts'\nimport type { SettingsScopeLike } from './settings/scopeFaces.ts'\n\nexport interface StatusSnapshot {\n status: UpdateStatus | null\n loading: boolean\n error: string | null\n}\n\nconst INITIAL_STATUS: StatusSnapshot = { status: null, loading: true, error: null }\n\nexport class StatusStore implements Observable<StatusSnapshot> {\n private snapshot: StatusSnapshot = INITIAL_STATUS\n private readonly listeners = new Set<() => void>()\n private inFlight: Promise<boolean> | undefined\n private stopped = false\n\n constructor(private readonly connection: ConnectionClient) {}\n\n getSnapshot = (): StatusSnapshot => this.snapshot\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n /** First mount reads the Host's cached status; it never starts a browser timer. */\n async load(cacheTtlMinutes: number = DEFAULT_CACHE_TTL_MINUTES): Promise<void> {\n await this.request(false, this.snapshot.status?.channel ?? 'latest', cacheTtlMinutes)\n }\n\n /** User gesture only: force the Host to bypass TTL (while retaining single-flight). */\n async refresh(channel: ReleaseChannel = this.snapshot.status?.channel ?? 'latest', cacheTtlMinutes: number = DEFAULT_CACHE_TTL_MINUTES): Promise<void> {\n await this.request(true, channel, cacheTtlMinutes)\n }\n\n /** Channel selection re-projects the Host cache; it is not a forced refresh. */\n async selectChannel(channel: ReleaseChannel, cacheTtlMinutes: number = DEFAULT_CACHE_TTL_MINUTES): Promise<void> {\n // A persisted preference or a newer selection can arrive while another\n // channel is in flight. Keep checking after every joined request until the\n // requested projection is actually the published snapshot.\n for (let attempts = 0; attempts < 3 && !this.stopped && this.snapshot.status?.channel !== channel; attempts += 1) {\n const pending = this.inFlight\n if (pending !== undefined && !await pending) return\n if (this.stopped || this.snapshot.status?.channel === channel) return\n if (!await this.request(false, channel, cacheTtlMinutes)) return\n }\n }\n\n stop(): void {\n this.stopped = true\n this.listeners.clear()\n }\n\n private publish(next: StatusSnapshot): void {\n if (this.stopped) return\n this.snapshot = next\n for (const listener of this.listeners) listener()\n }\n\n private request(force: boolean, channel: ReleaseChannel, cacheTtlMinutes: number): Promise<boolean> {\n if (this.inFlight !== undefined) return this.inFlight\n const rpc = this.connection.rpc\n if (rpc === undefined || typeof rpc.call !== 'function') {\n this.publish({ ...this.snapshot, loading: false, error: 'DSH connection RPC is unavailable' })\n return Promise.resolve(false)\n }\n\n this.publish({ ...this.snapshot, loading: true, error: null })\n const run = (async () => {\n try {\n const endpoint = force ? UPDATE_ENDPOINTS.checkUpdate : UPDATE_ENDPOINTS.getStatus\n const ttl = isCacheTtlMinutes(cacheTtlMinutes) ? cacheTtlMinutes : DEFAULT_CACHE_TTL_MINUTES\n const raw = await rpc.call(UPDATE_STATUS_CHANNEL, endpoint, force\n ? { force: true, channel, cacheTtlMinutes: ttl }\n : { channel, cacheTtlMinutes: ttl })\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('invalid update-status RPC response')\n const envelope = raw as { ok?: unknown; value?: unknown; error?: { message?: unknown } }\n if (envelope.ok !== true) throw new Error(typeof envelope.error?.message === 'string' ? envelope.error.message : 'update-status RPC failed')\n const status = updateStatusOf(envelope.value)\n if (status === undefined) throw new Error('invalid update-status payload')\n this.publish({ status, loading: false, error: null })\n return true\n } catch (error) {\n this.publish({ ...this.snapshot, loading: false, error: errorMessage(error) })\n return false\n }\n })()\n this.inFlight = run\n void run.finally(() => {\n if (this.inFlight === run) this.inFlight = undefined\n })\n return run\n }\n}\n\nexport interface PreferencesSnapshot {\n sidebarEnabled: boolean\n channel: ReleaseChannel\n cacheTtlMinutes: number\n writable: boolean\n status: 'loading' | 'ready' | 'unavailable'\n}\n\nconst INITIAL_PREFERENCES: PreferencesSnapshot = {\n sidebarEnabled: true,\n channel: 'latest',\n cacheTtlMinutes: DEFAULT_CACHE_TTL_MINUTES,\n writable: false,\n status: 'loading',\n}\n\nexport class PreferencesStore implements Observable<PreferencesSnapshot> {\n private snapshot: PreferencesSnapshot = INITIAL_PREFERENCES\n private readonly listeners = new Set<() => void>()\n private scope: SettingsScopeLike | undefined\n\n getSnapshot = (): PreferencesSnapshot => this.snapshot\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n attach(scope: SettingsScopeLike): () => void {\n this.scope = scope\n const sync = () => {\n const raw = scope.getSnapshot()\n const value = raw.value !== null && typeof raw.value === 'object' && !Array.isArray(raw.value)\n ? raw.value as Record<string, unknown>\n : {}\n const status = raw.status === 'ready' || raw.status === 'unavailable' ? raw.status : 'loading'\n this.publish({\n sidebarEnabled: value.sidebarEnabled !== false,\n channel: isReleaseChannel(value.channel) ? value.channel : 'latest',\n cacheTtlMinutes: isCacheTtlMinutes(value.cacheTtlMinutes) ? value.cacheTtlMinutes : DEFAULT_CACHE_TTL_MINUTES,\n writable: raw.writable === true,\n status,\n })\n }\n sync()\n return scope.subscribe(sync)\n }\n\n setSidebarEnabled(enabled: boolean): void {\n const previous = this.snapshot\n this.publish({ ...previous, sidebarEnabled: enabled })\n const scope = this.scope\n if (scope === undefined || !previous.writable) return\n void scope.set('sidebarEnabled', enabled).catch(() => {\n // A rejected Host-backed write restores the authoritative scope snapshot.\n try {\n const raw = scope.getSnapshot()\n const value = raw.value !== null && typeof raw.value === 'object' && !Array.isArray(raw.value)\n ? raw.value as Record<string, unknown>\n : {}\n this.publish({\n sidebarEnabled: value.sidebarEnabled !== false,\n channel: isReleaseChannel(value.channel) ? value.channel : 'latest',\n cacheTtlMinutes: isCacheTtlMinutes(value.cacheTtlMinutes) ? value.cacheTtlMinutes : DEFAULT_CACHE_TTL_MINUTES,\n writable: raw.writable === true,\n status: raw.status === 'ready' || raw.status === 'unavailable' ? raw.status : 'loading',\n })\n } catch {\n this.publish(previous)\n }\n })\n }\n\n setChannel(channel: ReleaseChannel): void {\n const previous = this.snapshot\n this.publish({ ...previous, channel })\n const scope = this.scope\n if (scope === undefined || !previous.writable) return\n void scope.set('channel', channel).catch(() => { this.publish(previous) })\n }\n\n setCacheTtlMinutes(cacheTtlMinutes: number): void {\n if (!isCacheTtlMinutes(cacheTtlMinutes)) return\n const previous = this.snapshot\n this.publish({ ...previous, cacheTtlMinutes })\n const scope = this.scope\n if (scope === undefined || !previous.writable) return\n void scope.set('cacheTtlMinutes', cacheTtlMinutes).catch(() => { this.publish(previous) })\n }\n\n private publish(next: PreferencesSnapshot): void {\n const previous = this.snapshot\n if (previous.sidebarEnabled === next.sidebarEnabled && previous.channel === next.channel\n && previous.cacheTtlMinutes === next.cacheTtlMinutes\n && previous.writable === next.writable && previous.status === next.status) return\n this.snapshot = next\n for (const listener of this.listeners) listener()\n }\n}\n\nexport type PanelOrigin = 'brand' | 'rail'\n\nexport interface PanelSnapshot {\n open: boolean\n origin: PanelOrigin\n}\n\n/** Open state also retains its trigger, so the desktop card sits beside it. */\nexport class PanelStore implements Observable<PanelSnapshot> {\n private snapshot: PanelSnapshot = { open: false, origin: 'brand' }\n private readonly listeners = new Set<() => void>()\n\n getSnapshot = (): PanelSnapshot => this.snapshot\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n toggle(origin: PanelOrigin): void {\n const open = !(this.snapshot.open && this.snapshot.origin === origin)\n this.set({ open, origin })\n }\n\n close(): void { this.set({ ...this.snapshot, open: false }) }\n\n private set(next: PanelSnapshot): void {\n if (this.snapshot.open === next.open && this.snapshot.origin === next.origin) return\n this.snapshot = next\n for (const listener of this.listeners) listener()\n }\n}\n\n/** The one namespace name used by Host registration and browser binding. */\nexport const SETTINGS_NAMESPACE = PLUGIN_ID\n","/**\n * dsh-update-status — client settings-scope faces (structural).\n *\n * The runtime scope object comes from `ctx.settingsScope.bind({ namespace })`\n * (dsh-client-ui-settings). The plugin never imports that package at runtime, so\n * the consumed members are re-typed here against the published SettingsScope\n * contract and re-proved at the boundary.\n *\n * `set(field, value)` is the member this plugin actually calls; the official\n * scope implements it as `mutate([{ op: 'set', path: [field], value }])`, so the\n * direct Host channel (hostDirectScope.ts) mirrors that exact semantic.\n */\n\n/** Mirrors dsh-client-ui-settings' SettingsScopeSnapshot<T>. */\nexport interface SettingsScopeSnapshotLike {\n status: 'loading' | 'ready' | 'unavailable'\n /** Raw namespace section; `undefined` before the first accepted answer. */\n value: unknown\n /** Composition layer (the built-in base), when the owner declared one. */\n base: unknown\n /** Raw user layer as stored, when one exists; presence marks user overrides. */\n user: unknown\n /** Namespace revision fencing the next write. */\n revision: number | undefined\n /** Whether the Host document accepts writes; memory mode never does. */\n writable: boolean\n mode: 'host' | 'memory'\n}\n\n/** One path-addressed settings edit (the wire op shape). */\nexport type SettingsPathOpLike =\n | { op: 'set'; path: string[]; value: unknown }\n | { op: 'unset'; path: string[] }\n\n/** The bound settings scope (ctx.settingsScope.bind result), reduced to what is used here. */\nexport interface SettingsScopeLike {\n getSnapshot(): SettingsScopeSnapshotLike\n subscribe(listener: () => void): () => void\n /** Queue one scalar field write inside the namespace section. */\n set(field: string, value: unknown): Promise<void>\n}\n\n/** The ctx.settingsScope binder face. */\nexport interface SettingsScopeBinderFace {\n bind(spec: { namespace: string }): SettingsScopeLike\n}\n\n/** Guard: does an object look like a binder with bind()? */\nexport function binderOf(raw: unknown): SettingsScopeBinderFace | undefined {\n if (raw === null || typeof raw !== 'object') return undefined\n const candidate = raw as { settingsScope?: unknown }\n const binder = candidate.settingsScope\n if (binder === null || typeof binder !== 'object') return undefined\n if (typeof (binder as { bind?: unknown }).bind !== 'function') return undefined\n return binder as SettingsScopeBinderFace\n}\n\n/** Guard: does an object look like a bound scope? */\nexport function scopeOf(value: unknown): SettingsScopeLike | undefined {\n if (value === null || typeof value !== 'object') return undefined\n const scope = value as { getSnapshot?: unknown; subscribe?: unknown; set?: unknown }\n if (typeof scope.getSnapshot !== 'function') return undefined\n if (typeof scope.subscribe !== 'function') return undefined\n if (typeof scope.set !== 'function') return undefined\n return scope as unknown as SettingsScopeLike\n}\n","/**\n * dsh-update-status — direct Host settings channel (non-loopback pages).\n *\n * WHY THIS EXISTS\n *\n * DSH's settings Client keeps Host persistence disabled on any page whose\n * location is not a loopback authority. `settingsScope.bind()` then answers\n * `status: 'unavailable'` immediately and NEVER sends `settings.describe`\n * (dsh-client-ui-settings README, \"Known Limitations\": \"Non-loopback pages get\n * no durable settings — this Client keeps Host persistence disabled there, so a\n * scope starts `unavailable` and never crosses the wire; every row it backs is\n * inert even though Connection authentication covers the API\").\n *\n * The decision itself is one line in the official client:\n *\n * const persistence = ctx.remote.$host.isLoopback ? \"host\" : \"memory\"\n *\n * and `isLoopback` is true only for `localhost` / `[::1]` / `127.0.0.0/8` pages\n * (or a transport that declares `ownsHost`). A LAN page reached through a\n * forwarding plugin is therefore non-loopback even though the API answers and\n * the page is authenticated.\n *\n * This plugin's settings — the sidebar toggle, the followed release channel and\n * the cache policy — live in the Host namespace `dsh-update-status`, so without a\n * channel they all silently fall back to defaults on a LAN page (the host's\n * configured `channel` would be ignored, silently switching the followed release\n * line). This module speaks the SAME public Remote the official scope speaks\n * (`settings.describe` / `settings.mutate`, the generated Typert face mounted by\n * `@deepseek-ai/dsh-api-remotes`) and derives the same per-namespace snapshot\n * shape, so the ONE shared Host document keeps serving every device.\n *\n * This channel is opened ONLY when the official scope reports `unavailable`\n * (see `settingsChannel.ts`), so loopback pages keep the official path and pay\n * no extra wire read. Everything here is guarded: an absent Remote, a refused\n * read, or a malformed answer leaves the preferences exactly as unavailable as\n * they were before this channel existed.\n */\nimport type { SettingsPathOpLike, SettingsScopeLike, SettingsScopeSnapshotLike } from './scopeFaces.ts'\n\n/** One namespace row of a `settings.describe` answer (the fields consumed here). */\nexport interface SettingsNamespaceViewLike {\n readonly ns: string\n readonly value: unknown\n readonly base?: unknown\n readonly user?: unknown\n readonly revision: number\n}\n\n/** The `settings.describe` answer body. */\nexport interface SettingsDescribeViewLike {\n readonly writable: boolean\n readonly hasDocument?: boolean\n readonly namespaces: readonly SettingsNamespaceViewLike[]\n}\n\n/** The Remote envelope every generated client call answers with. */\nexport type SettingsRemoteOutcome<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly error?: { readonly code?: string; readonly message?: string } }\n\n/** The `remote.settings` namespace face (structural; never imported at runtime). */\nexport interface SettingsRemoteLike {\n describe(): Promise<SettingsRemoteOutcome<SettingsDescribeViewLike>>\n mutate(\n namespace: string,\n ops: readonly SettingsPathOpLike[],\n expectedRevision?: number,\n ): Promise<SettingsRemoteOutcome<SettingsNamespaceViewLike>>\n}\n\n/** A refused namespace write; `code` keeps the Host's own diagnosis. */\nexport class SettingsWriteFailure extends Error {\n readonly code: string\n\n constructor(code: string, message?: string) {\n super(message === undefined || message === '' ? code : message)\n this.name = 'SettingsWriteFailure'\n this.code = code\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\n/**\n * Guard: does this value look like the `remote.settings` namespace face?\n * Applied to a bare service object (`ctx.get('remote.settings')` or a payload\n * member), and it never calls anything.\n */\nexport function settingsRemoteFace(value: unknown): SettingsRemoteLike | undefined {\n if (!isRecord(value)) return undefined\n if (typeof value.describe !== 'function' || typeof value.mutate !== 'function') return undefined\n return value as unknown as SettingsRemoteLike\n}\n\n/**\n * Guard: read the `remote.settings` face off an injection payload.\n *\n * The payload a `ctx.inject(['remote.settings'], …)` callback receives exposes\n * the LITERAL service key. Reading the dotted PARENT off that injected context\n * (`payload.remote`) throws `cannot get property \"remote\" without inject` — a\n * throw that used to abort a whole wiring callback on a LAN page. So the literal\n * key is read FIRST and every read is contained; the nested\n * (`payload.remote.settings`) shape is only a fallback for a plain object\n * payload. Both absence and refusal answer `undefined`.\n */\nexport function settingsRemoteOf(raw: unknown): SettingsRemoteLike | undefined {\n if (!isRecord(raw)) return undefined\n try {\n const direct = settingsRemoteFace((raw as Record<string, unknown>)['remote.settings'])\n if (direct !== undefined) return direct\n } catch {\n // Injected-context proxy refusal: fall through to the nested shape.\n }\n try {\n const remote = (raw as { remote?: unknown }).remote\n if (isRecord(remote)) return settingsRemoteFace(remote.settings)\n } catch {\n // Same refusal: no face is readable from this payload.\n }\n return undefined\n}\n\n/**\n * Guard: subscribe to the Host's settings-document invalidation on the Remote\n * service object (`remote.$on`). Returns undefined when the service exposes no\n * event seam — the preferences then refresh on reconnect only.\n */\nexport function settingsInvalidationsOf(service: unknown): ((listener: () => void) => () => void) | undefined {\n if (!isRecord(service)) return undefined\n const on = service.$on\n if (typeof on !== 'function') return undefined\n return (listener) => {\n const dispose = (on as (event: string, callback: () => void) => unknown)\n .call(service, 'settings/document-updated', listener)\n return typeof dispose === 'function' ? dispose as () => void : () => {}\n }\n}\n\n/** The channel consumers subscribe to, plus the refresh/dispose seams the client wires. */\nexport interface HostDirectScope extends SettingsScopeLike {\n /** Re-read the Host document; resolves after the snapshot reflects the answer. */\n load(): Promise<void>\n /** Drop every listener and stop accepting work. */\n dispose(): void\n /** Last Host error message (diagnostics only; never rendered as plugin state). */\n lastError(): string | undefined\n}\n\nfunction cloneOps(ops: readonly SettingsPathOpLike[]): SettingsPathOpLike[] {\n try {\n return structuredClone(ops) as SettingsPathOpLike[]\n } catch {\n // structuredClone absent/refusing the shape: copy by hand (the op payload is\n // plain JSON by contract, so a shallow structural copy is equivalent).\n return ops.map(op => (op.op === 'set'\n ? { op: 'set' as const, path: [...op.path], value: op.value }\n : { op: 'unset' as const, path: [...op.path] }))\n }\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction sameSnapshot(a: SettingsScopeSnapshotLike, b: SettingsScopeSnapshotLike): boolean {\n return a.status === b.status\n && a.revision === b.revision\n && a.writable === b.writable\n && a.mode === b.mode\n && a.value === b.value\n && a.base === b.base\n && a.user === b.user\n}\n\nfunction readySnapshot(\n row: SettingsNamespaceViewLike,\n writable: boolean,\n): SettingsScopeSnapshotLike {\n return {\n status: 'ready',\n // The raw section crosses this boundary untouched: PreferencesStore is the\n // plugin's own strict judge and reports a hand-edited or future-schema\n // document truthfully instead of silently dropping it.\n value: row.value,\n base: row.base,\n user: row.user,\n revision: row.revision,\n writable,\n mode: 'host',\n }\n}\n\nfunction unavailableSnapshot(writable: boolean): SettingsScopeSnapshotLike {\n return {\n status: 'unavailable',\n value: undefined,\n base: undefined,\n user: undefined,\n revision: undefined,\n writable,\n mode: 'host',\n }\n}\n\n/**\n * Build the direct Host settings scope for one namespace.\n *\n * Snapshot semantics mirror the official per-namespace derivation: a namespace\n * row answers `ready` with the resolved section, its `base`/`user` layers and its\n * revision fence, and the document's `writable` decides editability. A row the\n * Host does not serve answers `unavailable`; a refused read keeps the last\n * confirmed document and reports `unavailable` only while it has never held one.\n *\n * Writes are serialized in issue order, fenced by the latest known revision and\n * REJECT with {@link SettingsWriteFailure} on a Host refusal — that rejection is\n * what makes the settings page surface a conflict instead of pretending the edit\n * landed (the official scope resolves on `!ok` after its recovery read).\n *\n * @param remote - the `remote.settings` face of the page's Remote service.\n * @param namespace - the settings namespace this scope derives from.\n * @returns the scope plus its load/dispose seams.\n */\nexport function createHostDirectScope(remote: SettingsRemoteLike, namespace: string): HostDirectScope {\n let snapshot: SettingsScopeSnapshotLike = {\n status: 'loading',\n value: undefined,\n base: undefined,\n user: undefined,\n revision: undefined,\n writable: false,\n mode: 'host',\n }\n let held: SettingsScopeSnapshotLike | undefined\n let error: string | undefined\n let disposed = false\n let tail: Promise<void> = Promise.resolve()\n const listeners = new Set<() => void>()\n\n const publish = (next: SettingsScopeSnapshotLike): void => {\n if (disposed || sameSnapshot(next, snapshot)) return\n snapshot = next\n for (const listener of [...listeners]) {\n try {\n listener()\n } catch {\n // A broken listener must never break the channel.\n }\n }\n }\n\n const adopt = (next: SettingsScopeSnapshotLike): void => {\n if (next.status === 'ready') held = next\n publish(next)\n }\n\n async function load(): Promise<void> {\n if (disposed) return\n let outcome: SettingsRemoteOutcome<SettingsDescribeViewLike>\n try {\n outcome = await remote.describe()\n } catch (caught) {\n outcome = { ok: false, error: { message: messageOf(caught) } }\n }\n if (disposed) return\n if (!outcome.ok) {\n error = outcome.error?.message ?? outcome.error?.code ?? 'settings/describe-failed'\n // A refused read never invents state: the last confirmed document stays\n // authoritative; without one the channel is unavailable.\n publish(held ?? unavailableSnapshot(snapshot.writable))\n return\n }\n error = undefined\n const view = outcome.value\n const writable = view.writable === true\n const row = Array.isArray(view.namespaces)\n ? view.namespaces.find(candidate => candidate.ns === namespace)\n : undefined\n if (row === undefined || typeof row.revision !== 'number') {\n publish(unavailableSnapshot(writable))\n return\n }\n adopt(readySnapshot(row, writable))\n }\n\n /** Fold one write answer's namespace row in without a second wire read. */\n function fold(row: SettingsNamespaceViewLike, writable: boolean): void {\n if (typeof row.revision !== 'number') return\n adopt(readySnapshot(row, writable))\n }\n\n function mutate(ops: readonly SettingsPathOpLike[], expectedRevision?: number): Promise<void> {\n const owned = cloneOps(ops)\n const run = tail.then(async () => {\n if (disposed) throw new SettingsWriteFailure('settings/unavailable')\n const revision = expectedRevision ?? snapshot.revision\n let outcome: SettingsRemoteOutcome<SettingsNamespaceViewLike>\n try {\n outcome = revision === undefined\n ? await remote.mutate(namespace, owned)\n : await remote.mutate(namespace, owned, revision)\n } catch (caught) {\n await load()\n throw new SettingsWriteFailure('settings/unreachable', messageOf(caught))\n }\n if (outcome.ok) {\n // A successful write proves the document accepts writes on this page.\n fold(outcome.value, true)\n return\n }\n await load()\n throw new SettingsWriteFailure(\n outcome.error?.code ?? 'settings/unknown',\n outcome.error?.message,\n )\n })\n // The queue never poisons itself: the caller sees the rejection, the next\n // queued write still runs.\n tail = run.then(() => undefined, () => undefined)\n return run\n }\n\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n // The official scope implements `set` exactly this way; the revision fence\n // falls back to the latest known one, same as the official pendingRevision.\n set: (field, value) => mutate([{ op: 'set', path: [field], value }]),\n load,\n dispose() {\n disposed = true\n listeners.clear()\n },\n lastError: () => error,\n }\n}\n","/**\n * dsh-update-status — settings source selection.\n *\n * The plugin's ONE storage contract is the Host settings namespace\n * `dsh-update-status`. Two client channels can serve it:\n *\n * 1. the official `settingsScope` service (loopback pages), and\n * 2. the direct Host channel (`hostDirectScope.ts`), which exists exactly\n * because the official scope is deliberately inert on a non-loopback page —\n * the LAN page a phone uses.\n *\n * This module picks between them without ever guessing:\n *\n * - The official scope wins whenever it is not `unavailable`, so a loopback page\n * keeps the official semantics (schema decode, revision fences, document\n * invalidation) and pays NO extra wire read.\n * - Only when the official scope reports `unavailable` (the documented\n * non-loopback degradation) is the direct channel opened, lazily and once.\n * - With neither source the channel stays `loading`, i.e. exactly the state this\n * plugin had before the direct channel existed.\n */\nimport type { SettingsScopeLike, SettingsScopeSnapshotLike } from './scopeFaces.ts'\n\n/** Snapshot used while no channel has answered yet. */\nconst LOADING_SNAPSHOT: SettingsScopeSnapshotLike = Object.freeze({\n status: 'loading',\n value: undefined,\n base: undefined,\n user: undefined,\n revision: undefined,\n writable: false,\n mode: 'host',\n}) as SettingsScopeSnapshotLike\n\nexport interface SettingsChannelOptions {\n /**\n * Build the direct Host channel. Called at most once, and only when the\n * official scope cannot serve this page. Returning `undefined` (the Remote\n * service is not composed) leaves the channel on the official/unavailable\n * path; a later {@link SettingsChannel.refresh} retries.\n */\n openDirect(): SettingsScopeLike | undefined\n}\n\nexport interface SettingsChannel extends SettingsScopeLike {\n /** Point the channel at the official scope once the service answers. */\n setOfficial(scope: SettingsScopeLike | undefined): void\n /** Re-evaluate the selection (e.g. the Remote service just arrived). */\n refresh(): void\n /** Ask the active source for a fresh read (no-op for the official scope). */\n reload(): void\n dispose(): void\n}\n\nfunction sameSnapshot(a: SettingsScopeSnapshotLike, b: SettingsScopeSnapshotLike): boolean {\n return a.status === b.status\n && a.revision === b.revision\n && a.writable === b.writable\n && a.mode === b.mode\n && a.value === b.value\n && a.base === b.base\n && a.user === b.user\n}\n\n/** The load seam a direct channel exposes (the official scope refreshes itself). */\ntype LoadableScope = SettingsScopeLike & { load?: () => Promise<void> }\n\n/** Ask a channel for its first/next read; a refusal surfaces as a snapshot, never a throw. */\nfunction loadSource(source: SettingsScopeLike | undefined): Promise<void> {\n const loadable = source as LoadableScope | undefined\n if (loadable === undefined || typeof loadable.load !== 'function') return Promise.resolve()\n return Promise.resolve(loadable.load()).then(() => undefined, () => undefined)\n}\n\nexport function createSettingsChannel(options: SettingsChannelOptions): SettingsChannel {\n let official: SettingsScopeLike | undefined\n let direct: SettingsScopeLike | undefined\n let active: SettingsScopeLike | undefined\n let snapshot: SettingsScopeSnapshotLike = LOADING_SNAPSHOT\n let disposed = false\n const disposers: Array<() => void> = []\n const listeners = new Set<() => void>()\n\n const notify = (): void => {\n for (const listener of [...listeners]) {\n try {\n listener()\n } catch {\n // A broken listener must never break the channel.\n }\n }\n }\n\n const publish = (next: SettingsScopeSnapshotLike): void => {\n if (disposed || sameSnapshot(next, snapshot)) return\n snapshot = next\n notify()\n }\n\n const select = (): SettingsScopeLike | undefined => {\n // The official scope is authoritative unless it reports the documented\n // non-loopback degradation.\n if (official !== undefined && official.getSnapshot().status !== 'unavailable') return official\n if (direct === undefined) {\n try {\n direct = options.openDirect()\n } catch {\n direct = undefined\n }\n if (direct !== undefined) {\n try {\n disposers.push(direct.subscribe(() => {\n if (active === direct) publish(direct!.getSnapshot())\n }))\n } catch {\n // An unsubscribable direct channel is still readable synchronously.\n }\n // The direct channel has no owner to `ensure()` it (the official scope is\n // ensured by its own plugin), so the initial read is issued here.\n void loadSource(direct)\n }\n }\n return direct ?? official\n }\n\n const reselect = (): void => {\n if (disposed) return\n const next = select()\n if (next !== active) {\n active = next\n publish(next?.getSnapshot() ?? LOADING_SNAPSHOT)\n return\n }\n // Same source: forward its current snapshot (a change must have come from it).\n publish(next?.getSnapshot() ?? LOADING_SNAPSHOT)\n }\n\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n set(field, value) {\n const source = active\n if (source === undefined) {\n return Promise.reject(new Error('dsh-update-status: settings channel is unavailable'))\n }\n return source.set(field, value)\n },\n setOfficial(scope) {\n if (disposed) return\n official = scope\n if (scope !== undefined) {\n try {\n disposers.push(scope.subscribe(() => {\n reselect()\n }))\n } catch {\n // Guarded read path: selection still happens synchronously below.\n }\n }\n reselect()\n },\n refresh: reselect,\n reload() {\n if (active === undefined || active === official) return\n void loadSource(active)\n },\n dispose() {\n disposed = true\n listeners.clear()\n for (const dispose of disposers.splice(0)) {\n try {\n dispose()\n } catch {\n // Contained.\n }\n }\n const disposable = active as (SettingsScopeLike & { dispose?: () => void }) | undefined\n if (disposable !== undefined && typeof disposable.dispose === 'function') {\n try {\n disposable.dispose()\n } catch {\n // Contained.\n }\n }\n },\n }\n}\n","/** Plugin-owned CSS only; no shell DOM selection or official SVG manipulation. */\n\nexport const UPDATE_STATUS_CSS = `\n.dus-brand-name{align-items:center;gap:6px;height:24px;max-width:100%;min-width:0;display:inline-flex}\n.dus-brand-deepseek{font-size:14px;font-weight:650;letter-spacing:-.015em;line-height:24px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n/* The chip is a NEUTRAL SECOND-LEVEL SURFACE, never a high-contrast pill. It used\n to be label-primary + label-primary-inverted, which is a near-white box with dark\n text on the dark shell — a white frame that swallows the amber halo and fights the\n warn colour. --dsw-alias-button-floating-hover is the one palette surface that is\n grey in BOTH themes (#f1f3f5 light / #353638 dark), matching the reference: a light\n grey chip on the light shell, a dark grey chip with white text on the dark one. The\n sibling button-floating-fill is pure white in light mode and changes nothing there.\n Text stays label-primary (#0f1115 light / #f9fafb dark), so it is never low-contrast.\n Every colour here is a theme token, so the chip follows whatever appearance DSH\n resolves — light, dark, or system-follows-the-OS — with no plugin-side detection,\n no media query and no per-theme branch to keep in sync. */\n.dus-badge{--dus-badge-surface:var(--dsw-alias-button-floating-hover,#f1f3f5);align-items:center;background:var(--dus-badge-surface);border:0;border-radius:4px;color:var(--dsw-alias-label-primary,#0f1115);cursor:pointer;display:inline-flex;flex:none;font-family:var(--ds-font-family-code,var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace));font-size:10px;font-variant-numeric:tabular-nums;font-weight:600;gap:4px;height:16px;line-height:16px;max-width:140px;outline:none;padding:0 6px;touch-action:manipulation;user-select:none;white-space:nowrap}\n.dus-badge:focus-visible,.dus-footer-button:focus-visible,.dus-action:focus-visible,.dus-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n/* Hover has to read as \"raised\" in BOTH themes. brightness() only ever brightens, and on\n the light shell it turns the grey chip pure white — the chip all but disappears. Mixing\n a little of the theme's own label colour into the surface darkens it in light mode and\n lightens it in dark mode, so one declaration stays correct in both. The brightness\n filter remains the fallback: an engine without color-mix drops only the second\n declaration, never the whole hover rule. */\n.dus-badge:hover{filter:brightness(1.08)}\n@supports (background:color-mix(in srgb,red 50%,transparent)){.dus-badge:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 8%,var(--dus-badge-surface));filter:none}}\n/* The failure state is the one deliberate repaint, and it follows the shell's own\n badge pattern (brokenBadge): error fill with a bg-layer-3 label, which is white\n text on the dark red of the light theme and dark text on the light red of the dark\n theme — readable in both, unlike the fixed white text it replaces. */\n.dus-badge[data-error=true]{background:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-bg-layer-3,#fff)}\n.dus-badge-version{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dus-dot{background:currentColor;border-radius:50%;display:inline-block;flex:0 0 auto;height:5px;opacity:.9;width:5px}\n/* An available update repaints NOTHING: the chip keeps its normal fill and only\n the amber dot breathes. The halo is animated with the dot so the glow grows\n and shrinks as one light, never as a separate ripple. The update state is\n declared AFTER the loading state, so a re-check on a known update keeps the\n amber glow instead of falling back to the neutral loading pulse.\n Each box-shadow is declared twice: a literal amber first for any engine\n without color-mix, then the halo mixed from the theme's own warn colour so the\n glow always matches the dot in both light and dark themes. */\n/* The dot carries the whole status. Three states, in precedence order:\n up to date = a plain green circle (success token, never the text colour, so a\n dark theme no longer paints a black dot that reads as \"off\"); checking = the\n neutral grey pulse; a pending update = the amber dot below, which is the only\n state allowed to breathe or glow.\n The up-to-date rule is declared BEFORE loading and update: equal specificity, so\n the later, louder states win if two ever land on the same dot. */\n.dus-dot[data-state=current]{background:var(--dsw-alias-state-success-primary,#22c55e);opacity:1}\n.dus-dot[data-loading=true]{animation:dus-pulse .9s ease-in-out infinite}\n.dus-dot[data-update=true]{--dus-update-color:var(--dsw-alias-state-warn-primary,#f59e0b);animation:dus-update-pulse 1.6s ease-in-out infinite;background:var(--dus-update-color);height:6px;opacity:1;width:6px}\n@keyframes dus-pulse{0%,100%{opacity:.45;transform:scale(.82)}50%{opacity:1;transform:scale(1.15)}}\n@keyframes dus-update-pulse{0%,100%{box-shadow:0 0 2px 0 rgba(245,158,11,.4);box-shadow:0 0 2px 0 color-mix(in srgb,var(--dus-update-color) 45%,transparent);transform:scale(.8)}50%{box-shadow:0 0 7px 2px rgba(245,158,11,.72);box-shadow:0 0 7px 2px color-mix(in srgb,var(--dus-update-color) 72%,transparent);transform:scale(1.15)}}\n@media (prefers-reduced-motion:reduce){.dus-dot[data-update=true],.dus-dot[data-loading=true]{animation:none}.dus-dot[data-update=true]{box-shadow:0 0 5px 1px rgba(245,158,11,.6);box-shadow:0 0 5px 1px color-mix(in srgb,var(--dus-update-color) 62%,transparent)}}\n.dus-footer-button{align-items:center;background:transparent;border:0;border-radius:10px;color:var(--dsw-alias-label-secondary);cursor:pointer;display:flex;height:44px;justify-content:center;min-height:44px;min-width:44px;padding:0;position:relative;touch-action:manipulation;width:44px}\n.dus-footer-button:hover{background:var(--dsw-alias-button-floating-hover);color:var(--dsw-alias-label-primary)}\n.dus-footer-icon{font-size:18px;line-height:1}\n.dus-footer-dot{border:1.5px solid var(--dsw-specific-sidebar-fill);position:absolute;right:9px;top:9px}\n.dus-overlay-root{background:transparent;border:0;color:inherit;height:100dvh;inset:0;margin:0;max-height:none;max-width:none;padding:0;pointer-events:none;position:fixed;width:100vw}\n.dus-overlay-root::backdrop{background:transparent}\n.dus-backdrop{background:transparent;inset:0;pointer-events:auto;position:absolute}\n.dus-panel{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:14px;box-shadow:var(--dsw-elevation-panel);box-sizing:border-box;color:var(--dsw-alias-label-primary);max-height:min(640px,calc(100dvh - 32px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));overflow:auto;padding:14px;pointer-events:auto;position:absolute;top:12px;width:min(390px,calc(100vw - 24px));z-index:1}\n.dus-panel[data-origin=brand]{left:min(292px,calc(100vw - 402px))}\n.dus-panel[data-origin=rail]{left:min(68px,calc(100vw - 402px))}\n.dus-panel-head{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-bottom:12px}\n.dus-panel-title{font-size:14px;font-weight:650}\n.dus-close{align-items:center;background:transparent;border:0;border-radius:8px;color:inherit;cursor:pointer;display:inline-flex;font-size:20px;height:32px;justify-content:center;line-height:1;min-height:32px;min-width:32px;padding:0;touch-action:manipulation;width:32px}\n.dus-close:hover,.dus-action:hover{background:var(--dsw-alias-button-floating-hover)}\n.dus-cache-setting{border:1px solid var(--dsw-alias-border-l2);border-radius:9px;margin:0 0 12px;padding:9px}\n.dus-cache-field{align-items:center;display:flex;font-size:12px;font-weight:600;gap:10px;justify-content:space-between}\n.dus-cache-input-wrap{align-items:center;display:flex;gap:6px;font-size:11px;font-weight:400}\n.dus-cache-input{background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:7px;color:inherit;font:inherit;min-height:30px;padding:0 7px;text-align:right;width:72px}\n.dus-cache-input:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n.dus-cache-input:disabled{cursor:not-allowed;opacity:.55}\n.dus-cache-hint{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.4;margin:7px 0 0}\n.dus-state{border-radius:9px;color:var(--dsw-alias-label-primary);font-size:12px;line-height:1.45;margin:0 0 12px;padding:8px 9px}\n.dus-state[data-kind=update],.dus-state[data-kind=warning]{background:var(--dsw-alias-state-warn-tertiary)}\n.dus-state[data-kind=ok]{background:var(--dsw-alias-state-success-tertiary)}\n.dus-state[data-kind=error]{background:var(--dsw-alias-bg-layer-3)}\n.dus-metadata{display:grid;gap:9px;margin:0 0 12px}\n.dus-metadata-row{align-items:baseline;display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto}\n.dus-meta-label{color:var(--dsw-alias-label-secondary);font-size:12px}\n.dus-meta-value{font-family:var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:12px;max-width:210px;overflow-wrap:anywhere;text-align:right}\n.dus-meta-sub{color:var(--dsw-alias-label-secondary);font-size:11px;grid-column:1 / -1}\n.dus-warning{border-left:2px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.45;margin:0 0 12px;padding-left:8px}\n.dus-actions{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 12px}\n.dus-action{background:var(--dsw-alias-button-floating-fill);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:inherit;cursor:pointer;font-size:12px;line-height:30px;min-height:32px;padding:0 10px;touch-action:manipulation}\n.dus-action:disabled{cursor:wait;opacity:.65}\n.dus-command-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;margin:0 0 6px}\n.dus-command{background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;display:block;font-family:var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:11px;line-height:1.5;margin:0;overflow-wrap:anywhere;padding:9px;tab-size:2;user-select:text;white-space:pre-wrap}\n.dus-command:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}\n.dus-note{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.45;margin:8px 0 0}\n.dus-copy-message{color:var(--dsw-alias-state-business-primary);font-size:11px;margin:7px 0 0}\n.dus-settings{display:grid;gap:14px;max-width:640px;padding:4px 0}\n.dus-settings-heading{font-size:16px;font-weight:650;margin:0}\n.dus-settings-card{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:12px}\n.dus-settings-toggle{align-items:flex-start;cursor:pointer;display:flex;gap:10px;font-size:13px;line-height:1.4}\n.dus-settings-toggle input{accent-color:var(--dsw-alias-state-business-primary);height:18px;margin:0;min-height:18px;min-width:18px;width:18px}\n.dus-settings-field{display:grid;font-size:13px;font-weight:600;gap:8px}\n.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%}\n.dus-channel-list{border-top:1px solid var(--dsw-alias-border-l2);margin:12px 0 0;padding-top:2px}\n.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}\n.dus-channel-row[data-selected=true]{color:var(--dsw-alias-state-business-primary)}\n.dus-channel-row code{font-size:11px}\n.dus-channel-compat{color:var(--dsw-alias-label-secondary)}\n.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}\n.dus-channel-action:disabled{cursor:default;opacity:.55}\n.dus-channel-action:not(:disabled):hover{background:var(--dsw-alias-button-floating-hover)}\n.dus-channel-compat[data-compatibility=verified]{color:var(--dsw-alias-state-success-primary)}\n.dus-channel-compat[data-compatibility=incompatible]{color:var(--dsw-alias-state-error-primary)}\n.dus-settings-hint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.45;margin:8px 0 0}\n@media (max-width:640px),(hover:none) and (pointer:coarse){.dus-panel[data-origin]{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-footer-button{height:48px;min-height:48px;min-width:48px;width:48px}.dus-action{min-height:40px;line-height:38px}.dus-close{height:40px;min-height:40px;min-width:40px;width:40px}}\n`\n","/**\n * DSH Web client half. It shadows only `sidebar.brand.name` with a compact\n * name + version chip that fits the 24px brand row, leaves the official fish\n * (`sidebar.brand.mark`) untouched, and adds a collapsed-rail fallback at\n * `sidebar.footer.action`. The detail panel is an additive `shell.overlay`,\n * never a chat-area floating widget.\n *\n * The status read and the detail panel are NOT restricted to a loopback page.\n * DSH disables Host settings *persistence* on a non-loopback page, but the\n * Connection RPC stays authenticated and reachable there — see\n * settings/settingsChannel.ts for the one place the loopback distinction still\n * matters.\n */\n\nimport type { ClientContext, ConnectionClient } from './contract.ts'\nimport { BrandName, FooterAction, type SharedUi, UpdatePanel, UpdateSettings } from './components.tsx'\nimport { SETTINGS_NAMESPACE, PanelStore, PreferencesStore, StatusStore } from './stores.ts'\nimport { binderOf, scopeOf } from './settings/scopeFaces.ts'\nimport {\n createHostDirectScope,\n settingsInvalidationsOf,\n settingsRemoteFace,\n settingsRemoteOf,\n type SettingsRemoteLike,\n} from './settings/hostDirectScope.ts'\nimport { createSettingsChannel } from './settings/settingsChannel.ts'\nimport { t } from './i18n.ts'\nimport { UPDATE_STATUS_CSS } from './styles.ts'\n\nconst PLUGIN_ID = 'dsh-update-status'\nconst CSS_TAG_ID = `${PLUGIN_ID}/styles`\n\nfunction installStyles(): () => void {\n if (typeof document === 'undefined') return () => {}\n const existing = document.querySelector(`style[data-plugin-css=\"${CSS_TAG_ID}\"]`)\n if (existing !== null) return () => {}\n const tag = document.createElement('style')\n tag.dataset.plugin = PLUGIN_ID\n tag.dataset.pluginCss = CSS_TAG_ID\n tag.textContent = UPDATE_STATUS_CSS\n document.head.appendChild(tag)\n return () => {\n const live = document.querySelector(`style[data-plugin-css=\"${CSS_TAG_ID}\"]`)\n if (live?.parentNode !== undefined && live?.parentNode !== null) live.parentNode.removeChild(live)\n }\n}\n\nfunction connectionOf(ctx: ClientContext): ConnectionClient {\n try {\n const candidate = ctx.get('connection')\n return candidate !== null && typeof candidate === 'object' ? candidate as ConnectionClient : {}\n } catch {\n return {}\n }\n}\n\nfunction apply(ctx: ClientContext): void {\n const connection = connectionOf(ctx)\n const status = new StatusStore(connection)\n const preferences = new PreferencesStore()\n const panel = new PanelStore()\n const ui: SharedUi = { status, preferences, panel }\n\n const disposers: Array<() => void> = []\n let stopped = false\n /** Own a subscription raised from an injection callback; release after teardown at once. */\n const own = (dispose: unknown): void => {\n if (typeof dispose !== 'function') return\n if (stopped) {\n try {\n (dispose as () => void)()\n } catch {\n // Contained.\n }\n return\n }\n disposers.push(dispose as () => void)\n }\n\n // The plugin's ONE storage contract is the Host namespace `dsh-update-status`,\n // but two client channels can serve it: the official `settingsScope` (loopback\n // pages) and — only when that scope reports the documented non-loopback\n // degradation — a direct Host channel over the same public Remote. A LAN page\n // is exactly that non-loopback case; without the direct channel every\n // preference silently falls back to its default there. See settingsChannel.ts.\n let remoteSettings: SettingsRemoteLike | undefined\n /** Resolve the direct channel's Remote face; `ctx.get` covers a payload we could not read. */\n const directRemote = (): SettingsRemoteLike | undefined => {\n if (remoteSettings !== undefined) return remoteSettings\n try {\n remoteSettings = settingsRemoteFace(ctx.get('remote.settings'))\n } catch {\n // The dotted service key is not readable on this context.\n }\n return remoteSettings\n }\n const channel = createSettingsChannel({\n openDirect: () => {\n const remote = directRemote()\n return remote === undefined ? undefined : createHostDirectScope(remote, SETTINGS_NAMESPACE)\n },\n })\n\n ctx.effect(() => {\n const disposeStyles = installStyles()\n const detach = preferences.attach(channel)\n // Changing cache policy deliberately does not issue a network request. Its\n // value is sent on the next ordinary status read or manual check.\n let selected = status.getSnapshot().status?.channel ?? 'latest'\n const syncPreferences = () => {\n const next = preferences.getSnapshot()\n if (next.channel !== selected) {\n selected = next.channel\n void status.selectChannel(selected, next.cacheTtlMinutes)\n }\n }\n syncPreferences()\n const unsubscribe = preferences.subscribe(syncPreferences)\n // Every mount reads the Host's cached status, whichever page it is on; the\n // transport is authenticated and the Host route is the plugin's own.\n void status.load(preferences.getSnapshot().cacheTtlMinutes)\n return () => {\n stopped = true\n unsubscribe()\n detach()\n for (const dispose of disposers.splice(0)) {\n try {\n dispose()\n } catch {\n // Contained.\n }\n }\n channel.dispose()\n status.stop()\n disposeStyles()\n }\n }, 'dsh-update-status: styles, settings channel and first status read')\n\n // Official seam first: it stays authoritative whenever it is not `unavailable`,\n // so a loopback page keeps the official semantics and pays no extra wire read.\n try {\n ctx.inject(['settingsScope'], (raw: unknown) => {\n try {\n const binder = binderOf(raw)\n if (binder === undefined) return\n channel.setOfficial(scopeOf(binder.bind({ namespace: SETTINGS_NAMESPACE })))\n } catch {\n // Unreadable settings seam: the preferences stay on their defaults.\n }\n })\n } catch {\n // No settingsScope seam on this host: the direct channel is the only hope.\n }\n\n // Direct Host channel. The Remote service can arrive before or after the\n // official scope, so `refresh()` re-evaluates the selection either way; the\n // document invalidation keeps this page in step with edits made elsewhere,\n // and a reconnect retries a read that was refused.\n //\n // Every read here is contained: the injected payload intentionally refuses the\n // dotted PARENT (`payload.remote` throws \"cannot get property … without\n // inject\"), so one unreadable member must never abort the whole wiring.\n try {\n ctx.inject(['remote.settings'], (raw: unknown) => {\n try {\n remoteSettings = settingsRemoteOf(raw) ?? remoteSettings\n channel.refresh()\n const invalidations = settingsInvalidationsOf(ctx.get('remote'))\n if (invalidations !== undefined) own(invalidations(() => channel.reload()))\n } catch {\n // No usable Remote seam: the direct channel stays closed and the official\n // scope keeps its verdict.\n }\n })\n } catch {\n // No Remote seam: non-loopback pages keep the honest unavailable state.\n }\n if (typeof ctx.on === 'function') {\n try {\n own(ctx.on('connection/reset', () => channel.reload()))\n } catch {\n // No lifecycle event seam on this host.\n }\n }\n\n // Single slot: a compact name + version chip shadows the official wordmark\n // (priority 0). Lowest priority renders; never touch sidebar.brand.mark.\n ctx.slots.inject('sidebar.brand.name', () => ctx.slots.register(\n { name: 'sidebar.brand.name', priority: -10 },\n () => BrandName({ ui }),\n ))\n\n // additive fallback; returns null in wide mode to avoid two visible badges.\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register(\n { name: 'sidebar.footer.action', id: 'dsh-update-status', order: 40 },\n props => FooterAction({ wide: props.wide, ui }),\n ))\n\n // additive frame overlay for click/tap panel and narrow-view bottom sheet.\n ctx.slots.inject('shell.overlay', () => ctx.slots.register(\n { name: 'shell.overlay', id: 'dsh-update-status', order: 40 },\n () => UpdatePanel({ ui }),\n ))\n\n // Independent settings section provides the required way to hide our entry.\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n { name: 'settings.section', id: 'dsh-update-status', order: 80, label: () => t('settings.title') },\n () => UpdateSettings({ ui }),\n ))\n}\n\nmodule.exports = {\n name: PLUGIN_ID,\n inject: ['slots', 'connection'],\n apply,\n}\n"],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -4,6 +4,23 @@ import { Context } from "@deepseek-ai/cordis";
|
|
|
4
4
|
declare const RELEASE_CHANNELS: readonly ['latest', 'next', 'alpha'];
|
|
5
5
|
type ReleaseChannel = (typeof RELEASE_CHANNELS)[number];
|
|
6
6
|
type ReleaseCompatibility = 'verified' | 'unverified' | 'incompatible';
|
|
7
|
+
/**
|
|
8
|
+
* Severity of `warning`, so a surface can tell "the plugin could not determine
|
|
9
|
+
* the update state" from "here is something worth knowing".
|
|
10
|
+
*
|
|
11
|
+
* - `failure`: no usable answer — a failed registry read, a channel the registry
|
|
12
|
+
* does not publish, or a version SemVer cannot compare. This is what justifies
|
|
13
|
+
* repainting the sidebar chip.
|
|
14
|
+
* - `notice`: the answer is complete and usable; the text is advisory, e.g. a
|
|
15
|
+
* preview channel whose release this bundle has not been verified against.
|
|
16
|
+
* The chip must NOT repaint for this — an operator upgrading DSH ahead of the
|
|
17
|
+
* plugin would otherwise see the chip turn red for a plugin-side bookkeeping
|
|
18
|
+
* fact.
|
|
19
|
+
*
|
|
20
|
+
* Optional on purpose: a Host older than this field leaves it `undefined`, and
|
|
21
|
+
* the client then falls back to treating any warning as a failure.
|
|
22
|
+
*/
|
|
23
|
+
type UpdateWarningKind = 'failure' | 'notice';
|
|
7
24
|
type InstallKind = 'npm-global' | 'pnpm-global' | 'source-checkout' | 'unknown';
|
|
8
25
|
interface ChannelRelease {
|
|
9
26
|
channel: ReleaseChannel;
|
|
@@ -23,6 +40,8 @@ interface UpdateStatus {
|
|
|
23
40
|
cached: boolean;
|
|
24
41
|
checkedAt: string | null;
|
|
25
42
|
warning: string | null;
|
|
43
|
+
/** Severity of `warning`; absent from a Host older than the field. */
|
|
44
|
+
warningKind?: UpdateWarningKind | null;
|
|
26
45
|
installKind: InstallKind;
|
|
27
46
|
upgradeCommand: string;
|
|
28
47
|
releaseUrl: string;
|
|
@@ -74,6 +93,7 @@ declare class UpdateStatusService {
|
|
|
74
93
|
private refreshRelease;
|
|
75
94
|
private statusAfterFailure;
|
|
76
95
|
private statusFromCache;
|
|
96
|
+
/** No remote data at all: the registry read itself failed, never a notice. */
|
|
77
97
|
private statusWithoutRemoteRelease;
|
|
78
98
|
}
|
|
79
99
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -21,7 +21,11 @@ const UPDATE_STATUS_CHANNEL = "/api";
|
|
|
21
21
|
* release the registry reports stays `unverified` — the plugin never claims a
|
|
22
22
|
* compatibility nobody checked.
|
|
23
23
|
*/
|
|
24
|
-
const VERIFIED_DSH_VERSIONS = [
|
|
24
|
+
const VERIFIED_DSH_VERSIONS = [
|
|
25
|
+
"0.1.5-rc.1",
|
|
26
|
+
"0.1.6-alpha.1",
|
|
27
|
+
"0.1.6-alpha.2"
|
|
28
|
+
];
|
|
25
29
|
const RELEASE_CHANNELS = [
|
|
26
30
|
"latest",
|
|
27
31
|
"next",
|
|
@@ -477,13 +481,16 @@ var UpdateStatusService = class {
|
|
|
477
481
|
const missingWarning = selected.version === null ? `npm registry 未发布 ${channel} 通道。` : null;
|
|
478
482
|
const comparisonWarning = selected.version !== null && comparison === void 0 ? `无法按 SemVer 比较当前版本 ${this.installation.currentVersion} 与 ${channel} 通道版本 ${selected.version}。` : null;
|
|
479
483
|
const previewWarning = channel !== "latest" && selected.version !== null && selected.compatibility !== "verified" ? `${channel} 是预览通道,版本 ${selected.version} 尚未验证与本插件兼容。` : null;
|
|
484
|
+
const failureWarning = warningWith(warningWith(initialWarning, missingWarning), comparisonWarning);
|
|
485
|
+
const warning = warningWith(failureWarning, previewWarning);
|
|
480
486
|
return {
|
|
481
487
|
currentVersion: this.installation.currentVersion,
|
|
482
488
|
latestVersion: selected.version,
|
|
483
489
|
hasUpdate: comparison !== void 0 && comparison < 0,
|
|
484
490
|
cached,
|
|
485
491
|
checkedAt: new Date(cache.checkedAtMs).toISOString(),
|
|
486
|
-
warning
|
|
492
|
+
warning,
|
|
493
|
+
warningKind: warning === null ? null : failureWarning === null ? "notice" : "failure",
|
|
487
494
|
installKind: this.installation.installKind,
|
|
488
495
|
upgradeCommand: upgradeCommandFor(this.installation.installKind, this.installation.packageName || "@deepseek-ai/dsh", channel),
|
|
489
496
|
releaseUrl: this.releaseUrl,
|
|
@@ -495,6 +502,7 @@ var UpdateStatusService = class {
|
|
|
495
502
|
canApplyInPlace: false
|
|
496
503
|
};
|
|
497
504
|
}
|
|
505
|
+
/** No remote data at all: the registry read itself failed, never a notice. */
|
|
498
506
|
statusWithoutRemoteRelease(channel, warning) {
|
|
499
507
|
return {
|
|
500
508
|
currentVersion: this.installation.currentVersion,
|
|
@@ -503,6 +511,7 @@ var UpdateStatusService = class {
|
|
|
503
511
|
cached: false,
|
|
504
512
|
checkedAt: null,
|
|
505
513
|
warning,
|
|
514
|
+
warningKind: "failure",
|
|
506
515
|
installKind: this.installation.installKind,
|
|
507
516
|
upgradeCommand: upgradeCommandFor(this.installation.installKind, this.installation.packageName || "@deepseek-ai/dsh", channel),
|
|
508
517
|
releaseUrl: this.releaseUrl,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-update-status",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Read-only DeepSeek Harness Web plugin that shows the running DSH version, discovers latest/next/alpha releases, warns about compatibility, and generates copy-only upgrade commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"CHANGELOG.md",
|
|
25
25
|
"LICENSE",
|
|
26
26
|
"assets/update-panel.png",
|
|
27
|
+
"assets/sidebar-chip.png",
|
|
27
28
|
"docs/RELEASING.md"
|
|
28
29
|
],
|
|
29
30
|
"engines": {
|
|
@@ -57,7 +58,8 @@
|
|
|
57
58
|
"compatibility": {
|
|
58
59
|
"dshReleases": {
|
|
59
60
|
"0.1.5-rc.1": "compatible",
|
|
60
|
-
"0.1.6-alpha.1": "compatible"
|
|
61
|
+
"0.1.6-alpha.1": "compatible",
|
|
62
|
+
"0.1.6-alpha.2": "compatible"
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
},
|