dsh-milestone 0.6.5 → 0.6.6

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.
Files changed (3) hide show
  1. package/README.md +56 -126
  2. package/lib/client.js +110 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,79 +44,49 @@ npx @deepseek-ai/dsh web # → http://127.0.0.1:3080
44
44
 
45
45
  > 要求 Node.js `>= 24`(harness 官方要求)。
46
46
 
47
- ## 功能总览
48
-
49
- 按类别分组,一屏扫完所有能力。
50
-
51
- | 类别 | 功能 · 一句话 | 键位 |
52
- | --- | --- | --- |
53
- | 定位导航 | 圆点时间线:每问一个圆点,点击平滑跳转 | |
54
- | 定位导航 | 当前位置高亮:视口最近的提问亮起白环 | |
55
- | 定位导航 | 加载更早:「···」继续加载历史,提示已显示条数 | |
56
- | 定位导航 | 全部提问列表:序号 + 轮次 + 预览一次看全,点击直达 | |
57
- | 定位导航 | 深链接:`#msg=` 锚点,刷新/分享后直达同一条 | |
58
- | 搜索过滤 | 站内搜索:匹配完整消息内容,实时 N/M 命中 | Enter 下一个 / Esc 清空 |
59
- | 搜索过滤 | 跨会话搜索:搜所有会话,点击打开对应会话 | |
60
- | 搜索过滤 | 收藏书签:悬停点星,★ 只看收藏 | |
61
- | 状态感知 | 轮次健康徽章:出错红 / 上限黄 / 重试橙 / 运行蓝 / 等待脉冲 | |
62
- | 状态感知 | 悬停元信息:时间 · 轮次 · 用时 · 结束原因 · TTFT · tok/s · 模型 · token 用量 | |
63
- | 个性化与效率 | 键盘导航:全程不用鼠标 | ↑↓ 移动 · Enter 跳转 · Home/End 首尾 |
64
- | 个性化与效率 | turn 分组折叠:按轮次成组,长轮次折成一条 | |
65
- | 个性化与效率 | 复制与 fork:一键复制提问全文 / 从此处分支 | |
66
- | 个性化与效率 | 聚焦模式:淡化 / 折叠思考与工具调用,强度可调、自由搭配 | |
67
- | 个性化与效率 | 折叠工具栏:功能键默认收起,箭头展开;设置菜单可把常用键钉到折叠外(持久化 + 恢复默认) | |
68
- | 个性化与效率 | 个性化外观:强调色 / 图标圆点大小 / 距侧边距离 / 位置左右(设置内即调即存) | |
69
- | 个性化与效率 | 语言切换:跟随系统 / 中文 / English,设置内一键切换 | |
70
- | 个性化与效率 | 点击外部关闭:搜索 / 列表等浮层自动收起 | |
71
- | 个性化与效率 | 新手教程:首次使用教练气泡引导,锚定真实组件(点击真实推进),设置内可重看 | |
72
- | 发布运维 | 更新检测:6h 缓存自动检查,新版徽章,一键去 npm | |
73
- | 发布运维 | 设置推广区:GitHub / Star / Issue / npm 四入口 | |
74
-
75
- 另:滚轮可在里程碑条上直接滑动选点;圆点的固定间距与蓝色渐变见下方「圆点时间线」一节。
76
-
77
- ## 核心功能详解
78
-
79
- ### 圆点时间线与悬停元信息
80
-
81
- - 每个提问一个圆点,点击平滑跳转;悬停即看内容与元信息。
82
- - 圆点等距排列、不随对话长度变形;颜色由浅入深标出先后,同 Git 提交图。
47
+ ## 功能详解
83
48
 
84
- ```
85
- ┌──────────────────────────────────────────┐
86
- 3 / 5 条 · 第 2 轮 ☆ 复制 ✂ │ ← 序号 + 轮次 + 收藏/复制/fork
87
- │ 帮我优化这段代码的性能 │ ← 消息预览(前 80 字)
88
- │ 5 分钟前 · 用时 1m30s · 首字 1.2s · 12.4 tok/s │ ← 时间 · 耗时 · TTFT · 吞吐
89
- │ v4 · continue · 1280 / 2560 tok │ ← 模型 · 用途 · token 用量
90
- └──────────────────────────────────────────┘
91
- ```
49
+ ### 圆点时间线
50
+
51
+ 每个提问一个圆点,点击平滑跳转;悬停即看内容与元信息。圆点等距排列、不随对话长度变形,颜色由浅入深标出先后,同 Git 提交图。滚轮可在里程碑条上直接滑动选点,视口内最近的提问亮起白环。
92
52
 
