dsh-viewtune
给 DeepSeek Harness 的阅读体验插件。
它把一段会话按阅读需要重新排版:一整页可读的版式、可调的磨砂与壁纸、产物与差异就地可看、长会话滚动仍然顺。 宿主原有的功能全部照旧 —— 它只增加自己的部分,不替换宿主。
它是什么,以及它不是什么
它加的三件事:
- 阅读页:把一轮对话排成一整页可读的版式(正文、流程、思考、工具、产物分别放在各自的位置),并提供阅读侧的整套操作:折叠、跳转、历史加载、正文显现。
- 一套可调的皮肤:磨砂玻璃(九个面各有两条档:透明度与模糊值)、壁纸、以及伸到对话页的同款皮肤与整页纯色模式。
- 长会话的处理:折叠窗口、按视口渲染、以及历史加载失败时的明确说明(不让按钮看起来像坏了)。
它不是:
- 不是主题替换:宿主的结构、类名、组件都还在原处;本插件增加自己的部分(阅读页)或注入样式(对话页)。
- 不是宿主的替代品:宿主自己的刻度条、差异面板、审查、产物卡、目标栏、MCP 承载都照旧存在;本插件要么沿用它们,要么在旁边增加自己的部分。
- 不猜数字:能证明的才显示(例如用量胶囊);不能证明的留空,并说明为什么。
安装
平台版本:本版按 DSH
0.2.1-rc.5构建。 客户端半边是预编译 bundle,平台导出的名字在构建时固定,因此这一份产物只服务 rc.5,不保证能在更早的 DSH 上加载。
先在插件目录里构建(依赖由宿主用 pnpm 管理):
npm run build
然后把这个目录作为插件加入宿主(--profile web 是默认的 Web profile):
# 加入(在包含 dsh-viewtune 目录的父目录里执行)
dsh plugin --profile web add ./dsh-viewtune
# 卸载
dsh plugin --profile web remove dsh-viewtune
也可以从打包好的 tarball 安装(npm run build 之后 npm pack 得到 .tgz,把上面的 ./dsh-viewtune 换成该文件即可)。
安装后重启 Host(或按宿主提示重载),随后:
- 侧栏出现 viewtune ⚙ —— 那是全部设置;
- 新会话默认落到哪一页,由「新会话默认视图」决定(出厂是阅读页);
- 出厂状态已经按读者调好的数值设置:皮肤开着、自带壁纸覆盖整窗、卡片与代码块本来就是磨砂的 —— 不必先调整任何一条。
一轮长什么样(阅读页)
自上而下:
| 位置 | 内容 |
|---|---|
| 顶部 | 工具栏(左侧会话信息、右侧 viewtune ⚙ 与收起按钮);吸顶 |
| 每轮 | 你的消息(气泡)→ 流程(思考卡、工具行、差异)→ 正文 → 产物栏(交付 / 编辑 / 提交) |
| 右侧 | 轮次刻度条:全会话每一轮都在上面,点一下跳过去;当前读到哪一轮会实时标出来 |
| 浮层 | **「回到最新」**胶囊(只有在你确实离开了尾部、且下面还有内容时才出现) |
流程可以折起来。 每轮的流程默认按需展开:顶部那条正文可以理解成"这一轮的经过",点它展开、再点收起。 「自动收起更早流程」与「自动折叠更早的轮次」分别控制流程与整轮的折叠。
折叠窗口只在"新一轮开始时"重新计算一次,阅读过程中不会重新计算 —— 所以读到一半时页面内容不会被改变。想看更早的轮次,用那一轮上方的按钮把更早的轮次显示出来。
对话页也有一套(独立开关)
- 磨砂与用户气泡跟随上面的九个档位;
- 整页纯色模式:主题底色 + 输入框上方一条淡出,与皮肤互不依赖;
- 宿主自己的「回到底部」按钮与阅读页的「回到最新」胶囊共用一个下限:旋钮调到最低也不会把它调到看不见。
阅读页的分区说明
正文与 Markdown
正文按块渲染,支持标题、列表、表格、引用、任务列表、脚注与围栏代码块;代码块的语言标签、行号与复制按钮沿用宿主的结构。 Markdown 一侧做了三件自己的事:
- CJK 强调:中文标点与中英混排下的
**粗体**/*斜体*判定按实际排版需要处理(markdown/cjkFriendlyStrong.ts); - 数学公式:KaTeX 渲染,并在语法不兼容时不至于整段失败(
markdown/katex.tsx、mathCompatibility.ts); - 增量解析:流式追加时只重解析变化的部分(
markdown/incremental.ts)。
思考卡
一轮里的思考内容渲染成一张卡片:
- 跟随方式可选:跟随最新(连续跟随)/ 手动;跟随时的速度可调;
- 暂停跟随 / 跟随最新按钮就在卡片底部;在卡片里滚动、选中文字都会暂停跟随,回到最新之后继续;
- 焦点思考展开:正在被写入的卡片会展开显示内容;滚动卡片内部会保持它展开(视为你在读它);
- 可滚动阅读:内容超出卡片高度时可以单独滚动,不影响整页位置。
工具行与差异
工具调用渲染成一行(名称、参数摘要、状态、耗时),展开后是输入、结果与结构;失败、被中断、被拒绝分别有各自的标记。 差异单独渲染:就近展开的面板、行内差异、行号槽,以及「增删 N 行」的统计按钮。
流程折叠与收起按钮
- 「自动收起更早流程」:较早的轮次默认收起流程,只显示正文;
- 「自动折叠更早的轮次」:只渲染最近若干轮,更早的轮次由轮次上方的按钮取回;
- 「收起按钮的行为」三选一:收起上一轮 / 收起全部 / 默认(
collapse-mode.ts),工具栏与快捷键共用这一个行为。
正文显现
- 「逐词显现」:新到的正文按词显示,而不是整段跳出;
- 「逐词显现的模糊」:显现过程中带模糊(出厂关闭 —— 逐帧重绘的代价由读者自己权衡后决定);
- 「正文更新节奏」:跟随显示刷新率,或固定 60 次/秒(出厂为跟随显示)。
原生行与 MCP
- 上下文注入行:宿主记录的上下文(系统提示、召回内容等)按宿主原生的行样式渲染;
- 工具行与提问卡:与宿主同一套结构,本插件只负责在阅读页里排好它们;
- MCP 应用框:MCP 应用(如网页类工具)在自己的框里运行,框的高宽可调,消息通道与宿主一致。
目标栏、工具栏与快捷键
- 目标栏:输入框上方那条目标提示沿用宿主的结构,本插件只调整它的显示方式;
- 工具栏:吸顶的一条,左侧会话信息,右侧 viewtune ⚙ 与收起按钮;
- 快捷键:本插件自己有两个绑定(收起当前轮、收起全部),在设置面板的快捷键页录制;
- 竖条滚轮:把鼠标滚轮用于滚动条形状的区域(例如横向列表)时的手感处理。
设置存放在哪
设置保存在宿主的插件记录里(/better-display/settings),由宿主负责落盘:
- 只写读者改过的键:没有改过的项不进记录,缺的键用出厂值;
- 读取方式是防御式的:早期版本写下的记录缺少后来新增的键时,每一项都按"缺失即出厂值"处理,不会因为升级而丢掉读者的选择;
- 整条覆盖写:每次改动 PUT 整份记录(详见「已知限制」里的代价说明)。
它适合谁
- 想按阅读的方式看会话:一次读一轮、流程按需展开、产物与差异就地可见;
- 会话很长:需要折叠、跳转与历史加载,并且希望滚动保持顺;
- 想统一阅读页与对话页的外观:磨砂、壁纸、整页纯色;
- 愿意为确定性接受一些限制:能证明的才显示、加载失败会明确说明、做不到的事直接说做不到。
它可能不适合:
- 想要一个主题/皮肤包(它附带皮肤,但主要工作在阅读页的排版与交互上);
- 想要完全英文界面(插件自身文案目前是中文,见「已知限制」);
- 想要替换宿主自己的刻度条、差异面板或产物卡(本插件沿用它,或在旁边增加自己的部分)。
产物与差异
- 产物展示方式:简略 / 平衡 / 卡片三档,决定一轮末尾的产物画成什么;
- 交付气泡:点一下按「产物用侧边栏打开」的开关决定去宿主审查还是外部打开;
- 差异:就地展开的差异面板 + 行内差异;「差异在侧边栏审查」可把它交给宿主自己的审查面板;
- 提交:开了「记录 git 提交」后,一轮末尾会多出「提交」那一栏气泡(消息作标签、短哈希进悬停、点击复制)。
历史与刻度条(与宿主的差别)
先说清楚哪些是宿主的:宿主的对话页本来就有轮次刻度条,它同样列出整个会话的轮次,也一样能加载更早的记录。 本插件在阅读页里也提供一条,并在这几处不同:
| 情况 | 本插件怎么做 |
|---|---|
| 目标轮次被折叠起来了 | 会先把折叠窗口扩大,让那一行存在,再跳过去(窗口没扩大时没有那一行,跳过去也没有可定位的目标) |
| 历史加载失败或没有取回 | 界面上明确说明(「没能读到更早的记录…」),而不是没有任何反应 |
| 宿主说没有更早了 | 显示「已经是最早的记录。」 |
| 某个区域取不回来 | 提供一个明确标注代价的备选操作:「一次性加载到最早(较慢)」 —— 取回整段历史,渲染仍然只渲染需要渲染的部分 |
| 短到根本不能滚动的会话 | 「回到最新」不会出现(它没有"下面"可回) |
有一种"取不回来"不属于本插件:宿主的事件馈送会被某个"撤回物化目标"的事件打断,此后任何历史页都取不回来,而且它总是从窗口最旧处连续往回取,所以换个目标也躲不开。这一条已经作为缺陷提交给 DeepSeek Harness 本体(含完整的栈、推导与复现步骤);本仓库不复制那份正文,因为它描述的是宿主的行为,而非本插件的实现。
性能:长会话为什么不卡
三层,各自有明确的分工:
- 折叠窗口限制渲染多少轮(提交式,见上);
- 按视口渲染:远离视口的轮次交给浏览器跳过布局与绘制(
content-visibility),占位高度只声明块轴(宽度不设,否则轮次里的控件会被压窄)。正在写作的那一轮豁免 —— 思考卡的自动跟随发生在卡片自己的子树里,被包含就会失灵; - 刻度条的读数是对已渲染行的二分查找(对数级),并且只在窗口真的变化时重测。
代价写在明处:一轮第一次进入视口时,浏览器要计算一次它的布局(大轮次里还有卡片、差异块与思考区)。这一点只能减小,不能消除,改用 JS 做同样的按视口渲染也不能消除。
设置面板
打开方式:工具栏右侧 viewtune ⚙。分三页(键盘用方向键 / Home / End 切换):
- 视效:动效、磨砂玻璃(九个面)、壁纸;
- 功能:小功能(产物打开方式、产物展示方式、新会话默认视图、记录 git 提交、差异在侧边栏审查、自动收起更早流程、自动折叠更早的轮次、收起按钮行为)、正文显示(逐词显现、模糊、节奏)、流程展示设置(跟随到最新、思考卡自动跟随、速度、焦点思考展开);
- 快捷键:本插件自己那两个绑定的录制。
每一行、每一个初始值、以及九个磨砂面分别管什么,都在 docs/settings.md。
一条排版约定:加一个偏好就是加一行,加一类就是加一页 —— 工具栏不会因此变宽;只有标签说不清的选项才带说明,那句说明挂在该行的 title 上(浏览器原生提示框),平时不占版面。
「说不出来的时候」它怎么做
- 状态写进行里:被系统覆盖、录制中、组合被拒这类状态,直接写在对应那一行;
- 缺数字就不显示:宿主只在能精确证明某轮计费数据时才给用量,本插件跟随这一取向,不猜、不估算、也不为它加标记;
- 不谎报成功:历史加载的提示只在确定时出现(宿主明确说没有更早的内容,才会显示"已经是最早");做不到的事直接说做不到。
已知限制
- 中文界面:宿主提供了本地化接口,本插件借它翻译了宿主的两条文案,但插件自身的文案没有做本地化 —— 完整的 zh/en 需要先给插件加一个 locale provider 边(要重启 Host)。这是取舍,不是遗漏。
- 设置是"整条覆盖写":没有版本号也没有时间戳。两个窗口同时改设置时,后写的会静默覆盖先写的。这份记录是一个读者自己的偏好,所以接受;要修改就得引入版本号与冲突策略,比它保护的内容更复杂。
- 读取回来的那几百毫秒里做的改动会被回滚:设置是启动时读一次;在它返回之前改动发不出去,随后到达的记录会覆盖它们。
- 观测属性:若干
data-reader-*/data-ud-check在插件自身代码里没有读取方,它们是工作区脚本(render-dump.mjs、feature-matrix.mjs等)的观测入口,也是"运行一次真实页面并检查状态机走到哪一步"的唯一方式。 - 平台侧的一处缺陷(上文的"取不回来"):插件只能绕过它,不能修它。
开发
构建
npm run build # 打包客户端与服务端
改代码
客户端模块地图(src/client/):
| 文件 | 管什么 |
|---|---|
Reader.tsx |
阅读页本体:一轮的排版、折叠、按钮、历史交互 |
ReasoningCard.tsx |
思考卡:跟随、阅读、焦点、展开 |
motion.tsx |
动效与跟随:滚动接管、锚点补偿、节奏 |
SettingsMenu.tsx / store.ts / settings-sync.ts |
设置面板、状态、与宿主记录同步 |
deliverables.ts / DiffPanel.tsx / ToolActivity.tsx |
产物、差异、工具行 |
TimelineRail.tsx / projection.ts / turn-fold.ts |
刻度条、流程投影、折叠窗口 |
glass.ts / conversation-glass.ts / composer-glass.ts / app-backdrop.ts |
磨砂档位与注入样式 |
markdown/ / native/ |
Markdown 渲染(含 CJK、KaTeX、增量解析)、宿主原生行 |
reader-*、turn-*、text-cadence.ts 等 |
阅读行为的小模块(每个文件开头都写清它为什么存在) |
验证
npm run build
node ../verify-types.mjs # TYPES OK
node ../run-plugin-tests.mjs # 用例
node scripts/guard/run.mjs # 产物守卫(不变式)
node scripts/guard/negcheck-shape-markers.mjs # 反向自检(变异必须被守卫拒绝)
四条必须全绿才提交。 守卫里每一条 marker 都对应一个曾经真的出过的 bug;反向自检会把产物改成"坏掉的样子", 要求守卫确实变红、并要求产物逐字节还原 —— 它自己也被修正过两次(变异被压缩器抹平、搜索串写成了源码形态), 这两件事都记在 CHANGELOG 里。
版本策略
- 打过 tag 的版本字节固定:同一版本号不对应两份不同的字节,所以任何改动都会起新版本号;
CHANGELOG.md按时间顺序写,并且把**"先做错、后撤回"**的条目也留在里面(写明原因)—— 因为"为什么这条路走不通"本身是信息;- 一个提交只做一件事:修 bug 与精致化分开,一次只动一项,这样定位回归时不必先撤精致化。
出处与许可
MIT,见 LICENSE。
起点是 aa2246740/dsh-better-display(MIT):阅读标签、流式动效与 Markdown 渲染来自那里;此后本插件与它已经大不相同。
呈现与 Markdown 的一部分来自 DeepSeek Harness(MIT)。动效参考 Transitions.dev。
随包自带的默认壁纸 assets/sample-gradient.png 为本仓库制作,与代码同用 MIT。
No comments yet. Be the first to write one.