dsh-turn-navigator 0.1.0 → 0.4.0

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
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0] - 2026-08-29
4
+
5
+ ### Added
6
+
7
+ - **Rail display mode (Settings → General → Turn navigation)**: three-way choice between `DSH official` (the built-in rail), `DSH STN` (this plugin's rail — default), and `Hide all`. Registered into the official `settings.general.item` seat (root scope), styled like the built-in preference rows (EnterBehaviorRow pattern), persisted browser-locally (localStorage — the official `settingsScope` store needs a Host-registered namespace that an external client-only plugin has no seam to create).
8
+ - **Subtractive takeover of the official rail**: the official built-in TurnNavigator has no off-switch, so in `DSH STN` mode it is hidden with a container-scoped stylesheet override (`body.tn-hide-official [data-conversation-scroll] nav { display: none !important }` — our rail is fixed OUTSIDE the conversation scroll container, so the rule cannot match ours), and our rail takes over the right-edge center position. `Hide all` hides both.
9
+ - **Official-rail detection fix**: the old check matched `nav[aria-label*="轮次"/"Turn navigation"]` anywhere, which also matched OUR OWN rail (same localized label) — so the rail stayed nudged into the header zone even when the official rail was absent (e.g. 1-turn sessions). The check is now scoped to `[data-conversation-scroll]` and reads the COMPUTED display value, so the nudge follows reality: official visible → we nudge; official hidden by our override → we stay centered.
10
+ - **Brand name**: DSH Smoothly Turn Nav (DSH STN) — used in the README, settings row, and changelog.
11
+
12
+ # Changelog
13
+
14
+ ## [0.3.0] - 2026-08-29
15
+
16
+ ### Added
17
+
18
+ - **Full history restored on dsh 0.1.2+ (journal channel)**: the 0.2.0 refactor removed the browser→host `sessions.history` RPC, so the rail degraded to loaded-window turns. This release restores the full-history rail in the browser with **zero host changes and zero new dependencies**: the plugin pages the same persisted log the official window reads through the Typert Remote `session/page` endpoint (`ctx.remote.session` — the namespace is mounted by the base web assembly into `ctx.get('remote.session')`, read without adding to the cordis `inject` list because `ctx.get` is the inject-free store read; the traced `ctx.get('remote').session` path would hit the "without inject" gate). Every persisted turn (including turns far outside the window) is shown as plain data, paged incrementally, with no prepends into the conversation flow on open — the same performance story as 0.1.x, now purely client-side.
19
+ - **Official-store jump path**: window expansion for out-of-window jumps now goes through the official session store (`sessions.binding(id).session.loadOlder()`), with the authoritative `hasMore` from `binding(id).eventSource.getSnapshot().hasMore` as the loop terminator (no more DOM "Load earlier" button sniffing on 0.1.2+), and row polling instead of fixed sleeps so fast renders jump immediately. The "Load earlier" button path remains as a fallback on older hosts.
20
+ - **Journal readiness wait**: the official session binding is staged a moment after the conversation view mounts; the full-history fetch now waits (bounded, 15s) for the window's seq bounds instead of silently skipping, so the rail fills with all turns shortly after open.
21
+
22
+ ### Changed
23
+
24
+ - History-channel priority: 0.1.2+ journal (`session/page`) → legacy `sessions.history` RPC → window-only turns (each layer degrades gracefully, including when `remote.session` is not yet mounted at boot — handles are resolved lazily and retried at render).
25
+
26
+ ## [0.2.0] - 2026-08-29
27
+
28
+ ### Changed
29
+
30
+ - **Adapted to dsh 0.1.2+ (ui-chat refactor)**: the conversation data model changed — `ConversationSnapshot` lost its `chat` field and the old `dsh-client-runtime` package was removed. The rail now reads turn data through the new `useChat` hook (ChatSnapshot, `navigation.items()`), falls back to the legacy `.chat`-wrapped snapshot on older dsh, and imports `ClientContext` from `@deepseek-ai/cordis` (with the `dsh-client-ui-renderer/client` merge for `ctx.slots`). This fixes the crash that hid the rail after upgrading.
31
+ - **Coexists with the official built-in TurnNavigator**: the official dsh rail (in-chat, always rendered, cannot be disabled) now overlaps-free — when it is present our rail nudges up into the header zone (`.tn-nudge`) instead of the scrollport center. Mirrored the official narrow-viewport hide (`@media max-width: 900px`).
32
+ - **Added follow-scroll active-turn highlight**: the capsule for the turn at the reading line is tinted (brand color) as you scroll — a capability the official rail has and we previously lacked.
33
+ - **History-as-data is window-scoped on 0.1.2+**: the browser→host `sessions.history` RPC (`connection.api`) was removed in the refactor, so the full-history read is unavailable in the browser on new dsh; the rail degrades to the loaded-window turns (same data as the official rail) and keeps its UI/UX (wave hover, scroll buttons, center-on-click, tooltip, jump feedback, follow highlight). Full-history support is a known follow-up (host-half route) if desired.
34
+
35
+ ## [0.1.1] - 2026-08-19
36
+
37
+ ### Changed
38
+
39
+ - **History-as-data architecture (fixes stalls on very long sessions)**: the rail's turn list is now read from the HOST through the browser→host `sessions.history` RPC — every persisted turn (including ones far outside the conversation window) is shown as plain data, paged incrementally, with **zero prepends into the conversation flow on open**. Previously the rail extended the flow window by auto-clicking "Load earlier", which re-renders the whole flow per page and stalled the UI on ~150-turn sessions. Now the flow window is only extended **on demand**: clicking a capsule for a turn already in the window scrolls directly; for a turn outside the window, the rail extends the window page by page (respecting the paging button's in-flight state) until the target turn is in the window, then scrolls and highlights it.
40
+ - **Immediate jump feedback**: clicking an out-of-window capsule now shows instant feedback — the clicked capsule pulses and a "Locating turn N…" bubble appears beside it (in the DSH tooltip visual style) for the whole duration of the on-demand window extension; on failure a brief "Could not locate turn N" notice shows instead. No more silent waits.
41
+ - **Oldest-turn jumps load to the true start**: jumping to the oldest turn now extends the window until there is no more history to load (hasMore false) — previously the window could include the target's boundary while earlier events were still pending, so the jump landed before the true first turn with a "Load earlier" button remaining. Readiness is judged by the target row being actually rendered in the DOM, not just the turn being listed in the window.
42
+ - **Bilingual README switch**: README (English default) and README.zh.md now link to each other.
43
+ - **Layer stacking**: the rail's z-index is now 10 — above the conversation flow content (max 8) but below full-screen overlays like the kanban board plugin (z-index 50) — matching the header's "Session log" button tier, so an open full-screen page always paints over the rail.
44
+ - **README screenshot**: added `docs/turn-nav-rail.png` to the bilingual README (English default) as marketing, shipped in the npm package.
45
+
3
46
  ## [0.1.0] - 2026-08-19