93
- 元信息全部来自 harness 原生 session 快照(`turnTimings` / `timeline.turns` / `turn-tail`),无额外依赖。
53
+ ### 全部提问列表
54
+
55
+ 一键打开面板,序号 + 轮次 + 预览一次看全,点击任意一条直接跳转。打开时会**自动加载整个会话**的历史,不用手动翻页——对藏在最早期的消息来说,比在小圆点上逐个找快得多。
94
56
 
95
57
  ### 站内搜索
96
58
 
97
- - 搜索框过滤圆点,匹配的是**完整消息内容**(不是 80 字摘要),实时显示命中数 N/M
98
- - `Enter` 跳到下一个匹配,`Esc` 一键清空。
99
- - 范围 = 当前已加载窗口;更早的历史先点「···」加载进来。
59
+ 搜索框过滤圆点,匹配的是**完整消息内容**(不是 80 字摘要),实时显示命中数 N/M。`Enter` 跳到下一个匹配,`Esc` 一键清空。
100
60
 
101
- ### 收藏书签
61
+ ### 跨会话搜索
102
62
 
103
- - 悬停圆点点星收藏,刷新后仍在(`store.persist`,按会话隔离)。
104
- - 顶部「★」一键只看收藏,把一次性跳转变反复回访。
63
+ 一键搜索**所有会话**的消息内容(harness 原生索引),点击命中结果直接打开对应会话。
105
64
 
106
- ### 深链接 `#msg=`
65
+ ### 收藏书签与深链接
107
66
 
108
- - 跳转时 URL 带上 `#msg=`,刷新或分享链接后仍回到同一条消息。
109
- - 目标在已加载窗口外时,自动先加载更早历史再定位(受加载上限约束)。
67
+ 悬停圆点点星收藏,刷新后仍在(按会话持久化),顶部「★」一键只看收藏。跳转时 URL 自动带上 `#msg=` 锚点,刷新或分享后仍回到同一条消息。
110
68
 
111
- ### 跨会话搜索
69
+ ### 悬停元信息
112
70
 
113
- - 一键搜索**所有会话**的消息内容(harness 原生索引)。
114
- - 点击结果直接打开对应会话;最多 20 条结果,片段 ≤240 字符。
71
+ 时间 · 轮次 · 用时 · 结束原因 · TTFT · tok/s · 模型 · 用途 · token 用量,一张卡片看全。数据全部来自 harness 原生会话快照,零额外依赖:
72
+
73
+ ```
74
+ ┌──────────────────────────────────────────┐
75
+ │ 第 3 / 5 条 · 第 2 轮 ☆ 复制 ✂ │ ← 序号 + 轮次 + 收藏/复制/fork
76
+ │ 帮我优化这段代码的性能 │ ← 消息预览(前 80 字)
77
+ │ 5 分钟前 · 用时 1m30s · 首字 1.2s · 12.4 tok/s │ ← 时间 · 耗时 · TTFT · 吞吐
78
+ │ v4 · continue · 1280 / 2560 tok │ ← 模型 · 用途 · token 用量
79
+ └──────────────────────────────────────────┘
80
+ ```
115
81
 
116
- ### 更新检测
82
+ ### 更多效率功能
117
83
 
118
- - 自动检查 npm 新版本,6 小时缓存,不打扰。
119
- - 发现新版:功能键亮起提醒徽章;弹窗展示当前/最新版本与**已适配版本线**;一键去 npm 升级。
84
+ - **键盘导航**:`↑↓` 移动 · `Enter` 跳转 · `Home/End` 首尾,全程不用鼠标。
85
+ - **turn 分组折叠**:长轮次折成一条,汇总圆点带可见 ×N 徽标,一眼知道藏着几条。
86
+ - **复制与 fork**:一键复制提问全文 / 从此处分支。
87
+ - **聚焦模式**:淡化 / 折叠思考与工具调用,强度可调、自由搭配。
88
+ - **折叠工具栏**:功能键默认收起,常用键可钉到折叠外;搜索 / 列表等浮层点击外部自动关闭。
89
+ - **个性化**:强调色 / 圆点大小 / 距侧边距离 / 左右位置即调即存;中文 / English 一键切换。
120
90
 
121
91
  ## 工作原理
122
92
 
