dsh-claude-style 0.12.3 → 0.13.1

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,40 @@ All notable changes to `dsh-claude-style` are documented here, newest first.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.13.1] - 2026-10-10
8
+
9
+ [中文](#cn-0.13.1) | [English](#en-0.13.1)
10
+
11
+ <h3 id="cn-0.13.1">问题修复</h3>
12
+
13
+ - 装了 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/*` 即失败。
14
+
15
+ <h3 id="en-0.13.1">Bug Fixes</h3>
16
+
17
+ - 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`.
18
+
19
+ ## [0.13.0] - 2026-10-10
20
+
21
+ [中文](#cn-0.13.0) | [English](#en-0.13.0)
22
+
23
+ <h3 id="cn-0.13.0">体验优化</h3>
24
+
25
+ - 会话的统计与 Token 用量现在默认「收进上下文」:这些数字就在输入区上下文圆环那张弹层里,输入卡片下面只留那个圆环,不再多出一行;想一眼看到全部读数的人仍可在设置页「对话」页把「对话统计信息位置」改回「独立成行」。已有安装的设置不会被改写,这一项没设过的人升级后看到的是新默认。
26
+
27
+ ### 问题修复
28
+
29
+ - 收起侧栏后,「新会话」那枚加号圆片不再比同一栏的其它图标靠左:宿主把这一格换成一枚 36 像素的图标按钮并让它里面的东西居中,而它自己那个图标座位在收起状态下是空的(图标已隐藏、文字宽度为 0),却仍占着这一行的一份宽度,把圆片顶到栏内中线左边 7 像素;现在这一格交出座位,圆片与上面的收起按钮、下面的插件与搜索图标落在同一条中线上。
30
+ - 手机宽度下,统计数字收进上下文弹层时那枚圆环不再跑出输入卡片:铺在工具栏行上的那条 dock 一直带着桌面端那份左右留白(给权限档位与模型、推理等级两块让出位置),它比这一宽度下的卡片还宽,把贴着它右缘的圆环一起顶出卡片、顶到窗口外;这一宽度下那份留白让位,圆环回到卡片右端、落在推理等级按钮之后。
31
+
32
+ <h3 id="en-0.13.0">Improvements</h3>
33
+
34
+ - The session statistics and the token usage now live Inside the context popover by default: the figures are in the panel the composer's context ring opens, and the input card keeps only the ring below it rather than a line of its own. A reader who wants every reading at a glance can still set Conversation statistics position back to On its own line on the settings page's Conversation tab. Settings already stored are not rewritten: an installation that never set this preference shows the new default after the upgrade.
35
+
36
+ ### Bug Fixes
37
+
38
+ - With the sidebar collapsed, the New session chip no longer stands left of the column's other icons: the host turns that cell into a 36px icon button and centres what is inside it, while its own icon seat is empty in that state (the icon is hidden, the label is 0 wide) and still took a share of the flex row, pushing the chip 7px left of the column's centre line. The seat now gives up its share, and the chip lines up with the collapse toggle above it and the plugin and search icons below.
39
+ - At a phone's width the context ring no longer leaves the input card when the numbers live in the context popover: the dock laid over the toolbar row kept the desktop reserve that clears the permission preset and the model and reasoning-level controls, which is wider than the card at that width, and it carried the ring pinned to its right edge out of the card and past the window. The reserve now stands down at this width, and the ring returns to the card's right end, after the reasoning-level control.
40
+
7
41
  ## [0.12.3] - 2026-10-10
8
42
 
9
43
  [中文](#cn-0.12.3) | [English](#en-0.12.3)
@@ -12,7 +12,7 @@ What each feature does and where its switch lives; the five tabs, their rows and
12
12
  - **Mascot**: A pixel companion stands on the composer's top edge and changes its animation with what the agent is doing (thinking, writing and calling tools, several sessions at work, subagents, waiting on you, compacting the context, finished, failed, asleep). Follow the brand shows the pixel crab under Claude and Deepy the whale under DeepSeek; either can be picked for good, or none. Where it appears keeps it to the new-conversation page, or puts it in conversations as well.
13
13
  - **Greeting and hint**: The new-conversation page greets you by name with a line that changes with the time of day, and the empty composer shows Claude's own hint.
14
14
  - **Permission control**: The composer's permission menu becomes a row of segments. The presets are the host's own catalog, a plugin's preset included; the new-conversation page shows the one a new session will start in.
15
- - **Session numbers**: The session statistics and the token usage follow the host's data live, on a line of their own below the input card — turns and steps, the output speed, the tokens read in and written back, the cache-hit share, with the context ring itself at the end. The line is not only a reading: pressing it opens that ring's popover, which still holds the host's own context rows followed by this session's detail rows, and pressing it again puts them away (opening and closing on hover stays the ring's own path); Inside the context popover keeps the numbers there alone. A number that moves re-enters character by character (the last two a beat behind), no separator glyph stands between the groups — the line's own gap is what sets them apart — and the cache share and the ring's occupancy each take their own colour band.
15
+ - **Session numbers**: The session statistics and the token usage follow the host's data live and stand in the context ring's popover by default: the host's own context rows, followed by this session's detail rows — turns and steps, the output speed, the tokens read in and written back, the cache-hit share. On its own line writes them on a line of their own below the input card with the ring at its end, and pressing that line opens and closes the same popover (opening and closing on hover stays the ring's own path). A number that moves re-enters character by character (the last two a beat behind), no separator glyph stands between the groups — the line's own gap is what sets them apart — and the cache share and the ring's occupancy each take their own colour band.
16
16
  - **Peak rate meter**: Every row of the model picker carries the rate in force for that model right now: peak, off-peak and a dated promotion each get their own mark, the colour says expensive or cheap, and the countdown says how long it lasts; hovering reads the whole judgement, the state in force and what it becomes and when. Each provider is judged on its own clock, public holidays are off-peak in full, and windows crossing midnight and daylight-saving changes are resolved against real instants.
17
17
  - **Model picker**: The composer's model menu becomes a two-level card: the first level holds the official service and the quick providers you picked, each model with its vendor mark and a line of description; the second level holds the rest.
18
18
  - **Effort**: The reasoning effort gets a trigger of its own beside the model, opening a slider. It switches with the model picker, since the host keeps the effort inside its own model menu.
package/docs/FEATURES.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - **吉祥物**:输入框上沿站着一只像素小伙伴,随智能体的工作状态换动画(思考、写回答与调用工具、多个会话同时工作、子代理、等你操作、压缩上下文、完成、失败、睡着)。「跟随品牌」在 Claude 品牌下是像素螃蟹,在 DeepSeek 品牌下是小鲸鱼 Deepy,也可以固定选一个或者关闭;「出现位置」决定它只在新会话页出现,还是新会话页和对话页都出现。
13
13
  - **问候语与提示语**:新会话页的问候语按时段换着说,带上你的名字;输入框的空白提示换成 Claude 的那一句。
14
14
  - **权限控件**:输入区的权限菜单换成一排分段按钮,档位以宿主的目录为准,别的插件加的档位也在里面;新会话页上显示新会话会用的那一档。
15
- - **会话数字**:会话统计与 Token 用量随宿主的数据实时更新,在输入卡片下面单独占一行:轮数与步数、输出速度、读入与写回的 tokens、缓存命中率,末尾就是上下文圆环自己;这一行不只是读数——按一下它,圆环那张弹层就打开,里面照旧是宿主自己的上下文行加上这一段会话的数字明细,再按一下收起(悬停开合只归那个圆环);也可以选「收进上下文」,把数字只留在那张弹层里。数字一有变化就逐个字符重新入场(末两位稍慢),各段之间没有分隔符号、只靠间距分开,缓存命中率与圆环占用按各自的无极色阶取色。
15
+ - **会话数字**:会话统计与 Token 用量随宿主的数据实时更新,默认收进上下文圆环那张弹层:里面是宿主自己的上下文行,后面接上这一段会话的数字明细——轮数与步数、输出速度、读入与写回的 tokens、缓存命中率。也可以选「独立成行」,把这些数字写在输入卡片下面单独一行、末尾就是那个圆环,按一下那一行同样开合这张弹层(悬停开合只归那个圆环)。数字一有变化就逐个字符重新入场(末两位稍慢),各段之间没有分隔符号、只靠间距分开,缓存命中率与圆环占用按各自的无极色阶取色。
16
16
  - **峰谷电表**:模型选择器的每一行标出该模型此刻的峰谷:峰价、谷价与限时活动三种状态各有自己的图标,颜色表示贵还是便宜,并跟上距离切换还有多久;悬停读到完整的一条,讲出此刻的判定与切换后的状态。各家供应商按各自时区的作息判定,法定节假日整日算谷价,跨零点与夏令时都按真实时刻计算。
17
17
  - **模型选择器**:输入区的模型菜单换成两级卡片:第一级是官方服务与你挑的快捷供应商,每个模型带厂商标志与一句说明,第二级是其余模型。
18
18
  - **工作强度**:模型旁边有一个自己的工作强度按钮,点开是一条滑杆;它跟着模型选择器一起开关,因为宿主把工作强度放在自己的模型菜单里。
@@ -46,7 +46,7 @@ In the settings dialog, this page's heading row ends with a GitHub mark; pressin
46
46
 
47
47
  **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.
48
48
 
49
- **Conversation statistics position**: the session's eight figures have two places to live. On its own line (the default) writes the turns and steps, the output speed, the tokens read in, the tokens written back and the cache-hit share on a line of their own below the input card, with the ring itself at the end; Inside the context popover puts them in the panel that ring opens and leaves only the ring under the input card. A figure on that line re-enters character by character whenever it moves (every step, and all through a streaming answer), and the cache share and the ring each take their own colour band: the share runs from red in the nineties to dark green at 99%, the occupancy from green at a fifth to red at two fifths. A window too narrow for the whole line makes it give way one step at a time: first the cache share's words, then the turns and steps, then the number beside the ring (the ring stays), then the tokens read in and written back as one total, and last the counts folded into k / M / B — and the same order gives them back when the window grows.
49
+ **Conversation statistics position**: the session's eight figures have two places to live. Inside the context popover (the default) puts them in the panel that ring opens and leaves only the ring under the input card; On its own line writes the turns and steps, the output speed, the tokens read in, the tokens written back and the cache-hit share on a line of their own below the input card, with the ring itself at the end. A figure on that line re-enters character by character whenever it moves (every step, and all through a streaming answer), and the cache share and the ring each take their own colour band: the share runs from red in the nineties to dark green at 99%, the occupancy from green at a fifth to red at two fifths. A window too narrow for the whole line makes it give way one step at a time: first the cache share's words, then the turns and steps, then the number beside the ring (the ring stays), then the tokens read in and written back as one total, and last the counts folded into k / M / B — and the same order gives them back when the window grows.
50
50
 
51
51
  **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.
52
52
 
package/docs/SETTINGS.md CHANGED
@@ -46,7 +46,7 @@
46
46
 
47
47
  **输入框插入符动效**:输入框里的光标由插件自己绘制,移动时滑过去;提问卡片的作答框与排队消息的行内编辑框同样覆盖。设置页的「对话」页有三档:每一格(默认)、只在移动时、关闭。
48
48
 
49
- **对话统计信息位置**:会话的八个数字有两个去处。「独立成行」(默认)把其中的轮数与步数、输出速度、读入的 tokens、写回的 tokens 与缓存命中率写在输入卡片下面单独一行,末尾就是那个计量环;「收进上下文」把它们放进那个计量环的弹层,输入卡片下方只留圆环。这一行里的数字一有变化就逐个字符重新入场(每走一个步骤、流式输出期间都在动),缓存命中率与圆环按各自的无极色阶取色:命中率从九成的红走到 99% 的深绿,占用从两成的绿走到四成的红。窗口窄到这一行放不下时它会依次让位:先去缓存那截的字样,再去轮数与步数,再去圆环旁的数字(环留下),然后把读入与写回合成一个合计,最后把数字折叠成 k / M / B;窗口宽回来时按同一顺序退回。
49
+ **对话统计信息位置**:会话的八个数字有两个去处。「收进上下文」(默认)把它们放进那个计量环的弹层,输入卡片下方只留圆环;「独立成行」把其中的轮数与步数、输出速度、读入的 tokens、写回的 tokens 与缓存命中率写在输入卡片下面单独一行,末尾就是那个计量环。这一行里的数字一有变化就逐个字符重新入场(每走一个步骤、流式输出期间都在动),缓存命中率与圆环按各自的无极色阶取色:命中率从九成的红走到 99% 的深绿,占用从两成的绿走到四成的红。窗口窄到这一行放不下时它会依次让位:先去缓存那截的字样,再去轮数与步数,再去圆环旁的数字(环留下),然后把读入与写回合成一个合计,最后把数字折叠成 k / M / B;窗口宽回来时按同一顺序退回。
50
50
 
51
51
  **对话导航**:对话区右侧那列短横线每一轮一条,跟着你读到的位置走。鼠标碰到它,它就展开成一张列表:每一行写着开启这一轮的那条消息,正好落在它那条短横线原来的位置上——你正在读的那一轮还在原处,鼠标下面还是刚才那一轮;滚轮上下翻,点一行页面滑到那一轮,还没加载的早期对话先加载再跳;指针停在列表上时,对话区顶部的对话 / 轨迹标签照常显示。Alt+↑ / Alt+↓ 跳到上一轮或下一轮,连按会一轮一轮接着走;输入框里有草稿时这两个键留给输入框。跳到的那一轮开头会闪一条短横线。同时装着 [dsh-plugin-msg-nav](https://github.com/SherUnlocked-4869/dsh-plugin-msg-nav) 时,Alt+↑ / Alt+↓ 留给它。
52
52
 
@@ -7,7 +7,7 @@
7
7
  ## 决定
8
8
 
9
9
  - 宿主画在输入卡片下方的统计行(轮次与步数 · tok/s、总 tokens · 缓存命中)与它的两张明细面板一起隐藏(`features/composer/inline-bar.css`):统计行仍留在文档里,只为读出「性能与用量」这一档(详细 / 简洁)的结构。`context` 那一档在弹层里给出的内容按这一档分:详细档给出宿主那两组所有的行,简洁档只给四个数字——总用时(模型用时加工具调用用时,宿主没有这个合计,用皮肤自己的文案)、首 token 平均、输出速度与缓存命中,排成无标题的 2×2 网格。
10
- - 数字的落点是一个选择项 `statsPosition`(设置页「对话」页的「对话统计信息位置」):`inline`(默认)把同一批数字写在输入卡片下面单独一行,末尾就是宿主画的计量环,这一行本身就是那张弹层的第二个触发口;`context` 只把这批数字写进计量环的弹层。两种取值下宿主的统计行都隐藏,弹层里的两段(宿主自己的上下文行加上这一段会话的数字明细)两种取值下都在,都由同一份数据填:这一项只决定 `inline` 那一行在不在,所以中途改选不刷新页面;取值随 `<body>` 的 `STATS_POSITION_ATTR` 上屏,隐藏统计行、把 dock 覆盖到输入行、给计量环留位置的规则都按它分档。覆盖到输入行的这一层照抄输入行自己的盒子:行高与它距容器的距离由 composer 的每次经过量出,写成 `--dsh-claude-meter-row-offset` / `--dsh-claude-meter-row-height`(`features/composer/composer.ts`),样式表读这两个值——卡片在那一行下方留的空隙与行高都属于宿主,写死会让计量环离开那一行;量在行或容器尺寸变化时做,不挂在每一帧上。给行尾留出的宽度(`--dsh-claude-meter-room`)同理按盒子算:宿主停放计量环的那个节点与它里面的触发按钮两个盒子并起来再加 8px,触发按钮比停放节点宽时行尾文字也不会被药丸底色压住。这一项不再归 `permissionsControl` 管(D29)。
10
+ - 数字的落点是一个选择项 `statsPosition`(设置页「对话」页的「对话统计信息位置」):`context`(默认)只把这批数字写进计量环的弹层;`inline` 把同一批数字写在输入卡片下面单独一行,末尾就是宿主画的计量环,这一行本身就是那张弹层的第二个触发口。两种取值下宿主的统计行都隐藏,弹层里的两段(宿主自己的上下文行加上这一段会话的数字明细)两种取值下都在,都由同一份数据填:这一项只决定 `inline` 那一行在不在,所以中途改选不刷新页面;取值随 `<body>` 的 `STATS_POSITION_ATTR` 上屏,隐藏统计行、把 dock 覆盖到输入行、给计量环留位置的规则都按它分档。覆盖到输入行的这一层照抄输入行自己的盒子:行高与它距容器的距离由 composer 的每次经过量出,写成 `--dsh-claude-meter-row-offset` / `--dsh-claude-meter-row-height`(`features/composer/composer.ts`),样式表读这两个值——卡片在那一行下方留的空隙与行高都属于宿主,写死会让计量环离开那一行;量在行或容器尺寸变化时做,不挂在每一帧上。给行尾留出的宽度(`--dsh-claude-meter-room`)同理按盒子算:宿主停放计量环的那个节点与它里面的触发按钮两个盒子并起来再加 8px,触发按钮比停放节点宽时行尾文字也不会被药丸底色压住。这一项不再归 `permissionsControl` 管(D29)。
11
11
  - 那一行是宿主自己的话包住宿主自己的数(`features/context-stats/inline-stats.ts`):轮数与步数、输出速度、读入的 tokens、写回的 tokens、缓存命中率,模板取自宿主 `chat` 命名空间(`stats.counts`、`message.tokensPerSecond`、`message.turnUsage.count`、`stats.cacheHit`,分词符 `number.groupSeparator`),宿主统计行上那些图标一个都不出现;各段之间只有这一行自己的间距(10px),不插 `·` 之类的分隔符。这一行也是计量环那张弹层的第二个触发口(`stats-binding.ts` 的 `bindLine`):按一下它弹层打开、再按一下收起,回车与空格同样开合、Esc 收起,展开状态写在它自己的 `aria-expanded` 上(值取自宿主那个触发按钮)。悬停开合只归计量环那条路径:指针停在数字上不展开弹层,也不接管弹层的收起。
12
12
  - 那一行宽过它所在的输入卡片时按台阶让位(`inline-stats.ts` 的 `fit`),顺序固定:先去掉缓存那一截的字样,再去掉轮数与步数,再去掉计量环旁的数字(环留下),然后把读入与写回两个数合成一个合计并去掉上下箭头,最后把数字折叠成 k / M / B。每上一个台阶都重新量一次「行 + 计量环」的宽度与卡片的可用宽度,宽回来时按同一顺序退回一个台阶。量宽只在数字真的变了、或视口与卡片尺寸变化时做。让位只让出宽度:第三个台阶让出计量环旁的数字时,宿主那个触发按钮保持它自己一行的高度(`features/composer/inline-bar.css`),输入区底部这一行的高度不随台阶起落变化——上方的对话按这一行排、并把末尾钉在它上面,行高一变,整屏内容就跟着上下跳。
13
13
  - 每个数写成逐字符的 digit 组(`stats-digits.ts`):数一变就换掉它的字符 span,新 span 播 transitions.dev 的 Number pop-in(末两位延后一次;THIRD_PARTY_NOTICES.md),没变的数一个字节都不写。重画挂在投影帧上,所以每经历一个 step、流式输出的每一帧到达时动的就是那几个数。动画归皮肤自己的动效偏好管(D26),不另设开关。入场是每个字符从自己位置下方 8px 滑上来(`--dsh-claude-digit-distance`):这一行与对话共用宿主的同一个滚动容器,滑上来那几像素会越过输入区底边、把容器的可滚动高度顶大几个像素,末尾跟随随即补偿,整屏内容跟着起落;那一行因此把自己裁掉(`features/composer/inline-bar.css` 的 `overflow: clip`),入场留在行内,容器高度不受它影响。
@@ -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` 一次导出);`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` 工具调用单独成行并带调用编号、结果之后的回答渲染出来;`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` 场景的逐帧采样,替身采样并入同一份采样;其余草稿在会话结束时删除。