dsh-claude-style 0.12.0 → 0.12.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.en.md +5 -26
  3. package/README.md +5 -24
  4. package/THIRD_PARTY_NOTICES.md +63 -0
  5. package/docs/FEATURES.en.md +2 -0
  6. package/docs/FEATURES.md +2 -0
  7. package/docs/SETTINGS.en.md +7 -5
  8. package/docs/SETTINGS.md +7 -5
  9. package/docs/STYLE.md +9 -0
  10. package/docs/decisions/D10-settings-transport.md +1 -0
  11. package/docs/decisions/D12-fail-fast.md +1 -0
  12. package/docs/decisions/D32-chat-interactions.md +1 -1
  13. package/docs/decisions/D36-modules-and-bundling.md +1 -1
  14. package/docs/decisions/D38-assets.md +3 -3
  15. package/docs/decisions/D39-on-demand-loading.md +19 -9
  16. package/docs/decisions/D42-feature-manifest.md +3 -4
  17. package/docs/decisions/D49-visual-yield.md +2 -2
  18. package/docs/decisions/D5-model-copy.md +5 -2
  19. package/docs/decisions/D51-css-toolchain.md +3 -1
  20. package/docs/decisions/D54-peakrate-meter.md +33 -0
  21. package/docs/decisions/D57-reader-view.md +44 -0
  22. package/docs/decisions/D9-style-performance.md +2 -0
  23. package/docs/decisions/README.md +4 -2
  24. package/lib/assets/05af73ae052e.js.br +0 -0
  25. package/lib/assets/0f01ab580fd7.js.br +0 -0
  26. package/lib/assets/174b8df9435c.js.map.br +0 -0
  27. package/lib/assets/244d8652af79.js.map.br +0 -0
  28. package/lib/assets/31253ed42440.js.br +0 -0
  29. package/lib/assets/41e470a78fff.js.map.br +0 -0
  30. package/lib/assets/5e05b29d5005.js.br +0 -0
  31. package/lib/assets/65476dea0432.js.br +0 -0
  32. package/lib/assets/6be8e4fa01d6.js.br +0 -0
  33. package/lib/assets/853c3ac35bee.js.map.br +0 -0
  34. package/lib/assets/930546669455.js.map.br +0 -0
  35. package/lib/assets/968fcbfc3e22.js.br +0 -0
  36. package/lib/assets/ac9b0e625837.js.map.br +0 -0
  37. package/lib/assets/b0e3c7732358.js.map.br +0 -0
  38. package/lib/assets/c52e41b4842b.js.map.br +0 -0
  39. package/lib/assets/db89dccfb643.js.br +0 -0
  40. package/lib/assets/manifest.json +80 -0
  41. package/lib/client.js +8 -7754
  42. package/lib/client.js.map +4 -4
  43. package/lib/host/peakrate-catalog.js +171 -0
  44. package/lib/host/peakrate.js +137 -0
  45. package/lib/host/routes.js +19 -0
  46. package/lib/host/settings.js +5 -2
  47. package/lib/model-descriptions.json +589 -96
  48. package/lib/peakrate-catalog.json +1601 -0
  49. package/package.json +17 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,72 @@ All notable changes to `dsh-claude-style` are documented here, newest first.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.12.1] - 2026-10-09
