# 思磨力轮次胶囊条（Smoothly Turn Nav）

**思磨力 · Smoothly** — 品牌 · 英文名：**Smoothly Turn Nav**（简称 **Smoothly TN**）

**[English](README.md) · 简体中文**

**整场会话，一眼纵览。**

思磨力轮次胶囊条（**Smoothly Turn Nav**，简称 **Smoothly TN**）是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)（dsh）的外部插件，在每条会话右侧放一条**钢琴键式轮次胶囊条**——竖向一列小胶囊，一轮一个。它是整场会话的迷你地图：**全部历史轮次一目了然**（不只是当前已加载的窗口），悬停预览、点击跳转到任意轮次起点、滚动跟随高亮。它还可以**取代官方内置轮次胶囊条**（官方没有自己的关闭开关）。

![轮次导航胶囊条](docs/turn-nav-rail.png)

## 为什么需要

默认 DSH Web UI 的官方轮次胶囊条**历史上**只显示**当前已加载窗口内**的轮次——长会话里，大部分轮次要滚动加载后才可见。从 dsh **0.1.3-alpha.1** 起，官方胶囊条也通过宿主侧 `turnOutline` 投影补上了紧要的全会话范围与窗口外跳转，但它依然**无法关闭**。思磨力轮次胶囊条解决长会话问题，并且始终可替换：

- **全量历史一眼可见**——所有持久化轮次以纯数据呈现，包括远在已加载窗口之外的轮次。不滚动、不加载、不等待。
- **悬停即预览**——胶囊以主题色亮起并泛起波浪涟漪；DSH 风格 Tooltip 展示该轮序号、时间与用户消息摘要。
- **点击跳转任意轮**——包括尚未加载的轮次（按需扩展窗口，带即时反馈）。
- **随时知道自己在哪**——滚动时当前轮次高亮。

## 特性

| | |
|---|---|
| 🗺️ **全量历史迷你地图** | 所有轮次立即可见——从持久化会话日志以数据读取，**打开会话零 prepend**（长会话保持流畅） |
| 🎹 **钢琴键设计** | 每轮一个约 3px 高的胶囊，右缘右对齐；长度自适应（上限 30vh），超出后内部滚动（滚动条隐藏） |
| 🌊 **波浪悬停** | 悬停的胶囊以主题色亮起并向左加宽 150%，相邻两个加宽 125%——滑过时如波浪起伏 |
| 💬 **丰富 Tooltip** | 轮次**序号、时间戳、完整用户消息摘要**，始终完整显示在视口内 |
| 🎯 **跳转任意轮** | 精确 `scrollTop` 定位（不与 `scrollIntoView` 打架）；窗口外跳转按需扩展窗口并显示"正在定位第 N 轮…"脉冲+气泡；最老轮次跳转加载到**真正第一轮**（`hasMore = false`） |
| 👁️ **滚动跟随高亮** | 阅读线所在轮次的胶囊随滚动点亮——且胶囊条自身视口会跟随当前轮次保持可见 |
| ⬆️⬇️ **滚动按钮** | 点击或悬停持续滚动；无可滚动内容时置灰 |
| 🎛️ **胶囊条模式开关** | 设置 → 通用 → *轮次导航*：`DSH 官方` / `思磨力轮次胶囊条`（默认）/ `全部隐藏`——跨刷新持久保存，官方胶囊条终于可以**关掉** |
| 🔌 **纯外部插件** | 不改 DSH 源码；零宿主改动；零新增依赖；仅只读 DOM |

## 与官方 DSH 轮次胶囊条对比

官方内置 `TurnNavigator` **没有关闭开关**，始终渲染在会话视图里。下表对比对象为 **dsh 0.1.3-alpha.1**（当前 DSH Web 的 `TurnNavigator`，该版也通过自己的宿主投影恢复了全会话范围）与本插件 **思磨力轮次胶囊条 v0.4.3**：

