vanddccd/dsh-todo-dock

dsh-todo-dock

DSH Web 任务面板重制:三段式进度条 + 已完成折叠 + transitions.dev 动效 | Rebuilt todo dock for DeepSeek Harness

待分类deepseek-harnessdshplugin

dsh-todo-dock

DSH Web 任务面板(todo dock)重制 —— 三段式进度条 + 已完成折叠 + transitions.dev 动效体系。

A rebuilt todo dock for DeepSeek Harness: tri-segment progress rail, collapsed completed section, and motion built on the transitions.dev motion-token scale.

dsh-todo-dock


它换了什么

DSH 内置的任务面板是一条平铺列表:已完成的 6 条和待办的 1 条用同一种亮度(label-secondary)排在一起,"现在在哪"要靠逐行读。

这个插件把同一个 todos 投影重排成一条进度轨:

内置dsh-todo-dock
顶部文字「6 已完成 · 1 进行中 · 1 待处理」计数 6/8 + 三段式进度条(已完成绿 / 进行中蓝 / 待办灰)
已完成全部平铺,占 70% 高度折成一行,点开才展开明细
进行中与其它行同亮度唯一被强调的一行(label-primary + 500 字重 + 旋转环)
待办与已完成同亮度降到 label-tertiary,层级拉开但不消失
全部完成仍占满整个列表收成一行
全部待办0/8 + 一条纯灰进度线进度条整体隐去
大量任务无限增高活动区封顶滚动 + 底部渐隐

泛类,不是场景专用

组件只读每个 todo 的 { content, status }:不解析、不切分、不假设 content 里有任何结构标记(章节号、编号、前缀)。分组只用 status。

这条约束是被验证过的 —— 同一套渲染逻辑喂过 10 组完全不同的数据:开发任务、视频批改、数据回测、文案翻译、CI 长会话、纯英文、单条、超长文本、22 条、0 条。渲染分支里没有任何一处依赖任务文本的内容。

文案跟随宿主语言

不新增任何 locale key,全部复用宿主已有的 todo.title / todo.progress.done / todo.completed。插件不带字典,界面语言跟着 DSH 设置走。


动效(transitions.dev)

颜色一律取自宿主主题 token(--dsw-*),所以深/浅色自动成立;运动一律取自 transitions.dev 的 motion-token 尺度(--duration-* / --ease-* / --distance-* / --scale-* / --blur-*)。

场景transition关键参数
完成一个任务#25 checkbox check圆环描边 --check-box 150ms → 勾按真实路径长度自绘 --check-draw 350ms
状态图标三态互切#09 icon swap250ms cross-fade + blur(2px) + scale(0.25),三态同格叠放
已完成区折叠#21 accordiongrid-template-rows 0fr ↔ 1fr(无需 JS 量高)+ 内容 opacity/blur + chevron 垂直翻转
计数 6/8 变化#02 number pop-in逐位重入 500ms,末两位错开 1×/2× --digit-stagger(70ms)
进度条段宽变化#01 card resize300ms --ease-smooth-out
行入场 / 新任务加入#18 texts reveal500ms 逐行错开 40ms + blur(3px)

两处实现细节值得点名,因为它们是这套动效最容易翻车的地方:

  1. 勾的 stroke-dasharray 不硬编码 —— 运行时用 path.getTotalLength() 上取整后写进 --check-len。写死数值会预显或过画。
  2. number pop-in 重播必须强制 reflow —— 移除类 → 重排 span → void el.offsetHeight → 重新加类。少了 reflow,浏览器会把两次动画合并成一次,根本不播。

每段 snippet 都保留了自己的 @media (prefers-reduced-motion: reduce) 守卫;in_progress 环的匀速旋转是 DSH 原生视觉语言,一并保留。

有意的偏离:transitions.dev 把 token 定义在 :root,这里改定义在组件根 .dsh-td。原因是 --duration-fast 这类通用名挂在 :root 会污染宿主页面、并可能和其它插件的同名令牌互相覆盖;挂在组件根同样能沿子树继承,但作用域封闭。


安装

dsh plugin --profile web add dsh-todo-dock

插件自带 cordis.patch.yml(自激活),装完存盘即生效,不需要重启 dsh web —— 客户端插件会热重载;若未生效,刷新页面即可。

从源码构建

