# dsh-conversation-nav 需求分析 > Codex 桌面版风格对话轮次导航 + 时间显示 + 轮次标记。 > 本文档基于两个参考实现的源码级分析(`_refs/` 下有副本)与 DSH rc.2 官方客户端源码核实。 ## 0. 参考实现对比结论 | 维度 | kelearns/dsh-navigation-bar ★1 | cokiscarazo-rgb/dsh-plugin-message-timeline-navigation ★1 | |---|---|---| | 数据源 | 数据驱动:`ctx.sessions.binding(id).session` 快照(useSyncExternalStore) | DOM 驱动:抓取 `[data-time-hover-root]` 行 | | 挂载 | 官方 slots 服务:`shell.overlay`(点击穿透层,刻度区自行恢复 pointer-events) | 裸 `createRoot` 挂 `document.body` | | 布局 | 固定 pitch 10px 垂直居中聚簇,溢出压缩至 min 6px;hover 阶梯 26/20/14/10 | 按 index 均分导轨高度 | | tooltip | 用户消息 1 行省略 + 模型回复 3 行 clamp(宽度模型预算截断,CJK=1 拉丁=0.5) | 仅消息纯文本 | | 性能 | 事件驱动(scroll/resize/ResizeObserver/快照订阅),无常驻轮询;注释明确指出全文档 MutationObserver 会烧掉渲染进程 50% 核 | 900ms setInterval + 全子树 MutationObserver(重) | | 跳转 | scrollIntoView smooth | 顶对齐 scrollTo + 目标行短暂 outline 高亮 | | current 判定 | 首个 `row.bottom > scrollport.top + 8` 的 entry | scrollport 顶边落在哪个轮次区间 | **结论**:以 kelearns 版为主架构蓝本(数据驱动 + slots 挂载 + 事件驱动),吸收 cokiscarazo 版的跳转高亮与"顶边即当前轮"语义。本插件完全自写代码,不复制两方源码(均为 MIT 但需保留署名的部分仅限思路参考)。 ## 1. 官方机制核实结果(DSH 0.1.1-rc.2 源码) - **时间数据存在**:用户消息节点 `node.data.time`(epoch ms,源自会话事件)。官方渲染见 `dsh-client-ui-conversation/lib/client.js` `UserMessageNodeView` → `MessageIconActions({ time: data.time, clock: "start" })`。 **无需 DOM 抓取**,快照直接可取。 - **时间格式**:官方 `formatMessageClock`——当天 `HH:mm`;当年 `M月d日 HH:mm`(`clock.md` 模板);更早 `yyyy/m/d HH:mm`(`clock.ymd` 模板)。我们复刻同样的三段格式(无 locale seat 时硬编码中文模板,后续可接 locale)。 - **跳转锚点存在**:每条会话行 `div[data-chat-anchor-key=""]`(同节点还带 `data-chat-flow-kind`)。滚动容器 `[data-conversation-scroll]`。 - **槽位**:`shell.overlay`(root 作用域 list 槽,框架级浮层,默认点击穿透)。standardProps 提供 `useSessions`(可取当前会话 id)。注册:`ctx.slots.inject('shell.overlay', …)` → `ctx.slots.register({ name:'shell.overlay', id, order }, Component)`。 - **快照形状**(kelearns 实测):`snapshot.chat.order`(key 数组)+ `snapshot.chat.nodes.get(key)`;节点 `kind` ∈ `user | steering | assistant-step | …`;用户节点 `data.content`(blocks 数组)、`data.time`、`location.turn.turn`;`assistant-step` 节点 `data.blocks` / `data.turn`。 - **当前会话 id**:slot props `useSessions((s) => s.current)`。 ## 2. 功能需求 ### F1 轮次导轨(完全还原 Codex 交互) - F1.1 对话区最左侧一条垂直导轨,**每条用户消息一个刻度**(steering 消息也计入,与 kelearns 一致)。 - F1.2 非悬浮态:全部刻度最短(6px);当前阅读中的轮次刻度深色高亮(随滚动实时更新,判定以 scrollport 顶边所在轮次为准)。 - F1.3 悬浮态:被悬浮刻度伸长(26px)并变色,上下相邻刻度呈三级阶梯衰减(20/14/10px),第 4 个及以外回到基长;边缘自然截断。 - F1.4 悬浮显示详情卡片:用户消息首行(单行省略)+ 模型回复摘要(最多 3 行)+ **发出时间**(见 F2)+ **标记控制**(见 F3)。 - F1.5 点击刻度平滑滚动跳转至对应消息,顶对齐,目标行短暂高亮(约 1.6s outline)。 - F1.6 密集对话时刻度间距自动压缩,所有刻度始终可见(不随滚动跑动——导轨是相对会话的固定索引列)。 - F1.7 明/暗主题自适应(`body[data-ds-dark-theme]` + `prefers-color-scheme` 双通道)。 ### F2 时间显示(新需求:用户发出时间) - F2.1 悬浮详情卡片顶部显示该轮用户消息发出时间,格式与官方一致(当天 `HH:mm`;当年 `M月d日 HH:mm`;跨年 `yyyy/M/d HH:mm`)。 - F2.2 数据源:快照用户节点 `data.time`。缺字段时优雅降级为不显示(不显示占位符,避免误导)。 - F2.3 时间只读展示,不参与导轨视觉(不做时间轴按比例分布——Codex 原型是等距索引,保持还原)。 ### F3 轮次标记(新需求:打标 + 导轨异色) - F3.1 每条刻度可打/取消标记。交互:悬浮详情卡片上的「标记」按钮切换;另提供右键刻度快捷切换。 - F3.2 已标记刻度在导轨上以**不同颜色**显示(默认琥珀色 `#f59e0b` 系,明暗主题各一套),非悬浮态同样生效,优先级高于 active/hover 配色但低于 hover 伸长动效(即悬浮时仍然伸长,只是颜色保持标记色或更深)。 - F3.3 标记按会话持久化:`localStorage["dcn:marks:"]` = `{ [anchorKey]: { color, markedAt } }`。刷新/重开浏览器不丢;切换会话各自独立。 - F3.4 数据结构预留 `color`/`note` 字段,v1 只做单色标记 + 无备注,v2 可扩展多色/备注(悬浮卡编辑)。 - F3.5 悬浮详情卡片中可见该轮标记状态并可切换;标记操作有即时视觉反馈。 - F3.6 会话 id 变更(切会话/删会话)不影响其它会话的标记;孤儿数据自然留存不清理(体积可忽略)。 ### F4 边界与降级 - F4.1 无会话/无用户消息/scrollport 未挂载 → 导轨整体隐藏。 - F4.2 流式输出期间:用户消息集合不变则不重排导轨(kelearns 的 entries-signature 策略);新用户消息到达时平滑追加刻度。 - F4.3 快照 `chat.order/nodes` 缺省时回退 `snapshot.nodes` 数组路径(kelearns 已验证的双路径)。 - F4.4 渲染异常兜底:组件级 try/catch,panic 时显示小红条报错而不是白屏崩掉整个 overlay 槽。 ## 3. 非功能需求 - NF1 **性能**:事件驱动(scroll 捕获 + resize + ResizeObserver + 快照订阅 + entries 签名变化),无常驻定时器、无全文档 MutationObserver。刻度行 DOM 引用缓存并校验 `isConnected`(虚拟滚动会回收重建行)。 - NF2 **纯客户端插件**:host 半面 no-op,`dsh.bundle.patch` 声明装配行;本地开发 `dsh plugin --profile web add link:`,也可用 dev_inject_plugin 热注入。 - NF3 **生命周期可逆**:slots 注册/CSS 注入/DOM 监听全部在 disposer 中清理,卸载即净。 - NF4 **无障碍**:刻度 `aria-label` 为用户消息摘要;`prefers-reduced-motion` 下关闭过渡动画;tooltip `pointer-events:none` 不挡内容。 - NF5 **z-index 纪律**:低于官方 popover 层(≥100),刻度层用 ~50/60,永不遮挡菜单弹窗。 ## 4. 架构决策(AD 摘要) | # | 决策 | 理由 | |---|---|---| | AD1 | 数据驱动(sessions 快照)而非 DOM 驱动 | 时间戳、turn 归属、steering 区分都只有数据层可靠;DOM 抓取拿不到 epoch 时间 | | AD2 | `shell.overlay` 官方槽位挂载 | 与宿主同生命周期,replaceRisk none,点击穿透语义现成 | | AD3 | 标记存 localStorage 而非 host 持久化 | v1 纯客户端零 host 代码;数据量极小;后续如需跨设备再升级 host 存储 | | AD4 | 手写单文件 client bundle(无构建步骤) | 参考 kelearns:DSH 客户端模块走 `window.__ModuleLoader__.load`,无 TS/JSX 转换,免去构建链 | | AD5 | 等距索引导轨,不按时间比例分布 | 还原 Codex;时间仅作为详情信息展示 | ## 5. 里程碑 - **M1 导轨还原**:F1 全部(布局/阶梯悬浮/tooltip 双行/跳转高亮/active 追踪/主题)✅ v0.1.0 - **M2 时间显示**:F2(tooltip 时间行 + 降级)✅ v0.1.0 - **M3 标记系统**:F3(tooltip 按钮 + 右键切换 + 异色渲染 + localStorage 持久化)✅ v0.1.0 - **M4 打磨**:F4/NF 全量、离线测试页(参考 kelearns test/index.html + UMD React)、README/截图 ## 5.1 v0.2.0 迭代(P0 修复 + P1 增强,2026-08-23) **P0 正确性** - P0-1 ✅ 虚拟滚动跳转:行未挂载时按索引比例先滚到大致位置,120ms × 12 次有界重试直至行挂载后精确定位 - P0-2 ✅ tooltip 视口 clamp:中心限制在 [110+8, innerHeight-110-8],上下边缘刻度不再飞出屏幕 - P0-3 ✅ 标记 key 稳定性:优先 node.key,缺省回退改为发送时间指纹 `t`(epoch 不可变,位置 seq 会漂移);同 key 去重加 `_` **P1 体验** - P1-4 ✅ 多色标记 + 备注:琥珀/红/蓝/绿四色(明暗主题各一套);tooltip 色点选色 + 备注输入框;右键刻度循环 无→琥珀→红→蓝→绿→无;v0.1 标记数据自动迁移 - P1-7 ✅ tooltip 轮次指标:assistant-step 的 timing(耗时)与 usage(output tokens)按轮次累加,meta 行显示 `19:04 · ⏱ 12.3s · 1,234 tok` **待做**:P1-5 标记导航(上/下一个标记跳转)、P1-6 时间轴模式、P2 工程打磨 ## 6. 命名与元数据(暂定) - 包名:`dsh-conversation-nav`(发布时可加 scope,如 `@jaeger/dsh-conversation-nav`) - 仓库目录:`E:\deepseek-harness-learn\plugins\dsh-conversation-nav` - License:MIT