| 能力 | DSH 官方胶囊条（0.1.3-alpha.1） | 思磨力轮次胶囊条（v0.4.3） |
|---|---|---|
| 显示的轮次 | **全部轮次**——宿主 `turnOutline` 投影（0.1.3+） | 全部持久化轮次——**客户端**读取 journal |
| 全会话历史怎么读 | 宿主侧投影内嵌在会话快照里 | 客户端分页读持久化日志（`session/page`）；旧版 dsh 退回 `sessions.history` RPC——**零宿主改动** |
| 跳转窗口外轮次 | ✅（0.1.3+ 未加载锚点按 seq 分页） | ✅ 按需扩展窗口 + "正在定位第 N 轮…"脉冲/气泡 |
| 长会话打开性能 | 读投影 | **零 prepend**——纯数据、不重渲染会话流、不卡顿 |
| 滚动跟随高亮 | ✅（0.1.3+ 让 active mark 保持在胶囊条视口内，带指针守卫） | ✅（v0.4.1+ 同款指针守卫跟随） |
| 悬停预览 | prompt + response（各 ≤3 行），无时间戳 | 轮次号 + **时间戳** + 完整用户消息摘要 |
| 波浪涟漪动画 | ❌（改为固定节距 tick 加宽） | ✅ 波浪涟漪 |
| 滚动按钮（点击 / 悬停持续） | ❌（滚轮 + 渐隐） | ✅ 点击 / 悬停持续 |
| 胶囊条高度 | 动态 band（自然高…420px） | 自适应（≤30vh）+ 内部隐藏滚动条 |
| 窄窗口（<900px） | 自动隐藏 | 自动隐藏（与官方对齐） |
| **隐藏 / 切换胶囊条** | ❌ 无开关 | ✅ 设置 → 通用 → 三档；`全部隐藏` 两者皆隐 |
| 键盘可达 | ✅ 焦点环 + `aria-current`/`aria-busy`/`aria-describedby` | ✅ 可聚焦按钮（aria-label=`Turn N — 时间 — 摘要`） |
| 来源 | 内置、无法关闭 | 外部插件，**可替换 / 可关闭** |

dsh 0.1.3 起官方胶囊条已在紧要的全会话范围与窗口外跳转上追平。**思磨力轮次胶囊条仍然独占的**：可以**关掉它**（官方关不掉）、Tooltip 带**时间戳与完整摘要**、**滚动按钮 + 波浪悬停**、以及它始终是**零宿主改动的外部只读插件**。在 dsh ≤ 0.1.2 上官方胶囊条更简单（仅加载窗口），思磨力轮次胶囊条填补的差距更大。

## 版本对照

我们的版本与 dsh 版本的对应关系：

| 思磨力轮次胶囊条 | dsh | 说明 |
|---|---|---|
| v0.1.x | dsh ≤ 0.1.1 | 通过旧版 `sessions.history` 浏览器→宿主 RPC 读全量历史 |
| v0.2.x – v0.4.1 | dsh 0.1.2+ | 适配 `ui-chat` 重构；通过 journal `session/page` 通道读全量历史；v0.4.1 修复真实轮次号与胶囊条视口跟随 |
| **v0.4.2** | dsh 0.1.2+（含 **0.1.3-alpha.1**） | 对照 dsh 0.1.3 官方胶囊条更新对比与定位（见上文） |
| **v0.4.3** | dsh 0.1.2+（含 **0.1.3-alpha.1**） | 本版：品牌命名规范化为 **Smoothly**（思磨力）/ **Smoothly Turn Nav**（**Smoothly TN**）/ **思磨力轮次胶囊条**——技术标识符（npm 包名 `dsh-turn-navigator`、插件/slot ID、locale 命名空间、CSS 前缀、localStorage key）不变 |

本文 README 的对比对象为 **dsh 0.1.3-alpha.1**；在更旧的 dsh 上官方胶囊条更简单，思磨力轮次胶囊条的优势更大。

## 安装

```sh
dsh plugin --profile web add dsh-turn-navigator
```

然后重启 `dsh web`：

```sh
dsh web
```

## 使用