git clone https://github.com/vanddccd/dsh-todo-dock.git
cd dsh-todo-dock
bash scripts/build.sh                    # 默认复用 dsh checkout 里的 esbuild
# 或:bash scripts/build.sh /path/to/esbuild

scripts/build.sh 会把 client/src/index.tsx 打成 client.js(CommonJS factory,注册到 window.__ModuleLoader__),并对产物做一次自检 —— 缺少模块表注册就直接失败,避免"装上去静默不生效"。

卸载

dsh plugin --profile web remove dsh-todo-dock

或删掉 profile 的 cordis.patch.yml 里对应的 insert 块。


设计演进

这个面板不是一次写成的,两版设计稿都留在仓库里(docs/design/,双击即可打开):

  1. 01-exploration-6-directions.html —— 先并排做了 6 个方向:现状基线 / 进度轨 / 账本表 / 分组卡 / 终端行 / 折叠胶囊。每个都标注了「解决什么、代价是什么」,用来挑方向而不是挑好看。
  2. 02-v2-generic-final.html —— 选定「进度轨」之后,把它泛类化:组件只读 { content, status },用同一套渲染逻辑喂 10 种状态空间(开发步骤、视频批改、数据回测、翻译、长 CI 会话、纯英文、单条、超长文本、22 条、空列表),确认没有任何一格依赖任务文本的结构。

最终实现(client/src/TodoDock.tsx)来自第 2 版。两个 HTML 是设计稿,不是可安装产物 —— 安装走上面的 dsh plugin add。


验证

仓库自带两个验证台,跑的都是真实的组件源码 / 构建产物,不是静态稿:

页面验证什么怎么开
test/index.html组件源码 + 真实 React 18 + 真实 DOM,覆盖 10 种状态空间;主样本可点「推进一个任务」看状态流转bash scripts/build-test.sh → 浏览器打开
test/harness.html构建产物 client.js 在 dsh 模块契约下的注册 / apply / slot 替换 / 样式注入bash scripts/build-harness.sh → 浏览器打开

产物验证台在本机的实测结果(dsh 0.1.5-rc.2 / React 18.3.1):

PASS  模块注册 id 正确        dsh-todo-dock
PASS  产物导出 apply         function
PASS  slot 条目 component 被替换
PASS  样式表已注入            12485 字符
PASS  动效令牌已注入
PASS  effect 注册数 ≥ 2      dsh-todo-dock: stylesheet / dsh-todo-dock: slot shadow
PASS  原组件可还原(dispose 未即时触发)

动效就位的运行时证据(同页可复现):--check-len 由 path.getTotalLength() 实测写入为 9; 各段动效的 transition-property 分别是 stroke-dashoffset、opacity, filter, transform、transform、width; 计数动画名为 dsh-td-digit-pop。

深色主题:

dsh-todo-dock dark

装完没生效? 新增一个 host 侧 bundle 需要 dsh 重新装配一次配置树 —— 重启一次 dsh web 即可(~/.dsh/bin/dsh-web-restart.sh)。想先确认是否已进树,不用重启也能查: dsh --profile web --dump-config | grep -A1 dsh-todo-dock。

重启后想确认运行实例真的加载了:bash scripts/verify-live.sh —— 它读当前下发给浏览器的 客户端插件清单,直接回答「生效了没」,未生效时还会指出是「没重启」还是「没装进 profile」。


与其它插件共存

这个插件通过替换 conversation.input.dock 中 id: 'todo' 条目的 component 来接管渲染。如果另一个插件也在替换同一个条目(例如 dsh-ui-polish-motion 也实现了自己的 TodoDock),后加载者生效 —— 两者不会叠加,只会有一个赢。

要么只留一个,要么在 profile 的加载顺序里把想用的那个排后面。

开发

client/src/
  index.tsx          浏览器端入口:slot 条目替换 + 样式注入
  TodoDock.tsx       面板组件(泛类实现)
  icons.tsx          14×14 图标层(路径与宿主同源)
  digits.tsx         number pop-in 数字组
  motion-tokens.css  transitions.dev 运动令牌(作用域在 .dsh-td)
  todo.css           组件样式
host.js              宿主端占位(无服务逻辑)
scripts/build.sh     构建 + 产物自检
scripts/verify-live.sh  查运行中的 dsh web 是否已加载本插件

改完源码跑一次 bash scripts/build.sh,刷新页面即可看到效果。

License

MIT © 2026 VanB