8
+
9
+ [中文](#cn-0.12.1) | [English](#en-0.12.1)
10
+
11
+ <h3 id="cn-0.12.1">新增功能</h3>
12
+
13
+ - 「聊天区动画效果」的「重绘」档换上插件自己的会话视图:它直接占据宿主「对话」视图的位置,标签条上不再多出一个标签,宿主转到「对话」的跳转同样落到它;档位离开「重绘」时宿主的「对话」原样回来。轮次进行中,新出现的一段思考把同一条链上此前的思考、正文与工具收成一行(思考×N · 输出×M · 工具×K),数字随内容跳变,用户插话重新开一条链,收拢走收缩、计数、停顿、展开四段时序;轮次成功结束后过程与中间叙述收起、只留最终回答与它的复制、分叉、反馈操作,失败、中断与等待批准的轮次保持展开,读者选中文字期间推迟折叠,历史轮次随时可以点开。思考在卡片里按阅读节奏滑动跟随,卡片只放思考的文字,读者滚回末尾即重新跟随;收进折叠的思考每段只占一行,写作「思考 · 内容」并带着思考卡片的边框,与中间输出一眼分开,按一下边框随全文展开;正文由宿主的 Markdown 渲染,新出现的词逐个淡入;工具行写出目标、改动行数与慢调用的计时,展开后是宿主自己的工具详情、输入与原始记录。轮次进行中的每一种状态(等待模型、思考、输出、准备与使用工具、等你操作)都由最新一条消息下面同一行说出;等待模型时这一行计时,过 10 秒标出「暂未响应」,进行中的过程也从这一行展开与收起。页面上装有 dsh-better-display 的阅读视图时这一份不登记;桌面端随带的 0.2.0 宿主与 npm 上的 0.2.1 宿主都能读。「动画」设为「减弱」时一律直接落位。
14
+ - 模型选择器的每一行标出该模型此刻的倍率:峰价、谷价与限时活动三种状态各有自己的图标,颜色区分贵与便宜,倍数后面跟着距离切换还有多久,鼠标停在徽章上读到「此刻的判定 · 多久后转为哪个状态」的完整一句,距切换不足一分钟时读 `<1m`。判定按各家供应商自己发布的作息表与自己的时区做(工作日窗口之外、周末与法定节假日整日算谷价,跨零点的窗口归它开始的那一天,夏令时切换按真实时刻计算,落进夏令时空档的窗边落在跳变那一刻);没有作息表的模型不显示徽章,把标准费率整天在走的供应商按其作息表显示。限时活动按文档声明的日期与时刻边界起止(结束时刻就是切换那一刻,已经结束的活动不会再生效)。作息表来自公开目录 offpeakclock.com 的一份快照:宿主半边取一次、在 `$DSH_HOME/cache/dsh-claude-style/peakrate.json` 留一份缓存、每 24 小时刷新一次,经 `/dsh-claude-style/peakrate` 下发(`POST` 强制重拉),连不上时按插件包里带的那份判定;浏览器半边只在功能装上时读一次,第三方地址不经过页面。构建核对匹配表里每一行点到的那条作息确实在包内快照里、且属于同一个供应方,对不上即构建失败。设置页「输入区」页新增「峰谷电表」子项,关闭后各行的徽章消失,模型选择器关闭时该项目变灰不可操作。
15
+
16
+ ### 体验优化
17
+
18
+ - 设置页「对话」页的「聊天区动画效果」从开关改成三档分段控件:关闭、增强、重绘。「关闭」把聊天区交回宿主;「增强」是原来的全套动效(跟随、自动开合与卷帘门、新文字淡入、文件变更行、聊天气泡动效);「重绘」交给另一套动效,接上的是聊天气泡动效与插件自己的阅读视图。旧版本存下的开关取值照旧生效(开 = 增强,关 = 关闭),不需要重新设置。
19
+ - 页面启动时插件占用的主线程时间减少:设置页、内容搜索、吉祥物、发送动效、对话导航、上下文弹层与封号彩蛋这七个首屏可以缺席的功能离开 `lib/client.js`,各自成为一个块,由插件已有的资产路由 `/dsh-claude-style/assets/` 提供,页面被接管后立即请求、到达后安装。在本地临时宿主上实测(两种产物各载入 20 次取中位数):`lib/client.js` 从 776 KB 降到 712 KB,前 3 秒的主线程长任务从 713 ms 降到 648 ms,皮肤生效时刻从导航后 298 ms 提前到 280 ms;七个块经 brotli 压缩后共 33 KB,比主产物晚约 5 ms 到达。块与它的 source map 各自按自身内容的哈希命名,浏览器不会把旧的 map 当成新的那份;构建拒绝主产物与块同时携带同一个模块(构建期生成的模块也在其中,它经共享表读主产物里的那一份)。插件更新后页面换代时,块已经在磁盘上:构建把主产物写在最后,先写新资产、再写清单、最后删清单里没有的文件。
20
+ - 设置页「侧栏」页新增「面板悬浮卡片」开关:开启(默认)时右侧栏停靠的面板仍是悬浮卡片——四周留 8px、16px 圆角、1px 描边与投影;关闭后停靠的面板铺满整列、贴住四边,分栏时每栏铺满各自那一栏,两栏之间只留宿主的接缝线。切换不需要刷新页面。
21
+ - 模型选择器每一行的说明改由一张按名字模式认领的有序规则表得出:带上日期后缀、把官方点号写成连字符或下划线(`glm-5-3-flashx`)或换一个售卖方前缀,仍然读到同一句;`k3-256k`、`ernie-5`、`minimax-m3-1m`、`deepseek-v41-flash` 这类目录拼法各自落到所在产品线的规则上,不必再逐条登记。带版本数字或最高级的说明写成锚定该版本的规则(`glm-5.3`、`gpt-5.4`、`qwen3.8-max`、`step-3.7-flash`、`celeris-1` 等),同产品线的其他版本读到线级文案;目录里现有的 25 个模型 id 里,除 Grok 与 ERNIE 两行按另一条记录改写外,其余解析出的句子与改动前逐条相同。
22
+ - 「重绘」档里轮次进行中的那条状态行(「正在思考」「正在使用工具」这些)上下各收进 30px,贴着最新一条消息读,不再像独占一行。实测:该行与上方轮次之间原有的 26px 空隙收掉,行内文字与它的下一条内容之间的距离也少 30px;这一行仍按内容居中,高度不变。为了让它在压上去时不露出旧内容的接缝,这一行补了一圈底色投影,并抬到上方内容之上。
23
+ - 「重绘」档的思考卡片只留思考本身:参考实现搬来的「思考 · 第 N 步」抬头、「暂停跟随 / 跟随最新」与「完整阅读 / 收起」两个按钮,以及为展开态在会话里量出的阅读高度都不再出现,卡片的边框里就是思考的文字,高度由预览高度与内容决定。跟随仍按阅读节奏走,滚回卡片末尾就重新跟随,而上一版要靠按钮点回来的那一步现在由滚动自己完成;展开一段思考仍是从折叠里那行文字点开,于是每段思考在折叠里保持一行,读全文按一下即可。
24
+ - 「重绘」档的一轮过程现在按宿主「工作步骤展示」的当前档位成形,两侧设置不再各行其是:插件不新增自己的档位控件,只读宿主 `ui-chat` 命名空间的档位并在每次有轮次开始时重读,读不到时按网页端缺省档「详细」。「简洁」把整条链收成一行,中间输出的正文也进这一行的计数;「标准」收拢最后一段思考之前的全部步骤,中间输出随最后一段思考留在行外;「详细」把结尾的中间输出留成自己的一行、收拢它之前的全部步骤;「完全展开」与「详细」同形,另外把收进折叠的思考画成整张推理卡片(全文,而不是「思考 · 内容」一行),工具仍由读者自己按开。宿主自己的设置行仍是唯一控件,插件不代写。
25
+ - 模型选择器里 Grok 与 ERNIE 两行的说明改写:Grok 的规则从匹配 4.5 改成匹配 Grok 4 整条产品线,`grok-4`、`grok-4.5`、`grok-4-2026` 这些拼法读到同一句「全球最智能的模型之一,支持原生工具使用以及实时搜索功能。」;ERNIE 5 一行改成「2.4 万亿参数的原生全模态大模型。」,不再列激活比例与上下文长度。
26
+ - 插件的样式表以压缩后的文本随包发出:注释与排版只留在源文件里,整张样式表从 349 KB 降到 208 KB,`lib/client.js` 从 712 KB 降到 570 KB。页面画出的样子不变:端到端通道把压缩前后的两份样式表放到同一个页面上,22 个页面状态逐元素比对计算值,全部一致。压缩还把 `!important` 审计过去豁免的一部分规则变成可以逐条判定的(同一个属性写两遍的规则,压缩后只剩一条声明):判定之后,输入区停止按钮、侧栏进行中指示与聊天区跟随尾部上页面并不需要的 46 处 `!important` 从源样式表里去掉了。审计也随之收紧:处在条件不成立的 `@media` / `@supports` 里的规则不再参与判定,它们的 `!important` 不会因为页面当时不在那个条件里就被判成多余。在本地临时宿主上实测(两种产物各载入 20 次、做两轮,取中位数):插件包的求值时间从约 9.0 ms 降到约 8.3 ms;启动阶段的其余计时差别落在两轮之间的波动以内。
27
+
28
+ ### 问题修复
29
+
30
+ - 长会话里流式输出期间的卡顿减少:跟随在每一批文字到达时都要问的几个问题不再落到整个文档上——会话的滚动容器与消息列读一次后保留(换会话、切「轨迹」页签时旧节点会从文档里移除,届时重新读取),流式标记改在消息列里查找,宿主「回到底部」按钮的存在本身就是「跟随已关闭」的判据,而同一帧里读到的高度只读一次,交给收回宿主写下的末尾、判断是否继续滑行这两处共用。在 120 轮、4286 个节点的临时会话上实测:插件发出的文档级查询从 3263 次降到 2762 次(−15%),整页最长的一次停顿从 270ms 降到 234ms;文档越大,省下的每次全页样式重算越多。
31
+ - 模型选择器里当前在用的模型、当它所属的供应商不在第一级列表里时,会单独占一级列表最下面一行——这一行过去绕过了其余各行共用的行构造,因此没有峰谷徽章,也没有厂商标志与说明;现在它与列表里的每一行走同一个构造,徽章、标志、说明和行高内边距都一致。
32
+ - 「重绘」档里收进折叠的一段思考,展开后再点它的文字没有反应,只能点右侧的折角按钮才能收起。文字上的点击此前只在收起时挂着,展开后整行文字只剩选择文字的作用。现在文字两态都可点:收起时点它展开,展开时点它收起;一次以选中文字收尾的落点不改开合,选中文字期间推迟折叠的行为照旧。
33
+ - 进行中的状态行不再随流式内容上下跳动。此前该行只在宿主自己的「跟随还开着」标记下钉住,而流式期间跟随的滑动会把滚动位置停在末尾之前,宿主把这次写入读成读者离开了末尾并摘掉标记,于是每一片内容到达时该行退回文档流、被内容底部推下去,滑动追上来再把它拉回:视口 1280×600 下实测,标记缺席的 115 帧里该行顶边在 447px 与 573px 之间移动,而钉住位是 426px。现在滑动持有位置期间在聊天列上写下自己的标记,该行按宿主标记或滑动标记任一钉住,内容突发的追加不再带动它:同一场景里标记缺席的 112 帧内顶边恒为 426px。往回翻看历史时两个标记都不在,该行照常留在文档位置,不会浮在读者正在读的内容上。
34
+ - 「重绘」档里轮次状态行的文字在长短之间切换时,被替换下去的那一段会暂时折成两行、溢出状态行的高度(视口 1280×900 实测:状态行高始终 32px,而退出中的文字片段为 84×48px)。原因是两层文字在替换的同一帧里用新文字的宽度排布了旧文字:状态行按隐藏的丈量层取宽,绝对定位的文字层虽然被同一层撑开,浏览器在这一帧仍把旧文字按它原来的窄宽度断行。现在丈量层与被替换的两层都不换行,文字生长期间各行始终单行,状态行高度不再跳变。
35
+ - 修复 **重新编译后设置页等延后功能从设置里消失、要重新开关插件才回来**:构建原先先写 `lib/client.js`、几秒之后才重写 `lib/assets/`,而客户端热重载只认主产物,页面在新块落盘之前就换代,按当时那份清单请求新块得到 404,延后的功能各报告一次并退役。现在资产按「先写新文件、再写清单、最后删清单里没有的文件」落盘,`lib/client.js` 写在最后:换代的那一代要的块已经在磁盘上、也已经在清单里。
36
+
37
+ ### 其他变更
38
+
39
+ - 开发机上新增构建标记 `DSH_CLAUDE_STYLE_DEBUG=1`:链接安装的检出重建时,资产清单继续带着上一代仍然存在的条目并保留它们的文件,宿主在启动时读到旧主产物的页面不会因为一次重建丢掉延后的功能。发布构建与持续集成里这个标记不生效,清单就是这一次构建自己的资产;构建日志会说明当前处于哪一档。
40
+ - 构建把 Deepy 帧图的矢量化结果与资产的 brotli 压缩结果按输入哈希缓存在 `.debug/build-cache/`,只改 TypeScript 后的重建从约 14 秒降到约 1.7 秒,`lib/` 与不带缓存时逐字节相同;新增 `npm run dev`,监听构建读取的目录,每次保存后重新构建(带 `DSH_CLAUDE_STYLE_DEBUG=1`),构建失败时保留上一次的 `lib/` 并继续监听。
41
+
42
+ <h3 id="en-0.12.1">New Features</h3>
43
+
44
+ - The Redraw tier of Chat-area animations swaps in the plugin's own conversation view: it takes the place of the host's Chat view itself, no extra tab appears in the strip, and whatever the host sends to Chat lands on it; leaving the Redraw tier brings the host's Chat back as it was. While a turn runs, a new thought folds the thinking, output and tools before it in the same chain into one row (Thinking×N · Output×M · Tools×K) whose figures roll as the work grows, a message from the reader starts a new chain, and each fold runs a shrink, count, pause, reveal sequence. Once a turn completes, its process and interim narration fold away and the final answer stays with its copy, branch and feedback actions; a failed, stopped or waiting turn stays open, a text selection holds the fold, and any past turn opens again. Thinking glides inside its card at reading pace, the card holds the thought's text alone, and scrolling it back to its end resumes the follow; a thought inside a fold takes one line, reading "Thinking · …" inside the thinking card's frame so it never passes for interim output, and a press grows the frame to its whole text; the answer is the host's own Markdown with each new word fading in; a tool row names its target, the lines it changed and how long a slow call has run, and opens onto the host's own tool view, its input and the raw record. Every live state of a running turn (waiting on the model, thinking, writing, preparing and using a tool, waiting for you) is said by one line under the newest message; while the model is silent the line times the wait and marks it "No response yet" past 10 seconds, and the running process opens and closes from it. With dsh-better-display's reading view on the page this one does not register; it reads both the 0.2.0 host the desktop ships and the 0.2.1 host on npm. With Animation set to Reduced everything lands at once.
45
+ - Every row of the model picker now carries the rate in force for that model: peak, off-peak and a dated promotion each get their own mark, the colour separates expensive from cheap, the multiplier is followed by how long it lasts, hovering the badge reads one line — the judgement now, and what it becomes and when — and a switch under a minute away reads `<1m`. The judgement is made from each provider's own published schedule in that provider's own time zone (outside the weekday windows, and on weekends and public holidays in full, the rate is off-peak; a window crossing midnight belongs to the day it starts; a daylight-saving change is resolved against real instants, so a window edge inside a skipped hour falls at the transition), and a model with no schedule published carries no badge while a provider billing a flat standard rate all day keeps the badge its schedule describes. A dated promotion runs between the day and instant bounds the document states (its closing instant is the switch itself, so an expired promotion never applies again). The schedules come from one snapshot in the public offpeakclock.com catalog: the host half fetches it, keeps a copy in `$DSH_HOME/cache/dsh-claude-style/peakrate.json`, refreshes every 24 hours and serves it over `/dsh-claude-style/peakrate` (`POST` fetches again first), and falls back to the copy shipped in the package when it cannot be reached; the browser half reads it once when the feature installs, and the third-party address never goes through the page. The build checks that every row of the mapping table names a profile the shipped snapshot yields and belongs to the same provider, and fails on a row that does not. The Composer settings tab gains a Peak rate meter sub-row under the model picker: off removes the badges, and turning the picker off greys the row out.
46
+
47
+ ### Improvements
48
+
49
+ - The Conversation tab's Chat-area animations row is a three-way segmented control now — Off, Enhanced and Redraw — in place of its switch. Off hands the conversation back to the host; Enhanced is the set that was there before (the follow, the automatic folding with its rolling door, the text fade, the file change rows and the send flight); Redraw hands the area to a second set, whose members are the send flight and the plugin's own reading view. A switch value stored by an earlier build keeps its meaning (on reads as Enhanced, off as Off), so nothing has to be set again.
50
+ - Less main-thread time at page start: seven features the first frames can do without — the settings page, content search, the mascot, the send animation, the conversation navigator, the context popover and the account-hold easter egg — leave `lib/client.js` for chunks of their own, served by the plugin's existing asset route `/dsh-claude-style/assets/`, requested as soon as the page is taken and installed on arrival. Measured on a local scratch host (20 page loads of each build, medians): `lib/client.js` falls from 776 KB to 712 KB, main-thread long tasks in the first 3 s from 713 ms to 648 ms, and the skin takes the page 280 ms after navigation instead of 298 ms; the seven chunks are 33 KB brotli-compressed and arrive about 5 ms after the bundle. A chunk and its source map are each named by their own content, so the browser never reads an older map as the current one; the build refuses a module the bundle and a chunk would both carry, generated modules included — those are read from the bundle's own copy through the shared table. After an update the chunks are already on disk when a page swaps to the new bundle, because the build writes the new assets first, the manifest next, the files it does not name last, and the main bundle after all of them.
51
+ - The Sidebar tab gains a Float docked panels switch: on (the default) keeps the right sidebar's docked panels as floating cards — 8px inside the column, a 16px radius, a hairline and an elevation; off fills the column and hugs its edges, each pane filling its own column when the dock is split, with only the dock's own resting line left on the seam. Switching needs no reload.
52
+ - Every model row's description now comes from one ordered table of name-pattern rules: a dated suffix, a separator spelled differently (`glm-5-3-flashx`) or another reseller's prefix still reads the line's own sentence, and catalog spellings such as `k3-256k`, `ernie-5`, `minimax-m3-1m` and `deepseek-v41-flash` land on their product line's rule without an entry of their own. Copy carrying a version's numbers or a superlative sits on a rule anchored to that version (`glm-5.3`, `gpt-5.4`, `qwen3.8-max`, `step-3.7-flash`, `celeris-1` and the rest), while another version of the same line reads the line-level copy; of the 25 model ids the catalog names, every one but the Grok and ERNIE rows another record rewrites resolves to the same sentence as before.
53
+ - In the Redraw tier the live status line ("Thinking", "Using a tool" and the rest) comes in by 30px above and below, so it reads with the newest message instead of as a row of its own. Measured: the 26px gap it used to leave to the turn above is gone, and the distance from its text to whatever follows it drops by 30px as well; the line stays centred in its own height, which is unchanged. A base-coloured shadow now runs under and over the line where it draws over the content above, so the older content never shows at its edge.
54
+ - A thinking card in the Redraw tier now holds the thought and nothing else: the "Thinking · Step N" heading, the Pause following / Follow latest and Read in full / Collapse controls that came with the ported implementation, and the reading height it measured against the conversation while expanded are gone, so the card's frame is the thought's text with its height set by the preview and the text itself. The follow still runs at reading pace, and scrolling the card back to its end resumes it — the step the buttons used to do is now the scroll's own. Opening a thought still starts from its line inside a fold, so each thought keeps one line there and a press reads it in full.
55
+ - In the Redraw tier a turn's process now takes the shape of the host's Work details setting, so the two settings stop disagreeing: the plugin adds no tier control of its own — it reads the host's `ui-chat` value, re-reads it whenever a turn opens, and falls back to the Web client's default, Detailed, when the host serves none. Compact gathers a whole chain into one row, the intermediate output's own text included in that row's figures; Standard gathers everything before the last thought and leaves the intermediate output outside it, beside that thought; Detailed keeps the trailing intermediate output as its own row and gathers what came before; Verbose matches Detailed and draws a thought inside a fold as its whole reasoning card instead of the one-line "Thinking · …", with tools still opened by the reader alone. The host's own settings row stays the only control: the plugin never writes the value.
56
+ - Two lines of the model picker's copy are rewritten: Grok's rule now claims the whole Grok 4 line rather than the 4.5 build, so `grok-4`, `grok-4.5`, `grok-4-2026` and the rest read one sentence — one of the most intelligent models in the world, with native tool use and real-time search — and the ERNIE 5 row now reads a native omni-modal model with 2.4 trillion parameters, without the activation share or the context length.
57
+ - The plugin's stylesheet ships minified: comments and formatting stay in the source files, the sheet falls from 349 KB to 208 KB and `lib/client.js` from 712 KB to 570 KB. Nothing it paints changes: the end-to-end lane puts the sheet before and after minifying on the same page and compares computed values element by element across 22 page states, all identical. Minifying also brings rules the `!important` audit used to exempt within reach of it (a rule declaring one property twice keeps a single declaration once minified): judged one by one, the 46 `!important`s the page never needed — on the composer's stop button, the sidebar's ongoing indicator and the chat area's follow tail — leave the source sheets. The audit tightens with it: a rule inside a `@media` or `@supports` condition the page is not in is no longer judged, so its `!important` cannot be called redundant for a condition that keeps it out of the cascade. Measured on a local scratch host (two rounds of 20 page loads of each build, medians): evaluating the plugin's bundle takes about 8.3 ms instead of about 9.0 ms; the other startup timings move by less than they vary between rounds.
58
+
59
+ ### Bug Fixes
60
+
61
+ - Less jank while an answer streams in a long session: the questions the follow asks on every batch of arriving text no longer go to the document — the conversation's scroller and its message column are read once and kept (a session switch or the Trajectory tab removes the old node, and the reading is taken again), the streaming mark is looked for inside the message column, and the presence of the host's back-to-end button is itself the reading that its follow is off. What a frame does read it reads once and shares between taking back the end the host wrote and deciding whether to keep walking. Measured on a 120-turn, 4286-node scratch conversation: the plugin's document-level queries fall from 3263 to 2762 (−15%) and the page's longest single stall from 270ms to 234ms; the larger the document, the more each avoided whole-page style recalculation is worth.
62
+ - In the model picker, the model in force gets a row of its own at the bottom of level 1 when its provider is not listed there. That row was built apart from every other row, so it carried no peak rate badge, no vendor mark and no description line; it now goes through the same row builder as the rest of the list, with the same badge, mark, description and row metrics.
63
+ - In the Redraw tier a thought inside a fold could only be closed from the chevron button: once its line was open, a press on the text itself did nothing, because the text carried a click only while closed and left the whole line to text selection afterwards. The text now toggles in both states — closed, a press opens it, open, a press closes it again — while a press that ended as a selection changes nothing, and a selection still holds the fold off as before.
64
+ - The live turn's status line no longer moves with the streaming answer. It was pinned only under the host's own following mark, and while an answer streams the follow's glide holds the scroll position short of the end; the host reads that write as the reader leaving the tail, takes the mark away, and the row falls back into the flow, where each burst pushes it down and the glide pulls it back (measured at 1280×600: with the mark off, the row's top travelled between 447px and 573px in 115 frames, while the pin sits at 426px). The glide now writes its own mark on the chat column while it holds a position, and the row is pinned under either mark, so a burst costs it nothing: in the same scenario the row's top stayed at 426px in all 112 frames the host's mark was off. Scrolled away from the tail neither mark is present, so the row keeps its document place instead of floating over what the reader went back to read.
65
+ - In the Redraw tier a turn's status label briefly broke onto two lines while the text changed — the leftover half hung out of the row (measured at a 1280×900 viewport: the status row stayed 32px tall while the outgoing segment measured 84×48px). Two layers of text replace each other, and in the same frame the old text was laid out at the new text's width: the row takes its width from a hidden sizer, and the absolutely positioned copies are widened by it, yet the browser still broke the old text at its earlier, narrower width in that frame. The sizer and both copies now keep to one line, so a growing label stays on one line and the row keeps its height.
66
+ - Fix **the settings page and the other deferred features vanishing from a rebuild until the plugin was switched off and on**: the build wrote `lib/client.js` first and replaced `lib/assets/` seconds later, and since the client half of hot reload watches only the bundle, a page swapped to the new bundle before its chunks were on disk, asked for them under the previous manifest, got a 404, and each deferred feature reported once and retired. New asset files now land first, the manifest after them, the files it does not name last, and `lib/client.js` is written after all of them: the generation a page swaps to finds its chunks already on disk and already in the manifest.
67
+
68
+ ### Chores
69
+
70
+ - New build flag for a linked checkout, `DSH_CLAUDE_STYLE_DEBUG=1`: a rebuild keeps the entries an earlier build shipped and their files, so a page that booted the previous bundle does not lose the deferred features to a rebuild. The flag is inert in a release build and in CI, where the manifest carries exactly this build's assets; the build log says which mode ran.
71
+ - The build caches the Deepy sheets' vectors and the assets' brotli payloads in `.debug/build-cache/` under the hash of their inputs, so a rebuild after a TypeScript-only change drops from about 14 s to about 1.7 s while `lib/` stays byte-identical to an uncached build; the new `npm run dev` watches what the build reads and rebuilds on every save (with `DSH_CLAUDE_STYLE_DEBUG=1`), keeping the previous `lib/` and watching on when a build fails.
72
+
7
73
  ## [0.12.0] - 2026-10-07
