dsh-claude-style 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +3 -10
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/docs/FEATURES.en.md +2 -3
  5. package/docs/FEATURES.md +2 -3
  6. package/docs/SETTINGS.en.md +5 -3
  7. package/docs/SETTINGS.md +5 -3
  8. package/docs/STYLE.md +48 -31
  9. package/docs/decisions/D11-private-routes.md +2 -2
  10. package/docs/decisions/D12-fail-fast.md +1 -1
  11. package/docs/decisions/D22-search-panel.md +1 -1
  12. package/docs/decisions/D32-chat-interactions.md +2 -2
  13. package/docs/decisions/D41-scroll-owner.md +1 -1
  14. package/docs/decisions/D45-testing.md +1 -1
  15. package/docs/decisions/D50-brand.md +1 -1
  16. package/docs/decisions/D53-usage-merge.md +2 -1
  17. package/docs/decisions/D54-peakrate-meter.md +1 -1
  18. package/docs/decisions/D58-redraw-retired.md +2 -2
  19. package/docs/decisions/D62-file-row-merge.md +28 -0
  20. package/docs/decisions/D63-turn-status-retired.md +29 -0
  21. package/docs/decisions/README.md +3 -2
  22. package/lib/assets/2c01384360ac.js.map.br +0 -0
  23. package/lib/assets/abf0547f08a4.js.br +0 -0
  24. package/lib/assets/ba68f60ecc71.js.map.br +0 -0
  25. package/lib/assets/e1291f79b7c9.js.br +0 -0
  26. package/lib/assets/manifest.json +8 -8
  27. package/lib/client.js +8 -8
  28. package/lib/client.js.map +4 -4
  29. package/lib/host/peakrate.js +14 -4
  30. package/lib/host/routes.js +28 -6
  31. package/lib/host/search.js +7 -3
  32. package/lib/host/session-events.js +29 -8
  33. package/lib/host/settings.js +1 -1
  34. package/lib/host/usage-ledger.js +8 -0
  35. package/lib/host/usage-summary.js +5 -5
  36. package/lib/host/usage.js +3 -2
  37. package/lib/model-descriptions.json +42 -22
  38. package/package.json +8 -4
  39. package/docs/decisions/D23-turn-status.md +0 -27
  40. package/lib/assets/2fb22164cb7e.js.map.br +0 -0
  41. package/lib/assets/5b286686ae65.js.br +0 -0
  42. package/lib/assets/abdc076dfd70.js.map.br +0 -0
  43. package/lib/assets/b7b1e2f247e3.js.br +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,106 @@ All notable changes to `dsh-claude-style` are documented here, newest first.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.13.2] - 2026-10-11
