dsh-open-at-last-input
打开已结束的 DSH 会话时,视口落在你最近一条本人发言处,而不是页面最底部;正在运行的会话仍跟到底。
- 纯客户端:不碰文件、凭据、子进程、网络;宿主半部只有空入口。
- 安装:
dsh plugin add github:xinming-chenniang/dsh-open-at-last-input(npm 尚未发布)。 - 失效边界:DOM 契约或
sessions.list形状变化时静默不生效,但不会把界面带崩。
背景与出处
- 出处:2026-10-20 用户反馈「切换到一个已完成的会话时,窗口默认停在最低端;我需要从我的最近一条发言那里看」。首版做成 asar 补丁(
D:\dsh\tools\host-open-at-last-input,2026-10-21 已验收通过);2026-10-21 因「要发布给别人用」改做成客户端插件(本包)。 - 是什么:一个纯客户端 DSH 插件。打开已结束的会话(首次进入或从别的会话切过来)时,把聊天视口落到你最近一条本人发言(
user/steering行)的顶部,上方留 24px;正在运行的会话仍跟到底部。 - 状态:唯一实现(2026-10-21 22:01 起)。asar 补丁已于 22:01 回滚(原厂
182,794,869 B/ SHA25647874618BF5B…),现在这个落点只有插件在做。此前 2026-10-21 已确认它独立生效:调试日志(window.__DSH_OPEN_LAST_INPUT_DEBUG__ = true)里出现切换/打开会话 <sid>→到位 <sid>,证明是插件自己找行、对齐、稳定住的。日志里可能先出现一行等不到本人发言行,那是会话刚切换、行还没挂载时第一次 tick 的落空,60ms 后重试即到位(重试路径在起作用,不是故障)。 - 处置:保留。失效条件很窄:DOM 契约(
[data-conversation-scroll]/[data-chat-flow-kind])或sessions.list快照形状变了 → 插件静默不生效,但不会把界面带崩(所有读取都在 try 里)。 - 关联:
D:\dsh\tools\host-open-at-last-input(asar 补丁版,同一需求的第一实现)·D:\dsh\dsh-closeout-button(同形态的客户端插件样板,sessions.list的用法就是从它抄的)·~/.dsh/profiles/desktop/package.json(注册点)。
为什么改成插件
asar 补丁能跑,但三个代价让它不适合发布:锚点绑死 0.2.1-rc.2 的 client.js、只支持 Windows(apply.ps1)、每次升级都要重打。插件不碰二进制,可装可卸。
能做成插件的依据(都来自 0.2.1-rc.2 的源码,不是猜的):
| 需要的能力 | 依据 |
|---|---|
| 知道「当前是哪个会话」 | sessions.list.getSnapshot() → { current, byId }(dsh-closeout-button 已在用) |
| 拿到滚动容器 | ChatViewport.attach() 里 list.closest("[data-conversation-scroll]") ?? list |
| 找到「本人发言」行 | 原生 scrollToLastInput 用的同一个选择器:`[data-chat-flow-kind="user" |
| 不和原生抢 | 原生的落点只在首次挂载跑一次(content.current.opened),切会话时不滚动 —— 空出来的这段正是我们可以补的 |
| 用户一插手就收手 | 原生把 wheel/touch/keydown 当「读取意图」;插件只在自己那 ~800ms 的落点窗口里监听,触发即放弃 |
行为
| 场景 | 打开后的落点 |
|---|---|
| 已结束会话,之前没读过 / 上次停在底部 | 最近一条本人发言的顶部 ← 本插件 |
| 已结束会话,上次停在中间 | 本插件会把它拉到最近一条本人发言(原生「记住中间位置」的语义在这里让位;若是插件的目标,就按插件来) |
| 正在运行的会话 | 跟到底部(插件读到 running 就跳过) |
| 没有本人发言的会话 | 保持在原位,不动作 |
安装
npm 已发布 dsh-open-at-last-input@0.1.0:
dsh plugin add dsh-open-at-last-input
也可以用 GitHub 源 dsh plugin add github:xinming-chenniang/dsh-open-at-last-input,
或 Release 资产 dsh-open-at-last-input-0.1.0.tgz。
装 / 卸
装(本机已完成,记录做法):
# 1) 包本体在 D:\dsh\plugins\dsh-open-at-last-input
# 2) profile 的 package.json 加依赖 + 加进 dsh.profile.bundles(后者才是"真正装载"的开关)
# "dsh-open-at-last-input": "link:D:/dsh/plugins/dsh-open-at-last-input"
# 3) node_modules 里建 junction(或 pnpm install)
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-open-at-last-input" -Target 'D:\dsh\plugins\dsh-open-at-last-input'
卸:插件管理器里 remove,或删掉 dependencies 与 bundles 里的两行、删 junction,重启。
怎么确认它活着
重启应用后按 Ctrl+Shift+I 打开控制台,应看到:
[dsh-open-at-last-input] 已启用:打开已结束会话时落在你最近一条发言处
想看每次落点的调试日志,先在控制台执行一次:
window.__DSH_OPEN_LAST_INPUT_DEBUG__ = true
之后切会话会打印 切换/打开会话 <sid>、到位 <sid> 或 用户插手,放弃本轮 <sid>。
兼容性
| DSH 版本 | 结果 |
|---|---|
0.2.1-rc.2 |
已测:client entry active;npm test 8/8;滚动行为未在其它版本复验 |
| 其他版本 | 未测;package.json 只声明 engines.dsh >= 0.2.0-rc.2 |
已知边界(没做,别当已做)
- 运行中会话不处理:靠
byId[sid].running(或busy/state/status)判断;宿主字段名若变,最坏是「运行中也被拉到你上一条发言」,不会崩。 - 懒加载引起的位移:用 60ms × 10 步(≈600ms)跟踪;若图片/代码块在这之后才撑开,位置会偏。原生补丁版有同样的边界。
- 首次启动就打开会话:会同样触发一次落点,这是有意的(重开应用时你也想从自己的发言接着看)。
2026-10-21 修复:开关右侧栏也会定位
现象:点开一个会话后马上打开右侧栏,聊天区会再跳一次到最近那条发言。
根因:落点有一个约 600–840ms 的跟踪窗口。窗口期内开/关右栏会改变聊天区尺寸,行的 getBoundingClientRect() 随之变化,跟踪器把这个变化当成「内容还没稳定」而重新对齐 —— 看起来就是「打开右栏也会定位」。
三处修复(都在 lib/client.js):
- 视口尺寸变了就放弃本轮:记下落点开始时的
scroller.clientWidth/clientHeight,后续 tick 一旦发现尺寸变化,直接detach()。面板开合/拖分隔条/缩放窗口都属于「用户在看别的东西」,不该被拉回。 - 多滚动容器取最宽的那个:右栏/抽屉里也可能挂一个窄的会话视图;窄于
MIN_SCROLLER_WIDTH(320px)的不认作主视图,避免把预览面板当成聊天区去滚。 - 跟踪窗口收窄:
MAX_STEPS14 → 10(≈600ms),SETTLE_PX1 → 2(吸收亚像素抖动),减少与用户操作的窗口重叠。
判据:改完后「点会话 → 立刻开右栏」不再跳;懒加载把位置顶偏时仍会在 600ms 内校正回来(两者不冲突:前者改的是 clientWidth/Height,后者只改 scrollHeight)。
No comments yet. Be the first to write one.