# @known-mouse/dsh-history-question-nav

一个 DeepSeek Harness **Web 客户端插件**：在窗口右侧列出当前会话里你问过的每一个问题，点击任意一条即可平滑滚动定位到对话中对应的位置（问题气泡会短暂高亮）。

- 右侧常驻「问题 / Questions」面板，按对话顺序列出当前会话的所有用户问题。
- 点击某条问题 → 会话滚动到该问题所在位置并短暂高亮。
- 面板可收起为右上角「问题」小按钮。
- 跟随当前会话：切换会话时列表自动换成那个会话的问题。
- **中英双语**：注册了 `question-nav` locale 命名空间（zh/en），随 DSH 的语言设置自动切换。

## 目录结构

```
dsh-history-question-nav/
├── package.json          # dsh.bundle.patch + dsh.client.platform = "web"
├── cordis.patch.yml      # profile 补丁层：insert 本插件行
├── lib/
│   ├── index.js          # host 半边（故意为空）
│   ├── client.js         # 浏览器半边（window.__ModuleLoader__.load 打包格式）
│   └── types/            # 最小 d.ts 声明
└── README.md
```

客户端半边只依赖外壳已提供的 `react`（无需任何 `dsh.client.external`），通过
`ctx.slots.inject("shell.overlay", ...)` 注册到布局的框架级悬浮层，用
`ctx.sessions.binding(currentId)` 读取当前会话的 conversation 快照；文案经
`ctx.locale.register("question-nav", { zh, en })` 注册并在组件里走 `t` 座位。

## 安装（发布到 npm 之后，其他用户）

```sh
dsh plugin --profile web add @known-mouse/dsh-history-question-nav
```

`dsh plugin` 会：① `pnpm add` 安装依赖；② 检测到本包声明了 `dsh.bundle`，
自动把它加进 profile 的 `bundles` 层，于是 `cordis.patch.yml` 的 insert 行自动生效。
最后重启 Web 服务并刷新页面：

```sh
dsh web
```

> 需要本机已安装 pnpm（`dsh plugin` 通过它管理依赖）。

## 手动安装（不依赖 `dsh plugin`）

1. 把本目录放到 profile 的 `node_modules` 下，并把 `@known-mouse/dsh-history-question-nav`
   加进 profile `package.json` 的 `dependencies`。
2. 把 `cordis.patch.yml` 的内容合并进 profile 的 `cordis.patch.yml`，或者直接把
   `@known-mouse/dsh-history-question-nav` 加进 `dsh.profile.bundles`。
3. 重启 `dsh web` 并刷新页面。

## 发布

1. 确认 `package.json` 里 `private` 已移除、`version` 正确、`files` 包含
   `lib/` 与 `cordis.patch.yml`。
2. 登录 npm 并**公开发布**（scoped 包必须显式 `--access public`）：
   ```sh
   npm login
   npm publish --access public
   ```
3. 发布后，其他用户即可用上面的 `dsh plugin --profile web add <包名>` 安装。

也可以不经 npm、直接从 git 仓库安装：

```sh
dsh plugin --profile web add git+https://github.com/known-mouse/dsh-history-question-nav
```

> 说明：git 方式会在安装时跑 `prepare` 脚本构建客户端 bundle，pnpm 默认拦截，
> 首次安装时按提示把 pnpm 打印的 key 加进 profile 的
> `pnpm-workspace.yaml` 的 `allowBuilds` 再重跑即可。

## 原理要点

- `shell.overlay` 是 `ui-layout` 声明的框架级悬浮层（additive list，root scope），
  本插件把面板注册进去，`pointer-events:auto` 让面板可交互，其余保持 click-through。
- 当前会话 id 来自全局标准钩子 `useSessions(s => s.current)`；会话的 conversation
  快照通过注入的 `ctx.sessions` 用 `binding(id).session` 取得，并用
  `useSyncExternalStore` 订阅。
- 问题列表来自 `ConversationSnapshot.chat`（`order` + `nodes.get(key)`），过滤
  `kind === "user"`；每个 chat 节点在 DOM 上带有 `data-chat-anchor-key`，点击时按
  该 key 找到元素，再对 `[data-conversation-scroll]` 滚动容器做平滑滚动。
