# DSH Conversation Navigator (dsh-conversation-navigator)

[English](README.en.md) | [中文](README.md)

[![npm](https://img.shields.io/npm/v/dsh-conversation-navigator?style=flat-square&color=blue)](https://www.npmjs.com/package/dsh-conversation-navigator)
[![downloads](https://img.shields.io/npm/dm/dsh-conversation-navigator?style=flat-square&color=blue)](https://www.npmjs.com/package/dsh-conversation-navigator)
[![downloads-total](https://img.shields.io/npm/dt/dsh-conversation-navigator?style=flat-square&color=blue)](https://www.npmjs.com/package/dsh-conversation-navigator)
[![stars](https://img.shields.io/github/stars/gjj-star/dsh-conversation-navigator?style=flat-square&color=green)](https://github.com/gjj-star/dsh-conversation-navigator)
[![license](https://img.shields.io/github/license/gjj-star/dsh-conversation-navigator?style=flat-square&color=teal)](https://github.com/gjj-star/dsh-conversation-navigator)
[![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square)](https://www.npmjs.com/package/dsh-conversation-navigator)
[![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
[![DeepSeek Harness](https://img.shields.io/badge/DeepSeek_Harness-Plugin-blue?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
[![dsh.so risk](https://www.dsh.so/badge/dsh-conversation-navigator-2.svg)](https://www.dsh.so/artifact/dsh-conversation-navigator-2/)
[![dsh.so install](https://www.dsh.so/badge/install/dsh-conversation-navigator-2.svg)](https://www.dsh.so/artifact/dsh-conversation-navigator-2/)

A **DeepSeek Harness (DSH) Web conversation navigator panel**: a turn-folded outline floating on the **right** side of the conversation page. Click any node to smooth-jump, watch the current reading position highlight as you scroll, with step badges colored to match the built-in "Trajectory" view.

Browser-only (no host behavior), plain JavaScript, zero build step, zero npm dependencies (buttons/tooltips reuse the official primitives from the DSH kernel seed).

| Dark mode | Light mode |
| :---: | :---: |
| ![Dark mode](https://raw.githubusercontent.com/gjj-star/dsh-conversation-navigator/main/assets/modes/dark.png) | ![Light mode](https://raw.githubusercontent.com/gjj-star/dsh-conversation-navigator/main/assets/modes/light.png) |

> The conversation navigator in dark and light mode. Three forms: full, hidden, and minimal-left (minimal-right is shelved as of 0.2.6, code retained). The minimal strips collapse to the in-row indicator bars; hover expands the positioning panel. More screenshots in [assets/screenshots](./assets/screenshots).

## Features

- **Turn-folded outline**: shows "Turn N + your question" rows by default, long conversations at a glance
- **Keyword filter**: click the top-left search icon to reveal the inline input; matches only your questions + the assistant's actual reply text (context, tool calls, commands, compaction and reasoning never match); hits are highlighted and the list text is windowed to the keyword
- **Expand / collapse steps**: the arrow button on the right of each turn row (`▸ N`, rotates to `▾` when expanded) smoothly expands or collapses the turn's step details (assistant replies, tool calls, commands, compaction points, etc.)
- **Hover full text**: rest the mouse on a turn row and a bubble shows the user's complete question, no longer truncated to one line
- **Full / Hidden / Minimal-left modes**: the header toggle cycles three states —
  - **Full**: the classic grouped view (system events like compaction are shown bold, at the same level as turns)
  - **Hidden**: every row is a trajectory badge + text (user = business blue, assistant = violet, compaction = neutral gray)
  - **Minimal-left**: the indicator-bar strip pinned to the left edge of the conversation area (at the right border of the DSH sidebar), leaving the right side for dsh-better-sidebar and zero overlap with the official rail
  - The minimal strip collapses to the in-row indicator bars (current = solid brand color, others = 40% foreground-color mix); hovering expands a fixed 7-row positioning panel (click to jump, hover bubble for the full question, thumb-only scrollbar beyond 7 rows; left-aligned expands from left to right); a floating button above the bars keeps cycling forward (minimal-left → Full); the toggle yields while the search input is open
- **Draggable panel**: in Full / Hidden mode, grab the header to drag the panel anywhere in the viewport (auto-clamped to the edges; the expanded height is capped at half the viewport with the top edge fixed and the panel growing downward — expanding a huge turn scrolls the list internally while the "Back to latest / Collapse all" footer stays reachable); the pin button in the header toggles between the default right-dock and the last dragged position; the dragged position and the minimal left/right alignment survive page reloads via localStorage
- **Resizable panel**: drag from the bottom-left rounded corner (Full / Hidden modes only) — width 240–560 px, height vertical. Height follows a content-adaptive + cap model: actual height = min(turn content, the cap you drag to), tracking the number of turns with no blank space under the footer; floor ~3 turns, ceiling within the conversation viewport. The drag tracks the pointer 1:1 and is fully reversible; width/height persist in localStorage
- **Silky motion**: panel fade, step expand/collapse height transitions, staggered fade-in of filter results, rotating collapse arrow — all pure CSS, zero dependencies
- **Click to locate**: click a turn or a step to smooth-scroll the conversation to that exact position (fold state is preserved)
- **Load earlier / Load all**: two buttons on top — "Load earlier" pages one batch back, "Load all" loads every historical turn into the navigator for arbitrary jumps (the page itself stays lazily loaded until you click)
- **Position tracking**: scrolling the conversation highlights and follows the turn you are currently reading
- **Right-side docking (default)**: the panel is anchored to the right edge of the viewport and stays put when the left sidebar collapses or expands; unpin (drag it or hit the pin) to place it anywhere
- **Back to latest / Collapse all**: two shortcut buttons at the bottom; "Collapse all" is disabled when no turn is expanded, so it never looks broken for doing nothing
- **Trajectory colors**: user/steering = business blue, context = success green, assistant = violet, tool = amber, compaction = neutral gray (`--dsw` theme tokens matching the built-in trajectory view, light/dark adaptive)
- **Native DSH look**: action buttons reuse the official `Button`/`Tooltip` components and official icons (search, close); the remaining icons (navigate, load earlier, load all, back to latest, collapse all, switch modes, …) are outline icons from **Lucide** (ISC License, [lucide.dev](https://lucide.dev)), `currentColor` adaptive to light/dark themes
- Follows workspace/session switches automatically and rebuilds the outline

## Install

This plugin is a spec-compliant **bundle** (`dsh.bundle` manifest + `dsh.client` declaration), plain JavaScript with no build step. Install with the official CLI:

```sh
# Option 1: npm (no build authorization needed once published)
dsh plugin --profile web add dsh-conversation-navigator

# Option 2: GitHub (pure JS package, no prepare/allowBuilds required)
dsh plugin --profile web add github:gjj-star/dsh-conversation-navigator

# Option 3: local tarball
pnpm pack
dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
```

`dsh plugin` forwards to pnpm inside the profile directory, so **pnpm must be on your PATH**; installation appends this package to the profile's `dsh.profile.bundles`, and its bundled `cordis.patch.yml` layer inserts the plugin row. Restart `dsh web` and the panel appears. **Dual-form defaults**: on official DSH < 0.1.2-rc.1 (no built-in navigator) the panel is expanded by default; on official DSH ≥ 0.1.2-rc.1 (the conversation view ships the built-in TurnNavigator rail) the panel starts closed in "minimal-left" form with the search box collapsed, summoned by the "导航" title-bar toggle, with zero overlap against the official right-edge rail.

> Manual install (no pnpm): place the repo at `<DSH_HOME>\profiles\<profile>\node_modules\dsh-conversation-navigator` and append the content of [`example.patch.yml`](./example.patch.yml) to the top-level array of the profile's `cordis.patch.yml`.

## Updates

Edit `lib/client.js` and restart `dsh web`. Docked state, dragged position, minimal alignment and the panel width/height persist (localStorage `dsh-cnvnav:ui:v2`); the open/closed state is deliberately NOT persisted — on the new host the panel always starts closed — and search keywords still live only within the page session.

> Published to npm as `dsh-conversation-navigator` (the version badge above always shows the latest release); to upgrade an installed copy, update from the marketplace or run `dsh plugin --profile web add dsh-conversation-navigator` again, then restart.

## How it works

- Slots: `conversation.session.header.utilities` (the "导航" toggle in the title bar) + `shell.overlay` (the floating panel)
- Data (dual-form): legacy hosts read the session-level standard props `useSession`; new hosts (official ≥ 0.1.2-rc.1) read the `uiConversation.binding(binding).target("chat")` snapshot — the primary channel groups by the official `timeline.turnOrder` + `locations` index, backs off to `ChatNodeStore.values()` (mapping via the node's own `location` or its `anchorSeq` against the official turn boundaries), and finally merges the official `turnOutline` projection (`sessions.projectionStore(sid).get("turnOutline")` / the slot standard prop `useProjection`, the same source the official rail's `mergeTurnRailItems` uses) to restore user messages the assembly index drops and to add out-of-window turns; re-derived automatically on `loadOlder`
- History backfill: pages back through `sessions`'s `binding(sessionId).session.loadOlder()`, "Load all" loops until `hasMore=false`
- Jumping: reuses the DSH chat view's own stable DOM anchor `[data-chat-anchor-key]` (the same anchor the product uses internally for paging/scroll positioning) with `scrollIntoView` smooth scrolling
- Position tracking: captures scroll events on the `[data-conversation-scroll]` container (throttled 120ms) and computes the first visible node at the viewport top
- Keyword filter: extracts searchable text only for `user` and `assistant-step` nodes (`dialogueText`), case-insensitive matching, hits wrapped in `<mark>` and the display windowed around the first hit
- Hover full text: the turn-row bubble reads `fullDialogueText` (all text blocks of the user node joined), shown via `Tooltip` with a 340px width cap
- Modes: the header button cycles `viewMode` (full/hidden/minimal-left; `minimal`/minimal-right is commented out and retained); hidden replaces the turn head title with a trajectory badge (`titleNode` strategy); minimal-left is pinned to `scrollport.left + 12` (the placement key includes `r.left` and a ResizeObserver on the chat area re-anchors it whenever the left sidebar expands or collapses); the toggle yields while the search input is open
- Drag & persistence: the full-panel header is draggable (pointer events, clamped to the viewport); dragging or the pin toggles `docked`; `place()` only clamps a freely-placed panel into the viewport instead of re-docking it; the position, docked state and alignment choice persist in localStorage (`dsh-cnvnav:ui:v1`)
- Resize: a transparent bottom-left corner grip captures pointer events; width writes `style.width` directly (240–560), height only adjusts `maxHeight` while `height` stays `auto`, so the actual height is always min(content, cap) with no blank space; dragging follows the start point linearly (`base + delta`) and shares the same container/viewport clamps as `place()` so nothing snaps on release; width/height persist as `panelWidth`/`panelHeight`
- Styling: `Button`/`Tooltip`/search & close icons reuse `@deepseek-ai/dsh-client-ui-primitives`, the rest are inline Lucide SVGs (ISC License); the panel injects its own `<style>` element, colors use `--dsw-*` theme tokens; everything is cleaned up with the fiber on unload

## Compatibility

- Target platform: DSH Web (`dsh.client.platform: web`), depends on the kernel seed's `react`, `slots`, `sessions` services and `@deepseek-ai/dsh-client-ui-primitives`; `dsh.client.inject` declares `@deepseek-ai/dsh-client-ui-conversation` (`dsh-client-runtime` was removed in official v0.1.2-rc.1 and is no longer injected)
- **Dual form (0.2.6)**: the host form is detected at runtime via feature detection — the presence of the `uiConversation` service marks the new host (official ≥ 0.1.2-rc.1); no hardcoded version numbers. `uiConversation` is now declared in the client `exports.inject`, so the runtime guarantees it is ready and host detection no longer flips with load timing. Legacy hosts keep the full navigator (expanded by default); new hosts coexist with the official TurnNavigator: closed by default, "minimal-left" form, search box collapsed by default — the official rail handles jumping, this plugin focuses on keyword search, bulk "Load all" and step-level badges
- **Host version note**: fully verified on official **DSH v0.1.2-rc.1** (Web) — the new `uiConversation` service, the `conversation.session.header.utilities` empty-props contract, the chat target snapshot (`timeline`/`locations`/`nodes`/`navigation`/`legacy`) and the `turnOutline` projection. Legacy hosts (no `uiConversation`) are covered by the `useSession` path; older official versions are not individually regression-tested
- **Host dependency declaration**: `@deepseek-ai/dsh-client-ui-conversation` is declared as a `peerDependencies` semver range (the awesome-dsh-plugin convention; dshmarket's dependency check surfaces host compatibility from it)
- **Version-sensitive points**: `[data-chat-anchor-key]` / `[data-conversation-scroll]` are the current DOM anchor conventions of the DSH chat view (verified still present in official v0.1.2-rc.1); if they change after a DSH upgrade, only `findAnchor` / `computeActiveKey` in `lib/client.js` need adjusting
- No hard `timer` dependency: the client timer service is used for throttling when present, and degrades to unthrottled otherwise

## Host ↔ plugin version

Official v0.1.2-rc.1 made a breaking change to the conversation data layer (conversation nodes moved from the `session.chat` snapshot to the `uiConversation` chat target snapshot), so the plugin forks by host version:

| Host `dsh-client-ui-conversation` | Matching plugin version | npm tag |
|---|---|---|
| **≥ 0.1.2-rc.1** (has the `uiConversation` service) | **0.2.6 and later** (currently 0.2.9) | `latest` (default) |
| **≤ 0.1.1-rc.2** (no `uiConversation`, legacy `session.chat` snapshot) | **0.2.5 and earlier** | `legacy` |

- New hosts can install the latest normally (`dsh plugin --profile web add dsh-conversation-navigator`).
- Legacy hosts should pin the legacy tag: `npm i dsh-conversation-navigator@legacy` (= 0.2.5).
- From 0.2.6 the `peerDependencies` range is `>=0.1.2-rc.1 <0.2.0` (new host only); 0.2.5 and earlier declare no such range.

## Structure

```
lib/
  index.js   # empty host entry (browser-only plugin)
  client.js  # full browser implementation (window.__ModuleLoader__ module format)
assets/
  screenshots/   # screenshots (README hero + market gallery)
cordis.patch.yml     # bundle patch layer (inserts the plugin row)
example.patch.yml    # patch example for manual installs
```

## Traffic

![Traffic trend](./docs/traffic/traffic-trend.svg)

- Refreshed daily by GitHub Actions (top: GitHub unique visitors / unique cloners; bottom: npm daily downloads; orange dashed lines mark releases).
- Details: [traffic-report.md](./docs/traffic/traffic-report.md) | raw archive: [traffic-archive.json](./docs/traffic/traffic-archive.json) | docs & manual refresh: [docs/traffic](./docs/traffic/README.md)
- Why an archive: GitHub's traffic API only keeps a rolling 14-day window, and older data is lost forever — this repo merges every fetch into a durable, append-mostly daily series.

## License

MIT