@@ -129,101 +99,61 @@ shell.overlay (root scope)
129
99
  ```
130
100
 
131
101
  - **注入点**:`shell.overlay` 全框架浮动层,附加式、点击穿透,不碰现有 UI。
132
- - **数据源**:`chat.order` + `chat.nodes`(user 消息与 `turn-error`/`turn-max-tokens`/`model-retry` 节点)+ `chat.timeline`(turn 元数据)+ `hasMore`/`loadingOlder`(分页)+ `running`/`pending`(徽章)+ `loadOlder`(inject face)。
133
- - **跳转**:DOM 锚点 `data-chat-anchor-key`,`scrollIntoView` 平滑定位。
134
- - **持久化**:`store.persist`(每会话 localStorage,key `dsh-milestone.bookmarks.<sessionId>`),经 `defineStore` 引擎读写;工具栏偏好同理。
135
- - **纯函数分层**:搜索过滤 / 位置计算 / 圆点状态都在 `rail-logic.ts` 纯函数里,单测覆盖。
102
+ - **数据源**:harness 原生会话快照(消息列表、turn 元数据、分页与运行状态),无自建抓取。
103
+ - **跳转**:以消息锚点做 DOM 定位,`scrollIntoView` 平滑滚动。
104
+ - **分页**:顶部「···」按需加载更早历史;全部提问列表打开时自动加载整个会话。
105
+ - **持久化**:书签与工具栏偏好经 `store.persist` 按会话写入 localStorage。
106
+ - **纯函数分层**:过滤、位置计算、圆点状态集中在 `rail-logic.ts` 纯函数层,单测覆盖。
136
107
 
137
108
  ## 版本与兼容
138
109
 
139
110
  - 当前官方支持线:**`0.1.1-rc.2`**(peer/dev 依赖 `^0.1.1-rc.2`,与 `@deepseek-ai/dsh` 最新 `latest` 标签一致)。
140
111
  - 官方客户端包(`dsh-client-runtime` 等)在 npm 上走 `next` 标签发布(`latest` 标签仍是远古版本);升级 harness 后若发现插件不匹配,请确认安装的依赖解析到了 `0.1.1-rc.2` 线。
141
- - 更新检测弹窗内展示本插件声明支持的版本线;harness 当前版本在浏览器端没有可信来源(`host.describe().version` 是占位值),因此不做精确探测,以声明线为准。
112
+ - harness 当前版本在浏览器端没有可信来源(`host.describe().version` 是占位值),因此不做精确探测,以插件声明的支持线为准。
142
113
 
143
114
  ## 已知限制
144
115
 
145
- > ⚠️ **最需要注意**:搜索只覆盖**当前已加载**的消息窗口(初始 50 条)——更早的历史需先点顶部「···」加载进来,才能被搜到。
116
+ > ⚠️ **最需要注意**:站内搜索只覆盖**当前已加载**的消息窗口(初始 50 条),更早的历史需先点顶部「···」加载进来才能被搜到。「全部提问」列表不受此限:打开时会自动加载整个会话,始终一次看全。
146
117
 
147
118
  <details>
148
119
  <summary>查看更多已知限制(点击展开)</summary>
149
120
 
150
- - TTFT / tokens/秒 依赖 turn 位置数据,窗口外或未完成的 turn 不显示(自动隐藏)。
151
- - 徽章的瞬态状态(运行中/等待输入)只点亮**最新一条可见提问**——若该轮次的提问在窗口外,则无脉冲。
152
- - 书签按**会话**隔离(不跨会话共享)。
153
- - 模型 / token 用量依赖该轮 assistant 节点的元数据,部分场景下缺失则自动隐藏该行。
121
+ - TTFT / tok/s 依赖 turn 位置数据,窗口外或未完成的 turn 不显示(自动隐藏)。
122
+ - 徽章的瞬态状态(运行中 / 等待输入)只点亮最新一条可见提问;该提问在窗口外时无脉冲。
123
+ - 书签按会话隔离,不跨会话共享。
154
124
  - fork 从选中消息所在轮次开始分支,不会自动打开子会话(需在会话列表手动打开)。
155
- - 深链接的目标消息若早于已加载窗口,会先自动加载更早历史再定位(受加载上限约束,极端深的历史可能定位失败)。
156
- - 跨会话搜索依赖 harness 的消息内容索引(`session.search`),仅返回片段(≤240 字符)、最多 20 条结果;命中过多时请细化关键词。
157
- - 聚焦模式作用于当前会话视图的思考区块,不影响其他插件或工具的展示。
158
- - 尚无全局快捷键聚焦里程碑条(需 Tab 键切换到)。
159
- - 功能键**默认折叠**,首次使用需点箭头展开;可在设置菜单里把常用键钉到折叠外。
160
- - 更新检测依赖浏览器能访问 npm 镜像(`registry.npmmirror.com` / `registry.npmjs.org`);若页面 CSP 限制 `connect-src` 或离线,检查会静默失败。结果缓存 6 小时。
161
- - 工具栏固定偏好存于浏览器 localStorage(不跨浏览器/设备同步)。
125
+ - 深链接目标若早于已加载窗口,会先自动加载更早历史再定位;受加载上限约束,极端深的历史可能定位失败。
126
+ - 跨会话搜索仅返回片段(≤240 字符)、最多 20 条结果,命中过多时请细化关键词。
162
127
 
163
128
  </details>
164
129
 
165
130
  ## 更新日志
166
131
 
167
132
  <details>
168
- <summary>v0.6.5 / v0.6.4 / v0.6.3 / v0.6.2 / v0.6.1 / v0.6.0(点击展开)</summary>
169
-
170
- **v0.6.5** · 真实组件教练教程 + 设置对比度修复 · 386 项测试
171
-
172
- - **新手教程改为教练气泡(coach-mark)**:气泡锚定真实组件——欢迎 → 点展开箭头(真实点击自动推进)→ 点设置齿轮(真实打开自动推进,设置开着气泡挂起)→ 圆点 → 完成;目标高亮环 + 就近气泡 + 进度点 + 随时跳过,全部双语。
173
- - **设置模态白色背景修复**:UA 默认按钮底色漏出导致浅底浅字不可读,基础态补透明规则(Playwright 计算色实测验证)。
174
-
175
- > [GitHub Release v0.6.5](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.5)
176
-
177
- **v0.6.4** · 新手教程 · 385 项测试
178
-
179
- - **首次使用引导**:第一次安装后进入首个可用会话时弹出教程(全局持久化,**印象即写**——欢迎页一显示即记录,关页/刷新不重弹;想再看走设置里的「重新查看教程」)。
180
- - 4 步教学 + 内置演示:迷你圆点条 hover/点击、玩具搜索与收藏、个性化与设置图例、更新检测与支持;进度点 + 上一步/下一步 + 随时跳过,全部双语。
181
- - 设置与教程模态视觉共用 `modal-tokens.ts` 单一来源。
133
+ <summary>v0.6.6 / v0.6.5 / v0.6.4(点击展开)</summary>
182
134
 
183
- > [GitHub Release v0.6.4](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.4)
184
-
185
- **v0.6.3** · 聚焦高级设置 + 设置模态重设计 · 363 项测试
186
-
187
- - **聚焦高级设置**:设置内「聚焦」可展开——自定义淡化/折叠 think 推理区与工具调用卡片,淡化强度 20%–80% 可调(稳定选择器 `[data-variant="think"]` / `[data-chat-call-id]`),搭配持久化。
188
- - **设置模态重设计**:功能悬停描述改为**就近 tip**(行旁悬浮,不再有大块常驻描述区);恢复表头说明(「开启后,折叠时仍显示在箭头旁」);个性化收进**可展开块**(默认折叠,头部实时摘要);间距/圆角/强调色统一,修复行 hover 洗色失效。
189
- - README 图片改绝对 URL(npm 渲染器不重写相对路径),npm 页 logo/演示图恢复正常。
190
-
191
- > [GitHub Release v0.6.3](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.3)
192
-
193
- **v0.6.2** · 设置模态化 + 个性化 + 语言切换 · 346 项测试
135
+ **v0.6.6** · 全部提问列表自动加载整个会话 · 折叠圆点 ×N 徽标 · 轮次连续显示 · 397 项测试
194
136
 
195
- - 设置改为**居中模态框**:功能行悬停显示说明、按类别分区。
196
- - **个性化模块**:强调色(预设 + 自定义)、图标 / 圆点大小、距侧边距离、位置左或右(浮层自动换侧),全部即调即存。
197
- - **语言切换**:跟随系统 / 中文 / English(zh/en 词典完整,缺译即编译错)。
198
- - 设置按钮进入展开队列(折叠态更干净);底部推广区改 2×2 卡片网格。
199
- - 圆点 turn 分隔线移除(改组间距表达);状态徽章改为柔边光晕。
200
- - npm 包纳入 `assets/*.svg`(npm 页 README 图片恢复显示)。
137
+ - **全部提问列表一次看全**:打开时自动加载整个会话的历史,不再受初始加载窗口限制,点击任意一条直达。
138
+ - **折叠轮次不藏消息**:汇总圆点带可见 ×N 计数徽标,悬停气泡显示覆盖的条数区间(第 a–b / m 条)。
139
+ - **轮次编号连续化**:显示轮号按圆点顺序重编号(1、2、3…),不再跳空或重复;分组与折叠逻辑不变。
140
+ - **列表点击外部自动关闭**:与搜索、跨会话搜索浮层同一套外部点击关闭契约。
141
+ - README 重写为产品宣传结构:演示图第一屏,功能介绍分组清晰。
201
142
 
202
- > [GitHub Release v0.6.2](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.2)
143
+ > [GitHub Release v0.6.6](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.6)
203
144
 
204
- **v0.6.1** · 对齐 `0.1.1-rc.2` 支持线 · 五项新功能 · 292 项测试
145
+ **v0.6.5** · 新手教程改为锚定真实组件的教练气泡引导,设置模态对比度修复 · 400 项测试
205
146
 
206
- - 折叠工具栏:功能键默认收起,箭头展开。
207
- - 设置菜单:常用功能「在折叠外显示」固定自定义;偏好持久化,一键恢复默认。
208
- - 设置底部推广区:GitHub / Star / Issue / npm 四入口。
209
- - 更新检测:6h 缓存自动检查、新版徽章、当前/最新版本与已适配版本线、一键去 npm。
210
- - 点击外部自动关闭各浮层(与官方 capture 阶段 pointerdown 契约一致)。
211
-
212
- > [GitHub Release v0.6.1](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.1)
147
+ > [GitHub Release v0.6.5](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.5)
213
148
 
214
- **v0.6.0** · P3 功能
149
+ **v0.6.4** · 首次使用引导:4 步双语教学 + 内置演示,印象即写、关页不重弹
215
150
 
216
- - 聚焦模式:淡化思考(thinking)区块,悬停或展开自动恢复。
217
- - 全部提问列表面板:序号 + 轮次 + 预览一次看全,点击即跳。
218
- - 深链接:`#msg=` 锚点,刷新或分享后直达同一条消息。
219
- - 跨会话搜索:搜索所有会话的消息内容,点击结果打开对应会话。
151
+ > [GitHub Release v0.6.4](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.4)
220
152
 
221
- > [GitHub Release v0.6.0](https://github.com/SnowCrescenter-tech/dsh-milestone/releases/tag/v0.6.0)
153
+ 更早版本见 [GitHub Releases](https://github.com/SnowCrescenter-tech/dsh-milestone/releases)
222
154
 
223
155
  </details>
224
156
 
225
- 维护者:发布清单见 [RELEASING.md](./RELEASING.md)。
226
-
227
157
  ## License
228
158
 
229
159
  [MIT](./LICENSE)
@@ -231,5 +161,5 @@ shell.overlay (root scope)
231
161
  ---
232
162
 
233
163
  <p align="center">
234
- 觉得好用?给个 star 支持一下,或把它推荐给正在 DeepSeek Harness 里挣扎的开发者吧。
164
+ 如果它帮你省下了一次翻找,欢迎给个 Star,或推荐给同样在 DeepSeek Harness 里长聊的开发者。
235
165
  </p>
package/lib/client.js CHANGED
@@ -598,6 +598,9 @@ window.__ModuleLoader__.load({
598
598
  "turn.label": "第 {n} 轮",
599
599
  /** Hover position: `第 {hover.index + 1} / {hover.total} 条`. */