8
+
9
+ [中文](#cn-0.13.2) | [English](#en-0.13.2)
10
+
11
+ <h3 id="cn-0.13.2">新增功能</h3>
12
+
13
+ - 设置页「侧栏」多了一行「左侧栏布局」:「宽松」是现在的样子,「紧凑」把左侧栏的间距收小——上侧各行(新会话、插件、自动化任务,以及搜索框独立成行时的那一行)到工作区之间的空隙、工作区标题自身的上下留白、对话行的高度与行间距、两个分区之间的间隔,以及账号区的内边距与它那一行的上下边距。控件的形状、字号、图标与配色不变。新增一项偏好 `sidebarLayout`(默认 `relaxed`),把它解析到 `<body data-dsh-claude-sidebar-layout>`;宿主半边由共享的偏好表生成,因此该键也会出现在 DSH 的设置文档里。
14
+
15
+ ### 体验优化
16
+
17
+ - 同一个文件在一段过程里被改了几次,聊天区就并排列出几行,改动越密集越难看出这个文件一共改了多少。现在同一个文件在同一个过程组里的多次编辑与写入读成一行:这一行是这一组里最早的那一次改动,行尾的 `+n -m` 是这些改动的累计值,每到达一次改动就滚动一次,展开这一行读到的是全部改动;只改动过一次的文件保持原样,失败与中断的行仍各自保留裁决信息。
18
+ - 「模型正在做什么的那一行」在跟随末尾时停在输入框上方:这一行的固定此前挂在「轮次状态行」这一个开关上,而它属于运行中的这一行本身。现在它随「聊天区动画效果」这一开关,与这一行的说法、扫光同一处;读者自己接管滚动,或跟随的滑行正持有位置时,它照旧让开,回到文档位置随内容走。
19
+ - 首页用量面板的用量热力图,格子悬停提示原本写的是那天的消息条数,现在改成那天的 Token 用量(「9月9日 — 15.9k Token」),与格子深浅取自同一个数字;两份数据来源都带每天的 Token 合计,提示不再随来源换口径。
20
+ - 侧栏搜索框选「独立在品牌行下方」时,那一行自己还留着 8 像素的左右内边距,盒子因此比下面的「新会话」等行窄 16 像素;现在这一行不再留内边距,盒子的左右边框与下面各行的宽度落在同两条边上。
21
+ - 侧栏「新会话」一行右侧的快捷键提示原本是等宽字体的普通文字(Ctrl + N),现在与搜索框的按键同一套样式:每个键一个 20 像素圆角键帽、1 像素描边、12 像素次级文字,两个键之间的「+」不再出现。
22
+ - 样式表里写死的颜色改读令牌:强调色按钮上的文字取填充自己的前景令牌(原先写死纯白,换成浅色填充后对比度没有保障),设置页里插件图标的底色取主文字令牌(原先写死十六进制)。docs/STYLE.md 的形状一节补上实际使用的圆角与描边,并写明它们来自被复制的那枚宿主控件。
23
+
24
+ ### 问题修复
25
+
26
+ - 会话很多时,用量汇总与内容搜索读日志会让宿主进程持续占住一个核心:它们经 `sessionQuery.readSession` 按 id 读,而这一版会为了定位一个 id 先把整份会话存档列一遍(列一次要打开并解压每个存档的头部帧),改动过的会话越多就越慢,首次运行或任何一次会话变动都按变更个数叠加。现在两处都改走宿主的观察接口 `sessionQuery.observeSession`,它只观察这一份存档、不先列档,读 N 个会话就是 N 次观察、零次列档(观察的租约在事件到手后立即释放);只发布 `readSession` 的宿主退回按 id 读,仍然照常工作。按大小与修改时间的增量缓存不变,没有变动的会话照旧跳过,用量数字与负载结构不变。
27
+ - 删除会话的请求体超过上限时,客户端原先只看到连接被重置;现在先答 400,这条应答送出之后再结束连接。
28
+ - 成本计量账本里出现不是真实日期的键时,合并原先按字符串把它当成最新的一天,本地折算出的所有日子与它们的会话一起消失,首页模型图表还会读到一个非法日期而报错;现在这类键在读取时被跳过,其余日子照常合并。
29
+ - 机器上没有成本计量插件时,用量汇总的来源原先报成 cost-meter+local;现在报 local,与「哪个来源作答」的定义一致。
30
+ - 峰谷目录的 24 小时定时器原先在页面问过一次之后一直拉取到进程退出,设置里关掉峰谷功能也停不下来;现在连续 48 小时没有请求就自己停下,下一次请求再启动。
31
+ - 宿主没有网页服务器服务时,插件原先静默地一条路由也没注册,卸载时又按每一条记一次失败;现在报告一次缺少的服务,路由的撤销表里只留真正的撤销函数。
32
+ - 会话内容检索里,整份列举取代在途的尾扫时,旧的一轮结束会把新一轮的状态一起清掉,于是可以再起第三轮并发地读同一批会话;现在只有仍然是最新的一轮才清状态。
33
+ - 关闭状态的弹层卡片原先只把不透明度降到 0,卡片里的按钮仍然留在 Tab 顺序里,键盘读者会停在完全看不见的行上;现在关闭的卡片同时移出绘制、命中与焦点顺序,开关的淡入淡出照旧。
34
+ - 进行中那一行:皮肤把宿主自己的文字藏起来之后,画的替身又标着「装饰」,读屏在这一行上收不到「模型正在做什么」「暂未响应」和计时;现在替身是这一行的可读文字,宿主自己的实时区域照旧播报。
35
+ - 把「动画效果」设为减弱之后,工作强度滑杆顶档的点阵仍按运行时写下的动画时长闪一次;现在这一档不写动画的周期与相位,点阵静止。
36
+ - 几个控件对读屏说不出自己是什么:会话统计那一行只有数字与箭头,工作强度与模型触发器从不报告菜单是否展开,设置页的开关没有名字,权限弹层的当前档位只有勾号,输入输出箭标进了名称。现在统计行有自己的名称、两个触发器跟着菜单报告展开状态、开关取所在行的标题为名、权限弹层的当前档位写 aria-checked、箭标对读屏隐藏。
37
+ - 手机宽度下输入卡片下方那一行与手机插件自己画的度量条落在同一条 28 像素带里,两行文字叠在一起(权限标签上压了 52 像素、模型标签上压了 21 像素);现在输入区会量出那条度量条的高度,把这一行连同上下文圆环整体抬起同样的距离,度量条出现与消失时分别抬起与落回。
38
+
39
+ ### 安全
40
+
41
+ - 宿主没有 connection 服务时,私有路由的回退栅栏原先只看请求方自己写的 Host 头,局域网连接谎报 localhost 即通过;现在先看连接的对端地址,回环地址之外一律拒绝。冒烟的宿主半边新增一条检查:局域网连接加回环 Host 必须被拒。
42
+ - 删除会话时「目录留在会话根目录内」的判定改为比较解析符号链接之后的路径:会话根目录下有指向别处的项目目录链接时,删除不再跟着链接走到根目录之外。
43
+
44
+ ### 移除
45
+
46
+ - 「轮次状态行」整体移除:这一项把宿主的过程控件搬到自己那一轮工作的末尾,并为进行中、已停止与失败的轮次换上插件自己画的火花与文字(用时、输出 tokens、当前动作)。宿主早已把运行中的状态做成对话末尾自己的一行(小鲸鱼、说法与计时),过程控件只在轮次结束之后出现,于是这条状态条只剩停止与失败两种情形还在渲染,画法又与那一行不同。现在宿主自己的控件留在它原来的位置与写法上:已停止与失败的轮次显示宿主自己的「已停止」「处理失败」,用时与输出 tokens 不再显示;设置页「对话」页的「轮次状态行」一行随之消失,早先存下的取值不再被读取。
47
+
48
+ ### 其他变更
49
+
50
+ - 模型标志的 `cap` 高度回退原先写在同一规则里的两条 `height`,端到端的 `!important` 审计因此整条跳过这条规则;现在两条高度分处两条规则,规则的每一处权重都受审计。
51
+ - 两处内部记录的清理:滚动主人记「哪个来源在滑这一格」的表改用弱引用键,弹簧自己走完之后不再把已经分离的对话子树留在内存里;页脚接管挂在宿主自己的账号行与菜单上的监听随功能的拆卸一并摘下,反复开关不再叠加。
52
+
53
+ <h3 id="en-0.13.2">New Features</h3>
54
+
55
+ - The settings page's Sidebar tab gains a Sidebar layout row: Relaxed is how the sidebar looks now, and Compact takes its spacing down — the gap between the top rows (New session, Plugins, Automation tasks, and the search box's own row when it stands alone) and the workspace, the workspace heading's own margins, the conversation rows' height and spacing, the distance between two sections, and the account area's padding and row margins. Control shapes, type sizes, icons and colours are unchanged. A new preference, `sidebarLayout` (default `relaxed`), resolves onto `<body data-dsh-claude-sidebar-layout>`; the host half is generated from the shared preference table, so the key appears in DSH's own settings document too.
56
+
57
+ ### Improvements
58
+
59
+ - A file changed several times in one piece of work was listed as one row per change, so a dense stretch of edits made it hard to see how much the file had changed in total. The edits and writes of one file inside one process group now stand as a single row: the earliest of them carries the total of those changes in its `+n -m` tail, rolls once per change as each one lands, and opens onto every change. A file changed once is drawn as before, and a failed or interrupted row keeps its own verdict.
60
+ - The line saying what the model is doing now holds its place above the composer while the conversation follows its tail: that hold used to hang on the Turn status line switch, a switch for a line of its own. It rides the Chat-area animations switch now, together with the line's wording and sweep, and it still steps aside — keeping its document place — while the reader owns the scroll or the follow's glide holds the position.
61
+ - On the home page's usage panel, the heat map's cell tip used to name the day's messages; it now names the tokens that day spent ("Sep 9 — 15.9k tokens"), the same figure the cell's shade is bucketed from. Both sources behind the grid carry a per-day token total, so the tip no longer changes its reading with the source.
62
+ - With the sidebar search box on Standalone, its own row still carried 8px of inline padding, which made the box 16px narrower than the New session row under it; the row adds no padding now, so the box's two edges stand where the rows under it stand.
63
+ - The shortcut hint at the right of the sidebar's New session row was plain monospace text (Ctrl + N); it now wears the search box's own key caps — one 20px cap per key with a 1px hairline and 12px secondary text — and the `+` between the two keys is gone.
64
+ - Literals in the stylesheets read tokens: the label on a filled accent button takes the fill's own foreground token (it was a hard-coded white, which promised nothing once a palette turns the fill light), and the settings page's plugin mark takes the primary label token (it was a hard-coded hex). The shape section of docs/STYLE.md records the radii and strokes actually in use, with the host control each copy came from.
65
+
66
+ ### Bug Fixes
67
+
68
+ - With many sessions the usage roll-up and the content search pinned a core while reading logs: they read by id through `sessionQuery.readSession`, which locates that id by listing the WHOLE stored corpus first (a listing opens and decompresses every stored session's header), so the pass grew with the number of changed logs on a first run and after any session change. Both now read through the host's observation entry point `sessionQuery.observeSession`, which resolves the one session without listing the corpus: N sessions cost N observations and no listings, each lease released as soon as its events are in hand. A host publishing only `readSession` falls back to the per-id read and keeps working. The size and modification time cache is unchanged, unchanged logs are still skipped, and the usage figures and payload shape are untouched.
69
+ - A session deletion whose body went past the limit left the client with a reset connection; the 400 is answered first now, and the connection is ended once that answer has gone out.
70
+ - A key in the cost meter's ledger that is not a real date used to become the newest day by string comparison, which dropped every day the local fold contributed, along with its sessions, and left the home page's model chart formatting an invalid date; such a key is skipped as the ledger is read now, and the rest of the days merge as before.
71
+ - With no cost-meter plugin on the machine, the usage roll-up reported its source as cost-meter+local; it reports local now, which is what "which source answered" means.
72
+ - The peak-rate catalog's daily timer kept reaching the source until the process exited once a page had asked, and switching the peak-rate feature off did not stop it; it stands down after 48 hours with no request now, and the next request starts it again.
73
+ - A host without a web server service registered no route in silence and then logged one failure per route as it unloaded; the missing service is reported once now, and the route disposers hold the real disposals alone.
74
+ - In session content search, when a full listing replaced a tail-only pass in flight, the older pass's end cleared the newer pass's state, which let a third pass read the same sessions alongside it; only the pass that is still current clears the state now.
75
+ - A closed popover card only dropped its opacity, so the buttons inside stayed in the tab order and a keyboard reader landed on rows nothing was drawing; a closed card leaves the paint, hit-testing and the tab order together now, and the open and close fades are unchanged.
76
+ - On the live turn's running row, the skin hid the host's own words and marked its replacement decorative, so a screen reader received neither what the model is doing, nor the wait badge, nor the clock; the replacement is the row's readable text now, and the host's own live region still announces the row.
77
+ - With Animation set to Reduced, the reasoning-effort slider's top-rung matrix still flashed once per block on the duration written at run time; that duration and phase are not written in this setting now, and the matrix stands still.
78
+ - Several controls could not say what they were: the conversation-statistics line held only figures and arrows, the effort and model triggers never reported whether their menu was open, the settings page's switches had no name, the permission popover marked its current tier with a check mark alone, and the input and output arrows joined a name. The line carries its own name now, both triggers report the open state with their menu, a switch takes its row's title as its name, the permission popover's current tier writes aria-checked, and the arrows are hidden from a reader.
79
+ - At phone width the line under the composer card and the statistics strip a mobile plugin draws of its own shared one 28px band, so the two rows of text printed over each other (52px of glyph-on-glyph on the permission label, 21px on the model label); the composer now measures that strip and lifts the line, the context ring with it, by the strip's own height, taking it up and letting it back down as the strip comes and goes.
80
+
81
+ ### Security
82
+
83
+ - With no `connection` service on the host, the private routes' fallback fence read only the Host header the requester writes, so a LAN client claiming `localhost` passed. The peer address of the connection is checked first now, and anything outside loopback is refused. The host half's smoke run carries the check: a LAN connection claiming a loopback Host is refused.
84
+ - The session deletion's "directory stays inside the sessions root" check compares resolved paths now: with a project directory inside the root linked elsewhere, the removal no longer follows the link out of the root.
85
+
86
+ ### Removals
87
+
88
+ - The Turn status line feature is removed: it moved the host's process control to the end of the turn's work and drew the plugin's own spark and text — elapsed time, output tokens, what the model was doing — for a running, stopped or failed turn. The host has long since drawn the running status as its own row at the end of the conversation (whale, wording and clock), and its process control appears only once the turn has closed, so the plugin's line was left rendering the stopped and failed cases alone, in a look of its own. The host's control now stays where the host puts it and reads as the host writes it: a stopped or failed turn shows the host's own Stopped / Failed with no elapsed time or output tokens, and the Turn status line row leaves the Conversation tab of the settings page, with any stored value no longer read.
89
+
90
+ ### Chores
91
+
92
+ - The model mark's `cap`-height fallback was two `height` declarations in one rule, which made the end-to-end `!important` audit skip that rule whole; the two heights sit in two rules now, so every weight in them is audited.
93
+ - Two internal records are released properly: the scroll owner's table of which source is easing a container uses weak keys now, so a spring that arrives on its own no longer keeps a detached conversation subtree in memory, and the footer takeover's listeners on the host's own account row and menu come off with the feature's teardown instead of stacking on every switch.
94
+
95
+ ## [0.13.1] - 2026-10-10
96
+
97
+ [中文](#cn-0.13.1) | [English](#en-0.13.1)
98
+
99
+ <h3 id="cn-0.13.1">问题修复</h3>
100
+
101
+ - 装了 0.12.3 或 0.13.0 之后 DSH 起不来(「The application could not start or stopped unexpectedly」),禁用本插件也一样,只有退回 0.12.2 才能启动:这两版把宿主本体 `@deepseek-ai/dsh` 写成了运行时依赖,安装本插件时把宿主的整份运行时闭包拉进 profile 的 node_modules(282 个 `@deepseek-ai/*` 包、lockfile 从 2,522 行长到 11,466 行),与宿主自己那份并存;其他插件按名字导入宿主包时落到这一份新副本上,版本或导出对不上就在冷启动时抛出。现在这条声明去掉了,装本插件不再多装任何宿主包,其他插件照旧从宿主那份解析;本插件运行时要用的宿主包(设置表单的 schema 工厂、加载器递过来的部件与 React)声明在 peerDependencies 里,另有一份 devDependency 供类型检查与测试使用。构建新增一条拒绝:`package.json` 的 `dependencies` 里出现 `@deepseek-ai/*` 即失败。
102
+
103
+ <h3 id="en-0.13.1">Bug Fixes</h3>
104
+
105
+ - DSH would not start after installing 0.12.3 or 0.13.0 ("The application could not start or stopped unexpectedly"); disabling this plugin did not help either, and only going back to 0.12.2 did. Those two versions declared the host itself, `@deepseek-ai/dsh`, as a runtime dependency, so installing the plugin pulled the host's whole runtime closure into the profile's node_modules (282 `@deepseek-ai/*` packages; the lockfile grew from 2,522 to 11,466 lines) beside the host's own copy. Another plugin importing a host package by name landed on that new copy, and a version or export that did not line up threw during a cold start. That declaration is gone: installing this plugin adds no host package, and other plugins resolve against the host's copy as before. The host packages this plugin does use at runtime — the schema factory behind the settings form, the parts the loader hands over, and React — are declared in peerDependencies, with a devDependency copy for the type check and the tests. The build now refuses `@deepseek-ai/*` in `dependencies`.
106
+
7
107
  ## [0.13.0] - 2026-10-10
8
108
 
9
109
  [中文](#cn-0.13.0) | [English](#en-0.13.0)
package/README.md CHANGED
@@ -84,18 +84,9 @@ dsh plugin --profile web add dsh-claude-style # npm 包(推
84
84
 
85
85
  同一时刻建议只启用一个主题。安装后推荐重启 `DeepSeek Harness`以获得完整能力。
86
86
 
87
- ## 功能与设置
88
-
89
- 本插件的功能与设置说明都在文档里:[功能一览](docs/FEATURES.md)、[设置说明](docs/SETTINGS.md)。
90
-
91
87
  ## 字体
92
88
 
93
- > **重要:Anthropic 字体不随 npm 包分发,仅在仓库供下载学习研究**。
94
-
95
- | 字体 | 用途 | 文件 |
96
- |---|---|---|
97
- | Anthropic Sans Web Text | 界面 / UI | [`packages/assets/src/fonts/anthropic/AnthropicSansWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/anthropic/AnthropicSansWebText.ttf) |
98
- | Anthropic Serif Web Text | 对话正文 / Markdown | [`packages/assets/src/fonts/anthropic/AnthropicSerifWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/anthropic/AnthropicSerifWebText.ttf) |
89
+ > **重要:Anthropic 没有对其字体进行公开发行,[`点击此处可以查看`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts),请自行下载,仅供学习或研究用途**。
99
90
 
100
91
  Anthropic 字体启用(二选一):
101
92
 
@@ -111,6 +102,8 @@ Anthropic 字体启用(二选一):
111
102
  | [架构决策](docs/decisions/README.md) | 每条决策一个文件:构建与源码、宿主边界、运行时与各个功能的取舍,以及正在进行的架构迁移 |
112
103
  | [更新日志](CHANGELOG.md) | 版本历史 |
113
104
  | [贡献指南](CONTRIBUTING.md) | 如何从 `packages/client/src/` 构建、提交规范与截图/回归工具(英文) |
105
+ | [功能一览](docs/FEATURES.md) | 每一项功能做什么、在设置页哪里开关 |
106
+ | [设置说明](docs/SETTINGS.md) | 五个分页(通用、外观、输入区、侧栏、对话)的设置项与各档效果 |
114
107
 
115
108
  ## 鸣谢
116
109
 
@@ -4,7 +4,7 @@ This package includes material from the projects below. Each keeps its own licen
4
4
 
5
5
  ## dsh-better-display
6
6
 
7
- The counted figures on a process group's header (`packages/client/src/features/chat-fold/process-summary.ts` and the `dsh-claude-process-summary-*` rules in its stylesheet) and the line while the model works (`packages/client/src/features/chat-wait/`, its stylesheet): their roll timing and curve, the shimmer sweep over the figures, the clock's type and colour and the overtime badge follow dsh-better-display's fold summary and live status line. The source files name it in their headers.
7
+ The counted figures on a process group's header (`packages/client/src/features/chat-fold/process-summary.ts` and the `dsh-claude-process-summary-*` rules in its stylesheet), the merged figures on a file-change row (`packages/client/src/features/chat-files/` and the `dsh-claude-file-roll-*` rules in its stylesheet) and the line while the model works (`packages/client/src/features/chat-wait/`, its stylesheet): their roll timing and curve, the shimmer sweep over the figures, the clock's type and colour and the overtime badge follow dsh-better-display's fold summary and live status line. The source files name it in their headers.
8
8
 
9
9
  Source: <https://github.com/aa2246740/dsh-better-display>
10
10
 
@@ -23,13 +23,12 @@ What each feature does and where its switch lives; the five tabs, their rows and
23
23
  - **One-frame theme switch**: Switching between light and dark changes borders, shadows and fills in the same frame, instead of the colours first and the borders easing in after.
24
24
  - **In progress / Archived**: The sidebar's workspace section splits into In progress and Archived; Archived is one flat list whose rows can be restored or deleted.
25
25
  - **Sidebar search**: A search box in the sidebar opens a palette that finds sessions (titles and content), projects, plugins, Skills and shortcuts, through the host's own dialog and navigation; picking a content hit lands on the turn holding that message rather than at the top of the session. Search box style places it: Cover the brand (the default) keeps it in the brand row, standing over the brand while the pointer is over the sidebar; Standalone gives it a row of its own under the brand row, shown always; Icon leaves the host's own search button in place and takes only its click over.
26
- - **Turn status line**: A running, stopped or failed turn shows its status at the end of the turn's work: the elapsed time, the output tokens and what the model is doing now (or that it stopped or failed); while the turn runs the line rests above the composer for as long as the conversation follows its tail, so content arriving below no longer drags it around, and scrolling back yourself leaves it travelling with the content. Off restores the host's turn status.
27
26
  - **Conversation navigator**: The column of short marks at the conversation's right edge holds one per turn and follows where you are reading. Reach it with the pointer and it opens into a list: each row carries the message that started its turn and sits exactly where that turn's mark was — the turn you are reading stays in place, and the turn under the pointer is still under it. The wheel scrolls the list and a click glides the page to the turn, loading earlier history first when it is not loaded yet; the Chat / Trajectory tabs at the top of the conversation stay up while the pointer is on the list. Alt+↑ / Alt+↓ jump to the previous or the next turn, and held or repeated they keep stepping; while the composer holds a draft the keys stay with it. The turn you land on flashes a short line over its start. With [dsh-plugin-msg-nav](https://github.com/SherUnlocked-4869/dsh-plugin-msg-nav) installed too, Alt+↑ / Alt+↓ stay with that plugin.
28
27
  - **Chat-area follow**: At the structural moments — a thinking row folding, a tool call row arriving — a reader sitting at the bottom is handed back to the host's own follow, instead of being left tens of pixels short by the burst of content; a process group capped in the Standard and Compact tiers (thinking and tool output kept in one scrolling body) is caught up the same way. Catching up runs on a curve: a trickle of characters settles softly and a burst glides at one steady speed before landing. While content streams, the main scroller's own follow is walked along that curve too rather than written to the end in a single frame — with a fast stream the newest lines trail just below the fold and slide into place once it stops. A message you send does not go through it: the host's scroll to bring it into view lands at once. Once the reader scrolls away from the bottom himself, the plugin stays out of it until he returns.
29
28
  - **Automatic folding and the rolling door**: A thinking row opens while the model reasons and folds back when it stops; a running process group opens and folds back once that piece of work ends. A row or group the reader pressed himself keeps what he chose for that phase. The open reasoning stands in a window that grows with it: the window is as tall as the text until it reaches the height reasoning may take, and there it stays while the text steps up two lines every 0.84 s (half a second of travel) behind a softened edge at each side, stopping at the newest line; once the reasoning stops the window comes off and the reader who opens the row reads it whole, and with Animation set to Reduced the window is never set. A process group's header says what the group holds — "2 thoughts, 1 tool call", empty figures left out — each number rolling as the work grows, and the whole line sweeps the way the host's words do while that piece of work runs; a header without the room for the sentence falls back to `Thinking×2 · Tools×1` and takes the sentence back when the room returns, with the sentence as the control's name either way. Opening or closing a row (a tool card, a thinking row, a command card) or a process group moves the height frame by frame, really pushing the content below away or pulling it back. The door only rolls the stretch the reader can see, so any length moves at the same speed, and a body holding several cards rolls as one door.
30
- - **The line saying what the model is doing**: While a turn runs, the line at the end of the conversation is no longer a fixed "Deep diving for N s ···": it names what that turn is doing now — "Thinking…", "Preparing a tool call…", "Running tools…", "Writing…" — with the time the turn has taken beside it in the host's own units. A wording that changes runs transitions.dev's "Text states swap": the old one blurs and fades upward, the new one rises from below (about 0.15 s). Where no state can be read (the model has written nothing and no tool is running) it falls back to the host's own "Deep diving", and once the model has been silent for ten seconds "No response yet" appears beside the clock. The line sweeps, and the host's whale and its screen-reader announcement stay; with Animation set to Reduced the wording is written at once, with none of those three phases.
29
+ - **The line saying what the model is doing**: While a turn runs, the line at the end of the conversation is no longer a fixed "Deep diving for N s ···": it names what that turn is doing now — "Thinking…", "Preparing a tool call…", "Running tools…", "Writing…" — with the time the turn has taken beside it in the host's own units. A wording that changes runs transitions.dev's "Text states swap": the old one blurs and fades upward, the new one rises from below (about 0.15 s). Where no state can be read (the model has written nothing and no tool is running) it falls back to the host's own "Deep diving", and once the model has been silent for ten seconds "No response yet" appears beside the clock. The line sweeps, and the host's whale and its screen-reader announcement stay; with Animation set to Reduced the wording is written at once, with none of those three phases. As long as the conversation follows its tail the line rests above the composer, so content arriving below no longer drags it around; scrolling back yourself leaves it travelling with the content.
31
30
  - **New text fades in**: Characters arriving in a streaming answer start faint and settle over about 0.12 s, staggered slightly by arrival order; a block arriving whole, a burst of thousands of characters and text that just reflowed from a fold stay solid.
32
- - **File change rows**: A write or edit dispatched from inside a run_code program carries the `+n -m` tail and an expandable diff card, and its path opens the file; a failed or interrupted row keeps its verdict.
31
+ - **File change rows**: A write or edit dispatched from inside a run_code program carries the `+n -m` tail and an expandable diff card, and its path opens the file; several changes to one file inside one process group stand as a single row whose tail is their total, rolling as each change lands, and opening that row reads every change; a failed or interrupted row keeps its verdict.
33
32
  - **Send flight**: On submission the composer card lifts and narrows into the bubble as it travels, the flying copy carrying the bubble's colour from its first frame and its words re-flowing into the shape; it meets the message row, where the real bubble shows underneath and the flying copy fades out, so the words sharpen into place.
34
33
  - **Composer caret motion**: The composer's text caret is drawn by the plugin and glides when it moves; a question card's answer box and a queued message's inline editor are covered as well. The Conversation tab offers Every move (the default), Explicit moves and Off.
35
34
  - **Chat / Trajectory tabs**: The Chat / Trajectory tab strip at the top of the conversation is redrawn as a segmented control and lifted onto the title's line when it fits.
package/docs/FEATURES.md CHANGED
@@ -23,13 +23,12 @@
23
23
  - **亮暗切换一步到位**:切换亮色与暗色时,边框、阴影与底色在同一帧换好,不会先换颜色、再慢慢补上边框。
24
24
  - **进行中 / 已归档**:侧栏的工作区分成「进行中」与「已归档」两档;已归档是一张平铺的列表,每一行可以恢复或删除。
25
25
  - **侧栏搜索**:侧栏里有一个搜索框,打开的面板能找会话(标题与内容)、项目、插件、Skill 与快捷键,用宿主自己的对话框与导航;点开一条内容命中会落到那条消息所在的一轮,而不只是会话开头。「搜索框样式」选它的位置:覆盖品牌(默认)把它放在品牌行、指针移到侧栏时盖过品牌,独立在品牌行下方给它自己一行并一直显示,图标则保留宿主自己的搜索按钮、只把单击接管过来。
26
- - **轮次状态行**:进行中、已停止与失败的轮次,状态移到这一轮工作的末尾:用时、输出的 tokens,以及模型此刻在做什么(或已停止、处理失败);进行中时,只要对话还在跟随末尾,这一行就停在输入框上方,不断追加的内容不再带着它上下移动,读者自己往回翻看时它照常随内容走。
27
26
  - **对话导航**:对话区右侧那列短横线每一轮一条,跟着你读到的位置走。鼠标碰到它,它就展开成一张列表:每一行写着开启这一轮的那条消息,正好落在它那条短横线原来的位置上——你正在读的那一轮还在原处,鼠标下面还是刚才那一轮;滚轮上下翻,点一行页面滑到那一轮,还没加载的早期对话先加载再跳;指针停在列表上时,对话区顶部的对话 / 轨迹标签照常显示。Alt+↑ / Alt+↓ 跳到上一轮或下一轮,连按会一轮一轮接着走;输入框里有草稿时这两个键留给输入框。跳到的那一轮开头会闪一条短横线。同时装着 [dsh-plugin-msg-nav](https://github.com/SherUnlocked-4869/dsh-plugin-msg-nav) 时,Alt+↑ / Alt+↓ 留给它。
28
27
  - **聊天区跟随**:思考行收起、工具调用行出现这些结构时刻,贴着底部的读者被交还给系统自己的跟随,内容成片到达时不再停在离底部几十像素的地方;「标准」与「简洁」档里封顶的过程组(思考与工具输出收在一个带滚动条的组体里)同样补到底部。补到底部走的是曲线:零散到达的字收得住尾巴,成片涌进的文字以一段稳速滑过去,再落到末尾。流式输出期间主滚动条的新内容也沿这条曲线推进,不再一帧写到底;输出很快时最新几行会短暂拖在屏幕下缘之外,流一停就滑到位。你自己发出的消息不走这条曲线:宿主把它滚进视野的那一下照旧一步到位。读者自己滚动离开底部之后,插件不再插手,直到他自己回到底部。
29
28
  - **自动开合与卷帘门**:模型还在思考时思考行开着,思考停下就收回去;运行中的过程组自动展开,这一段结束再收起。读者自己按过的行或组在当时的阶段里不再被改动。开着的那段思考停在一个跟着它长高的窗口里:窗口先是和文本一样高,长到推理能到的最大高度就停在那里,只有到了这一刻文字才每 0.84 秒往上走两行(0.5 秒滑过去),走到最新一行就停住,上下边缘渐隐,也不再往下推整列对话;推理一停,窗口撤下,读者打开这一行读到的是完整文本,「动画」为「减弱」时不设这个窗口。过程组的标题改写成这一组装了什么:「执行 2 次思考,调用 1 次工具」,没有的项不写,数字随内容增加逐位滚动,这一段还在进行时整行带着宿主那样的扫光;放不下整句时改用「思考×2 · 工具×1」,位置宽回来再换回整句,控件的可访问名字始终是整句。点开或收起一行(工具卡片、思考行、命令卡片)以及过程组时,高度逐帧变化,下方内容被真的推开或收回;门只走读者看得见的那一段,内容再长也是同一速度,多张卡片的展开体整扇门一起走。
30
- - **模型正在做什么的那一行**:轮次进行中,对话末尾那一行不再是固定的「深度求索中,用时 N 秒 ···」,而是写出这一轮此刻在做的事:「正在思考」「正在准备工具调用」「正在调用工具」「正在写回答」,后面跟着这一轮已用的时间(用宿主自己的单位)。说法换掉时旧的向上淡出、新的从下方升起(约 0.15 秒,来自 transitions.dev 的「Text states swap」);读不出状态时(模型还没写出任何内容、也没有工具在跑)回到宿主自己的「深度求索中」;模型连续 10 秒没有开口时,计时后面多一枚「暂未响应」。这一行带扫光,宿主的小鲸鱼与读屏播报照旧;「动画」设为「减弱」时说法直接换掉,不做上面那三段。
29
+ - **模型正在做什么的那一行**:轮次进行中,对话末尾那一行不再是固定的「深度求索中,用时 N 秒 ···」,而是写出这一轮此刻在做的事:「正在思考」「正在准备工具调用」「正在调用工具」「正在写回答」,后面跟着这一轮已用的时间(用宿主自己的单位)。说法换掉时旧的向上淡出、新的从下方升起(约 0.15 秒,来自 transitions.dev 的「Text states swap」);读不出状态时(模型还没写出任何内容、也没有工具在跑)回到宿主自己的「深度求索中」;模型连续 10 秒没有开口时,计时后面多一枚「暂未响应」。这一行带扫光,宿主的小鲸鱼与读屏播报照旧;「动画」设为「减弱」时说法直接换掉,不做上面那三段。只要对话还在跟随末尾,这一行就停在输入框上方,不断追加的内容不再带着它上下移动;读者自己往回翻看时它照常随内容走。
31
30
  - **新文字淡入**:流式回答里新出现的字符先淡后实(约 0.12 秒),并按到达次序略作错开;整段一次到达的内容、几千字的突发与刚被折叠重排过的文字保持本色。
32
- - **文件变更行**:run_code 程序里派发出去的写入与编辑带上行尾的 `+n -m` 与可展开的改动卡片,路径可点开文件;失败与中断的行保留裁决信息。
31
+ - **文件变更行**:run_code 程序里派发出去的写入与编辑带上行尾的 `+n -m` 与可展开的改动卡片,路径可点开文件;同一个文件在同一个过程组里的多次改动读成一行,行尾的数字是这些改动的累计值、随每次改动到达滚动,展开这一行读到的是全部改动;失败与中断的行保留裁决信息。
33
32
  - **聊天气泡动效**:提交消息时输入卡片浮起、一路收成那条气泡:飞行的这一份从第一帧就是气泡的颜色,草稿里的字跟着形状重新排,最后接上真实的消息行——真实气泡在下面先显示出来,飞行的那份淡出,文字逐渐变清晰。
34
33
  - **输入框插入符动效**:输入框里的光标由插件自己绘制,移动时滑过去;提问卡片的作答框与排队消息的行内编辑框同样覆盖。设置页的「对话」页有三档:每一格(默认)、只在移动时、关闭。
35
34
  - **对话 / 轨迹标签条**:对话区顶部的对话 / 轨迹标签条重画成分段控件,放得下时抬到标题那一行。
@@ -11,8 +11,8 @@ The settings page appears both in the settings dialog (the "Claude Style" tab) a
11
11
  | General | Theme style, Username, Animation, Open popovers on hover, Account-hold easter egg language |
12
12
  | Appearance | Colours, Typefaces, Mascot, Where it appears |
13
13
  | Composer | Composer restyle, Home layout, Redraw the model picker (with Quick providers under it), Peak rate meter, Redraw the permission control |
14
- | Sidebar | Collapse the sidebar settings area, Sidebar search, Search box style, In progress / Archived view, Float docked panels |
15
- | Conversation | Turn status line, Conversation navigator, Chat-area animations (Off / Enhanced), Conversation statistics position (Inside the context popover / On its own line), Composer caret motion, Chat / Trajectory tabs |
14
+ | Sidebar | Collapse the sidebar settings area, Sidebar search, Search box style, Sidebar layout (Relaxed / Compact), In progress / Archived view, Float docked panels |
15
+ | Conversation | Conversation navigator, Chat-area animations (Off / Enhanced), Conversation statistics position (Inside the context popover / On its own line), Composer caret motion, Chat / Trajectory tabs |
16
16
 
17
17
  Every feature that takes over part of the host's interface has its own switch; turning it off brings the host's original back at once, without a reload. The conversation area's effects — the follow, the automatic folding with its rolling door, the text fade, the file change rows, the send flight, the counted figures on each process group's header and the line saying what the model is doing — share the one Chat-area animations switch: on plays this plugin's set, off hands the conversation back to the host with the host's own words; the caret keeps its own three-way choice.
18
18
 
@@ -24,6 +24,8 @@ In the settings dialog, this page's heading row ends with a GitHub mark; pressin
24
24
 
25
25
  **Sidebar search**: with Sidebar search on, the sidebar carries a search box whose palette finds sessions (titles and content), projects, plugins, Skills and shortcuts; picking a content hit lands the page on the turn holding that message, paging that turn's records in first on a long history. Search box style places it: Cover the brand (the default) puts it in the brand row, standing over the brand while the pointer is anywhere over the sidebar and giving the brand back when the pointer leaves; Standalone gives it a row of its own under the brand row, shown always; Icon draws no box of the plugin's, keeping DSH's own search button where the host draws it and taking only its click over, so pressing it opens this plugin's palette. The content match reads your own messages and the model's answers alone: injected context, the Skill catalog and reasoning never reach the results.
26
26
 
27
+ **Sidebar layout**: Relaxed (the default) is how the sidebar looks now; Compact takes the spacing down alone — the gap between the top rows (New session, Plugins, Automation tasks, and the search box's own row when it stands alone) and the workspace, the workspace heading's own margins, the conversation rows' height and spacing, the distance between two sections, and the account area's padding and row margins. Control shapes, type sizes, icons and colours are untouched, so both layouts hold the same things to press.
28
+
27
29
  **Alongside other theme plugins**: with Colours and Typefaces set to Follow the host, the skin no longer rewrites the host's colours and fonts and keeps only its layout and controls; the colours are left to DSH itself, or to another theme plugin enabled at the same time. With [dsh-wallpaper-engine](https://github.com/elysia395/dsh-wallpaper-engine), for example, the wallpaper shows through the sidebar and the conversation, and the skin's own popovers take that plugin's glass, translucent and blurring the picture behind them. **Alongside [dsh-chat-ux](https://github.com/alm-allen/dsh-chat-ux)**: that plugin implements the same chat-area interactions (the rolling door, automatic folding, the token fade, file change rows, the send flight, the drawn caret), and two copies of them intercept each other's clicks and press the same controls. So this skin stands down when it sees that plugin: the Chat-area animations and Composer caret motion rows on the settings page's Conversation tab have their controls disabled while each still shows the value you set, with a line underneath in the accent colour reading "Managed by dsh-chat-ux". Your settings are not rewritten, and they take effect as they are once dsh-chat-ux is removed.
28
30
 
29
31
  **Chat-area follow**: at the structural moments — a thinking row folding, a tool call row arriving — a reader sitting at the bottom is handed back to the host's own follow, instead of being left tens of pixels short by the burst of content; a process group capped in the Standard and Compact tiers (thinking and tool output kept in one scrolling body) is caught up the same way. Catching up runs on a curve: a trickle of characters settles softly and a burst glides at one steady speed before landing. While content streams, the main scroller's own follow is walked along that curve too rather than written to the end in a single frame — with a fast stream the newest lines trail just below the fold and slide into place once it stops. A message you send does not go through it: the host's scroll to bring it into view lands at once. Once the reader scrolls away from the bottom himself, the plugin stays out of it until he returns.
@@ -34,7 +36,7 @@ In the settings dialog, this page's heading row ends with a GitHub mark; pressin
34
36
 
35
37
  **Counted process headers**: a process group's header says what the group holds, in place of the host's list of activities — "2 thoughts, 1 tool call". Empty figures are left out, each number rolls as the work grows, and the whole line sweeps the way the host's words do while that piece of work runs. A header without the room for the sentence (a narrow window, a narrow row) falls back to the compact figures, `Thinking×2 · Tools×1`, and takes the sentence back when the room returns; the control's accessible name is the sentence either way.
36
38
 
37
- **The line saying what the model is doing**: while a turn runs, the line at the end of the conversation is no longer a fixed "Deep diving for N s ···": it names what that turn is doing now — "Thinking…", "Preparing a tool call…", "Running tools…", "Writing…" — with the time the turn has taken beside it in the host's own units. A wording that changes runs transitions.dev's "Text states swap": the old one blurs and fades upward, the new one rises from below (about 0.15 s). Where no state can be read (the model has written nothing and no tool is running) it falls back to the host's own "Deep diving", and once the model has been silent for ten seconds "No response yet" appears beside the clock. The line sweeps, and the host's whale and its screen-reader announcement stay; with Animation set to Reduced the wording is written at once, with none of those three phases.
39
+ **The line saying what the model is doing**: while a turn runs, the line at the end of the conversation is no longer a fixed "Deep diving for N s ···": it names what that turn is doing now — "Thinking…", "Preparing a tool call…", "Running tools…", "Writing…" — with the time the turn has taken beside it in the host's own units. A wording that changes runs transitions.dev's "Text states swap": the old one blurs and fades upward, the new one rises from below (about 0.15 s). Where no state can be read (the model has written nothing and no tool is running) it falls back to the host's own "Deep diving", and once the model has been silent for ten seconds "No response yet" appears beside the clock. The line sweeps, and the host's whale and its screen-reader announcement stay; with Animation set to Reduced the wording is written at once, with none of those three phases. As long as the conversation follows its tail the line rests above the composer, so content arriving below no longer drags it around; scrolling back yourself leaves it travelling with the content.
38
40
 
39
41
  **Send flight**: on submission the composer card lifts as it is and narrows into the bubble as it travels, its words re-flowing into the shape, landing on the message row: the real bubble shows underneath and the flying copy fades out, so the words sharpen into place.
40
42
 
package/docs/SETTINGS.md CHANGED
@@ -11,8 +11,8 @@
11
11
  | 通用 | 主题风格、用户名、动画效果、悬停展开弹层、封号彩蛋语言 |
12
12
  | 外观 | 配色、字体、吉祥物、出现位置 |
13
13
  | 输入区 | 输入区重绘范围、首页版面、重绘模型选择器(及其下的快捷供应商)、峰谷电表、重绘权限控件 |
14
- | 侧栏 | 折叠侧栏设置区、侧栏搜索、搜索框样式、进行中 / 已归档视图、面板悬浮卡片 |
15
- | 对话 | 轮次状态行、对话导航、聊天区动画效果(关闭 / 增强)、对话统计信息位置(收进上下文 / 独立成行)、输入框插入符动效、对话 / 轨迹标签条 |
14
+ | 侧栏 | 折叠侧栏设置区、侧栏搜索、搜索框样式、左侧栏布局(宽松 / 紧凑)、进行中 / 已归档视图、面板悬浮卡片 |
15
+ | 对话 | 对话导航、聊天区动画效果(关闭 / 增强)、对话统计信息位置(收进上下文 / 独立成行)、输入框插入符动效、对话 / 轨迹标签条 |
16
16
 
17
17
  每一项接管宿主界面的功能都有自己的开关,关闭后宿主原来的界面原样回来,不需要刷新页面。对话区的那几项动效(跟随、自动开合与卷帘门、新文字淡入、文件变更行、聊天气泡动效、过程组标题上的计数、说明模型正在做什么的那一行)共用一个开关「聊天区动画效果」:开启时播放本插件的这一套,关闭后聊天区交回宿主,宿主自己的说法也一并回来;插入符动效仍是自己的三档。
18
18
 
@@ -24,6 +24,8 @@
24
24
 
25
25
  **侧栏搜索**:开启后侧栏里多一个搜索框,打开的面板能找会话(标题与内容)、项目、插件、Skill 与快捷键;点开一条内容命中,页面落到那条消息所在的一轮,历史很长时先把这一轮的记录翻页读进来。「搜索框样式」决定它站在哪里:「覆盖品牌」(默认)把它放进品牌行,指针移到侧栏上时它盖过品牌,指针移开再把品牌让回来;「独立」在品牌行下方给它自己一行,一直显示;「图标」不画插件自己的搜索框,让 DSH 自己的搜索按钮留在原处,只把单击接管过来,点它打开的是本插件的面板。内容匹配只认你发出的消息与模型的回答,注入的环境提示、Skill 目录与推理过程都不进结果。
26
26
 
27
+ **左侧栏布局**:「宽松」(默认)是现在的样子;「紧凑」只把间距收小——上侧各行(新会话、插件、自动化任务,以及搜索框独立成行时的那一行)到工作区之间的空隙、工作区标题自身的上下留白、对话行的高度与行间距、两个分区之间的间隔,以及账号区内边距与它那一行的上下边距。控件的形状、字号、图标与配色都不动,因此两种布局下点得到的东西一样多。
28
+
27
29
  **与其他主题插件同时使用**:「配色」与「字体」选「跟随宿主」后,本插件不再改写宿主的颜色与字体,只保留布局和控件;颜色交给 DSH 自己,或者交给同时启用的其他主题插件。例如与 [dsh-wallpaper-engine](https://github.com/elysia395/dsh-wallpaper-engine) 一起用时,壁纸透过侧栏与对话区显示出来,插件自己的弹层随壁纸插件的玻璃效果变成半透明并模糊背后的画面。**与 [dsh-chat-ux](https://github.com/alm-allen/dsh-chat-ux) 同时使用时**:那个插件实现了同一批聊天区交互(卷帘门、自动开合、文字淡入、文件变更行、聊天气泡动效、插入符动效),两套同时生效会互相拦点击、抢同一批按钮。所以本插件在检测到它时让位:设置页「对话」页的「聊天区动画效果」与「输入框插入符动效」两行控件禁用,但仍显示你自己设的值,下面另起一行用强调色注明「该选项由 dsh-chat-ux 管理」;你的设置不会被改写,卸载 dsh-chat-ux 之后原样生效。
28
30
 
29
31
  **聊天区跟随**:思考行收起、工具调用行出现这些结构时刻,贴着底部的读者被交还给系统自己的跟随,内容成片到达时不再停在离底部几十像素的地方;「标准」与「简洁」档里封顶的过程组(思考与工具输出收在一个带滚动条的组体里)同样补到底部。补到底部走的是曲线:零散到达的字收得住尾巴,成片涌进的文字以一段稳速滑过去,再落到末尾。流式输出期间主滚动条的新内容也沿这条曲线推进,不再一帧写到底;输出很快时最新几行会短暂拖在屏幕下缘之外,流一停就滑到位。你自己发出的消息不走这条曲线:宿主把它滚进视野的那一下照旧一步到位。读者自己滚动离开底部之后,插件不再插手,直到他自己回到底部。
@@ -34,7 +36,7 @@
34
36
 
35
37
  **过程组标题上的计数**:过程组的标题不再只写「执行了命令,已读取文件,已调用工具等」,而是写这一组装了什么,例如「执行 2 次思考,调用 1 次工具」;没有的项不写,数字随内容增加逐位滚动,这一段还在进行时整行带着与宿主同样的扫光。这一行放不下整句时(窗口窄、过程组所在的行窄)改用简洁写法「思考×2 · 工具×1」,位置宽回来再换回整句;控件的可访问名字始终是整句。
36
38
 
37
- **模型正在做什么的那一行**:轮次进行中,对话末尾那一行不再是固定的「深度求索中,用时 N 秒 ···」,而是写出这一轮此刻在做的事:「正在思考」「正在准备工具调用」「正在调用工具」「正在写回答」,后面跟着这一轮已用的时间(用宿主自己的单位)。说法换掉时旧的向上淡出、新的从下方升起(约 0.15 秒,来自 transitions.dev 的「Text states swap」);读不出状态时(模型还没写出任何内容、也没有工具在跑)回到宿主自己的「深度求索中」;模型连续 10 秒没有开口时,计时后面多一枚「暂未响应」。这一行带扫光,宿主的小鲸鱼与读屏播报照旧;「动画」设为「减弱」时说法直接换掉,不做上面那三段。
39
+ **模型正在做什么的那一行**:轮次进行中,对话末尾那一行不再是固定的「深度求索中,用时 N 秒 ···」,而是写出这一轮此刻在做的事:「正在思考」「正在准备工具调用」「正在调用工具」「正在写回答」,后面跟着这一轮已用的时间(用宿主自己的单位)。说法换掉时旧的向上淡出、新的从下方升起(约 0.15 秒,来自 transitions.dev 的「Text states swap」);读不出状态时(模型还没写出任何内容、也没有工具在跑)回到宿主自己的「深度求索中」;模型连续 10 秒没有开口时,计时后面多一枚「暂未响应」。这一行带扫光,宿主的小鲸鱼与读屏播报照旧;「动画」设为「减弱」时说法直接换掉,不做上面那三段。只要对话还在跟随末尾,这一行就停在输入框上方,不断追加的内容不再带着它上下移动;读者自己往回翻看时它照常随内容走。
38
40
 
39
41
  **聊天气泡动效**:提交消息时输入卡片原样浮起、一路收成那条气泡,草稿里的字跟着形状重新排,落地正好接上真实的消息行:真实气泡在下面先显示出来,飞行的那份淡出,文字逐渐变清晰。
40
42
 
package/docs/STYLE.md CHANGED
@@ -30,7 +30,7 @@ fish take it as their ink. The neutrals are the host's own cool family (its
30
30
  blue-black in dark. User bubbles take a blue tint as the host's own do, and
31
31
  inline code keeps the body ink as the host draws it. The skin's Claude marks
32
32
  (the account row's
33
- picture without an avatar, the turn status line's spark) become DeepSeek's
33
+ picture without an avatar) become DeepSeek's
34
34
  whale (`packages/assets/src/brand/deepseek-mark.svg`, the host's `FISH_LOGO_PATH`); the
35
35
  violet top rung of the reasoning slider stays.
36
36
 
@@ -197,8 +197,14 @@ and scrolls to its end.
197
197
 
198
198
  ## Shapes
199
199
 
200
- - Radius: 4 / 8 / 16 px; pills for CTAs (`9999px`).
201
- - Borders: 1px warm hairline.
200
+ - Radius: 4 / 8 / 16 px; 12px for a popover card and 6px for its rows (the
201
+ popover table below); pills for CTAs (`9999px`). A shape copied from a host
202
+ control keeps that control's own geometry rather than the scale above: the
203
+ composer card's 18px (the host's 22px less 4), the inline composer's 14px and
204
+ 11px, the permission and workspace segments' 7px, the view tabs' 5px, the
205
+ peak-rate and file pills' 999px.
206
+ - Borders: 1px warm hairline. The same host copies keep their own stroke: the
207
+ sidebar's 1.5px session-row ring, the inline composer's 1.6px stop square.
202
208
  - Spacing: 4 px rhythm.
203
209
  - Bottom edge: the transcript never fades behind the composer by shadow. The
204
210
  composer seat paints an opaque `var(--dsw-alias-bg-base)` bar across its own
@@ -430,18 +436,24 @@ pass that stamps it) which `features/hero-menu/hero-menu.css` reads with
430
436
  ## Search · 搜索
431
437
 
432
438
  **Sidebar box.** The box takes the brand's place in the logo row while the
433
- pointer is over the sidebar: 32px tall, 8px radius, a 1px `#E8E6DC` hairline on
439
+ pointer is over the sidebar: 28px tall, 8px radius, a 1px `#E8E6DC` hairline on
434
440
  the ivory canvas fill (dark: `#2E2C29` on `#1E1E1D`), a 16px search glyph in the
435
441
  secondary label, the "Search" label in the tertiary label at 14px, and the
436
- host's search shortcut as 20px keycaps at the right end. It is a button like
437
- the New session and Plugins rows below it: the pointer cursor, and on hover the
438
- same plate those rows take (`--dsh-claude-hover-bg`, dark `rgba(255, 255, 255,
439
- 0.08)`) laid over its opaque fill. Box and brand share one grid cell and
440
- cross-fade over 0.16s, the timing the workspace heading and its segmented
441
- control trade places with; the box is excluded from the window drag region. In
442
- the desktop titlebar mode the brand row rises 10px, since the sidebar column
443
- starts under the 40px titlebar — as far as it goes with the box still wholly
444
- below that edge, which clips the column.
442
+ host's search shortcut as 20px keycaps at the right end. The New session row's
443
+ own shortcut wears that same keycap: a 20px box with a 1px hairline, 4px radius
444
+ and 12px secondary ink, and the `+` the host writes between the two keys goes,
445
+ the way the box leaves it out. It is a button like the New session and Plugins
446
+ rows below it: the pointer cursor, and on hover the same plate those rows take
447
+ (`--dsh-claude-hover-bg`, dark `rgba(255, 255, 255, 0.08)`) laid over its
448
+ opaque fill. Box and brand share one grid cell and cross-fade over 0.16s, the
449
+ timing the workspace heading and its segmented control trade places with; the
450
+ box is excluded from the window drag region. Standalone gives the box a row of
451
+ its own under the brand row, shown without a hover; that row adds no inline
452
+ padding, so the box's hairline stands on the two edges of the column's own
453
+ inset and is exactly as wide as the New session plate below it. In the desktop
454
+ titlebar mode the brand row rises 6px, since the sidebar column starts under
455
+ the 40px titlebar — as far as it goes with the box still wholly below that
456
+ edge, which clips the column.
445
457
 
446
458
  **Palette.** A modal card set 8vh from the top, 760px wide at most, 16px radius,
447
459
  on the same ivory fill (dark `#1E1E1D`) under the host's mask. From the top: a
@@ -459,20 +471,25 @@ and shows the Enter glyph; the others hide it. The card fades in and settles
459
471
  from 6px above at 98.5% scale over 0.18s; on close card and mask fade out
460
472
  together over 0.14s before the modal unmounts.
461
473
 
462
- ## Turn status · 轮次状态行
463
-
464
- A running, stopped or failed turn's status line sits after the turn's work (its
465
- footer and queued messages below it), 4px under the last row, with no rule under
466
- it: a 14px Claude spark in the brand clay (`--dsw-alias-brand-primary`,
467
- `#D97757`), 8px gap, then the text in the tertiary label at the host's secondary
468
- content size (13px, 24px line), one line with an ellipsis. Parts are joined by
469
- ` · `: `elapsed · N tokens · action` while the turn runs, `Stopped` or `Failed`
470
- (the host's words) `· duration · N tokens` after; the token part is left out
471
- until a finished step has reported usage. While the turn runs the spark turns a
472
- full circle every 2.4s, breathing down to 78% at the half turn, in every
473
- animation setting, like the sidebar's background-work ring; a stopped or failed
474
- turn's spark stands still. A turn that finishes normally keeps the host's own
475
- control.
474
+ ## The waiting line · 模型正在做什么的那一行
475
+
476
+ The host's running row — its whale tail, its divider and its live region — stays
477
+ as the host draws it; the host's own shimmering words step aside for the skin's
478
+ line beside the whale: the wording in the secondary label at 14px on a 24px line,
479
+ the turn's clock 8px later in the caption at 13px with tabular figures, and the
480
+ "No response yet" badge 8px after that (11px on a 16px line, 0 6px padding, a 5px
481
+ radius, the warning label over 16% of itself). The wording carries a highlight
482
+ band swept across it over 2s, and a wording that changes runs the three-phase
483
+ swap over 0.15s: the old one leaves upward with a 2px blur, the new one enters
484
+ from 4px below; under Reduced motion the band is left out and the wording is
485
+ written at once.
486
+
487
+ While the conversation follows its tail the row is pinned at the place the
488
+ follow's glide comes to rest — the composer's own height, the fade band above it
489
+ and the host's 16px transcript padding — the canvas painted behind it, since the
490
+ transcript's tail would otherwise show through. The pin stands only while the
491
+ host reports the tail (or the stream glide holds the position) and steps aside
492
+ for the reader's own scrolling.
476
493
 
477
494
  ## Home layouts · 首页版面
478
495
 
@@ -546,10 +563,10 @@ Claude Code's blue data ramp (`#3b6ecf` at 20/40/65/100 over the neutral empty
546
563
  cell); because the columns are fractions of the panel's own width, the newest
547
564
  week can never fall past the edge. Hovering a cell shows Claude Code's day tip at
548
565
  once: a solid pill in the label ink with the canvas tone for text (so it inverts
549
- with the theme), 13px medium, reading the day in the shell's language and its
550
- messages ("Sep 9 — 15,955"); over the three columns at either end the pill lines
551
- up with the cell's outer edge so it stays inside the panel. The session list's
552
- fallback has no per-day message count, and its tip names the day's tokens.
566
+ with the theme), 13px medium, reading the day in the shell's language and the
567
+ tokens that day spent ("Sep 9 — 15.9k tokens"), the same figure the cell's heat
568
+ is bucketed from; over the three columns at either end the pill lines
569
+ up with the cell's outer edge so it stays inside the panel.
553
570
  Models is Claude Code's own shape: one column
554
571
  per day of the chart's thirty-day window, stacked from the axis up with each
555
572
  model's slice in its rank's colour (ranks past the ramp share its last, grey
@@ -6,9 +6,9 @@
6
6
 
7
7
  ## 决定
8
8
 
9
- - 宿主半边的私有路由(`/username`、`/usage`、`/session-search`、`/hdsl`、`/hdsl-skin.png`、`POST /session-delete`)处理前先调 `connection.requestRejection(req)`,拒绝即回 401/403。宿主没有这个服务时,插件自带的替代只放行回环请求:回环 Host、无跨站标记、Origin 与 Host 一致。
9
+ - 宿主半边的私有路由(`/username`、`/usage`、`/session-search`、`/hdsl`、`/hdsl-skin.png`、`POST /session-delete`)处理前先调 `connection.requestRejection(req)`,拒绝即回 401/403。宿主没有这个服务时,插件自带的替代只放行回环请求:连接的对端地址在本机回环口上、回环 Host、无跨站标记、Origin 与 Host 一致。四项里前三项都要成立,其中对端地址是请求方写不了的那一项,回环 Host 只是附带条件。
10
10
  - 公开静态资产不经栅栏,但请求里的文件名必须符合固定格式,读取只落在固定表给出名字的目录里:模型文案取自构建输出;字体的名字先查 `$DSH_HOME/dsh-claude-style/fonts/`,没有再看包内的 `lib/fonts/`;走路由的资源是构建清单(`lib/assets/manifest.json`,D38)里的哈希加扩展名。
11
- - 删除会话另加四道:只接受 POST;id 符合宿主的会话 id 格式;正在打开的会话拒绝;解析后的目录必须留在会话根目录内。删除发生在宿主半边,浏览器只提交 id。
11
+ - 删除会话另加四道:只接受 POST;id 符合宿主的会话 id 格式;正在打开的会话拒绝;解析符号链接之后的目录必须留在会话根目录内。删除发生在宿主半边,浏览器只提交 id。请求体超过上限时先答 400,应答送出之后再结束这条连接。
12
12
  - 删除成功时宿主半边经 `workspaceRegistry.unarchiveSession` 把 id 移出归档集合;归档集合里存储目录已消失的条目同样按删除成功应答并移出,否则归档列表会一直留着这一行。会话根目录读不了是故障,答 500。
13
13
  - 用量汇总只读:别的插件的账本只读不写,自己的缓存放在 `$DSH_HOME/cache/dsh-claude-style/`,两个来源按日期合并(D53)。
14
14
  - 浏览器半边:来自设置、账号服务、系统或其他插件的字符串只用 `textContent` 或元素属性写入;图标复制节点,不重新解析标记。
@@ -12,7 +12,7 @@
12
12
  - 需要隔离、但错误必须可见的地方用 `reportError`:`notifyAll` 逐个通知监听函数,观察总线逐个通知订阅、帧管线逐个运行任务(D40),某个出错就报告、其余照常;卸载时单个功能失败报告后继续;功能开关(D29)关闭某个功能时它的 teardown 抛错,同样报告,这个功能在本代内不再装回。
13
13
  - 浏览器半边允许的 catch,每处写明原因:
14
14
  - 上述隔离。
15
- - 宿主以抛错表达的预期状态:设置表单的 `set()` 拒绝不属于该配置的字段;`modelDirectories.directoryFor` 对还没建立的会话抛错,下一轮重试;`sessionQuery.readSession` 以 `SESSION_QUERY_CORRUPT_SESSION` / `SESSION_QUERY_SESSION_NOT_FOUND` 表示某个存档读不了或已被删,内容搜索与用量汇总把这一个会话记为空并记一条警告。
15
+ - 宿主以抛错表达的预期状态:设置表单的 `set()` 拒绝不属于该配置的字段;`modelDirectories.directoryFor` 对还没建立的会话抛错,下一轮重试;`sessionQuery` 的观察(`observeSession`)与按 id 读(`readSession`)以 `SESSION_QUERY_CORRUPT_SESSION` / `SESSION_QUERY_SESSION_NOT_FOUND` 表示某个存档读不了或已被删,内容搜索与用量汇总把这一个会话记为空并记一条警告,其余会话照旧。
16
16
  - 文件变更行解析流式工具的原始参数:参数逐块到达,半截 JSON 是正常的中途状态,解析不了就交回宿主的输入 / 输出卡片(`features/chat-files/file-row-model.ts`)。
17
17
  - Promise 上表示「宿主半边没有应答」或「宿主自己会提示」的失败回调。
18
18
  - 宿主半边允许的 catch,每处写明原因:
@@ -12,7 +12,7 @@
12
12
  - 数据读宿主的客户端服务:`sessions.list` 与 `workspaces.list`(去掉已归档、子代理与空白占位)、`remote.pluginInventory` 与 `remote.pluginManager`、当前会话的 `remote.skills`、`shortcuts.catalog`。远程命名空间在面板打开时用 `ctx.get('remote.<名字>')` 读(在根上下文直接读 `remote` 的子属性会被 cordis 以「没有 inject」拒绝)。
13
13
  - 导航用宿主自己的:`uiWorkspace.openSession` / `startSession`、`pluginNavigation.openBundle`、`layout.selectPanel`;Skill 经会话输入的 `setDraft` 与 `focus`;设置页与快捷键列表经它们在插槽登记里声明的 store(`slots.entries(key)` 条目上的 `store.create()`)打开。快捷键行打开快捷键列表,不代为执行命令(宿主的快捷键命令没有公开的执行入口)。内容命中还要落到那条消息所在的一轮:面板开完会话,等这一轮进了窗口再按命中消息的事件序号定位(按 `turnOutline` 找轮次,跳转本身走 D34 那一套,按宿主的刻度跳)。
14
14
  - 匹配沿用宿主侧栏搜索的子串规则,排序为:标题开头、标题中的词开头、标题其他位置、第二个键(路径、包名、描述、别名)。
15
- - 消息内容由宿主半边的 `/session-search` 路由搜(`packages/host/src/search.ts`):日志经共享的 `session-events.ts` 读,只收人类发出的 `user/message`(按 `data.source.kind === 'user'` 排除注入的环境快照、Skill 目录、AGENTS.md 与压缩检查点)与助手消息里的 text 块,`reasoning`、`tool-call`、`tool/result` 都不进语料;匹配照搬 `sessionQuery` 的 `text` 过滤(字面、不分大小写、空白可伸缩)。每个会话的语料落一份 `$DSH_HOME/cache/dsh-claude-style/search/<会话 id>.json`(走共享的 `cache-file.ts`,与峰谷目录、用量缓存同目录):热启动读这些文件,只重读修订号变了的存档会话;宿主开着的会话按 `seq` 只读新增的那一段。存档会话的清单每 3 秒列一次(列一次要开每个日志的头部帧,是这条路上最贵的一步);面板打开时先发一次不带查询的请求预热;每个会话只回最新的一处命中,附摘录与命中位置。
15
+ - 消息内容由宿主半边的 `/session-search` 路由搜(`packages/host/src/search.ts`):日志经共享的 `session-events.ts` 读(宿主的观察接口,按 id 读起,不先列整份存档),只收人类发出的 `user/message`(按 `data.source.kind === 'user'` 排除注入的环境快照、Skill 目录、AGENTS.md 与压缩检查点)与助手消息里的 text 块,`reasoning`、`tool-call`、`tool/result` 都不进语料;匹配照搬 `sessionQuery` 的 `text` 过滤(字面、不分大小写、空白可伸缩)。每个会话的语料落一份 `$DSH_HOME/cache/dsh-claude-style/search/<会话 id>.json`(走共享的 `cache-file.ts`,与峰谷目录、用量缓存同目录):热启动读这些文件,只重读修订号变了的存档会话;宿主开着的会话按 `seq` 只读新增的那一段。存档会话的清单每 3 秒列一次(列一次要开每个日志的头部帧,是这条路上最贵的一步);面板打开时先发一次不带查询的请求预热;每个会话只回最新的一处命中,附摘录与命中位置。
16
16
 
17
17
  ## 理由
18
18
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  - **状态**:已实施
4
4
  - **分组**:功能
5
- - **关联**:D26、D29、D40、D41、D44
5
+ - **关联**:D26、D29、D40、D41、D44、D62
6
6
  - **迁移**:聊天区契约表并入契约模块(D44,待实施)
7
7
 
8
8
  ## 决定
@@ -11,7 +11,7 @@
11
11
  - 它们只读宿主自己写的属性;聊天区的整份宿主契约(流式容器、工具调用行、过程组、思考行的推理正文槽位、会话滚动条、输入框与回显气泡等)集中在 `packages/contracts/src/dom.ts` 一张表里。
12
12
  - 功能自己写出的标记(DOM 标记、类名、`::highlight()` 名称、自定义属性、关键帧名称)一律用 `data-dsh-claude-` 前缀,与上游包同时装着时两套实现读不到对方的状态。
13
13
  - 不止读标记的三处:
14
- - 文件变更行接管宿主 `tool.call.toolview` 座位上的 `edit` 与 `write` 两个键位(优先级 -1,低于宿主自己那两个),改动派生与宿主的卡片模型只差「不排除子调用」这一条。
14
+ - 文件变更行接管宿主 `tool.call.toolview` 座位上的 `edit` 与 `write` 两个键位(优先级 -1,低于宿主自己那两个),改动派生与宿主的卡片模型只差「不排除子调用」这一条;同一个文件在同一个过程组里的多次改动读成一行,规则见 D62。
15
15
  - 聊天气泡动效在 `<body>` 末尾挂一个临时替身,带 `inert` 与 `aria-hidden`,摘掉身份标记但保留样式钩子;飞行期间给真实那一行写标记把它藏起来(保留布局盒),到达后真实行先显示、替身淡出。
16
16
  - 增强跟随在流式输出期间接管会话滚动条的跟随:宿主每次内容增长都把位置写到最底,跟随在同一帧绘制之前把这次位移撤回、交给滚动主人(D41)滑过去。宿主写这一下的地方是它挂上消息列时新建的 ResizeObserver(观察消息列、会话滚动条、输入框座位),浏览器按建立先后调用回调,所以跟随的观察者每遇到一条新的消息列就重建一次,排在宿主之后、观察同样三个元素。跟随期间把宿主自己的「回到底部」按钮临时藏起来(只写自己的标记,宿主的状态不动);读者自己发出的消息到达时跟随让开一段,宿主把新消息滚进视野的那一下照旧一步到位。
17
17
  - 设置:这些功能共用皮肤自己的偏好条目与设置页,不动上游 `dsh-chat-ux` 的配置。对话区各项共用一个开关 `chatAnimations`(「聊天区动画效果」):开启时安装,关闭时这些功能一起卸载、各自把界面交还宿主。各功能的清单用 `pref` 声明自己挂在哪个偏好上,用 `prefValues` 声明一项选择偏好下自己运行的取值(D42);插入符动效是自己的三选一(`caretMotion`)。随偏好出现的样式规则由功能把标记写到 `<body>` 上(如 `data-dsh-claude-chat-follow`)。
@@ -17,7 +17,7 @@
17
17
  - 读者自己的滚轮是浏览器的:滚动主人不接管这个事件,只把滚轮、触摸、指针按下、滚动键与页内查找记成「读者动过」,跟随与流式滑行的请求因此被拒绝。指针按下落在滚动条自己的条带上才算(会话滚动条与过程组主体用同一个判据),落在内容上是读者在打开什么。滚轮的减速曲线、触控板的惯性、边界处的表现都由平台自己画。
18
18
  - 仲裁规则:
19
19
  - 容器上正在滑行的来源优先级更高时,请求被拒绝;同级或更高的请求照常执行,直接写入还会结束正在滑行的低优先级来源。
20
- - 读者接管一个容器后,`follow`、`stream`、`process` 的请求被拒绝,正在进行的滑行在下一帧结束;读者回到这个容器底部的阈值以内(会话 25px,过程组主体 4px),接管结束。结束由滚动事件当场判定,内容在读者回到底部之后才增长也不会留下接管。对话导航跳到某一轮时同样取一次接管(`holdReader`):跳转是读者自己选定的阅读位置,跟随在这一期间让开。接管期间容器带着 `data-dsh-claude-reader-hold`,进行中状态行的固定读这个标记,只在跟随自己移动位置时钉住,读者往回翻看时该行留在文档位置。
20
+ - 读者接管一个容器后,`follow`、`stream`、`process` 的请求被拒绝,正在进行的滑行在下一帧结束;读者回到这个容器底部的阈值以内(会话 25px,过程组主体 4px),接管结束。结束由滚动事件当场判定,内容在读者回到底部之后才增长也不会留下接管。对话导航跳到某一轮时同样取一次接管(`holdReader`):跳转是读者自己选定的阅读位置,跟随在这一期间让开。接管期间容器带着 `data-dsh-claude-reader-hold`,等待行的固定读这个标记(`packages/client/src/features/chat-wait/chat-wait.css`),只在跟随自己移动位置时成立,读者往回翻看时该行留在文档位置。
21
21
  - 读者自己发出的消息(或宿主替它挂上的回显)到达后的 1.2 秒内,`stream` 的请求被拒绝、滑行结束,宿主把新消息滚进视野的那一下照旧一步到位。这段时间属于一个会话:会话一换当场作废,因为打开会话本身就会挂上这一轮自己的消息行。
22
22
  - 减少动态效果时,滑行请求直接写目标位置。
23
23
  - 读者的滚动意图在 `packages/client/src/shared/reader-intent.ts` 判定。功能问「读者在某一时刻之后动过没有」用 `readerMovedSince`。
@@ -12,7 +12,7 @@
12
12
  - `packages/testing/dsh-web.cjs` 起一个临时实例:`$DSH_HOME` 在 `.debug/` 下,本仓库以链接装进它的 web 配置,启动后交出带令牌的地址;通道自己的补丁走 `--patch` 覆盖层,profile 的用户层留给宿主;`resetState` 清掉会话与侧栏缓存,断言页面的场景因此从空会话列表开始。实例的标准输入是一条只由启动进程持有的管道,`packages/testing/lifeline.cjs` 经 `NODE_OPTIONS` 预加载进宿主,读到管道关闭就结束宿主:启动进程无论正常退出、抛出未捕获的错误、被 Ctrl+C 或 `taskkill /F` 终止,实例都随它结束,不会留在后台。
13
13
  - `packages/testing/mock-llm.cjs` 是脚本化的模型服务,按 Messages 协议回答宿主路由,脚本按请求顺序交付;只有模型是脚本化的,agent 循环、会话、客户端各层与渲染都是发行版宿主。
14
14
  - 端到端通道按职责分成几份:`packages/testing/e2e.cjs` 是运行器,为每个场景起一套实例、发出提示词、在轮次进行中逐帧采样、失败时写下现场;`packages/testing/scenarios.cjs` 是场景表,自带读取逻辑的场景各成一个模块挂进表里;`packages/testing/lane.cjs` 放运行器与场景共用的页面标记与断言形状;`packages/testing/contract-probe.cjs` 放契约表的逐条核对。场景可以给宿主的命令行与浏览器各加参数(`hostArgs`、`browserArgs`)。
15
- - 每个场景各起一套实例,断言各自的东西:`conversation` 读者的消息成行、脚本回答的 Markdown 与思考过程归组;`narrow` 以 390×844 的手机窗口打开,断言输入框下沿那一行放得下权限档位、模型、推理等级与上下文圆环(权限收起下三角、两个选择器按窄屏上限截断、圆环不显示数字、四者都在卡片内且圆环不与旁边重叠);`statsPosition` 用补丁层把「对话统计信息位置」存成「独立成行」,断言这个值经宿主的设置命名空间到达页面、宿主的统计行与计量环在卡片下面排成自己的一行、皮肤自己的数字块没有建起来(`packages/testing/stats-position.cjs`:存档值与宿主设置表单这一段路,替代的宿主页面证明不了);`statsOverlay` 在「收进上下文」那一档下把计量环所在的 dock 与输入行两个盒子逐边对起来,把卡片在那一行下方留的空隙分别改成 0 与 16px 断言 dock 跟着那一行走、不停在写死的 4px 上,再把触发按钮在停放节点里左移 14px 断言行尾留出的宽度按两个盒子的并集算(`packages/testing/stats-overlay.cjs`;两个场景由 `stats-position.cjs` 一次导出);`sidebarRail` 收起侧栏,断言新会话那一格是宿主的 36 像素图标按钮、宿主留给它自己图标的座位不再占这一行的宽度、圆片由按钮自己居中并落在收起按钮与各面板图标同一条中线上,再把座位放回这一行量一次它占住的宽度,让这条中线不是巧合(`packages/testing/sidebar-rail.cjs`);`tool` 工具调用单独成行并带调用编号、结果之后的回答渲染出来;`send` 发送起飞期间替身离开页面、真行不可见、读者的字每一帧都看得见、替身带走了输入卡片的字体;`scroll` 逐帧记录滚动位置与内容高度,断言没有一帧在内容没有变短时倒退、没有一帧在内容没有增长时把尾部留在视野之外;`readerScroll` 在轮次进行中自己滚一次滚轮,断言状态行的固定只在跟随自己移动位置时钉住、读者往回翻看时该行随内容走而不浮在内容上,它自己逐帧采样(`packages/testing/reader-scroll.cjs`);`processSummary` 让脚本化的模型先沉默过 10 秒再开口(`packages/testing/mock-llm.cjs` 的 `holdMs`),断言运行中那一行换成皮肤的等待行、计时用宿主的单位、过 10 秒挂出「暂未响应」、模型一开口就撤下,并在轮次结束后按每个过程组标题上的计数核对它收住的东西(`packages/testing/process-summary.cjs`);`importance` 走过首页、对话、草稿、流式输出、各个弹层与菜单、附件、暗色与品牌和配色的切换以及每条 `:hover` 规则的悬停,逐条判定皮肤的 `!important` 是否需要,一条多余即失败;设置 `DSH_IMPORTANCE_REFERENCE` 时每个状态还与一张旧样式表逐元素比对(D51);`remoteSettings` 让宿主多信任一个名字 `remote.test`、浏览器把它解析到本机,同一实例分别以 loopback 地址与这个名字打开,在本机页面改主题风格,断言远程页面不刷新就跟上、刷新后读到的仍是宿主的值、它的设置页开头有说明行且控件全部禁用、本机设置页照常可写,结束前把主题风格写回原值,因为设置文档在各场景共用的临时目录里(`packages/testing/remote-settings.cjs`,D60);`shots` 亮暗两套截图。
15
+ - 每个场景各起一套实例,断言各自的东西:`conversation` 读者的消息成行、脚本回答的 Markdown 与思考过程归组;`narrow` 以 390×844 的手机窗口打开,断言输入框下沿那一行放得下权限档位、模型、推理等级与上下文圆环(权限收起下三角、两个选择器按窄屏上限截断、圆环不显示数字、四者都在卡片内且圆环不与旁边重叠);`statsPosition` 用补丁层把「对话统计信息位置」存成「独立成行」,断言这个值经宿主的设置命名空间到达页面、宿主的统计行与计量环在卡片下面排成自己的一行、皮肤自己的数字块没有建起来(`packages/testing/stats-position.cjs`:存档值与宿主设置表单这一段路,替代的宿主页面证明不了);`statsOverlay` 在「收进上下文」那一档下把计量环所在的 dock 与输入行两个盒子逐边对起来,把卡片在那一行下方留的空隙分别改成 0 与 16px 断言 dock 跟着那一行走、不停在写死的 4px 上,再把触发按钮在停放节点里左移 14px 断言行尾留出的宽度按两个盒子的并集算(`packages/testing/stats-overlay.cjs`;两个场景由 `stats-position.cjs` 一次导出);`sidebarRail` 收起侧栏,断言新会话那一格是宿主的 36 像素图标按钮、宿主留给它自己图标的座位不再占这一行的宽度、圆片由按钮自己居中并落在收起按钮与各面板图标同一条中线上,再把座位放回这一行量一次它占住的宽度,让这条中线不是巧合(`packages/testing/sidebar-rail.cjs`);`tool` 工具调用单独成行并带调用编号、结果之后的回答渲染出来;`fileMerge` 让脚本化的模型在一个轮次里改同一个文件四次、另一个文件写一次,断言这个文件在页面上只留一行可见、行尾数字是这四次改动的累计值并随每次改动滚动、其余三行整行离开页面且不占高度、展开代表行读到全部改动,另一个文件的单行不被卷进来(`packages/testing/file-merge.cjs`,D62);`send` 发送起飞期间替身离开页面、真行不可见、读者的字每一帧都看得见、替身带走了输入卡片的字体;`scroll` 逐帧记录滚动位置与内容高度,断言没有一帧在内容没有变短时倒退、没有一帧在内容没有增长时把尾部留在视野之外;`readerScroll` 在轮次进行中自己滚一次滚轮,断言状态行的固定只在跟随自己移动位置时钉住、读者往回翻看时该行随内容走而不浮在内容上,它自己逐帧采样(`packages/testing/reader-scroll.cjs`);`processSummary` 让脚本化的模型先沉默过 10 秒再开口(`packages/testing/mock-llm.cjs` 的 `holdMs`),断言运行中那一行换成皮肤的等待行、计时用宿主的单位、过 10 秒挂出「暂未响应」、模型一开口就撤下,并在轮次结束后按每个过程组标题上的计数核对它收住的东西(`packages/testing/process-summary.cjs`);`reasoningStream` 让脚本化的模型分几步推理半分钟,推理进行中每 250 毫秒读一次窗口,断言窗口跟着文本长高、长到宿主给过程组正文的上限就停住并开始按两行一步往上走,长满之前文本铺满窗口、没有遮罩也没有位移,走到的位置不超过内容末尾、最新一行始终留在窗口里,推理结束后窗口撤下、整段文本原样站着(`packages/testing/reasoning-stream.cjs`);`importance` 走过首页、对话、草稿、流式输出、各个弹层与菜单、附件、暗色与品牌和配色的切换以及每条 `:hover` 规则的悬停,逐条判定皮肤的 `!important` 是否需要,一条多余即失败;设置 `DSH_IMPORTANCE_REFERENCE` 时每个状态还与一张旧样式表逐元素比对(D51);`remoteSettings` 让宿主多信任一个名字 `remote.test`、浏览器把它解析到本机,同一实例分别以 loopback 地址与这个名字打开,在本机页面改主题风格,断言远程页面不刷新就跟上、刷新后读到的仍是宿主的值、它的设置页开头有说明行且控件全部禁用、本机设置页照常可写,结束前把主题风格写回原值,因为设置文档在各场景共用的临时目录里(`packages/testing/remote-settings.cjs`,D60);`shots` 亮暗两套截图。
16
16
  - 截图沿用写盘前的隐私替换与泄漏扫描(`scripts/shared/privacy.cjs`,与 `scripts/shoot.cjs` 共用),两套明暗各写一张到本次运行的输出目录供人查看;仓库不存比对基线,画面是否变样由用户看过再定。
17
17
  - 持续集成(`.github/workflows/ci.yml`):构建检查、单元测试与完整冒烟在 Linux 上跑;完整冒烟另在 Windows 上跑一遍,读者的桌面端在 Windows,宿主半边在那里读会话目录、盘符路径与文件时间;端到端的行为场景与截图捕获在 Linux 上跑,宿主版本固定;`contract` 场景另对宿主最新的预发布版(npm 的 `alpha` 标签)跑一遍,宿主改动了皮肤读的契约时先在这里报出,只作预警,不挡合并。每个任务都有时限;工作流用到的 Actions 由 Dependabot 每周提更新(`.github/dependabot.yml`)。
18
18
  - `.debug/` 里有价值的探针已升级为有维护的工具:逐帧滚动记录并入 `scroll` 场景,发送过程录制并入 `send` 场景的逐帧采样,替身采样并入同一份采样;其余草稿在会话结束时删除。
@@ -8,7 +8,7 @@
8
8
 
9
9
  - 品牌偏好有两档:Claude(默认)与 DeepSeek(值为 `deepseek`;早先存下的 `off` 读作 DeepSeek,`anthropic` 读作 Claude)。
10
10
  - DeepSeek 一档保留宿主自己的品牌区并画成 DeepSeek 蓝;配色为 Claude 时亮暗两套都换成蓝色系。
11
- - 皮肤自己画的 Claude 标(账号行的兜底头像、轮次状态行的火花)在 DeepSeek 一档换成宿主的鲸鱼标(`FISH_LOGO_PATH`)。
11
+ - 皮肤自己画的 Claude 标(账号行的兜底头像)在 DeepSeek 一档换成宿主的鲸鱼标(`FISH_LOGO_PATH`)。
12
12
  - 配色切换只靠令牌:DeepSeek 的亮暗两块(多一个品牌属性,优先级更高)整组重写宿主的别名令牌与皮肤的私有令牌,各功能样式表一律读令牌。
13
13
 
14
14
  ## 理由
@@ -13,6 +13,7 @@
13
13
  - `source` 在折算补进过日期时写 `cost-meter+local`,完全没有账本或账本补不上时写 `local`;两个值都不改变面板的取数路径。
14
14
  - 折到账本已覆盖日期的会话照旧留在折算缓存里,读它们的账本日不与之相加。
15
15
  - 账本不存在、解析不了或版本不认时,整条路径与从前一致:只用本地折算(D12)。
16
+ - 一个会话的日志经宿主的观察接口读(`sessionQuery.observeSession`,见 `session-events.ts`):它只按 id 观察这一份存档,不先列整份存档,所以读 N 个会话是 N 次观察、零次列档;宿主只发布 `readSession` 时退回按 id 读,那一版每读一个会话会先列一次整份存档。
16
17
 
17
18
  ## 理由
18
19
 
@@ -22,7 +23,7 @@
22
23
 
23
24
  ## 代价
24
25
 
25
- - 折算仍要遍历全部会话日志,成本由缓存摊平(只有变更过的日志重读)。
26
+ - 折算仍要遍历全部会话日志,成本由缓存摊平(只有变更过的日志重读);一趟里每个变更过的日志各观察一次,观察按存档修订号复用已还原的会话。
26
27
  - 一个会话的最后写入落在未覆盖日期、而它也在账本覆盖的日子里结算过时,会话数会重复计一次;账本的会话 id 与会话目录名不同格式,无法相减。
27
28
  - 「高峰时段」只由折算补上的那几天决定:账本没有小时维度,它覆盖的日子在被折到之前没有直方图,窗口的峰值可能因此偏低。
28
29