8
74
 
9
75
  [中文](#cn-0.12.0) | [English](#en-0.12.0)
package/README.en.md CHANGED
@@ -73,42 +73,18 @@ What the plugin does and what each setting changes is documented: [Features](doc
73
73
 
74
74
  ## Fonts
75
75
 
76
- > **Important: the Anthropic fonts are not bundled with the npm package.** They are available for download in this repository under [`packages/assets/src/fonts/anthropic/`](packages/assets/src/fonts/anthropic/). You can either install them on your system, or skip the install entirely — drop the two `.ttf` files into `$DSH_HOME/dsh-claude-style/fonts/` (`~/.dsh/dsh-claude-style/fonts/` when `DSH_HOME` is unset) and the host will serve them as webfonts (the files are identical, so the result is the same). Either way, refresh or restart the web UI for the fonts to take effect.
76
+ > **Important: the Anthropic fonts do not ship with the npm package; they are in this repository for download and study only.**
77
77
 
78
78
  | Font | Used for | File |
79
79
  |---|---|---|
80
80
  | Anthropic Sans Web Text | Interface / UI | [`packages/assets/src/fonts/anthropic/AnthropicSansWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/anthropic/AnthropicSansWebText.ttf) |
81
81
  | Anthropic Serif Web Text | Conversation body / Markdown | [`packages/assets/src/fonts/anthropic/AnthropicSerifWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/anthropic/AnthropicSerifWebText.ttf) |
82
- | JetBrains Mono Variable | Code / code blocks | [`packages/assets/src/fonts/JetBrainsMonoVariable.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/JetBrainsMonoVariable.ttf), [`packages/assets/src/fonts/JetBrainsMonoItalicVariable.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/JetBrainsMonoItalicVariable.ttf) |
83
- | Inter | Interface when Anthropic Sans is absent | [`packages/assets/src/fonts/InterVariable.woff2`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/InterVariable.woff2) |
84
- | Noto Serif | Conversation body when Anthropic Serif is absent | [`packages/assets/src/fonts/NotoSerifVariable.woff2`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/NotoSerifVariable.woff2) |
85
-
86
- JetBrains Mono, Inter and Noto Serif are licensed under the SIL Open Font License 1.1 and ship with the npm package; nothing to set up. Inter and Noto Serif nearly match the two Anthropic fonts in letter height and width, so without the Anthropic fonts they stand in and the interface and conversation text keep their layout. Both carry only the Latin characters the Anthropic fonts cover; Chinese text keeps using the system's Chinese fonts.
87
82
 
88
83
  To enable the Anthropic fonts, choose one of the following:
89
84
 
90
85
  ① Install them on your system — on Windows, double-click each `.ttf` and choose "Install"; on macOS, import them with Font Book.
91
86
 
92
- ② Skip the install — copy the `.ttf` files into `$DSH_HOME/dsh-claude-style/fonts/`, then refresh the page.
93
-
94
- > The Anthropic Sans and Serif fonts are the property of Anthropic, licensed for personal use only, and are not covered by this project's MIT license.
95
-
96
- ## Skin center handoff
97
-
98
- This theme is also offered as a **skin** in the DSH Skin Center. Selecting it there hands the whole page to this
99
- plugin; selecting another skin or the official default takes the page back without a reload.
100
-
101
- - The answer holds from the first frame: the Skin Center stamps `html[data-dsh-skin]` into the served document, so a
102
- page that boots with a skin already painting never flashes a frame of this theme first.
103
- - While another skin paints the page this theme mounts no stylesheet, installs no feature and claims no document
104
- attribute at all. It takes the page back when the skin leaves and gives it back when a skin returns, a skin
105
- picked after the page loaded included.
106
- - A wallpaper plugin is not another owner: while a wallpaper renders this theme keeps working, as described in
107
- [Settings](docs/SETTINGS.en.md) under "Alongside other theme plugins".
108
- - The settings page and every stored preference survive the handoff. A skin taking the screen does not uninstall
109
- anything, and closing the Skin Center does not throw away what you configured here.
110
- - `data-dsh-claude-style-handoff` on `body` marks a build that can do this; the Skin Center offers the row only
111
- while it is there.
87
+ ② Skip the install — copy the `.ttf` files into `$DSH_HOME/dsh-claude-style/fonts/`.
112
88
 
113
89
  ## Installation
114
90
 
@@ -126,6 +102,7 @@ dsh plugin --profile web add Nwflower/dsh-claude-style # GitHub source
126
102
  ```
127
103
 
128
104
  3. From the [plugin market](https://github.com/dsh-market/dsh-market)
105
+ 4. From the Skin Center of the dsh-web-all plugin
129
106
 
130
107
  Keep only one theme enabled at a time. dsh ≥ 0.1.7 is required, and a restart of DeepSeek Harness brings the full feature set.
131
108
 
@@ -144,6 +121,8 @@ The conversation navigator's opening list, keyboard jumps and landing line follo
144
121
 
145
122
  The animation frames of Deepy the pixel whale come from the Deepy whale theme pack drawn by calmly-eating-bugs ([@wp3171216237](https://github.com/wp3171216237)), and ship with the plugin by the author's permission. Many thanks to the author! GIFs of all 20 animations, contributed by the author, are in [docs/gifs/](docs/gifs/).
146
123
 
124
+ The enhanced animations in the chat area are modified from [dsh-chat-ux](https://github.com/alm-allen/dsh-chat-ux) (MIT), and the redraw animations from [dsh-better-display](https://github.com/aa2246740/dsh-better-display) (MIT).
125
+
147
126
  The pixel crab (Clawd) is a character of Anthropic, and all rights in it remain with Anthropic. Its laptop animation is taken from Claude Code; the animations of its other states are drawn by this project after that character. The crab's frames are not covered by the MIT license (see [LICENSE](LICENSE)). This plugin is an unofficial fan work, not affiliated with or endorsed by Anthropic.
148
127
 
149
128
  ## Related projects
package/README.md CHANGED
@@ -80,6 +80,7 @@ dsh plugin --profile web add dsh-claude-style # npm 包(推
80
80
  ```
81
81
 
82
82
  3. 通过[插件市场](https://github.com/dsh-market/dsh-market)安装
83
+ 4. 通过dsh-web-all插件的皮肤中心安装
83
84
 
84
85
  同一时刻建议只启用一个主题。安装后推荐重启 `DeepSeek Harness`以获得完整能力。
85
86
 
@@ -89,40 +90,18 @@ dsh plugin --profile web add dsh-claude-style # npm 包(推
89
90
 
90
91
  ## 字体
91
92
 
92
- > 本插件使用的字体如下。
93
- >
94
- > Anthropic Sans/Serif 字体版权归 Anthropic 所有,仅供个人使用,不适用 MIT 许可。
95
- >
96
- > **重要:Anthropic 字体不随 npm 包分发,仅在仓库 [`packages/assets/src/fonts/anthropic/`](packages/assets/src/fonts/anthropic/) 供下载**。
93
+ > **重要:Anthropic 字体不随 npm 包分发,仅在仓库供下载学习研究**。
97
94
 
98
95
  | 字体 | 用途 | 文件 |
99
96
  |---|---|---|
100
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) |
101
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) |
102
- | JetBrains Mono Variable | 代码 / 代码块 | [`packages/assets/src/fonts/JetBrainsMonoVariable.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/JetBrainsMonoVariable.ttf)、[`packages/assets/src/fonts/JetBrainsMonoItalicVariable.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/JetBrainsMonoItalicVariable.ttf) |
103
- | Inter | 没有 Anthropic Sans 时的界面字体 | [`packages/assets/src/fonts/InterVariable.woff2`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/InterVariable.woff2) |
104
- | Noto Serif | 没有 Anthropic Serif 时的正文字体 | [`packages/assets/src/fonts/NotoSerifVariable.woff2`](https://github.com/Nwflower/dsh-claude-style/raw/main/packages/assets/src/fonts/NotoSerifVariable.woff2) |
105
-
106
- JetBrains Mono、Inter 与 Noto Serif 采用 SIL Open Font License 1.1,随 npm 包分发,无需任何操作。Inter 与 Noto Serif 的字高、字宽与两款 Anthropic 字体几乎一致,没有启用 Anthropic 字体时由它们代替,界面与正文的排版不会因此变样;两者只含 Anthropic 字体覆盖的拉丁字符,中文照旧使用系统中文字体。
107
99
 
108
100
  Anthropic 字体启用(二选一):
109
101
 
110
102
  ① 安装到系统——Windows 双击 `.ttf` → 「安装」,macOS 用「字体册」导入;
111
103
 
112
- ② 免安装——把 `.ttf` 复制到 `$DSH_HOME/dsh-claude-style/fonts/`(没有设置 `DSH_HOME` 时是 `~/.dsh/dsh-claude-style/fonts/`)。完成后刷新页面生效。
113
-
114
- ## 皮肤中心交接
115
-
116
- 在 DSH 皮肤中心里,这套主题同时以**皮肤**的身份出现。选它即把整页交给本插件;换选别的皮肤或官方默认时,
117
- 页面在不需要刷新的情况下交还回来。
118
-
119
- - 判定从第一帧就成立:皮肤中心把 `html[data-dsh-skin]` 注入服务端 HTML,因此带着皮肤启动的页面不会先闪过
120
- 一帧本主题。
121
- - 别的皮肤在画时,本主题不挂样式表、不安装任何功能、不占用任何文档属性;皮肤离开时立刻接管,皮肤回来
122
- 时再次让出,页面启动之后才选的皮肤也一样。
123
- - 壁纸插件不算另一个属主:壁纸在渲染时本主题照常工作,见[设置说明](docs/SETTINGS.md)里的「与其他主题插件同时使用」。
124
- - 交接期间设置页与所有偏好都还在:别的皮肤占屏不等于卸载,关掉皮肤中心也不丢这里配过的东西。
125
- - `body` 上的 `data-dsh-claude-style-handoff` 标记支持交接的构建;皮肤中心只在这一属性存在时才提供该选项。
104
+ ② 免安装——把 `.ttf` 复制到 `$DSH_HOME/dsh-claude-style/fonts/`。
126
105
 
127
106
  ## 文档
128
107
 
@@ -139,6 +118,8 @@ Anthropic 字体启用(二选一):
139
118
 
140
119
  像素小鲸鱼 Deepy 的动画帧图来自 calmly-eating-bugs([@wp3171216237](https://github.com/wp3171216237))绘制的 Deepy 小鲸鱼主题包。
141
120
 
121
+ 聊天区域内的增强动效修改自 [dsh-chat-ux](https://github.com/alm-allen/dsh-chat-ux)(MIT),重绘动效修改自 [dsh-better-display](https://github.com/aa2246740/dsh-better-display)(MIT)。
122
+
142
123
  像素螃蟹(Clawd)是 Anthropic 的角色形象,相关权利归 Anthropic 所有。螃蟹掏出电脑敲代码的动画取自 Claude Code,其余各个状态的动画由本项目按这一形象绘制。螃蟹的帧图不适用 MIT 许可(见 [LICENSE](LICENSE))。本插件是非官方的爱好者作品,与 Anthropic 没有关联,也未获其认可。
143
124
 
144
125
  ## 友链
@@ -0,0 +1,63 @@
1
+ # Third-Party Notices
2
+
3
+ This package includes material from the projects below. Each keeps its own license; the MIT license of this package does not relicense it.
4
+
5
+ ## dsh-better-display
6
+
7
+ The reading view of the Redraw tier (`packages/client/src/features/chat-reader/`, shipped in the `chatReader` chunk under `lib/assets/`) is ported from dsh-better-display 0.3.6: its fold rules, fold choreography, reasoning card, word fade timing, stream pacing, tool rows and stylesheet. The ported source files name it in their headers.
8
+
9
+ Source: <https://github.com/aa2246740/dsh-better-display>
10
+
11
+ ```
12
+ MIT License
13
+
14
+ Copyright (c) 2026 dsh-better-display contributors
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the "Software"), to deal
18
+ in the Software without restriction, including without limitation the rights
19
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ copies of the Software, and to permit persons to whom the Software is
21
+ furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
+ SOFTWARE.
33
+ ```
34
+
35
+ ## @deepseek-ai/dsh-util-values
36
+
37
+ The `chatReader` chunk bundles `PartialArguments` from this host package, so a tool call's arguments read the same way on hosts that hand the view only their raw text.
38
+
39
+ Source: <https://www.npmjs.com/package/@deepseek-ai/dsh-util-values>
40
+
41
+ ```
42
+ MIT License
43
+
44
+ Copyright (c) 2026 DeepSeek
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining a copy
47
+ of this software and associated documentation files (the "Software"), to deal
48
+ in the Software without restriction, including without limitation the rights
49
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
50
+ copies of the Software, and to permit persons to whom the Software is
51
+ furnished to do so, subject to the following conditions:
52
+
53
+ The above copyright notice and this permission notice shall be included in all
54
+ copies or substantial portions of the Software.
55
+
56
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
57
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
58
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
59
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
60
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
61
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
62
+ SOFTWARE.
63
+ ```
@@ -13,6 +13,7 @@ What each feature does and where its switch lives; the five tabs, their rows and
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
15
  - **Session numbers**: The session statistics and the token usage move into the popover of the composer's context ring and follow the host's data live. It shares the permission control's switch: both take over the composer's bottom line.
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.
16
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.
17
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.
18
19
  - **Home page directory and preset menus**: The directory and agent-preset menus on the new-conversation page are drawn as the skin's popover cards, parked over their own buttons, one open at a time.
@@ -29,6 +30,7 @@ What each feature does and where its switch lives; the five tabs, their rows and
29
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.
30
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; a failed or interrupted row keeps its verdict.
31
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.
33
+ - **Reading view**: The redraw tier swaps in the plugin's own conversation view, in the place of the host's Chat view itself. While a turn runs, a new thought folds the steps before it in the same chain into one row (Thinking×N · Output×M · Tools×K · Records×J) whose figures roll as the work grows, and a message from the reader starts a new chain; what that row gathers follows the host's Work details setting — Compact takes the whole chain, Standard everything before the last thought, Detailed keeps the trailing intermediate output as its own row and gathers what came before, and Verbose matches Detailed while drawing a folded thought as its whole reasoning card (tools stay opened by the reader alone). Each fold runs a designed sequence — shrink, count, pause, reveal. Once a turn completes, its process and interim narration fold away and the final answer stays; a failed, stopped or waiting turn stays open, a text selection holds the fold, and any past turn opens again. Thinking glides inside its card at reading pace and a thought inside a fold takes one framed line ("Thinking · …") whose text toggles in both directions, the answer is the host's own Markdown with each new word fading in, and a tool row names its target, the lines it changed and how long a slow call has run, opening onto the host's own tool view, its input and the raw record. Every live state of a running turn is said by one line under the newest message, which times the wait while the model has not answered yet.
32
34
  - **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.
33
35
  - **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.
34
36
  - **Header in the title bar**: On the desktop the session title and the header's controls rise into the window's own caption row: whichever side fits is placed there — the title at the left, the controls at the right — and the rest of the title row closes up to the left.
package/docs/FEATURES.md CHANGED
@@ -13,6 +13,7 @@
13
13
  - **问候语与提示语**:新会话页的问候语按时段换着说,带上你的名字;输入框的空白提示换成 Claude 的那一句。
14
14
  - **权限控件**:输入区的权限菜单换成一排分段按钮,档位以宿主的目录为准,别的插件加的档位也在里面;新会话页上显示新会话会用的那一档。
15
15
  - **会话数字**:会话统计与 Token 用量收进输入区上下文圆环的弹层里,随宿主的数据实时更新;它与权限控件共用一个开关,两者都接管输入区的底边。
16
+ - **峰谷电表**:模型选择器的每一行标出该模型此刻的峰谷:峰价、谷价与限时活动三种状态各有自己的图标,颜色表示贵还是便宜,并跟上距离切换还有多久;悬停读到完整的一条,讲出此刻的判定与切换后的状态。各家供应商按各自时区的作息判定,法定节假日整日算谷价,跨零点与夏令时都按真实时刻计算。
16
17
  - **模型选择器**:输入区的模型菜单换成两级卡片:第一级是官方服务与你挑的快捷供应商,每个模型带厂商标志与一句说明,第二级是其余模型。
17
18
  - **工作强度**:模型旁边有一个自己的工作强度按钮,点开是一条滑杆;它跟着模型选择器一起开关,因为宿主把工作强度放在自己的模型菜单里。
18
19
  - **新会话页的目录与预设菜单**:新会话页上目录与智能体预设的两个菜单画成皮肤的弹层卡片,停在各自的按钮上方,一次只开一张。
@@ -29,6 +30,7 @@
29
30
  - **新文字淡入**:流式回答里新出现的字符先淡后实(约 0.12 秒),并按到达次序略作错开;整段一次到达的内容、几千字的突发与刚被折叠重排过的文字保持本色。
30
31
  - **文件变更行**:run_code 程序里派发出去的写入与编辑带上行尾的 `+n -m` 与可展开的改动卡片,路径可点开文件;失败与中断的行保留裁决信息。
31
32
  - **聊天气泡动效**:提交消息时输入卡片浮起、一路收成那条气泡:飞行的这一份从第一帧就是气泡的颜色,草稿里的字跟着形状重新排,最后接上真实的消息行——真实气泡在下面先显示出来,飞行的那份淡出,文字逐渐变清晰。
33
+ - **阅读视图**:「重绘」档换上插件自己的会话视图,直接占据宿主「对话」视图的位置。轮次进行中,新出现的一段思考把同一条链上此前的步骤收成一行(思考×N · 输出×M · 工具×K · 记录×J),数字随内容跳变,用户插话重新开一条链;这一行的收拢范围按宿主「工作步骤展示」的当前档位分岔:「简洁」收整条链,「标准」收最后一段思考之前的全部步骤,「详细」把结尾的中间输出留成自己的一行、收它之前的步骤,「完全展开」同「详细」并把折叠里的思考画成整张推理卡片(工具仍由读者自己按开)。收拢走一段设计好的时序(收缩、计数、停顿、展开)。轮次成功结束后过程与中间叙述收起、只留最终回答,失败、中断与等待批准的轮次保持展开;读者选中文字期间推迟折叠,历史轮次随时可以重新展开。思考在卡片里按阅读节奏滑动跟随,收进折叠的思考每段只占一行(「思考 · 内容」,带思考卡片的边框)、文字两态都可点(收起时点开、展开时点收),正文由宿主的 Markdown 渲染、新出现的词逐个淡入;工具行写出目标、改动行数与慢调用的计时,展开后是宿主自己的工具详情、输入与原始记录。轮次进行中的每一种状态都由最新一条消息下面同一行说出,模型还没开口时这一行计时等待。
32
34
  - **输入框插入符动效**:输入框里的光标由插件自己绘制,移动时滑过去;提问卡片的作答框与排队消息的行内编辑框同样覆盖。设置页的「对话」页有三档:每一格(默认)、只在移动时、关闭。
33
35
  - **对话 / 轨迹标签条**:对话区顶部的对话 / 轨迹标签条重画成分段控件,放得下时抬到标题那一行。
34
36
  - **会话标题进入顶栏**:桌面端把会话标题与顶栏一行的控件抬进系统标题栏:放得下时标题靠左、控件靠右,放不下的一侧留在原来的行里,标题行的其余内容顺势左移。
@@ -10,11 +10,13 @@ The settings page appears both in the settings dialog (the "Claude Style" tab) a
10
10
  |---|---|
11
11
  | General | Username, Animation, Open popovers on hover, Account-hold easter egg language |
12
12
  | Appearance | Brand mark, Colours, Typefaces, Mascot, Where it appears |
13
- | Composer | Composer restyle, Home layout, Redraw the model picker (with Quick providers under it), Redraw the permission control |
14
- | Sidebar | Collapse the sidebar settings area, Sidebar search, In progress / Archived view |
15
- | Conversation | Turn status line, Conversation navigator, Chat-area animations, Composer caret motion, Chat / Trajectory tabs |
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, In progress / Archived view, Float docked panels |
15
+ | Conversation | Turn status line, Conversation navigator, Chat-area animations (Off / Enhanced / Redraw), Composer caret motion, Chat / Trajectory tabs |
16
16
 
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 animations — the follow, the automatic folding with its rolling door, the text fade, the file change rows and the send flight — share one Chat-area animations switch; the caret keeps its own three-way choice.
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 animations — the follow, the automatic folding with its rolling door, the text fade, the file change rows and the send flight — make one three-way choice: Off hands the conversation back to the host, Enhanced plays this plugin's set, and Redraw hands the area to a second set, whose members are the send flight and the plugin's own reading view (in the place of the host's Chat view itself: a new thought folds the steps before it into one row carrying their figures, a completed turn keeps only its final answer, thinking glides inside its card after its own text and takes one framed line once folded away, each new word fades in, and every live state of a running turn is written on one line under the newest message); the caret keeps its own three-way choice.
18
+
19
+ **Peak rate meter**: every row of the model menu carries the rate in force for that model right now. Peak, off-peak and a dated promotion each get their own mark, the colour separates expensive from cheap, and the multiplier is followed by how long it lasts; hovering the badge prints the whole judgement and what it becomes. The judgement is made from each provider's own published schedule in that provider's own time zone: outside the weekday windows, and on weekends and public holidays in full, the rate is off-peak, and a window crossing midnight belongs to the day it starts. A model with no schedule published carries no badge. The schedules are one snapshot in the public [offpeakclock.com](https://offpeakclock.com) catalog: the host half — not the page — fetches it, keeps a copy on disk and refreshes it every 24 hours, and judges by the copy shipped in the package when it cannot be reached.
18
20
 
19
21
  **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.
20
22
 
@@ -28,7 +30,7 @@ Every feature that takes over part of the host's interface has its own switch; t
28
30
 
29
31
  **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.
30
32
 
31
- **A rolling door for folds**: 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. It rides the Chat-area animations switch, together with the entrance fade of an expanded body.
33
+ **A rolling door for folds**: 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. It belongs to the Enhanced setting of Chat-area animations, together with the entrance fade of an expanded body.
32
34
 
33
35
  **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.
34
36
 
package/docs/SETTINGS.md CHANGED
@@ -10,11 +10,13 @@
10
10
  |---|---|
11
11
  | 通用 | 用户名、动画效果、悬停展开弹层、封号彩蛋语言 |
12
12
  | 外观 | 品牌标识、配色、字体、吉祥物、出现位置 |
13
- | 输入区 | 输入区重绘范围、首页版面、重绘模型选择器(及其下的快捷供应商)、重绘权限控件 |
14
- | 侧栏 | 折叠侧栏设置区、侧栏搜索、进行中 / 已归档视图 |
15
- | 对话 | 轮次状态行、对话导航、聊天区动画效果、输入框插入符动效、对话 / 轨迹标签条 |
13
+ | 输入区 | 输入区重绘范围、首页版面、重绘模型选择器(及其下的快捷供应商)、峰谷电表、重绘权限控件 |
14
+ | 侧栏 | 折叠侧栏设置区、侧栏搜索、进行中 / 已归档视图、面板悬浮卡片 |
15
+ | 对话 | 轮次状态行、对话导航、聊天区动画效果(关闭 / 增强 / 重绘)、输入框插入符动效、对话 / 轨迹标签条 |
16
16
 
17
- 每一项接管宿主界面的功能都有自己的开关,关闭后宿主原来的界面原样回来,不需要刷新页面。对话区的那几项动画(跟随、自动开合与卷帘门、新文字淡入、文件变更行、聊天气泡动效)合成一个「聊天区动画效果」开关,插入符动效仍是自己的三档。
17
+ 每一项接管宿主界面的功能都有自己的开关,关闭后宿主原来的界面原样回来,不需要刷新页面。对话区的那几项动画(跟随、自动开合与卷帘门、新文字淡入、文件变更行、聊天气泡动效)合成「聊天区动画效果」这一个三档选择:「关闭」把聊天区交回宿主,「增强」播放本插件现有的这一套,「重绘」交给另一套动效,接上的是聊天气泡动效与插件自己的阅读视图(直接占据宿主「对话」视图的位置:新的一段思考把此前的步骤收成一行带数量的摘要,轮次成功结束后只留最终回答,思考在卡片里随文字滑动跟随、收进折叠后每段只占带边框的一行,新出现的词逐个淡入,轮次进行中的每一种状态都写在最新一条消息下面同一行);插入符动效仍是自己的三档。
18
+
19
+ **峰谷电表**:模型菜单的每一行标出该模型此刻的倍率。峰价、谷价与限时活动三种状态各有自己的图标,颜色区分贵与便宜,倍数后面跟着距离切换还有多久;鼠标停在徽章上,悬停提示写出此刻的完整判定与切换后的状态。判定按各家供应商自己发布的作息表与自己的时区做,工作日窗口之外、周末与法定节假日整日都算谷价,跨零点的窗口归它开始的那一天。没有任何作息表的模型不显示徽章。作息表是公开目录 [offpeakclock.com](https://offpeakclock.com) 里的一份快照,由宿主半边(而不是页面)取一次、在本机留一份缓存并按 24 小时刷新;连不上时按插件包里带的那份判定。
18
20
 
19
21
  **与其他主题插件同时使用**:「配色」与「字体」选「跟随宿主」后,本插件不再改写宿主的颜色与字体,只保留布局和控件;颜色交给 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 之后原样生效。
20
22
 
@@ -28,7 +30,7 @@
28
30
 
29
31
  **新文字淡入**:流式回答里新出现的字符先淡后实(约 0.12 秒),并按到达次序略作错开;整段一次到达的内容、几千字的突发与刚被折叠重排过的文字保持本色。
30
32
 
31
- **卷帘门过渡**:点开或收起一行(工具卡片、思考行、命令卡片)以及过程组时,高度逐帧变化,下方内容被真的推开或收回;门只走读者看得见的那一段,内容再长也是同一速度,多张卡片的展开体整扇门一起走。它与展开体的入场淡入一起挂在「聊天区动画效果」这个开关上。
33
+ **卷帘门过渡**:点开或收起一行(工具卡片、思考行、命令卡片)以及过程组时,高度逐帧变化,下方内容被真的推开或收回;门只走读者看得见的那一段,内容再长也是同一速度,多张卡片的展开体整扇门一起走。它与展开体的入场淡入一起属于「聊天区动画效果」的「增强」这一档。
32
34
 
33
35
  **输入框插入符动效**:输入框里的光标由插件自己绘制,移动时滑过去;提问卡片的作答框与排队消息的行内编辑框同样覆盖。设置页的「对话」页有三档:每一格(默认)、只在移动时、关闭。
34
36
 
package/docs/STYLE.md CHANGED
@@ -97,6 +97,8 @@ keeps where it states none of its own; `—` is a token that theme leaves unset.
97
97
  | `--dsw-specific-menu` | `#ffffff` | `#ffffff` | — | — | host's own | |
98
98
  | `--dsw-specific-bubble` | `var(--dsh-claude-hover-bg)` | `#eaf0fe` | — | `#232a3a` | host's own | user message bubble |
99
99
  | `--dsh-claude-chip` | `#f6f6f4` | `#f2f6fd` | — | — | `var(--dsw-specific-selector)` | segmented control and switch at rest |
100
+ | `--dsh-claude-meter-peak` | `#a4621d` | `#9a5a1a` | `#e2a45f` | `#e5ae67` | `var(--dsw-alias-state-warn-label)` | the peak rate badge: the expensive end of a provider's own billing clock |
101
+ | `--dsh-claude-meter-valley` | `#3d7550` | `#2e7554` | `#8cbf96` | `#7cc2a0` | `var(--dsw-alias-state-success-primary)` | the off-peak rate badge, and the dated promotions priced below it |
100
102
  <!-- /generated:tokens -->
101
103
 
102
104
  **Host token bindings.** The skin supplies its palette through the host's own
@@ -171,6 +173,13 @@ panel pulled out of the sidebar becomes the host's own floating window
171
173
  gives each pane its own card, with the drag divider left on the seam between
172
174
  them.
173
175
 
176
+ **Float docked panels** (the Sidebar tab) chooses it: on is the card above;
177
+ off drops every rule that draws one, so the pane stands in the host's own fill,
178
+ flush with the column on all four sides, and a split's two panes meet at the
179
+ dock's own resting line. Every card rule is gated on
180
+ `<body data-dsh-claude-dock-look="card">`, so off is the host's own panel look
181
+ with nothing added.
182
+
174
183
  **The start page.** The host centers its stack of entries in a box that never
175
184
  grows, so a list taller than the pane was clipped at both ends, its first rows
176
185
  out of reach. On the card the stack scrolls: `justify-content: flex-start`,
@@ -11,6 +11,7 @@
11
11
  - 宿主半边导出 `Config` 作为 schema,只有 `.volatile()` 字段进表单。schemastery 以顶层 await 加守卫导入,解析不到时 `Config` 为 `undefined`、皮肤照常加载——这是「导入不包 try/catch」规定的唯一例外。
12
12
  - 设置席位注册为 `plugins.bundle.config`(键为包名);宿主没有 `configForms` 时才注册整页的 `settings.section`。
13
13
  - 偏好字段只有一份声明:`packages/contracts/src/prefs.ts` 的 `PREFS_DEFAULT`。宿主半边 `packages/host/src/settings.ts` 用它生成 `Config`(宿主构建把 contracts 的值内联进产物,contracts 不进 npm 包);浏览器半边的默认值与取值集合给出表单答复之前的初值与读到值时的规整。
14
+ - 字段类型按默认值的类型取纯 `string` / `boolean` / 字符串数组:取值集合在消费处判定,浏览器半边读到什么都做规整。两个早先存成布尔值的偏好(`autoPopover`、`chatAnimations`,后来各自长成多档)额外保留一个 `boolean` 成员:宿主对类型不符的字段回落到默认值,只用纯 `string` 会把读者当时关掉的那一档重新打开;浏览器半边把布尔值读成它当时代表的那一档。
14
15
  - 偏好只存在宿主的表单里:早先存进浏览器本地存储的昵称与封号页语言,在表单第一次带值时写进表单,表单持有自己的值后删掉本地那份。
15
16
 
16
17
  ## 理由
@@ -14,6 +14,7 @@
14
14
  - 上述隔离。
15
15
  - 宿主以抛错表达的预期状态:设置表单的 `set()` 拒绝不属于该配置的字段;`modelDirectories.directoryFor` 对还没建立的会话抛错,下一轮重试;`sessionQuery.readSession` 以 `SESSION_QUERY_CORRUPT_SESSION` / `SESSION_QUERY_SESSION_NOT_FOUND` 表示某个存档读不了或已被删,内容搜索与用量汇总把这一个会话记为空并记一条警告。
16
16
  - 文件变更行解析流式工具的原始参数:参数逐块到达,半截 JSON 是正常的中途状态,解析不了就交回宿主的输入 / 输出卡片(`features/chat-files/file-row-model.ts`)。
17
+ - 阅读视图的错误边界(`features/chat-reader/blocks.tsx` 的 `BlockBoundary`,D57):一个块或一行渲染失败,只把这一处换成一行说明并用 `reportError` 报告,会话的其余部分照常渲染。
17
18
  - Promise 上表示「宿主半边没有应答」或「宿主自己会提示」的失败回调。
18
19
  - 宿主半边允许的 catch,每处写明原因:
19
20
  - `webServer.register` 以抛错拒绝已被别的插件占用的路径:逐条注册,被拒的记警告,其余照常(宿主半边的纤程失败会连带撤下浏览器半边的整个包)。
@@ -14,7 +14,7 @@
14
14
  - 文件变更行接管宿主 `tool.call.toolview` 座位上的 `edit` 与 `write` 两个键位(优先级 -1,低于宿主自己那两个),改动派生与宿主的卡片模型只差「不排除子调用」这一条。
15
15
  - 聊天气泡动效在 `<body>` 末尾挂一个临时替身,带 `inert` 与 `aria-hidden`,摘掉身份标记但保留样式钩子;飞行期间给真实那一行写标记把它藏起来(保留布局盒),到达后真实行先显示、替身淡出。
16
16
  - 增强跟随在流式输出期间接管会话滚动条的跟随:宿主每次内容增长都把位置写到最底,跟随在同一帧绘制之前把这次位移撤回、交给滚动主人(D41)滑过去。宿主写这一下的地方是它挂上消息列时新建的 ResizeObserver(观察消息列、会话滚动条、输入框座位),浏览器按建立先后调用回调,所以跟随的观察者每遇到一条新的消息列就重建一次,排在宿主之后、观察同样三个元素。跟随期间把宿主自己的「回到底部」按钮临时藏起来(只写自己的标记,宿主的状态不动);读者自己发出的消息到达时跟随让开一段,宿主把新消息滚进视野的那一下照旧一步到位。
17
- - 设置:这些功能共用皮肤自己的偏好条目与设置页,不动上游 `dsh-chat-ux` 的配置。对话区五项合成一个偏好键 `chatAnimations`(「聊天区动画效果」),关掉时五个功能一起卸载、各自把界面交还宿主。插入符动效是自己的三选一(`caretMotion`)。随偏好出现的样式规则由功能把标记写到 `<body>` 上(如 `data-dsh-claude-chat-follow`)。
17
+ - 设置:这些功能共用皮肤自己的偏好条目与设置页,不动上游 `dsh-chat-ux` 的配置。对话区五项合成一个偏好键 `chatAnimations`(「聊天区动画效果」),设置页那一行是三档分段控件:「关闭」`off`、「增强」`enhanced`、「重绘」`redraw`。各功能的清单用 `prefValues` 声明自己在这一偏好的哪些取值下运行(D42),取值落在自己那一档之外时装卸开关把它卸下、各自把界面交还宿主;现有的五项都声明 `enhanced`,`redraw` 一档留给后续接入的另一套动效,聊天气泡动效是已经接进这一档的一项。插入符动效是自己的三选一(`caretMotion`)。随偏好出现的样式规则由功能把标记写到 `<body>` 上(如 `data-dsh-claude-chat-follow`)。
18
18
  - 对上游让位:页面装着上游时这些功能整体让位(`packages/client/src/shared/peer-plugin.ts` 读宿主启动图里的条目,也读上游留下的样式表节点);设置页把两行控件禁用、保留读者的值,下面以强调色写明由谁接管。让位由清单的 `yieldsTo` 声明(D42),入口统一执行:上游在页面上时这些功能不安装,上游中途装卸会立刻卸下或装回(`<head>` 的观察重跑偏好监听),功能内部不再自己判断。
19
19
  - 动画效果读 `motionReduced()`(D26),样式表一侧以 `<body>` 上的动画属性为门控。
20
20
  - 没有搬的一处:上游会把网页端「工作步骤展示」的缺省档位补成「标准」。本仓库不代写宿主 `ui-chat` 的设置;网页端默认档位(「详细」)下组体不封顶,自动开合与组体跟随在那两处不发生,读者选到「标准」或「简洁」后生效,README 写明这一点。
@@ -8,7 +8,7 @@
8
8
 
9
9
  - 源码全部是 TypeScript 与 ES 模块,用显式的 `import` / `export` 表达依赖;`tsconfig.json` 打开 `strict`。构建先跑 `tsc`,再查打包后的导入图:漏引、循环依赖、使用尚未初始化的常量都在构建时报错,一个没有任何模块导入的源文件也报错。
10
10
  - 宿主服务与快照里的值在契约模块(D44)给出类型之前统一写成 `HostValue`;其余类型照常写全。
11
- - esbuild 打包,`react` 与宿主包标为 external,产物是满足 DSH 加载器的单文件:CommonJS 主体包进 `__ModuleLoader__.load` 的工厂,id 与包名一致(D33),压缩,并附 `lib/client.js.map`。零运行时依赖由 external 设定保证。
11
+ - esbuild 打包,`react` 与宿主包标为 external,产物是满足 DSH 加载器的单文件:CommonJS 主体包进 `__ModuleLoader__.load` 的工厂,id 与包名一致(D33),压缩,并附 `lib/client.js.map`。零运行时依赖由 external 设定保证。清单写 `load: 'deferred'` 的功能不在这个单文件里,各自成块(D39)。
12
12
  - 构建时才有的数据(样式表文本、厂商组合标、帧图的内容戳与内联地址、构建编号)经一个生成模块 `virtual:dsh-claude-style/generated` 进入源码,类型写在 `packages/client/src/generated.d.ts`。构建编号是产物内容哈希的前 12 位(D19),打包后写进等长的占位,source map 因此不受影响。
13
13
  - 原有构建检查各有去处:
14
14
  - 「每个源文件都在清单里」由模块导入图保证。
@@ -2,12 +2,12 @@
2
2
 
3
3
  - **状态**:已实施
4
4
  - **分组**:构建与源码
5
- - **关联**:承接 D24 的帧图、D36 的构建;D11、D12、D46
5
+ - **关联**:承接 D24 的帧图、D36 的构建;D11、D12、D39、D46
6
6
 
7
7
  ## 决定
8
8
 
9
9
  - 图片与图形资源都在 `packages/assets/src/` 下,构建(`packages/assets/assets.mjs`)为每个文件算出内容哈希(sha256 前 12 位),并由文件自己的字节数决定交付方式:不超过阈值的(16 KiB)内联进产物——图片写成数据地址,厂商组合标写成标记文本,因为选择器用 `innerHTML` 把它盖进行里,好让单色层继承那一行的颜色;超过阈值的写到 `lib/assets/<哈希>.<扩展名>`,由宿主半边一条通用路由 `/dsh-claude-style/assets/<哈希>.<扩展名>` 提供。
10
- - 路由只认清单里出现过的名字:构建把清单写在 `lib/assets/manifest.json`,请求名必须命中它,因此资源目录之外什么都读不到。名字里带着内容哈希,可以长期缓存;文本资源按 brotli 预压缩存放,不接受 brotli 的客户端由路由解压后送出。
10
+ - 路由只认清单里出现过的名字:构建把清单写在 `lib/assets/manifest.json`,请求名必须命中它,因此资源目录之外什么都读不到。名字里带着内容哈希,可以长期缓存;文本资源按 brotli 预压缩存放,不接受 brotli 的客户端由路由解压后送出。延后功能的块与它的 source map(D39)也登记在这份清单里、由同一条路由提供,总是走路由,各自的名字取自身内容的哈希。
11
11
  - 帧图与动画登记表的对应关系、帧数、裁切框、静止帧的合法性在生成清单时检查,另外核对表图自己的像素:裁切框必须整除表图,格子数必须装得下登记的帧数。没有任何登记认领的资源构建失败。
12
12
  - Deepy 的帧图在构建期转成矢量(`vectorizeSheet`):按裁切框把每帧摆成一格、四周留 `DEEPY_GUTTER` 个逻辑像素的透明边,逐行取同色游程,把同列同宽的同色游程向下并成矩形,每种颜色一条 path。产出的 SVG 就是浏览器播放的图形;表图 PNG 只作为构建的输入,不进 `lib/`。
13
13
  - 浏览器端的画布转换、Cache API 缓存与两处相关的 catch 随之删除(D12);运行时只剩一次图片加载。
@@ -25,4 +25,4 @@
25
25
 
26
26
  ## 重审条件
27
27
 
28
- - DSH 加载器支持资产 URL 时,资源改随客户端包分发,删除这条路由。
28
+ - DSH 加载器支持资产 URL 时,资源改随客户端包分发;块随 D39 的重审条件改走加载器之后,删除这条路由。
@@ -1,23 +1,33 @@
1
- # D39. 按需加载:重的功能拆成单独的块
1
+ # D39. 按需加载:首屏用不到的功能拆成单独的块
2
2
 
3
- - **状态**:待实施(等宿主给出入口)
3
+ - **状态**:已实施
4
4
  - **分组**:构建与源码
5
- - **关联**:D36、D42、D43
6
- - **现状**:全部功能在一个产物里,页面加载时整份解析;宿主目前没有插件内按需加载的入口,也不接受外部 issue 与 PR(D43),因此这条等宿主自己开放,不排在本仓库的待办里
5
+ - **关联**:D12、D36、D38、D42、D43、D49
7
6
 
8
7
  ## 决定
9
8
 
10
- - 设置页、对话导航、吉祥物、聊天气泡动效拆成单独的块,功能清单(D42)声明它们的按需入口,用到时再加载;首屏只解析主题与核心。
11
- - 块的加载走 DSH 加载器的公开机制(资产 URL 与动态 import,D43 向上游争取)。
9
+ - 清单写 `load: 'deferred'` 的功能离开主产物:它自己的模块打成一个块,由宿主半边的资产路由按内容哈希提供(D38),页面被接管后立即请求,块到达后安装。块到达之前它的句柄不在 `ui` 上,读它的功能照常把它当作缺席。现在延后的有设置页、内容搜索、吉祥物、发送动效、对话导航、上下文弹层与封号彩蛋。
10
+ - 主产物携带的模块一律不复制进块:块对这些模块的导入保持为导入,运行时经 `virtual:dsh-claude-style/chunk-modules` 解析到主产物自己的实例,宿主包按加载器的 `require` 原样交出;两个块都会携带的模块并入主产物(`scripts/chunks.mjs`)。构建期生成的模块(`virtual:dsh-claude-style/generated`,D36)与源码模块同样处理:共享表按它们的 `virtual:` 路径给出主产物里的那一份,块不再带走第二份树摇结果。构建拒绝主产物与块同时携带一个模块,源码模块与生成模块都算在内,也拒绝块读取构建编号。
11
+ - 块与它的 source map 各自按自身内容的哈希命名(`chunkFiles`),由资产路由的一条路径送出(D38):只改注释、不动代码时,map 换了名字,浏览器不会拿旧的那份。
12
+ - 块是经典脚本:运行时把工厂挂到自己的 script 元素上(`document.currentScript.__dshChunk`),`core/chunks.ts` 取下工厂、以共享表为 `require` 调用、拿到 `install`,脚本元素随即移除;同一代里同一个块只请求一次。
13
+ - 块装不上(加载失败、没有工厂、运行时抛错)等同于功能安装失败:报告一次并退役(D12)。块到达时这一代已经结束、页面已让给皮肤(设置页除外,D49)、功能已被关掉或已退役,就不安装。
14
+ - 样式表留在主产物:层叠顺序由全部样式表的统一序号决定(D42),块到达之前这些规则匹配不到元素。
15
+ - 只延后首屏可以缺席的功能:首屏就要改写宿主界面的(账号区、首页、输入区、模型与权限控件)留在主产物,否则页面先露出宿主原样再被改写。
16
+ - 构建的写入顺序是有意的:新资产先写、清单随后、清单里没有的文件最后删,`lib/client.js` 写在最后。页面跑着的客户端包一变就换代,而客户端热重载只认主产物:把主产物放到最后写,换代的那一代要的块已经在磁盘上、也已经在清单里,不会在新块落盘之前按旧清单请求块。
17
+ - 调试档 `DSH_CLAUDE_STYLE_DEBUG=1` 让清单继续带着上一代仍然存在的条目并保留它们的文件:宿主在启动时读到旧主产物的页面请求的是那一代的块名,名字还在清单里它才送得出去。发布构建不带这个标记,清单就是这一次构建自己的资产;持续集成里这个标记一律不生效。
12
18
 
13
19
  ## 理由
14
20
 
15
- - 产物未压缩约 1.6 MB,而首屏用得到的只是主题与少数常驻功能。
21
+ - 主产物在页面启动时整份求值;延后的功能在启动之后各自作为短任务求值与安装,启动阶段的主线程阻塞随之减少。本地临时宿主实测(两种产物各载入 20 次取中位数):主产物 789 KB → 690 KB,前 3 秒的主线程长任务 713 ms → 648 ms,皮肤生效时刻 298 ms → 280 ms。
22
+ - 宿主加载器没有插件内的按需入口,也不接受外部 issue 与 PR(D43);资产路由已经存在(D38),块走同一条路由与同一份清单,不需要新的宿主能力。
16
23
 
17
24
  ## 代价
18
25
 
19
- - 第一次打开设置页、第一次演吉祥物时多一次加载;块与主产物之间的接口要稳定。
26
+ - 延后的功能比主产物晚到一次请求(本地实测约 5 ms);块到达之前打开设置页、按下搜索或点账号行没有反应。
27
+ - 宿主在启动时缓存客户端包:没有客户端热重载的宿主上,插件更新后页面拿到的仍是旧主产物,只有重启宿主才能换到新的;链接安装的开发机可以在重建时带上 `DSH_CLAUDE_STYLE_DEBUG=1`,让上一代的块继续可以送达。
28
+ - 构建要计算模块归属:主产物的模块集合与块的模块集合互相影响,反复计算到没有模块被携带两次为止。
20
29
 
21
30
  ## 重审条件
22
31
 
23
- - 加载器的公开机制迟迟没有、而主产物压缩后超过约 1 MiB 时,重新评估是否改用官方包在用的包内异步分包 `require.async`(目前没有给第三方插件的公开契约)。
32
+ - DSH 加载器提供插件内的异步分块或资产 URL 时,块改走加载器的机制,删除共享表与经典脚本这层包装。
33
+ - 某个延后功能需要在首帧改写宿主界面时,把它移回主产物。