@idoall/dsh-session-colors 0.1.2 → 0.1.4

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 CHANGED
@@ -8,6 +8,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.1.4] - 2026-09-23
12
+
13
+ Verified against DeepSeek Harness `0.1.7-rc.1` (the latest release candidate) as
14
+ well as `0.1.7-alpha.2`. No code change: `0.1.3` already absorbed the `0.1.7`
15
+ changes, and this release confirms the same code on the RC and records it.
16
+
17
+ ### Added
18
+
19
+ - `dsh.compatibility.dshReleases` now records `0.1.7-rc.1` alongside
20
+ `0.1.7-alpha.2`; both READMEs state both versions.
21
+ - The design record documents the new `0.1.7-rc.1` behaviour that makes the peer
22
+ range load-bearing: boot evaluates `@deepseek-ai/dsh*` peers and **disables** a
23
+ plugin row whose range the running version does not satisfy.
24
+
25
+ ### Notes
26
+
27
+ - The peer and `dsh.engines.dsh` ranges stay `>=0.1.7-alpha.2 <0.2.0`. Under
28
+ node-semver's prerelease rule that range already admits `0.1.7-rc.1` (a
29
+ comparator names the same `major.minor.patch`), so it was deliberately **not**
30
+ widened for the RC — widening would admit versions nobody tested.
31
+ - `0.1.4` supports DSH `0.1.7-rc.1` and `0.1.7-alpha.2`. Installations on an
32
+ older DSH, including `0.1.6-alpha.2`, should stay on plugin `0.1.2`.
33
+
34
+ ## [0.1.3] - 2026-09-23
35
+
36
+ Verified against DeepSeek Harness `0.1.7-alpha.2`. The first fix below is what
37
+ made the previous build fail to load on that release; the other two are a
38
+ declaration correctness fix and a visible behaviour fix.
39
+
40
+ ### Fixed
41
+
42
+ - **The plugin could not load from a `link:` install on DSH 0.1.7.** From 0.1.7,
43
+ only a linked plugin's **peer** dependencies resolve from the running
44
+ installation; a plain dependency is looked up under the plugin's own
45
+ `node_modules`, which a link install does not populate. `@deepseek-ai/schemastery`
46
+ was a plain dependency and this repository ships no `node_modules`, so the Host
47
+ half failed to import and the plugin never loaded. It is a peer now (kept in
48
+ `devDependencies` for this repository's own tests).
49
+ - **The declared peer range excluded the release it was running on.**
50
+ `>=0.1.6-0 <0.2.0` does **not** admit `0.1.7-alpha.2` under node-semver's
51
+ default prerelease rule, so pnpm reports the peer as unmet (the plugin market's
52
+ discovery check passes `includePrerelease`, which is why it did not surface as
53
+ an outright incompatibility). The range is now `>=0.1.7-alpha.2 <0.2.0`, with a
54
+ `dsh.engines.dsh` requirement and `dsh.compatibility.dshReleases` recording
55
+ `0.1.7-alpha.2`.
56
+ - **Chips no longer stayed behind while rows glided.** DSH 0.1.7 moves sidebar
57
+ rows with the Web Animations API (`element.animate`), which fires neither
58
+ `transitionrun` nor `transitionend` and mutates no DOM. The layer only opened
59
+ its bounded follow window on a transition event, so a collapse, expand or
60
+ reorder left every chip at the position the glide started from. A row-list
61
+ mutation and a window resize now re-measure once and enter the same 40-frame
62
+ follow window; an unrelated body mutation re-measures once but opens no window,
63
+ so a streaming transcript cannot keep a measuring loop alive.
64
+
65
+ ### Changed
66
+
67
+ - The Session id is read from the row's own `data-row-key` (`session:<id>`, added
68
+ in DSH 0.1.7) before falling back to the React fiber, so the common case no
69
+ longer depends on React internals. The fiber fallback remains for older builds
70
+ and search-result rows.
71
+ - The client build marker is `host-routes+animated-rows`.
72
+ - The docs-consistency test now evaluates the peer range with the prerelease
73
+ rule instead of a substring check — the check that let the old, excluding range
74
+ pass.
75
+
76
+ ### Notes
77
+
78
+ - `0.1.3` supports DSH `0.1.7-alpha.2` only and declares
79
+ `>=0.1.7-alpha.2 <0.2.0` as its Host requirement. Installations on an older DSH,
80
+ including `0.1.6-alpha.2`, should stay on plugin `0.1.2`.
81
+
11
82
  ## [0.1.2] - 2026-09-22