4
47
 
5
48
  ### Added
package/README.md CHANGED
@@ -1,15 +1,54 @@
1
- # dsh-turn-navigator
1
+ # DSH Smoothly Turn Nav (DSH STN)
2
2
 
3
- An external [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that adds a **piano-key turn rail** to the conversation interface — a vertical column of tiny capsules on the right edge of the conversation, one per turn, so you can see every turn at a glance, hover to preview it, and click to jump to its start.
3
+ **English · [简体中文](README.zh.md)**
4
4
 
5
- ## Why
5
+ **Every turn of your conversation, one glance away.**
6
+
7
+ DSH Smoothly Turn Nav (**DSH STN**) is an external plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) that puts a **piano-key turn rail** on the right edge of every conversation — a vertical column of tiny capsules, one per turn. It gives you a full minimap of the conversation: **every turn ever made** (not just the ones currently loaded), hover previews, click-to-jump to any turn's start, and follow-scroll highlighting. It can also **replace the official built-in turn rail**, which has no off-switch of its own.
6
8
 
7
- In the default DSH web UI, finding a specific turn in a long conversation means scrolling — a lot. There's no overview of how many turns happened, what each turn was about, or where the current scroll position sits. `dsh-turn-navigator` solves this with a minimap-style rail:
9
+ ![Turn navigation rail](docs/turn-nav-rail.png)
10
+
11
+ ## Why
8
12
 