600
600
  "pos.of": "第 {n} / {m} 条",
601
+ /** Hover position of a collapsed-turn summary dot: `第 {a}–{b} / {m} 条`
602
+ * (the range of messages the summary dot represents). */
603
+ "pos.range": "第 {a}–{b} / {m} 条",
601
604
  /** Search input placeholder. */
602
605
  "search.placeholder": "搜索消息内容",
603
606
  /** aria-label on the search toggle button and the search input. */
@@ -658,6 +661,8 @@ window.__ModuleLoader__.load({
658
661
  "list.close": "收起列表",
659
662
  /** Header title of the all-prompts list panel. */
660
663
  "list.label": "全部提问",
664
+ /** Bottom hint of the all-prompts list while it drains older pages. */
665
+ "list.loading": "正在加载更早消息…",
661
666
  /** Header title + input placeholder of the cross-session search panel. */
662
667
  "search.cross": "跨会话搜索",
663
668
  /** aria-label + title on the cross-session search toggle when the panel is CLOSED. */
@@ -836,6 +841,8 @@ window.__ModuleLoader__.load({
836
841
  "window.hint": "Showing {n} messages · more below",
837
842
  "turn.label": "Turn {n}",
838
843
  "pos.of": "Message {n} of {m}",
844
+ /** Collapsed-summary dot position: the message RANGE the dot represents. */
845
+ "pos.range": "Messages {a}–{b} of {m}",
839
846
  "search.placeholder": "Search message content",
840
847
  "search.label": "Search messages",
841
848
  "bookmark.filter": "Bookmarks only",
@@ -866,6 +873,8 @@ window.__ModuleLoader__.load({
866
873
  "list.open": "Open list",
867
874
  "list.close": "Close list",
868
875
  "list.label": "All prompts",
876
+ /** Bottom hint of the all-prompts list while it drains older pages. */
877
+ "list.loading": "Loading earlier messages…",
869
878
  "search.cross": "Cross-session search",
870
879
  "search.cross.open": "Open cross-session search",
871
880
  "search.cross.close": "Close cross-session search",
@@ -974,6 +983,31 @@ window.__ModuleLoader__.load({
974
983
  //#endregion
975
984
  //#region src/client/turn-group-logic.ts
976
985
  /**
986
+ * Renumber the raw harness turn numbers into a compact 1-based DISPLAY
987
+ * sequence over the marks that actually render. The harness numbers every
988
+ * engine turn (subagent/injected turns included), so the raw numbers show
989
+ * gaps (turns that produced no user mark) and repeats (several marks sharing
990
+ * one turn); labels fed through this map read as clean 1, 2, 3, … rounds.
991
+ *
992
+ * Grouping and collapse logic keep operating on the RAW turn (same-turn marks
993
+ * are contiguous in rail order, so first-appearance ranking preserves the
994
+ * partition) — only labels consume this map.
995
+ *
996
+ * @param marks - marks in rail order (only `turn` is consulted).
997
+ * @returns raw turn -> display round (1-based), in first-appearance order;
998
+ * turns that never appear (or marks without turn info) get no entry.
999
+ */
1000
+ function buildDisplayTurns(marks) {
1001
+ const display = /* @__PURE__ */ new Map();
1002
+ let rank = 0;
1003
+ for (const mark of marks) {
1004
+ if (mark.turn === void 0) continue;
1005
+ if (display.has(mark.turn)) continue;
1006
+ display.set(mark.turn, ++rank);
1007
+ }
1008
+ return display;
1009
+ }
1010
+ /**
977
1011
  * Partition consecutive marks by turn. Marks with the same numeric turn that
978
1012
  * appear one after another share a group; each mark with `turn === undefined`
979
1013
  * becomes its own singleton group with `turn: null`.
@@ -1286,9 +1320,10 @@ window.__ModuleLoader__.load({
1286
1320
  /**
1287
1321
  * @param props - the panel anchor, the full marks array, and the rail's jump handler.
1288
1322
  */
1289
- function MilestoneListPanel({ panelTop, panelRight, marks, onJump, onClose, t }) {
1323
+ function MilestoneListPanel({ panelTop, panelRight, marks, onJump, onClose, loading = false, t }) {
1290
1324
  const panelRef = (0, react.useRef)(null);
1291
1325
  useOutsideDismiss(panelRef, true, onClose, { exclude: (target) => outsideDismissMatches(target, "[data-list-toggle]") });
1326
+ const displayTurns = (0, react.useMemo)(() => buildDisplayTurns(marks), [marks]);
1292
1327
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1293
1328
  ref: panelRef,
1294
1329
  "data-milestone-list": true,
@@ -1328,7 +1363,7 @@ window.__ModuleLoader__.load({
1328
1363
  children: marks.length
1329
1364
  })]
1330
1365
  }),
1331
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1366
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1332
1367
  style: {
1333
1368
  maxHeight: 300,
1334
1369
  overflowY: "auto",
@@ -1336,7 +1371,7 @@ window.__ModuleLoader__.load({
1336
1371
  flexDirection: "column",
1337
1372
  gap: 2
1338
1373
  },
1339
- children: marks.map((mark, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1374
+ children: [marks.map((mark, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1340
1375
  type: "button",
1341
1376
  "data-list-item": true,
1342
1377
  "data-jump-key": mark.key,
@@ -1363,7 +1398,7 @@ window.__ModuleLoader__.load({
1363
1398
  children: [t("pos.of", {
1364
1399
  n: i + 1,
1365
1400
  m: marks.length
1366
- }), mark.turn !== void 0 ? ` · ${t("turn.label", { n: mark.turn })}` : null]
1401
+ }), mark.turn !== void 0 ? ` · ${t("turn.label", { n: displayTurns.get(mark.turn) ?? mark.turn })}` : null]
1367
1402
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1368
1403
  style: {
1369
1404
  fontSize: 13,
@@ -1374,7 +1409,16 @@ window.__ModuleLoader__.load({
1374
1409
  },
1375
1410
  children: mark.preview || t("no.text")
1376
1411
  })]
1377
- }, mark.key))
1412
+ }, mark.key)), loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1413
+ "data-list-loading": true,
1414
+ style: {
1415
+ fontSize: 12,
1416
+ color: "#8b96ab",
1417
+ textAlign: "center",
1418
+ padding: "6px 8px"
1419
+ },
1420
+ children: t("list.loading")
1421
+ })]
1378
1422
  })
1379
1423
  ]