12
83
 
13
84
  ### Fixed
package/README.md CHANGED
@@ -23,6 +23,8 @@
23
23
  <a href="#development">Development</a>
24
24
  </p>
25
25
 
26
+ > **✅ Supports DSH `0.1.7-rc.1` — the latest `0.1.7` release candidate.** Verified on the running release candidate as well as on `0.1.7-alpha.2`; see [Compatibility](#compatibility).
27
+
26
28
  > DSH Session Colors is a DeepSeek Harness community plugin. It does not modify
27
29
  > DSH core and does not rewrite any Session or Workspace data: a mark is only
28
30
  > "Session id → one colour".
@@ -69,12 +71,16 @@ Requirements:
69
71
 
70
72
  - DeepSeek Harness with a Web profile
71
73
  - Node.js 20 or newer
72
- - **Verified DSH version: `0.1.6-alpha.2`** (see [Compatibility](#compatibility))
74
+ - **Verified DSH version: `0.1.7-rc.1`** (the latest release candidate) and
75
+ `0.1.7-alpha.2` — plugin `0.1.4` (see [Compatibility](#compatibility))
73
76
 
74
77
  ```sh
75
- dsh plugin --profile <profile> add @idoall/dsh-session-colors@0.1.0
78
+ dsh plugin --profile <profile> add @idoall/dsh-session-colors@latest
76
79
  ```
77
80
 
81
+ The [compatibility table](#compatibility) lists the exact versions, if you need to
82
+ pin one to a specific DSH release.
83
+
78
84
  Working on the plugin itself? Install the directory instead:
79
85
 
80
86
  ```sh
@@ -114,23 +120,49 @@ stored as HSVA, so alpha round-trips exactly.
114
120
 
115
121
  ## Compatibility
116
122
 
117
- Current release: plugin **`0.1.2`**, verified against DeepSeek Harness
118
- **`0.1.6-alpha.2`**.
123
+ Current release: plugin **`0.1.4`** is verified against DeepSeek Harness
124
+ **`0.1.7-rc.1`** — the latest `0.1.7` release candidate — and against
125
+ **`0.1.7-alpha.2`**.
119
126
 
120
127
  | Plugin version | Verified DeepSeek Harness | npm status | What it is |
121
128
  | --- | --- | --- | --- |
122
- | **`0.1.2`** | `0.1.6-alpha.2` | `latest` | Fixes chips staying at their old coordinates for ~20s when a workspace is collapsed or expanded: a second `MutationObserver.observe()` on the same target had silently replaced the `childList` watch. |
129
+ | **`0.1.4`** | **`0.1.7-rc.1`** (latest RC), `0.1.7-alpha.2` | `latest` | Confirms the `0.1.7` adaptation runs unchanged on the release candidate and declares it. No code change over `0.1.3`; upgrading needs no migration. |
130
+ | `0.1.3` | `0.1.7-alpha.2` | published | Adapts to DSH 0.1.7: the Session id comes from the row's own `data-row-key` (fiber fallback kept), chips follow the Web-Animations row gliding `0.1.7` introduced, and the Host dependency is declared the way `0.1.7` resolves a linked plugin. |
131
+ | `0.1.2` | `0.1.6-alpha.2` | published | Fixes chips staying at their old coordinates for ~20s when a workspace is collapsed or expanded: a second `MutationObserver.observe()` on the same target had silently replaced the `childList` watch. |
123
132
  | `0.1.1` | `0.1.6-alpha.2` | published | Documentation only: the packaged README no longer says "not published", and the market screenshots are declared. |
124
133
  | `0.1.0` | `0.1.6-alpha.2` | published | First release: Session colour marks, Host-side storage, visible inside a phone's sidebar drawer |
125
134
 
135
+ **`0.1.4` supports DSH `0.1.7-rc.1`, the latest release candidate.** The `0.1.7`
136
+ line changed the sidebar row markup and the linked-plugin dependency resolution,
137
+ and **`0.1.3` already absorbed both** — `0.1.4` confirms the same code runs
138
+ unchanged on the RC and records it. **Upgrading from `0.1.3` needs no migration.**
139
+ On an older DSH — including `0.1.6-alpha.2` — keep plugin **`0.1.2`**. Newer DSH
140
+ releases are not auto-declared compatible.
141
+
126
142
  - **Verified DeepSeek Harness** is the exact DSH version this plugin was actually
127
143
  run against. That list has one home — `dsh.compatibility.dshReleases` in
128
144
  [`package.json`](package.json) — and a test keeps both READMEs' compatibility
129
145
  sections word-for-word with it and inside the range `peerDependencies`
130
146
  declares. A DSH version that is not listed is **never claimed as compatible**.
131
- - `peerDependencies` declares `>=0.1.6-0 <0.2.0` for `dsh-client-ui-layout` and
132
- `dsh-client-ui-conversation`: that is the range allowed to **load**, which is
133
- not the same as verified.
147
+ - `peerDependencies` declares `>=0.1.7-alpha.2 <0.2.0` for `dsh-client-ui-layout`
148
+ and `dsh-client-ui-conversation`, and `dsh.engines.dsh` declares the same range
149
+ as the Host requirement: that is the range allowed to **load**, which is not the
150
+ same as verified. The range admits **both** `0.1.7-alpha.2` and `0.1.7-rc.1`
151
+ (a prerelease is admitted when some comparator names the same
152
+ `major.minor.patch`), so it was deliberately **not** widened for the RC — doing
153
+ so would admit versions nobody tested. The lower bound names the alpha on
154
+ purpose: a range like `>=0.1.6-0 <0.2.0` admits neither.
155
+ - DSH 0.1.7-rc.1 evaluates those peers at boot and **disables** a plugin row whose
156
+ range is not satisfied, so a correct range is now load-bearing rather than
157
+ cosmetic.
158
+ - `@deepseek-ai/schemastery` is a **peer**, not a plain dependency: DSH 0.1.7
159
+ resolves only a linked plugin's peer dependencies from the running
160
+ installation, so a `link:` install of this directory would otherwise fail to
161
+ import the Host half.
162
+ - `@deepseek-ai/schemastery` is a **peer**, not a plain dependency: DSH 0.1.7
163
+ resolves only a linked plugin's peer dependencies from the running
164
+ installation, so a `link:` install of this directory would otherwise fail to
165
+ import the Host half.
134
166
  - DSH moving faster than this plugin does not break it: chips depend on DSH's row
135
167
  DOM (see [Limitations](#limitations)), so a large change to the row structure
136
168
  can at worst stop chips from appearing — it cannot block clicking.
@@ -184,7 +216,7 @@ document.querySelector('.dsh-sc-layer').dataset
184
216
  // { dshScBuild, dshScStatus, dshScWritable, dshScMarks, dshScError }
185
217
  ```
186
218
 
187
- If `dshScBuild` is not `host-routes+drawer-aware`, the device is running a
219
+ If `dshScBuild` is not `host-routes+animated-rows`, the device is running a
188
220
  **cached client**: refresh the page (the browser half reloads on a refresh, no DSH
189
221
  restart needed). If `dshScStatus` is not `ready`, the store cannot be read — see
190
222
  the next item.
@@ -201,9 +233,10 @@ pages, because storage uses its own route. If you see the old "settings
201
233
  unavailable" behaviour, check which version is installed.
202
234
 
203
235
  **Chips are in the wrong place, or gone.**
204
- The layer finds rows by ARIA role and reads the Session id from the React fiber.
205
- A large change to DSH's sidebar structure can make it find nothing; chips then
206
- disappear, but **clicking is unaffected** because the layer is click-through.
236
+ The layer finds rows by ARIA role and reads the Session id from the row's
237
+ `data-row-key` (falling back to React internals). A large change to DSH's sidebar
238
+ structure can make it find nothing; chips then disappear, but **clicking is
239
+ unaffected** because the layer is click-through.
207
240
 
208
241
  ## Security boundaries
209
242
 
@@ -221,8 +254,8 @@ disappear, but **clicking is unaffected** because the layer is click-through.
221
254
  ## Limitations
222
255
 
223
256
  - **The chip depends on DSH's row DOM.** It reads rows by ARIA role and the
224
- Session id from React internals. A future DSH release can stop chips from
225
- appearing; it cannot break clicking.
257
+ Session id from the row's `data-row-key`, falling back to React internals. A
258
+ future DSH release can stop chips from appearing; it cannot break clicking.
226
259
  - **Marks are per DSH instance.** They live in one profile's data directory, so
227
260
  two independent DSH servers do not share them.
228
261
  - **No permission model.** Anyone who can reach the Host's authenticated routes
@@ -241,7 +274,7 @@ is not deleted for you; remove it yourself if you do not want it.
241
274
  ## Development
242
275
 
243
276
  ```sh
244
- npm test # 34 tests: bundle shape, store, route, cross-device, geometry, docs
277
+ npm test # 38 tests: bundle shape, store, route, cross-device, geometry, docs
245
278
  ```
246
279
 
247
280
  The browser half is a hand-authored `__ModuleLoader__` bundle, which is the only
package/README.zh.md CHANGED
@@ -23,6 +23,8 @@
23
23
  <a href="#开发">开发</a>
24
24
  </p>
25
25
 
26
+ > **✅ 已支持 DSH `0.1.7-rc.1` —— 最新的 `0.1.7` 候选版本(RC)。** 已在运行中的 RC 实例上实测通过,同时也兼容 `0.1.7-alpha.2`;详见[兼容性](#兼容性)。
27
+
26
28
  > DSH Session Colors 是 DeepSeek Harness 社区插件。它不修改 DSH 核心,也不改写任何会话或工作区数据:一个标记只是"会话 ID → 一个颜色"。
27
29
 
28
30
  工作分散在多个工作区里,而工作区列表就是一堵长得差不多的标题墙。任务跑完之后,刚才那条会话就很难再找回来——状态点已经回到空闲,标题也只是二十个里的一个。**自己选的颜色,是让一行变得好认的最省事办法。**
@@ -51,12 +53,14 @@
51
53
 
52
54
  - 带 Web profile 的 DeepSeek Harness
53
55
  - Node.js 20 或更新版本
54
- - **已验证的 DSH 版本:`0.1.6-alpha.2`**(见[兼容性](#兼容性))
56
+ - **已验证的 DSH 版本:`0.1.7-rc.1`**(最新候选版)与 `0.1.7-alpha.2`——插件 `0.1.4`(见[兼容性](#兼容性))
55
57
 
56
58
  ```sh
57
- dsh plugin --profile <profile> add @idoall/dsh-session-colors@0.1.0
59
+ dsh plugin --profile <profile> add @idoall/dsh-session-colors@latest
58
60
  ```
59
61
 
62
+ 要按 DSH 版本精确对应,用[兼容性](#兼容性)表里的具体版本号。
63
+
60
64
  要改插件本身就用目录安装:
61
65
 
62
66
  ```sh
@@ -88,16 +92,22 @@ dsh plugin --profile <profile> add link:/path/to/dsh-session-colors
88
92
 
89
93
  ## 兼容性
90
94
 
91
- 当前版本:插件 **`0.1.2`** 已针对 DeepSeek Harness **`0.1.6-alpha.2`** 验证。
95
+ 当前版本:插件 **`0.1.4`** 已针对 DeepSeek Harness **`0.1.7-rc.1`**(最新的 `0.1.7` 候选版本)与 **`0.1.7-alpha.2`** 验证。
92
96
 
93
97
  | 插件版本 | 已验证的 DeepSeek Harness | npm 发布状态 | 该版本是什么 |
94
98
  | --- | --- | --- | --- |
95
- | **`0.1.2`** | `0.1.6-alpha.2` | `latest` | 修掉折叠/展开工作区后色块停在旧位置约 20 秒:同一个 target 上第二次 `MutationObserver.observe()` 把 `childList` 监听顶掉了。 |
99
+ | **`0.1.4`** | **`0.1.7-rc.1`**(最新 RC)、`0.1.7-alpha.2` | `latest` | 确认 `0.1.7` 适配在候选版本上原样可用并加以声明。代码与 `0.1.3` 相同,升级无需迁移。 |
100
+ | `0.1.3` | `0.1.7-alpha.2` | 已发布 | 适配 DSH 0.1.7:会话 id 改从行自身的 `data-row-key` 读取(保留 fiber 回退),色块跟随 0.1.7 引入的 Web Animations 行滑动;宿主依赖按 0.1.7 对 link 插件的解析方式声明。 |
101
+ | `0.1.2` | `0.1.6-alpha.2` | 已发布 | 修掉折叠/展开工作区后色块停在旧位置约 20 秒:同一个 target 上第二次 `MutationObserver.observe()` 把 `childList` 监听顶掉了。 |
96
102
  | `0.1.1` | `0.1.6-alpha.2` | 已发布 | 仅文档:打包进 npm 的 README 不再写"尚未发布",并声明了市场截图。 |
97
103
  | `0.1.0` | `0.1.6-alpha.2` | 已发布 | 首个版本:会话颜色标记、宿主侧存储、手机抽屉内可见 |
98
104
 
105
+ **`0.1.4` 支持 DSH `0.1.7-rc.1`,即最新的候选版本(RC)。** `0.1.7` 线改了侧栏行的 DOM 与 link 插件的依赖解析方式,而**`0.1.3` 已经把这两处吸收完毕**——`0.1.4` 确认同一份代码在 RC 上原样可用,并把 RC 记入声明。**从 `0.1.3` 升级无需任何迁移。** 仍在更旧的 DSH(含 `0.1.6-alpha.2`)上时,请继续用插件 **`0.1.2`**。更高的 DSH 版本不会被自动宣称为兼容。
106
+
99
107
  - **已验证的 DeepSeek Harness** 是这个插件**实际跑过**的确切 DSH 版本。这份清单只有一个存放处——[`package.json`](package.json) 的 `dsh.compatibility.dshReleases`——并且有测试保证两个 README 的兼容性段落与它逐字一致、且落在 `peerDependencies` 声明的范围内。未列出的 DSH 版本**不会被宣称为兼容**。
100
- - `peerDependencies` 声明的范围是 `>=0.1.6-0 <0.2.0`(`dsh-client-ui-layout` 与 `dsh-client-ui-conversation`):这是**允许加载**的范围,不等于已验证。
108
+ - `peerDependencies` 声明的范围是 `>=0.1.7-alpha.2 <0.2.0`(`dsh-client-ui-layout` 与 `dsh-client-ui-conversation`),`dsh.engines.dsh` 把同一范围声明为宿主要求:这是**允许加载**的范围,不等于已验证。该范围**同时接纳** `0.1.7-alpha.2` 与 `0.1.7-rc.1`(只要范围里有比较符写了同一个 `major.minor.patch`,该版本的预发布就会被接纳),所以**刻意不为 RC 放宽**——放宽只会顺带接纳没人测过的版本。下界特意写成这个 alpha:`>=0.1.6-0 <0.2.0` 这样的范围两者都不接纳。
109
+ - DSH 0.1.7-rc.1 会在启动时校验这些 peer,范围不满足就**直接禁用该插件行**,所以范围写对已经不只是"声明问题"。
110
+ - `@deepseek-ai/schemastery` 声明为 **peer**,不是普通依赖:DSH 0.1.7 只把 link 插件的 **peer** 依赖解析到运行实例,所以用 `link:` 装入本目录时,普通依赖会让宿主半边 import 失败。
101
111
  - DSH 升级快于插件时,插件本身不会因此报错:色块依赖 DSH 的行 DOM(见[限制](#限制)),所以 DSH 大改行结构时最坏的结果是**色块不再显示**,不会挡住点击。
102
112
 
103
113
  ## 跨设备与局域网
@@ -141,7 +151,7 @@ document.querySelector('.dsh-sc-layer').dataset
141
151
  // { dshScBuild, dshScStatus, dshScWritable, dshScMarks, dshScError }
142
152
  ```
143
153
 
144
- `dshScBuild` 不是 `host-routes+drawer-aware` 就是**客户端缓存**,刷新页面即可(客户端半边刷新即热重载,不需要重启 DSH)。`dshScStatus` 不是 `ready` 则是存储读不到,见下一条。
154
+ `dshScBuild` 不是 `host-routes+animated-rows` 就是**客户端缓存**,刷新页面即可(客户端半边刷新即热重载,不需要重启 DSH)。`dshScStatus` 不是 `ready` 则是存储读不到,见下一条。
145
155
 
146
156
  **头部控制显示 ⚠。**
147
157
  表示读不到存储:`dshScError` 里有原因,`dshScWritable` 说明能不能写。宿主路由未注册(插件没被 profile 加载)或数据目录不可写时会这样。注意**这不是**"这个会话没有颜色"。
@@ -150,7 +160,7 @@ document.querySelector('.dsh-sc-layer').dataset
150
160
  从 `0.1.0` 起本插件在局域网/隧道页面与回环页面行为一致(存储走自有路由)。如果你看到的是旧版那种"设置读不到"的表现,先确认装的是哪个版本。
151
161
 
152
162
  **色块位置不对或消失。**
153
- 色块浮层靠 ARIA role 找会话行、靠 React fiber 取会话 ID。DSH 大改侧栏结构后可能找不到行;此时色块会消失,但**不会影响点击**,因为浮层是点击穿透的。
163
+ 色块浮层靠 ARIA role 找会话行、靠行自身的 `data-row-key` 取会话 ID(取不到时回退到 React fiber)。DSH 大改侧栏结构后可能找不到行;此时色块会消失,但**不会影响点击**,因为浮层是点击穿透的。
154
164
 
155
165
  ## 安全边界
156
166
 
@@ -162,7 +172,7 @@ document.querySelector('.dsh-sc-layer').dataset
162
172
 
163
173
  ## 限制
164
174
 
165
- - **色块依赖 DSH 的行 DOM。** 它靠 ARIA role 找行、靠 React 内部结构取会话 ID。DSH 未来改版可能让色块不再显示;但**不会影响点击**。
175
+ - **色块依赖 DSH 的行 DOM。** 它靠 ARIA role 找行、靠行自身的 `data-row-key` 取会话 ID(回退到 React 内部结构)。DSH 未来改版可能让色块不再显示;但**不会影响点击**。
166
176
  - **标记属于单个 DSH 实例。** 它存在某个 profile 的数据目录里,两个独立的 DSH 服务之间不共享。
167
177
  - **没有权限模型。** 凡是能访问该 profile 已鉴权路由的人,都能读写标记。
168
178
  - **只有颜色。** 没有文字标签、图标或 emoji。
@@ -178,7 +188,7 @@ dsh plugin --profile <profile> remove @idoall/dsh-session-colors
178
188
  ## 开发
179
189
 
180
190
  ```sh
181
- npm test # 34 项:bundle 结构、存储、路由、跨设备、几何、文档一致性
191
+ npm test # 38 项:bundle 结构、存储、路由、跨设备、几何、文档一致性
182
192
  ```
183
193
 
184
194
  浏览器半边是**手写的** `__ModuleLoader__` bundle——这是 DSH 客户端加载器接受的唯一格式。手写是刻意的:**没有构建步骤**,就不会有产物与目标 DSH 版本漂移的问题。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idoall/dsh-session-colors",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "DSH web plugin: give any Session a colour mark, shown as a chip in the sidebar and picked from the conversation header. Marks are stored on the Host, so a colour chosen on one device is visible on every other one — including over LAN and tunnels.",
6
6
  "main": "./src/index.js",
@@ -20,9 +20,13 @@
20
20
  ],
21
21
  "dsh": {
22
22
  "manifestVersion": 1,
23
+ "engines": {
24
+ "dsh": ">=0.1.7-alpha.2 <0.2.0"
25
+ },
23
26
  "compatibility": {
24
27
  "dshReleases": {
25
- "0.1.6-alpha.2": "compatible"
28
+ "0.1.7-rc.1": "compatible",
29
+ "0.1.7-alpha.2": "compatible"
26
30
  }
27
31
  },
28
32
  "bundle": {
@@ -42,12 +46,10 @@
42
46
  "publishConfig": {
43
47
  "access": "public"
44
48
  },
45
- "dependencies": {
46
- "@deepseek-ai/schemastery": "^3.18.2"
47
- },
48
49
  "peerDependencies": {
49
- "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.6-0 <0.2.0",
50
- "@deepseek-ai/dsh-client-ui-layout": ">=0.1.6-0 <0.2.0",
50
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.7-alpha.2 <0.2.0",
51
+ "@deepseek-ai/dsh-client-ui-layout": ">=0.1.7-alpha.2 <0.2.0",
52
+ "@deepseek-ai/schemastery": "^3.18.2",
51
53
  "react": "^18.2.0"
52
54
  },
53
55
  "peerDependenciesMeta": {
@@ -58,6 +60,11 @@
58
60
  "optional": true
59
61
  }
60
62
  },
63
+ "devDependencies": {
64
+ "@deepseek-ai/cordis": "^4.0.4",
65
+ "@deepseek-ai/schemastery": "^3.18.2",
66
+ "react": "^18.3.1"
67
+ },
61
68
  "license": "MIT",
62
69
  "engines": {
63
70
  "node": ">=20"
package/src/client.js CHANGED
@@ -15,12 +15,13 @@
15
15
  * `ChipsLayer` and `layerZIndexOf`.
16
16
  *
17
17
  * There is no Session-row slot, so a chip cannot be rendered inside a row. The
18
- * layer finds rows by ARIA role and reads each row's Session id from the React
19
- * fiber the renderer already attached — the only place the id exists, since the
20
- * row markup carries no id attribute. That coupling is deliberate and is kept
21
- * harmless: every step is guarded, an unreadable row is skipped, and the layer
22
- * never accepts pointer events, so a future DSH change can at worst stop the
23
- * chips from appearing.
18
+ * layer finds rows by ARIA role and reads each row's Session id from the
19
+ * `data-row-key` attribute DSH 0.1.7 puts on every row (`session:<id>`),
20
+ * falling back to the React fiber the renderer attaches — the only source on a
21
+ * build without that attribute, and still the source for search-result rows.
22
+ * That coupling is deliberate and is kept harmless: every step is guarded, an
23
+ * unreadable row is skipped, and the layer never accepts pointer events, so a
24
+ * future DSH change can at worst stop the chips from appearing.
24
25
  *
25
26
  * Written as a hand-authored `__ModuleLoader__` bundle (the only format the
26
27
  * client loader accepts for a package client half), so it needs no build step.
@@ -46,13 +47,13 @@ window.__ModuleLoader__.load({
46
47
 
47
48
  const NS = 'sessionColor'
48
49
  /**
49
- * Build marker. Stored marks are Host-side as of this build, and the chip
50
- * layer paints just above the sidebar as of the `drawer-aware` build — the
51
- * change that makes a chip visible inside a phone's sidebar drawer. A device
52
- * that still shows an older marker is running a cached client and must be
53
- * hard-refreshed.
50
+ * Build marker. Stored marks are Host-side as of the `host-routes` build,
51
+ * the chip layer paints just above the sidebar as of `drawer-aware`, and as
52
+ * of `animated-rows` it follows the Web-Animations row gliding DSH 0.1.7
53
+ * introduced. A device that still shows an older marker is running a cached
54
+ * client and must be hard-refreshed.
54
55
  */
55
- const BUILD = 'host-routes+drawer-aware'
56
+ const BUILD = 'host-routes+animated-rows'
56
57
 
57
58
  /**
58
59
  * This plugin's own Host route. Kept in sync with the Host half's
@@ -429,13 +430,28 @@ window.__ModuleLoader__.load({
429
430
 
430
431
  //#region row discovery
431
432
  /**
432
- * Session id of a rendered row, read from the React fiber the renderer
433
- * already attached. The row markup deliberately carries no id, so the fiber
434
- * is the only exact source; the walk is bounded and every failure returns
435
- * undefined, which makes the caller skip that row.
433
+ * Prefix DSH 0.1.7 uses for a Session row's `data-row-key`. It is the
434
+ * exact, stable DOM source of a row's Session id; older builds and
435
+ * search-result rows carry no such attribute.
436
+ */
437
+ const SESSION_ROW_KEY = 'session:'
438
+
439
+ /**
440
+ * Session id of a rendered row. The `data-row-key` attribute is read first
441
+ * because DSH 0.1.7 marks every row with it; the React fiber the renderer
442
+ * already attached remains the fallback, and the walk is bounded so an
443
+ * unexpected shape only costs that row its chip.
436
444
  * @param node - a Session row element.
437
445
  */
438
446
  function sessionIdOf(node) {
447
+ try {
448
+ const key = node === null || node === undefined || typeof node.getAttribute !== 'function'
449
+ ? undefined
450
+ : node.getAttribute('data-row-key')
451
+ if (typeof key === 'string' && key.startsWith(SESSION_ROW_KEY)) return key.slice(SESSION_ROW_KEY.length)
452
+ } catch {
453
+ // An unreadable attribute only sends this row to the fiber fallback.
454
+ }
439
455
  try {
440
456
  const key = Object.keys(node).find((name) => name.startsWith('__reactFiber$') || name.startsWith('__reactInternalInstance$'))
441
457
  if (key === undefined) return undefined
@@ -603,10 +619,23 @@ window.__ModuleLoader__.load({
603
619
  setLayer({ items, clip, zIndex: rows.length === 0 ? 1 : layerZIndexOf(rows[0]) })
604
620
  }
605
621
  sync()
622
+ /**
623
+ * Re-measure now, then keep re-measuring for a bounded window.
624
+ *
625
+ * DSH 0.1.7 glides rows with the Web Animations API
626
+ * (`element.animate`): that fires neither `transitionrun` nor
627
+ * `transitionend` and mutates no DOM, so the one MutationObserver
628
+ * callback for a collapse lands on the animation's first frame — every
629
+ * row still painted at its old coordinates — and nothing would ever
630
+ * report the movement again. Following each mutation for the same
631
+ * bounded window as a transition keeps the chips on their rows.
632
+ */
633
+ const kick = () => { sync(); follow() }
606
634
  // Rows move with their scroll container and re-render on every list
607
- // change, so follow both.
635
+ // change, so follow both. A scroll reports its own frames continuously,
636
+ // so it needs no follow window of its own.
608
637
  document.addEventListener('scroll', sync, true)
609
- window.addEventListener('resize', sync)
638
+ window.addEventListener('resize', kick)
610
639
  // A CSS transition moves every row without any scroll, resize or DOM
611
640
  // mutation: the phone adapter slides the whole sidebar in with a
612
641
  // transform on the drawer while the rows themselves never change. Only
@@ -629,19 +658,33 @@ window.__ModuleLoader__.load({
629
658
  // the peer poll happened to re-render the layer ~20s later.
630
659
  const observers = []
631
660
  try {
632
- const rowsObserver = new MutationObserver(sync)
661
+ /**
662
+ * Whether a mutation could have changed the row list, and so started a
663
+ * row glide. The childList watch is body-wide because rows live deep
664
+ * inside the sidebar, so a mutation unrelated to the list — a
665
+ * streaming transcript, a tooltip — re-measures once but must not open
666
+ * a follow window for every frame it produces.
667
+ */
668
+ const rowsChanged = (records) => records.some((record) => {
669
+ const target = record.target
670
+ return target !== null && target !== undefined
671
+ && typeof target.closest === 'function'
672
+ && target.closest('[role="tree"]') !== null
673
+ })
674
+ const onRowsMutation = (records) => { if (rowsChanged(records)) kick(); else sync() }
675
+ const rowsObserver = new MutationObserver(onRowsMutation)
633
676
  rowsObserver.observe(document.body, { childList: true, subtree: true })
634
677
  observers.push(rowsObserver)
635
678
  // The phone adapter opens its drawer by toggling a class on `body`.
636
679
  // With transitions disabled the geometry still changes, just without a
637
680
  // transition event to follow.
638
- const drawerObserver = new MutationObserver(sync)
681
+ const drawerObserver = new MutationObserver(kick)
639
682
  drawerObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] })
640
683
  observers.push(drawerObserver)
641
684
  // A workspace collapse that hides rows instead of removing them
642
685
  // produces no childList mutation, so follow the ARIA state and
643
686
  // `hidden` as well.
644
- const stateObserver = new MutationObserver(sync)
687
+ const stateObserver = new MutationObserver(kick)
645
688
  stateObserver.observe(document.body, { attributes: true, attributeFilter: ['aria-expanded', 'hidden'], subtree: true })
646
689
  observers.push(stateObserver)
647
690
  } catch {
@@ -650,7 +693,7 @@ window.__ModuleLoader__.load({
650
693
  return () => {
651
694
  cancelFrame(frame)
652
695
  document.removeEventListener('scroll', sync, true)
653
- window.removeEventListener('resize', sync)
696
+ window.removeEventListener('resize', kick)
654
697
  document.removeEventListener('transitionrun', onTransition, true)
655
698
  document.removeEventListener('transitionend', onTransition, true)
656
699
  for (const watcher of observers) watcher.disconnect()