9
- - A **vertical capsule per turn** floats on the right edge of the conversation (grey, ~3px tall, piano-key style).
10
- - **Hovering** a capsule makes it glow with the theme's primary label color and widen to 150% (the two neighbours widen to 125% too), so sliding across the rail ripples like a wave — and shows the full turn info (index, timestamp, user-message summary) in a native DSH tooltip.
11
- - **Clicking** a capsule scrolls the conversation to that turn's start and briefly highlights it.
12
- - **Older history auto-loads**: the conversation paginates its window; the plugin keeps loading earlier pages so every turn is reachable, with no manual "Load earlier" clicks.
13
+ In the default DSH web UI, the official turn rail only shows the **currently loaded window** of turns in a long conversation, most turns are invisible until you scroll and load more. DSH STN solves this:
14
+
15
+ - **Full history at a glance** every persisted turn is shown as plain data, including turns far outside the loaded window. No scrolling, no loading, no waiting.
16
+ - **Hover to preview** the capsule glows with the theme color and widens in a wave ripple; a DSH-style tooltip shows the turn's number, time, and user-message summary.
17
+ - **Click to jump anywhere** — jumps to any turn's start, even turns not yet loaded (the window is extended on demand with instant feedback).
18
+ - **Know where you are** — the current turn is highlighted as you scroll.
19
+
20
+ ## Features
21
+
22
+ | | |
23
+ |---|---|
24
+ | 🗺️ **Full-history minimap** | All turns visible immediately — read from the persisted session log as data, **zero prepends** into the conversation flow on open (long sessions stay responsive) |
25
+ | 🎹 **Piano-key design** | One capsule per turn, ~3px tall, right-aligned on the right edge; auto-sizing (up to 30vh) with an internal hidden scrollbar |
26
+ | 🌊 **Wave hover** | Hovered capsule glows with the theme color and widens 150% leftward; its two neighbours widen 125% — a ripple across the rail |
27
+ | 💬 **Rich tooltip** | Turn number, timestamp, and full user-message summary, always fully inside the viewport |
28
+ | 🎯 **Jump to any turn** | Precise `scrollTop` targeting (no `scrollIntoView` fights); out-of-window jumps extend the window on demand with a "Locating turn N…" pulse + bubble; the oldest-turn jump loads to the **true first turn** (`hasMore = false`) |
29
+ | 👁️ **Follow-scroll highlight** | The capsule of the turn at the reading line is tinted as you scroll |
30
+ | ⬆️⬇️ **Scroll buttons** | Click or hover-hold to scroll the rail; greyed out when there is nothing to scroll |
31
+ | 🎛️ **Rail mode switch** | Settings → General → *Turn navigation*: `DSH official` / `DSH STN` (default) / `Hide all` — persisted across reloads |
32
+ | 🔌 **Pure external plugin** | No DSH source code modified; no host changes; no new dependencies; read-only DOM access |
33
+
34
+ ## vs. the official DSH turn rail
35
+
36
+ The official built-in `TurnNavigator` has **no off-switch** and is always rendered in the chat view. DSH STN is the upgrade:
37
+
38
+ | Capability | DSH official rail | DSH STN |
39
+ |---|---|---|
40
+ | Turns shown | Only the **loaded window** | **Every persisted turn** (full history) |
41
+ | Jump to a turn outside the window | ❌ Not visible, not reachable | ✅ On-demand window extension + feedback |
42
+ | Long-session open performance | Window rendering | **Zero prepend** — no flow re-render, no stall |
43
+ | Follow-scroll highlight | ✅ | ✅ |
44
+ | Hover preview | Prompt + response (≤160 chars each) | Number + time + full summary tooltip |
45
+ | Wave ripple animation | ❌ | ✅ |
46
+ | Scroll buttons (click / hover-hold) | ❌ | ✅ |
47
+ | Rail height | Fixed band, compressed proportionally (≤420px) | Auto-sized (≤30vh), internal scroll |
48
+ | Narrow viewport (<900px) | Auto-hidden | Auto-hidden (mirrors official) |
49
+ | Hide / switch rail | ❌ No off-switch | ✅ Settings → General → 3 modes |
50
+ | Keyboard reachable | ✅ (focus ring) | ✅ (buttons) |
51
+ | Source | Built-in, cannot be disabled | External plugin, **can be replaced/disabled** |
13
52
 