1380
1424
  });
@@ -1831,10 +1875,7 @@ window.__ModuleLoader__.load({
1831
1875
  marginBottom: 4
1832
1876
  },
1833
1877
  children: [
1834
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("pos.of", {
1835
- n: hover.index + 1,
1836
- m: hover.total
1837
- }) }),
1878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hover.posLabel }),
1838
1879
  hover.turnLabel !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hover.turnLabel }),
1839
1880
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1840
1881
  type: "button",
@@ -2738,7 +2779,7 @@ window.__ModuleLoader__.load({
2738
2779
  * Installed plugin version. Injected at build time as
2739
2780
  * `__DSH_MILESTONE_VERSION__`; falls back to `0.0.0-dev` when unbuilt.
2740
2781
  */
2741
- const PLUGIN_VERSION = "0.6.5";
2782
+ const PLUGIN_VERSION = "0.6.6";
2742
2783
  //#endregion
2743
2784
  //#region src/client/MilestoneRail.tsx
2744
2785
  /**
@@ -3118,6 +3159,8 @@ window.__ModuleLoader__.load({
3118
3159
  const [bookmarksOnly, setBookmarksOnly] = (0, react.useState)(false);
3119
3160
  const [focusActive, setFocusActive] = (0, react.useState)(false);
3120
3161
  const [listOpen, setListOpen] = (0, react.useState)(false);
3162
+ const [drainPage, setDrainPage] = (0, react.useState)(false);
3163
+ const [drainFailed, setDrainFailed] = (0, react.useState)(false);
3121
3164
  const [crossOpen, setCrossOpen] = (0, react.useState)(false);
3122
3165
  const [copiedKey, setCopiedKey] = (0, react.useState)(null);
3123
3166
  const [forkedKey, setForkedKey] = (0, react.useState)(null);
@@ -3170,6 +3213,7 @@ window.__ModuleLoader__.load({
3170
3213
  }
3171
3214
  return counts;
3172
3215
  }, [displayMarks]);
3216
+ const displayTurns = (0, react.useMemo)(() => buildDisplayTurns(marks), [marks]);
3173
3217
  const [prefs, setPrefs] = (0, react.useState)(() => loadPrefs());
3174
3218
  const { pinned, accent, iconSize, inset, side } = prefs;
3175
3219
  const scale = iconSize / DOT_HIT;
@@ -3441,6 +3485,22 @@ window.__ModuleLoader__.load({
3441
3485
  window.addEventListener("keydown", onKey);
3442
3486
  return () => window.removeEventListener("keydown", onKey);
3443
3487
  }, [listOpen, crossOpen]);
3488
+ const listDraining = listOpen && (drainPage || hasMore && !drainFailed);
3489
+ (0, react.useEffect)(() => {
3490
+ if (!listOpen) return;
3491
+ setDrainFailed(false);
3492
+ }, [listOpen]);
3493
+ (0, react.useEffect)(() => {
3494
+ if (!listOpen || !hasMore || drainPage || drainFailed) return;
3495
+ setDrainPage(true);
3496
+ loadOlder().catch(() => setDrainFailed(true)).finally(() => setDrainPage(false));
3497
+ }, [
3498
+ listOpen,
3499
+ hasMore,
3500
+ drainPage,
3501
+ drainFailed,
3502
+ loadOlder
3503
+ ]);
3444
3504
  if (railBox === null || marks.length < MIN_MARKS) return null;
3445
3505
  const updateQuery = (query) => {
3446
3506
  setSearch({
@@ -3819,11 +3879,20 @@ window.__ModuleLoader__.load({
3819
3879
  }
3820
3880
  }
3821
3881
  const meta = deriveTurnMeta(nodes, locations, mark.turn, trajectoryRequests);
3882
+ const summaryCount = collapsedSummaries.get(mark.key);
3822
3883
  return {
3823
3884
  mark,
3824
3885
  index,
3825
3886
  total: displayMarks.length,
3826
- turnLabel: mark.turn !== void 0 ? t("turn.label", { n: mark.turn }) : null,
3887
+ posLabel: summaryCount !== void 0 ? t("pos.range", {
3888
+ a: index - summaryCount + 2,
3889
+ b: index + 1,
3890
+ m: displayMarks.length
3891
+ }) : t("pos.of", {
3892
+ n: index + 1,
3893
+ m: displayMarks.length
3894
+ }),
3895
+ turnLabel: mark.turn !== void 0 ? t("turn.label", { n: displayTurns.get(mark.turn) ?? mark.turn }) : null,
3827
3896
  durationLabel,
3828
3897
  reasonLabel,
3829
3898
  ttftLabel,
@@ -4838,7 +4907,7 @@ window.__ModuleLoader__.load({
4838
4907
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4839
4908
  style: { color: "#8b96ab" },
4840
4909
  children: [t("update.current"), ": "]
4841
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "0.6.5" })] }),
4910
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "0.6.6" })] }),
4842
4911
  updateCheck.phase === "ok" && updateCheck.latest !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4843
4912
  "data-update-latest": true,
4844
4913
  children: [
@@ -4943,6 +5012,8 @@ window.__ModuleLoader__.load({
4943
5012
  panelRight: panelRightFor(PANEL_WIDTH_STANDARD),
4944
5013
  marks,
4945
5014
  onJump: jump,
5015
+ onClose: () => setListOpen(false),
5016
+ loading: listDraining,
4946
5017
  t
4947
5018
  }),
4948
5019
  crossOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneSessionSearch, {
@@ -5026,7 +5097,7 @@ window.__ModuleLoader__.load({
5026
5097
  "aria-current": dotState === "active" ? "true" : void 0,
5027
5098
  "data-current": dotState === "current" ? "true" : void 0,
5028
5099
  "data-dimmed": dotState === "dimmed" ? "true" : void 0,
5029
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5100
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5030
5101
  style: {
5031
5102
  position: "relative",
5032
5103
  width: size,
@@ -5039,7 +5110,7 @@ window.__ModuleLoader__.load({
5039
5110
  opacity: isHovered || dotState !== "dimmed" ? 1 : .22
5040
5111
  },
5041
5112
  "data-bookmarked": bookmarked ? "true" : void 0,
5042
- children: ringStyle !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5113
+ children: [ringStyle !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5043
5114
  "data-badge": badge,
5044
5115
  style: {
5045
5116
  position: "absolute",
@@ -5050,7 +5121,31 @@ window.__ModuleLoader__.load({
5050
5121
  pointerEvents: "none",
5051
5122
  animation: ringStyle.pulse ? "milestone-badge-pulse 2s ease-in-out infinite" : void 0
5052
5123
  }
5053
- })
5124
+ }), summaryCount !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5125
+ "data-collapsed-badge": true,
5126
+ style: {
5127
+ position: "absolute",
5128
+ top: -5,
5129
+ right: -7,
5130
+ minWidth: 15,
5131
+ height: 15,
5132
+ padding: "0 3px",
5133
+ display: "flex",
5134
+ alignItems: "center",
5135
+ justifyContent: "center",
5136
+ borderRadius: 8,
5137
+ background: "rgba(20, 24, 32, 0.96)",
5138
+ border: `1px solid ${accentSoft}`,
5139
+ boxSizing: "border-box",
5140
+ color: "#e6e8ee",
5141
+ fontSize: 9,
5142
+ lineHeight: 1,
5143
+ fontWeight: 600,
5144
+ whiteSpace: "nowrap",
5145
+ pointerEvents: "none"
5146
+ },
5147
+ children: ["×", summaryCount]
5148
+ })]
5054
5149
  })
5055
5150
  }) }, mark.key);
5056
5151
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-milestone",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Git-style milestone timeline for DeepSeek Harness: hover for metadata, click to jump to any message. 会话里程碑导航条:圆点时间线,定位并跳转到每条提问。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",