0. **选择显示哪个胶囊条**（设置 → 通用 → **轮次导航**）：`DSH 官方`（内置 rail）、`思磨力轮次胶囊条`（本插件 rail——**默认**）、或`全部隐藏`。官方 rail 没有关闭开关，选择思磨力轮次胶囊条时以样式覆盖将其隐藏，我们的 rail 接管右缘居中位置。选择会跨刷新持久保存。
1. 打开任意包含至少一轮已完成轮次的会话。
2. 会话右侧出现一条竖向灰色胶囊列（每轮一个）。胶囊条**长度自适应**：轮次少则短，轮次多则达 30vh 上限后内部滚动（滚动条隐藏，无布局抖动）。
3. **悬停**某个胶囊：它以主题色亮起并泛起波浪、向左加宽，胶囊条左侧弹出 Tooltip（**序号、时间、摘要**），始终完整在视口内。
4. **点击**某个胶囊：会话滚动到该轮起点并短暂高亮目标行；窗口外轮次会先脉冲闪烁 + 弹出"正在定位第 N 轮…"气泡，按需扩展窗口后定位。被点击的胶囊自动滚动到胶囊条中央（首尾两条除外）。
5. **滚动**：滚轮、上下按钮、或按住按钮持续滚动。

## 原理

插件注册**两个加法 slot**——**不修改 DSH 源码**：

| Slot | 作用域 | 职责 |
|------|--------|------|
| `conversation.session.header.utilities` | session | 悬浮轮次胶囊条（`position: fixed`；通过框架 `useChat`/`useSession` kit 读取实时会话快照） |
| `settings.general.item` | root | 设置 → 通用 的 *轮次导航* 模式开关 |

- **全量历史即数据**：dsh 0.1.2+ 上，胶囊条通过 Typert Remote `session/page` 通道分页读取与官方窗口同一条持久化日志（`ctx.remote.session`——由基础 web 装配挂载；**零宿主改动、零新增依赖**）；旧版 dsh 走 `sessions.history` RPC。每轮从 `turn/start` / `user/message` / `turn/end` 原始事件派生为纯数据。
- **按需跳转**：点击窗口内轮次直接滚动；窗口外轮次通过官方会话 store 的 `loadOlder()` 逐页扩展窗口（以权威 `hasMore` 为终止条件）直到目标进入视图——这是唯一触碰会话流的路径，且只在点击时发生。
- **精确定位**：取该轮第一个 chat-node key，通过 `data-chat-anchor-key` 找到 DOM 行，直接设置滚动容器 `scrollTop`（比 `scrollIntoView` 更可控）。
- **取代官方胶囊条**：官方 rail 位于会话滚动容器内；模式开关驱动的容器限定样式规则将其隐藏，我们的 rail 接管右缘居中位置。已在结构层面核对 dsh 0.1.3-alpha.1——`[data-conversation-scroll] nav` 隐藏规则依然匹配。若未来 dsh 改变该结构，最坏情况是官方 rail 重新出现（并存）——绝不会崩溃。
- **只读**：插件从不写入 DSH 状态、从不外发数据，仅读取 DOM 用于定位。

## 兼容性

- DeepSeek Harness (dsh) Web 客户端（`dsh web`）；基于 dsh 0.1.2+ 开发与实测，并对照 dsh 0.1.3-alpha.1 复核。
- 需要 `conversation.session.header.utilities` 与 `settings.general.item` slot 声明（当前 DSH 已包含）。
- 默认 `思磨力轮次胶囊条` 模式以样式覆盖隐藏官方 rail，我们的 rail 居中接管；`DSH 官方` 模式显示内置 rail；`全部隐藏` 两者皆隐。900px 以下都自动隐藏。
- 与全屏插件页面（如看板）共存：胶囊条层级位于全屏 overlay 之下。

## 开发

- `pnpm typecheck` / `pnpm test` — TypeScript 检查（tsdown 只转译不检查）。
- `pnpm bundle` — 构建模块表 client bundle 到 `lib/`。
- `scripts/verify-*.mjs` — 针对真实 `dsh web` 的 Playwright 验收脚本（rail、全量历史 journal、模式开关、跳转、反馈、overlay、尺寸、UI）。
- `pnpm release:check` — 发布门禁（版本、tag、工作树、构建、registry）。

## 许可证

MIT