14
53
  ## Installation
15
54
 
@@ -25,30 +64,41 @@ dsh web
25
64
 
26
65
  ## Usage
27
66
 
67
+ 0. **Choose which rail to show** (Settings → General → **Turn navigation**): `DSH official` (the built-in rail), `DSH STN` (this plugin's rail — **default**), or `Hide all`. The official rail has no off-switch, so choosing DSH STN hides it with a stylesheet override and our rail takes over the right-edge center position. The choice persists across reloads.
28
68
  1. Open any conversation with at least one completed turn.
29
- 2. A vertical rail of grey capsules appears on the right edge of the conversation (one capsule per turn). The rail **auto-sizes**: its length grows with the turn count, capped at **30vh**a short conversation gets a short rail, a long one hits the cap and scrolls internally with a **hidden scrollbar** (no layout jitter). **Up/down scroll buttons** at its top and bottom support both click and **hover-hold auto-scroll**, and are greyed out when there is nothing to scroll in that direction — so the rail never stretches past the viewport, and you can wheel, click, or hold to move through the turns.
30
- 3. Hover a capsule to see the turn's index, timestamp, and user-message summary in a DSH-style tooltip anchored to the left of the rail, vertically centered on the hovered capsule and always fully inside the viewport. The capsule glows with the theme color and widens 150% LEFTWARDS (right-aligned — the right edge never moves), with the two neighbours widening a little too, a wave ripple across the rail.
31
- 4. Click a capsule to jump to that turn's start (the target row briefly highlights); the activated capsule scrolls to the center of the rail unless it is the first or last turn.
32
- 5. Older history loads automatically — the rail fills its visible height first, and scrolling the rail to its top keeps loading earlier turns. No manual "Load earlier" clicks needed.
69
+ 2. A vertical rail of grey capsules appears on the right edge (one per turn). It **auto-sizes** — short conversations get a short rail, long ones hit the 30vh cap and scroll internally (hidden scrollbar, no layout jitter).
70
+ 3. **Hover** a capsule: it glows, widens in a wave, and shows the turn's index, timestamp, and summary in a tooltip to the left of the rail.
71
+ 4. **Click** a capsule to jump to that turn's start. Out-of-window turns pulse the capsule and show a "Locating turn N…" bubble while the window is extended on demand; the target row is highlighted on arrival. The activated capsule centers in the rail (unless it is the first or last turn).
72
+ 5. **Scroll** with the mouse wheel, the up/down buttons, or hover-hold on the buttons.
33
73
 
34
74
  ## How it works
35
75
 
36
- The plugin registers **one additive slot** — **no DSH source code is modified**:
76
+ The plugin registers **two additive slots** — **no DSH source code is modified**:
37
77
 
38
78
  | Slot | Scope | Role |
39
79
  |------|-------|------|
40
- | `conversation.session.header.utilities` | session | The floating turn rail; reads `useSession` directly |
80
+ | `conversation.session.header.utilities` | session | The floating turn rail (renders `position: fixed`; reads the live chat snapshot via the framework `useChat`/`useSession` kit) |
81
+ | `settings.general.item` | root | The *Turn navigation* mode switch in Settings → General |
41
82
 
42
- Because the rail is session-scoped, it reads the live `ConversationSnapshot` straight from the framework `useSession` kit and renders as `position: fixed` (so it does not occupy the header's flex row).
43
-
44
- - **Turn extraction**: `chat.timeline.turnOrder` + `turns` map for boundaries; `chat.locations.getTurn(turn)` for each turn's node keys; the first `kind === 'user'` node's first text block for the summary; `turnTimings` for the timestamp. Turns without a user message fall back to their first node's kind.
45
- - **Jump-to-turn**: locate the turn's first chat-node key, find the DOM row via `data-chat-anchor-key="<key>"`, compute its position in the `[data-conversation-scroll]` scrollport, and set `scrollTop` precisely (more predictable than `scrollIntoView`). If the target row is not yet rendered (older page not loaded), it auto-clicks the "Load earlier" button and retries until the row appears no "scroll once first" friction.
46
- - **Auto-load (stall-free)**: the conversation paginates its history each page prepend re-renders the whole flow, so loading everything at once would stall the UI. The rail instead (a) slowly fills its visible height after open (slow cadence, back-pressure, page cap), then (b) keeps loading earlier turns while the rail is scrolled near its top — scroll-driven, the same way the conversation itself loads. Clicking a capsule also loads on demand to reach its turn. A shared busy-lock prevents the auto-load and scroll-load paths from ever clicking the paging button concurrently.
83
+ - **Full history as data**: on dsh 0.1.2+ the rail pages the same persisted log the official window reads, through the Typert Remote `session/page` channel (`ctx.remote.session` — mounted by the base web assembly; **no host changes, no new dependencies**). On older dsh it uses the `sessions.history` RPC. Every turn is derived from raw `turn/start` / `user/message` / `turn/end` events as plain data.
84
+ - **On-demand jumps**: clicking a turn already in the window scrolls directly; for a turn outside it, the rail extends the window page by page through the official session store (`loadOlder`, with the authoritative `hasMore` as the loop terminator) until the target is in view — the only path that touches the flow, and it runs only when you click.
85
+ - **Precise scrolling**: the rail locates the turn's first chat-node key, finds its DOM row via `data-chat-anchor-key`, and sets the scrollport's `scrollTop` directly (more predictable than `scrollIntoView`).
86
+ - **Replacing the official rail**: the official rail lives inside the conversation scroll container; a container-scoped stylesheet rule (driven by the mode switch) hides it, and the rail takes over its right-edge center position. If a future dsh changes that structure, the worst case is the official rail reappearing (side by side)never a crash.
87
+ - **Read-only**: the plugin never writes to DSH state, never sends data anywhere, and only reads the DOM for targeting.
47
88
 
48
89
  ## Compatibility
49
90
 
50
91
  - DeepSeek Harness (dsh) with the web client (`dsh web`).
51
- - Requires the `conversation.session.header.utilities` slot declaration (present in current DSH).
92
+ - Requires the `conversation.session.header.utilities` and `settings.general.item` slot declarations (present in current DSH).
93
+ - Default `DSH STN` mode hides the official rail (stylesheet override) and centers our rail in its place; `DSH official` mode shows the built-in rail instead; `Hide all` hides both. Both rails auto-hide below 900px width.
94
+ - Coexists with full-screen plugin pages (e.g. the kanban board): the rail sits below their overlay layer.
95
+
96
+ ## Development
97
+
98
+ - `pnpm typecheck` / `pnpm test` — TypeScript check (tsdown does not typecheck).
99
+ - `pnpm bundle` — build the module-table client bundle into `lib/`.
100
+ - `scripts/verify-*.mjs` — Playwright acceptance scripts against a live `dsh web` (rail, full-history journal, mode switch, jump, feedback, overlay, sizing, UI).
101
+ - `pnpm release:check` — release gates (version, tag, tree, build, registry).
52
102
 
53
103
  ## License
54
104
 
package/README.zh.md CHANGED
@@ -1,15 +1,54 @@
1
- # dsh-turn-navigator
1
+ # DSH Smoothly Turn Nav(DSH STN)
2
2
 
3
- 一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 外部插件,为会话界面增加**钢琴键式轮次胶囊条**——在会话界面右侧悬浮一条竖向胶囊列,每轮一个小胶囊,让你一眼纵览整场会话有哪几轮,悬停预览,点击跳转到任意轮次起始点。
3
+ **[English](README.md) · 简体中文**
4
4
 
5
- ## 为什么需要
5
+ **整场会话,一眼纵览。**
6
+
7
+ DSH Smoothly Turn Nav(**DSH STN**)是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的外部插件,在每条会话右侧放一条**钢琴键式轮次胶囊条**——竖向一列小胶囊,一轮一个。它是整场会话的迷你地图:**全部历史轮次一目了然**(不只是当前已加载的窗口),悬停预览、点击跳转到任意轮次起点、滚动跟随高亮。它还可以**取代官方内置轮次胶囊条**(官方没有自己的关闭开关)。
6
8
 
7
- DSH Web UI 中,在长会话里找某一轮只能靠滚动——没有轮次概览,不知道有几轮、每轮说了什么、当前滚到哪里。`dsh-turn-navigator` 用类似 minimap 的胶囊条解决这个问题:
9
+ ![轮次导航胶囊条](docs/turn-nav-rail.png)
10
+
11
+ ## 为什么需要
8
12
 
9
- - 会话界面右侧**悬浮一条竖向胶囊列**(每轮一个灰色小胶囊,约 3px 高,钢琴键式)。
10
- - **悬停**某个胶囊:该胶囊以**主题色**亮起并加宽 150%,相邻两个胶囊也稍微加宽(125%)——鼠标滑过时像波浪一样起伏;同时以 DSH 原生 Tooltip 完整展示该轮信息(序号、时间戳、用户消息摘要)。
11
- - **点击**某个胶囊:会话滚动到该轮起点并短暂高亮。
12
- - **历史自动加载**:会话历史是分页的,插件自动持续加载更早的页面,直到全部轮次可定位,无需手动点击"加载更早"。
13
+ DSH Web UI 的官方轮次胶囊条只显示**当前已加载窗口内**的轮次——长会话里,大部分轮次要滚动加载后才可见。DSH STN 解决这个问题:
14
+
15
+ - **全量历史一眼可见**——所有持久化轮次以纯数据呈现,包括远在已加载窗口之外的轮次。不滚动、不加载、不等待。
16
+ - **悬停即预览**——胶囊以主题色亮起并泛起波浪涟漪;DSH 风格 Tooltip 展示该轮序号、时间与用户消息摘要。
17
+ - **点击跳转任意轮**——包括尚未加载的轮次(按需扩展窗口,带即时反馈)。
18
+ - **随时知道自己在哪**——滚动时当前轮次高亮。
19
+
20
+ ## 特性
21
+
22
+ | | |
23
+ |---|---|
24
+ | 🗺️ **全量历史迷你地图** | 所有轮次立即可见——从持久化会话日志以数据读取,**打开会话零 prepend**(长会话保持流畅) |
25
+ | 🎹 **钢琴键设计** | 每轮一个约 3px 高的胶囊,右缘右对齐;长度自适应(上限 30vh),超出后内部滚动(滚动条隐藏) |
26
+ | 🌊 **波浪悬停** | 悬停的胶囊以主题色亮起并向左加宽 150%,相邻两个加宽 125%——滑过时如波浪起伏 |
27
+ | 💬 **丰富 Tooltip** | 轮次序号、时间戳、完整用户消息摘要,始终完整显示在视口内 |
28
+ | 🎯 **跳转任意轮** | 精确 `scrollTop` 定位(不与 `scrollIntoView` 打架);窗口外跳转按需扩展窗口并显示"正在定位第 N 轮…"脉冲+气泡;最老轮次跳转加载到**真正第一轮**(`hasMore = false`) |
29
+ | 👁️ **滚动跟随高亮** | 阅读线所在轮次的胶囊随滚动点亮 |
30
+ | ⬆️⬇️ **滚动按钮** | 点击或悬停持续滚动;无可滚动内容时置灰 |
31
+ | 🎛️ **胶囊条模式开关** | 设置 → 通用 → *轮次导航*:`DSH 官方` / `DSH STN`(默认)/ `全部隐藏`——跨刷新持久保存 |
32
+ | 🔌 **纯外部插件** | 不改 DSH 源码;零宿主改动;零新增依赖;仅只读 DOM |
33
+
34
+ ## 与官方 DSH 轮次胶囊条对比
35
+
36
+ 官方内置 `TurnNavigator` **没有关闭开关**,始终渲染在会话视图里。DSH STN 是它的升级版:
37
+
38
+ | 能力 | DSH 官方胶囊条 | DSH STN |
39
+ |---|---|---|
40
+ | 显示的轮次 | 仅**已加载窗口** | **全部持久化轮次**(全量历史) |
41
+ | 跳转窗口外轮次 | ❌ 看不到也跳不了 | ✅ 按需扩展窗口 + 即时反馈 |
42
+ | 长会话打开性能 | 窗口渲染 | **零 prepend**——不重渲染会话流、不卡顿 |
43
+ | 滚动跟随高亮 | ✅ | ✅ |
44
+ | 悬停预览 | prompt + response(各 ≤160 字符) | 序号 + 时间 + 完整摘要 Tooltip |
45
+ | 波浪涟漪动画 | ❌ | ✅ |
46
+ | 滚动按钮(点击 / 悬停持续) | ❌ | ✅ |
47
+ | 胶囊条高度 | 固定区域按比例压缩(≤420px) | 自适应(≤30vh)+ 内部滚动 |
48
+ | 窄窗口(<900px) | 自动隐藏 | 自动隐藏(与官方对齐) |
49
+ | 隐藏 / 切换胶囊条 | ❌ 无开关 | ✅ 设置 → 通用 → 三档 |
50
+ | 键盘可达 | ✅(焦点环) | ✅(按钮) |
51
+ | 来源 | 内置、无法关闭 | 外部插件,**可替换 / 可关闭** |
13
52
 
14
53
  ## 安装
15
54
 
@@ -25,30 +64,41 @@ dsh web
25
64
 
26
65
  ## 使用
27
66
 
28
- 1. 打开任意有至少一轮完成的会话。
29
- 2. 会话界面右侧出现一条竖向灰色胶囊列(每轮一个胶囊)。胶囊条**长度自适应**:随轮次多少自动增减,**上限 30vh**——轮次少时胶囊条短,轮次多时达到上限后内部滚动;滚动条隐藏(避免抖动)。上下各有一个**滚动按钮**:支持点击和**悬停持续自动滚动**,没有可滚动内容时自动置灰——轮次再多也不会超出浏览器范围。
30
- 3. 悬停某个胶囊:胶囊**以主题色亮起并向左加宽 150%**(右对齐,右缘不动;相邻两个胶囊也稍微加宽——滑过时形成波浪波纹),胶囊条左侧弹出 DSH 风格 Tooltip,**垂直居中于悬停的胶囊**、始终完整在视口内,展示完整信息(序号、时间戳、用户消息摘要)。
31
- 4. 点击某个胶囊:会话滚动到该轮起点(目标行短暂高亮),**该胶囊自动滚动到胶囊条中央**(首尾两条除外)。
32
- 5. 更早的历史会自动加载——胶囊条先填充自身可视高度,**滚动胶囊条到顶部继续加载更早轮次**。无需手动点击"加载更早"
67
+ 0. **选择显示哪个胶囊条**(设置 → 通用 → **轮次导航**):`DSH 官方`(内置 rail)、`DSH STN`(本插件 rail——**默认**)、或`全部隐藏`。官方 rail 没有关闭开关,选择 DSH STN 时以样式覆盖将其隐藏,我们的 rail 接管右缘居中位置。选择会跨刷新持久保存。
68
+ 1. 打开任意包含至少一轮已完成轮次的会话。
69
+ 2. 会话右侧出现一条竖向灰色胶囊列(每轮一个)。胶囊条**长度自适应**:轮次少则短,轮次多则达 30vh 上限后内部滚动(滚动条隐藏,无布局抖动)。
70
+ 3. **悬停**某个胶囊:它以主题色亮起并泛起波浪、向左加宽,胶囊条左侧弹出 Tooltip(序号、时间、摘要),始终完整在视口内。
71
+ 4. **点击**某个胶囊:会话滚动到该轮起点并短暂高亮目标行;窗口外轮次会先脉冲闪烁 + 弹出"正在定位第 N 轮…"气泡,按需扩展窗口后定位。被点击的胶囊自动滚动到胶囊条中央(首尾两条除外)。
72
+ 5. **滚动**:滚轮、上下按钮、或按住按钮持续滚动。
33
73
 
34
74
  ## 原理
35
75
 
36
- 插件只注册**一个加法 slot**——**不修改 DSH 源码**:
76
+ 插件注册**两个加法 slot**——**不修改 DSH 源码**:
37
77
 
38
78
  | Slot | 作用域 | 职责 |
39
79
  |------|--------|------|
40
- | `conversation.session.header.utilities` | session | 悬浮轮次胶囊条;直接通过 `useSession` 读取 |
80
+ | `conversation.session.header.utilities` | session | 悬浮轮次胶囊条(`position: fixed`;通过框架 `useChat`/`useSession` kit 读取实时会话快照) |
81
+ | `settings.general.item` | root | 设置 → 通用 的 *轮次导航* 模式开关 |
41
82
 
42
- 因为胶囊条是 session 作用域,它直接从框架 `useSession` kit 读取实时 `ConversationSnapshot`,并以 `position: fixed` 渲染(不占据 header flex 行)。
43
-
44
- - **轮次提取**:`chat.timeline.turnOrder` + `turns` map 得到轮次边界;`chat.locations.getTurn(turn)` 取该轮 node keys;首个 `kind === 'user'` 节点的首个 text block 作摘要;`turnTimings` 取时间戳。无用户消息的轮次降级显示其首个节点的 kind。
45
- - **跳转定位**:取该轮第一个 chat-node key,通过 `data-chat-anchor-key="<key>"` 找到 DOM 行,在 `[data-conversation-scroll]` 滚动容器中精确计算并设置 `scrollTop`(比 `scrollIntoView` 更可控)。若目标行尚未渲染(更早页面未加载),自动点击"加载更早"按钮并重试直到行出现——无需先滚动一下。
46
- - **自动加载(不卡顿)**:会话历史是分页的,每页 prepend 都会重渲染整个会话流,一次性全加载会卡住界面。胶囊条改为:(a) 打开后以缓慢节奏(背压 + 页数上限)先填充自身可视高度;(b) 之后**滚动胶囊条到顶部时继续加载更早轮次**(滚动驱动,与会话自身加载方式一致)。点击胶囊跳转时也会按需加载到目标轮。自动加载与滚动加载通过共享 busy 锁互斥,绝不并发点击分页按钮。
83
+ - **全量历史即数据**:dsh 0.1.2+ 上,胶囊条通过 Typert Remote `session/page` 通道分页读取与官方窗口同一条持久化日志(`ctx.remote.session`——由基础 web 装配挂载;**零宿主改动、零新增依赖**);旧版 dsh 走 `sessions.history` RPC。每轮从 `turn/start` / `user/message` / `turn/end` 原始事件派生为纯数据。
84
+ - **按需跳转**:点击窗口内轮次直接滚动;窗口外轮次通过官方会话 store 的 `loadOlder()` 逐页扩展窗口(以权威 `hasMore` 为终止条件)直到目标进入视图——这是唯一触碰会话流的路径,且只在点击时发生。
85
+ - **精确定位**:取该轮第一个 chat-node key,通过 `data-chat-anchor-key` 找到 DOM 行,直接设置滚动容器 `scrollTop`(比 `scrollIntoView` 更可控)。
86
+ - **取代官方胶囊条**:官方 rail 位于会话滚动容器内;模式开关驱动的容器限定样式规则将其隐藏,我们的 rail 接管右缘居中位置。若未来 dsh 改变该结构,最坏情况是官方 rail 重新出现(并存)——绝不会崩溃。
87
+ - **只读**:插件从不写入 DSH 状态、从不外发数据,仅读取 DOM 用于定位。
47
88
 
48
89
  ## 兼容性
49
90
 
50
91
  - DeepSeek Harness (dsh) Web 客户端(`dsh web`)。
51
- - 需要 `conversation.session.header.utilities` slot 声明(当前 DSH 已包含)。
92
+ - 需要 `conversation.session.header.utilities` 与 `settings.general.item` slot 声明(当前 DSH 已包含)。
93
+ - 默认 `DSH STN` 模式以样式覆盖隐藏官方 rail,我们的 rail 居中接管;`DSH 官方` 模式显示内置 rail;`全部隐藏` 两者皆隐。900px 以下都自动隐藏。
94
+ - 与全屏插件页面(如看板)共存:胶囊条层级位于全屏 overlay 之下。
95
+
96
+ ## 开发
97
+
98
+ - `pnpm typecheck` / `pnpm test` — TypeScript 检查(tsdown 只转译不检查)。
99
+ - `pnpm bundle` — 构建模块表 client bundle 到 `lib/`。
100
+ - `scripts/verify-*.mjs` — 针对真实 `dsh web` 的 Playwright 验收脚本(rail、全量历史 journal、模式开关、跳转、反馈、overlay、尺寸、UI)。
101
+ - `pnpm release:check` — 发布门禁(版本、tag、工作树、构建、registry)。
52
102
 
53
103
  ## 许可证
54
104
 
Binary file