dsh-claude-style 0.4.0 → 0.5.0

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
@@ -1,6 +1,44 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.5.0] - 2026-09-22
4
+
5
+ ### Added
6
+ - **插件页有了自己的图标(DSH 0.1.7)**:0.1.7 的插件清单会读 `package.json` 的 `icon`——必须是**相对清单的路径**、SVG/PNG/JPEG/WebP、realpath 后仍留在包目录内、且不超过 256 KiB(绝对路径与 URL 一律拒绝);宿主把文件读成 base64 data URI 交给插件卡片与详情页上的 `<img>`,失败只在这张卡片上留一条 `meta.error`,不影响插件本身。这里填的是**陶烬橙的 Claude 星芒**:源文件 `src/assets/brand/claude-mark-clay.svg`,构建期复制到 `lib/claude-mark.svg`(与模型文案同一套「源在 `src/`、`lib/` 是产物」的做法,`files` 里的 `lib` 已覆盖它,无需新增条目)。选 clay 版而不是纯黑版是有原因的:图标以 `<img>` 渲染、拿不到 `currentColor`,纯黑星芒在暖黑画布上会消失,陶烬橙两态都读得出。
7
+ - **插件卡片有了本地化的标题与描述(DSH 0.1.7)**:同一套元数据读取还会解析 `<包>/locale/en.json`,并以它所在目录为锚点枚举其余 `<语言>.json`,每个文件取 `meta.title` / `meta.description`(必须是非空字符串,文件名必须是语言 id),最后交给客户端一个 `{ en, zh, … }` 记录——`en` 永远兜底到 `package.json` 的 name/description,其余语言按 shell 语言取。此前卡片上显示的是包名 `dsh-claude-style`,现在 `locale/en.json` 给 `Claude Code Style`、`locale/zh.json` 给 `Claude Code 风格`,描述也一并本地化,`locale` 加进 `files`。**这里有个必须避开的陷阱**:宿主是逐个文件走包 `exports` 解析的,任何一个 locale 文件没被导出都会在枚举目录时抛错,而这个错会被外层捕获、把**整份元数据(含图标与两段文案)一起降级成 `meta.error`**——所以 `exports` 加的是通配 `"./locale/*": "./locale/*"` 而不是逐文件列举,以后新增语言文件不会漏。
8
+
9
+ ### Added
10
+ - **侧栏「工作区」标题改成 进行中 / 已归档 分段控件**:宿主本来就带这个筛选(视图选项 → 显示已归档 / 仅显示已归档),它的树会自己把已归档对话连同标题、时间一起渲染出来——所以这里没有再写一份归档列表,而是**去驱动宿主那个筛选**:分段控件替掉原来的「工作区」文字,点「已归档」时打开宿主的视图选项菜单、把筛选切到「仅显示已归档」再关掉菜单,点「进行中」时切回默认;控件的选中态则**从树里读回来**(树的会话行里出现已归档 id 就算已归档),因此从宿主菜单里手动改筛选,控件也跟着变。归档 id 由归档插件的 remote 提供(`ctx.get('remote.workspaceRegistry')`,按名字宽松取用,不声明别的插件的命名空间)。另外**给已归档的会话行挂一个 SVG 删除按钮**:绝对定位挂在宿主行右侧(不改宿主行布局),静止透明、指针落到行上才浮现,行上的时间戳同时淡出给它让位;点击直接调 `deleteSession`,成功后立刻把按钮摘掉。实测(0.1.7-alpha.1 真实 GUI):分段控件文字与选中态随点击翻转、「进行中」时树里 0 行已归档且无删除按钮、宿主筛选被切到「仅显示已归档」后树只列已归档分组、点回「进行中」树恢复 6 行活跃会话;删除请求以**打桩的传输**验证——请求带着正确的 sessionId 发出,随后按钮从行上摘除,真实会话未被删除。**注意**:宿主在已归档态按「分组方式」分组(当前是「按工作区」),组是折叠的,所以要先展开某个工作区才能看到里面的已归档对话与删除按钮;想要一进来就平铺,把分组方式切成「单列表」即可(这一条未做自动切换,见下方待办)。
11
+
12
+ ### Removed
13
+ - **模型行的厂商字体(Google Sans Flex)整块移除**:此前只有 Gemini 行会把名称换成 Google Sans Flex(一枚自子集化的拉丁字体),其余行都是界面字体——一个厂商有字体、别的厂商没有,这不是「厂商标记」而是一个特例,何况模型行已经在用厂商锁定标表达同一件事。现在模型行一律用界面字体:字体资产(`fonts/GoogleSansFlexPicker.woff2` 与它的 OFL 文本)、子集化脚本 `scripts/slim-google-sans.py`、`--dsw-font-brand-gemini` 令牌与对应的 `@font-face`、宿主字体白名单里的那一条,以及 package.json 的 `files` 条目一并删除,npm 包小约 9 KB。
14
+
15
+ ### Changed
16
+ - **推理等级从二级弹层改成一级弹层底部的推条**:此前「推理等级」是一行下钻,点开二级弹层再从列表里选档;现在它是一条推条——标签后面跟着当前档位名,两端写 `Faster` / `Smarter`(这两个词在任何界面语言下都保持英文:它们说的是方向,不是档位名,档位名就在标签旁边)。推条是**无极滑动**的:拖动时旋钮跟手走,松手或指针离开控件时才对齐到**最近的档位**,所以从一档到另一档不会像开关那样硬切。模型没有任何档位时推条照样显示——旋钮能拖、不改变任何值、松手回到右端,一级弹层底部因此永远长成同一个形状。键盘同样可用:方向键逐档、`Home` / `End` 到两端,推条带 `role="slider"` 与完整 aria 值。二级弹层此后只剩「更多模型」。
17
+ - **`model-picker.js` 拆出两块,回到仓库的 750 行停止线内**:推条控件进新碎片 `src/overrides/model-effort.js`(它只吃一个 `read()` 回调,自己把目录的档位表映射成推条的刻度),模型文案解析(精确条目 → 家族规则 → 档位规则 → 目录自带文本)进新碎片 `src/overrides/model-copy-lookup.js`(纯查表,不碰 picker 闭包)。拆完 `model-picker.js` 791 → 720 行;随二级档位列表一起消失的还有 `modelSubKind` 那套「二级弹层当前是哪一种」的状态与 `noEffort` 文案。
18
+ - **推条的滑槽与旋钮几乎等高**:滑槽从 6px 细线改为 14px 圆角槽,旋钮 16px 不变,对齐 Claude 真机 Effort 推条的比例——6px 的槽配 16px 的旋钮,控件读成「细线上骑着一颗珠子」;等高之后整条才读作一个滑杆,旋钮是嵌在槽里而不是浮在线上。纯样式改动:拖拽几何读的是轨道宽度与旋钮直径,不随槽高变化。
19
+ - **弹层悬停时间统一:停留 50ms 打开、离开 100ms 关闭**:此前打开一律是**即时**的(指针扫过触发器就展开),关闭各有各的延迟(账户抽屉与权限弹层 150ms、模型选择器 180ms、首页 hero 菜单与统计卡片 160ms)。现在 `createHoverIntent` 两侧都可调度:`scheduleOpen` 先等 50ms 的停留(扫过的指针不再展开任何东西),`scheduleClose` 等 100ms(够指针跨过触发器与卡片之间那道缝,且 `scheduleClose` 会顺手撤掉未到期的打开,所以离开只需要调一次);两个值作为 `POPOVER_OPEN_DELAY` / `POPOVER_CLOSE_DELAY` 放在共享碎片 `popover-utils.js`,模型选择器(含「更多模型」二级弹层,指针扫过 cell 也会撤销待打开的二级)、权限弹层、账户抽屉、首页 hero 菜单(它的悬停是委托 `mouseover` 去点宿主触发器,停留同样加在这一步)全部改用它。统计卡片的**关闭**并入 100ms;它的**打开停留**仍是你上一轮定的 300ms(触发区在工具栏行正中、是指针过路的必经之地,50ms 挡不住误触)。实测(0.1.7-alpha.1 真实 GUI,页面内时间戳,并按同环境 `setTimeout` 校准):模型选择器 104ms / 权限 101ms / 账户 128ms / 二级弹层 117ms(该次校准下裸 `setTimeout(50)` 就要 101ms),关闭 134–154ms(裸 `setTimeout(100)` 为 135ms)——即两侧都落在配置值上,多出来的部分是 headless 的定时器对齐。
20
+ - **「更多模型」二级弹层收口,模型不支持思考时不再画推条**:四处调整。**(1) 底对齐**——二级此前与一级**顶部对齐**,内容少时它吊在一级上半部、下方空出一段,指针从「更多模型」那一行挪过去要穿过这段空白;现在二级的**底边与一级底边对齐**(内容比一级还高时仍保留自己的顶边,免得被推出屏幕上沿)。**(2) 不重复供应商**——二级此前列**全部**供应商,一级已经露过面的会再出现一次;现在只列一级**没有**以分组形式显示的(官方服务 + 设置页勾选的快捷供应商)。只以**当前席位**一行出现的供应商**不算**「已显示」——那一行只有一个模型,把它的其余模型藏到二级之外就再也够不着了。**(3) 没得列就整行去掉**——一级的分组已经把全部供应商显示出来时,二级只会是一张空卡,于是「更多模型」这一行不再画。**(4) 不支持思考就不画推条**——目录里没有 reasoning 元数据的模型,此前照画一条无档位可停的推条(旋钮能拖、松手回右端),现在底部只剩分隔线。**(5) 底行空了连分隔线一起收掉**——分隔线的作用是把模型列表和它下面的东西分开;推条与「更多模型」都不画时,列表下面没有任何东西可分,一条孤零零的线只会读成多余的横线,于是整块底行(分隔线 + 两行)一起不画。实测(0.1.7-alpha.1 真实 GUI):二级底边与一级底边差 **0px**(顶边差 96px,即短的一级二级确实按底对齐);当前席位是 `r4-code/deepseek-v4.1-flash`(一级 = 官方分组 + 它的席位行)时二级只剩 `R4 Code` 一个分组、`DeepSeek` 不再重复出现;当前模型 `r4-code/deepseek-v4.1-flash`(无 reasoning,二级非空)底行为 `[分隔线, 更多模型]`,切到 `deepseek-official/deepseek-flash`(有 reasoning)底行为 `[分隔线, 推条, 更多模型]`。「推条与更多模型都不画」这一态需要一级已列全部供应商,本机实例的皮肤偏好写不进去(0.1.7 下 `settings.register` 缺失导致命名空间未注册,属另一处在办的事),所以这一态由条件本身保证(`if (showEffort || showMore)`)而未做现场对照。
21
+
22
+ ### Fixed
23
+ - **真圆被画成鹅卵石:`corner-shape` 是继承的**:页面从 `<html>` 继承 `corner-shape: superellipse(1.5)`(超椭圆 = 鹅卵石),于是任何 `border-radius: 50%` 的盒子都渲染成鹅卵石 —— 新会话行那枚圆形 chip 就是这样。宿主自己的圆形元素(spinner / dot / tag / toggle / thumb)都**显式**写了 `corner-shape: round`,皮肤照同一做法给六处真圆与滑槽补上声明:新会话 chip、会话行空槽圆点、进行中状态环、封禁页步骤序号、设置页开关钮、推条滑槽与旋钮。实测 chip 的 computed `corner-shape` 从 `superellipse(1.5)` 变为 `superellipse(1)`(Chrome 把 `round` 序列化成一阶超椭圆,即正圆)。**圆角矩形不受影响**:卡片与弹层的 6px / 12px / 18px 圆角仍按宿主的超椭圆语言渲染,只是「真圆」不再被拉成鹅卵石。
24
+ - **右侧边栏全屏展开时,对话区顶栏的 preset 标签与「N 个后台任务」会浮在面板之上**:`chrome.css` 给 `[data-slot="conversation.session.header.actions"] > *` 写了 `z-index: 100 !important`,本意是「压过消息层」,但它与同一段里的三个 composer overlay 处境不同——队列 dock、目标条、输入 dock 都长在 `.composerSeat` 内部(`position: sticky` + `z-index: 7`,自成一个层叠上下文),100 只在这个上下文内部排序,对整页没有影响;顶栏动作不在任何层叠上下文里,100 直接对整页生效。而全屏面板的覆盖靠 ui-dockkit 的 dock 层(`--dsh-dockkit-dock-layer`:普通 10、全屏 40),40 < 100,于是这一行穿过面板露出来。现在把这条规则降到 **9**:高于消息层(置顶消息头 7、回到底部控件 8),低于面板的 10 / 40,规则本意保留、穿透消失。宿主(DSH 本体)无需改动——它的对话区自身层高不超过 10。
25
+ - **热重载后模型选择器会失去点击效果**:HMR(及任何「清扫上一代而不销毁本代」的路径)会把上一代留下的弹层节点从 DOM 里拿掉,而新一代闭包里的 `modelPop` / `modelSubPop` 引用还指着那个已被摘除的节点——重建条件只写了 `=== null`,引用非空就不会重建,于是点触发器时 `data-open` 被写在一个不在文档里的节点上,弹层永远不出现,也就是「点击没效果」。本次把 `ensureModelChrome()` 的判空改成「为空**或已脱离文档**」(与账户区同款幂等护栏),重建时同时重置渲染签名让下一轮 pass 把内容画进新节点;触发器本就有同样的护栏,两级弹层补齐后选择器在双代并存期间也能自我修复。
26
+ - **宿主半边的资产路由在热重链后整代失联,选择器整代渲染成「无锁定标、英文标签」的降级态**:`lib/index.js` 原本在 `ctx.get('webServer')` 已就绪时直接 `registerRoutes(ctx)`,而 `ctx.webServer` 属性访问受 fiber 的 inject 声明门控——启动时 webServer 往往尚未挂载,走 inject 分支一直正常;但客户端 bundle 重建触发 generation 重链后,宿主半边会在 webServer 已运行时重挂,`ctx.get('webServer')` 宽松读到服务、`else` 分支被选中,三处注册全部抛 `cannot get property "webServer" without inject`(宿主日志可见成串告警),模型文案/偏好/用户名三条路由整代没有注册。浏览器半边每代只拉一次文案、失败不重试,于是那一代的选择器没有厂商锁定标、标签回退英文常量、简介降级到目录自带的英文文本,直到下一次重挂/HMR 重新拉到文档才恢复。现在无条件经 `ctx.inject(['webServer'], registerRoutes)` 注册:注入作用域带声明,启动与热重链两条时序都落在能工作的路径上;没有 webServer 的宿主只是等待,防御性契约(无 webServer 也照常激活)不变。
27
+ - **推条拖动掉帧、不跟手**:拖动路径在每个 pointermove 上都做一次强制回流——`place()` 先写 `transition: none`、再写位移、`void offsetWidth` 强制同步排版、最后把过渡还回去,可这套舞步是多余的:拖动期间 `data-dragging` 本就让样式表用 `!important` 关掉过渡。pointermove 的触发频率高于帧率,等于每秒上百次强制排版;每次 move 还无条件重写档位名的 `textContent`(同值写入也是一次 DOM 变更),喂给调度器的 MutationObserver;而调度器的 pass 自身又在无差别重写触发器 aria-label(正落在观察器过滤名单里)、模型名与档位两个 textContent、以及开着时弹层的 left/top——同值写入照样排队,pass 于是每帧自续一轮,弹层每次重定位都把排版弄脏,拖动的下一次几何读取只能强制重排。现在拖动位移并入每帧一次的 rAF(先读边界与几何、后写位移,松手前先落未决帧再结算档位),`place()` 在拖动期直写 transform,档位名/触发器标签/弹层定位全部加同值护栏——空闲时的 pass 循环随之断开,拖动每帧只剩一次 transform 写入。
28
+ - **二级弹层悬停打开后不随指针移开而收起**:「更多模型」的二级弹层是 hover 打开的,但它的归属判定缺了半边——`modelSubPop.mouseleave` 只负责排队全关,而指针一进一级弹层 `modelPop.mouseenter` 就无条件取消定时器,没有「收起二级」的动作;于是从二级弹层移到滑块(一级弹层 footer 内)时,定时器被取消、二级弹层保持打开,挡在旁边。更隐蔽的是另一条路径:hover cell 打开二级后,指针在一级弹层**内部**移到滑块——全程不穿越弹层边界,连 `mouseenter` 都不会触发,二级永远不收。而「指针回到一级、二级该收」本就是既有语义(`openModelPopover()` 打开一级时就显式关二级),只是 hover 路径漏了这一步。现在在一级弹层上委托 `mouseover`:二级开着时,指针落到入口 cell 以外的任何位置(滑块、模型行、卡片空白)即收起二级,一级保持打开;用委托而非 `mouseenter` 正是为了覆盖「cell → 滑块」这种纯内部移动。cell ↔ 二级弹层之间的往返不受影响(悬在入口上不收,跨过间隙进二级照常保持)。
29
+ - **切换推理档位后模型选择器会「一段时间不可用」**:推条每次落档都走宿主同一条 `selectModel` RPC,而宿主在整个往返期间把目录标记为 `selecting`——我们把这个状态当成「没有数据」,于是把整张卡片清空成一行「正在加载模型…」,分隔线、推条和「更多模型」一并被摘掉,要等宿主应答才重建。问题在于这个往返在插件注册的供应商上要**按秒计**:实测 `antigravity/gemini-3.8-flash`(用户当时的默认模型)2.0–3.2s,`kimi-code` 最高 7.7s,`deepseek-official` 也要 2.0–2.5s,所以每推一次档位,选择器就空转同样长时间。根因不在某个插件抢 DOM,而是我们的渲染判据错了:一次进行中的选择**不改变目录里有什么**,模型列表与档位表都还有效。现在 `renderModelBody()` 只在「解析不出当前席位且没有任何分组」时才显示加载行;已有列表时 `selecting` / `loading` / `idle` 一律继续画列表与 footer(进行中的选择在触发器上也不再置灰——置灰只留给「连席位都叫不出名字」的状态)。顺带说明慢的来历:宿主把每次选择(含纯档位切换)写进 `agent-default-model` 设置,广播 `settings/document-updated` 后重新枚举整个目录——实测每次选择触发 2–3 次 `modelCatalog`,每次 1.8–6.4s,而枚举会对每个供应商调用 `listModels()` 与逐模型 `resolveModelInfo()`,`@eddyskywalker/dsh-chatgpt-subscription` 注册的几个供应商在这里联网抓目录,这就是 RPC 慢的主因(宿主与对方插件的行为,本插件侧无法消除,只是不再被它冻住)。
30
+ - **composer 获焦时 `ui.heroMenu.close is not a function` 每次必抛**:`hero-menu.js` 只导出 `sync` 与 `reposition`(首页菜单的生死归宿主悬停状态管,`syncHeroMenu` 负责发现它消失),调度器的 `onComposerFocusIn` 却无条件调用 `close()`——于是每次焦点落进对话卡片都抛一次 TypeError,该处理器余下的收起动作也随之中断。改为与上一行 `closeStats` 同样的存在性护栏。
31
+ - **上下文标记在 DSH 0.1.7 上被落在「第二层」,现在归位到模型触发器右侧**:0.1.7 把输入条的 composer 重构成「卡片 + dock 行」两层——**上下文占用表(`ContextMeter`)从卡片内 `trailing` 组(0.1.5 里它挨着发送键)搬到了卡片下方新的 dock 行**,和会话统计药丸同一个 `conversation.composer.dock` 槽;本皮肤只把统计药丸搬进工具栏行(`mergeStatsIntoRow`),没人管那颗环,于是它独自留在 dock 里、按宿主的 `justify-content:center` 居中成卡片下方**单独一行**(截图里「◯ 42%」那条)。现在新增 `mergeContextMeterIntoRow()`:识别方式是它那颗触发按钮——`button[aria-haspopup="dialog"]` 且**整个标签就是占用读数**(`42%`,统计药丸永远不会是),因此不依赖宿主按构建哈希的类名;找到后插进 `trailing` 里**模型触发器所在簇的右侧**(触发器右侧、发送键之前),并打上 `data-dsh-claude-context-meter` 供样式表挂钩。旧宿主把环留在卡片里,这里找不到可搬的东西、不做任何事;皮肤关闭 composer 重绘时按统计药丸同样的路径把它放回 dock。实测(0.1.7-alpha.1 真实 GUI):`trailing` 子节点顺序为 `standardControls → 环 → activity → primary`,即环在模型触发器右侧;植入标签为 `95% Cache` 的假药丸原地不动(负对照)。
32
+ - **composer 底行三种控件统一字型**:环、模型触发器、统计句原本各带一套字(宿主环用 `--dsh-content-font-size-secondary`、统计 12px/400 tertiary、模型触发器 13px/500 secondary),挤在同一行里读起来像三种控件;现在统一为**同一字体、13px、500 字重、20px 行高、`--dsw-alias-label-secondary` 同色**(统计的内层按钮经既有的 `font: inherit` 继承,环的触发按钮因宿主自带颜色与字号而单独点名)。实测三者 computed style 完全一致(13px / 500 / 20px / `rgb(110,106,96)`)。
33
+ - **统计句改为自适应宽度**:此前 `flex: 1 1 auto` 让统计**盒子**填满左右两组控件之间的全部空隙(文字居中,但 hover 底板横跨半个行);现在 `flex: 0 1 auto` + `margin: 0 auto` —— 盒子贴合文字、由自动外边距在空隙里居中。实测文字中心与改动前一致(788px),盒子从整段空隙收窄到 259px(文字宽),底板只覆盖文字本身。
34
+ - **DSH 0.1.7 上设置静默失效:偏好改走官方 Config 表单,并按宿主世代分流**:0.1.7 删掉了 `settings.register(ns, schema)`——命名空间不再是插件自取的名字,而是 profile entry id,schema 就是插件导出的 `Config`,且只有 `.volatile()` 字段进表单、值写进 profile 的 Cordis patch。旧代码在 0.1.7 上抛 `register is not a function`(被 try/catch 吞成一条 warn),于是 `claude-style` 命名空间从未存在:`/dsh-claude-style/prefs` 永远返回默认值,写入抛 `No configurable plugin entry "claude-style"` 又被路由当成冲突,设置页还在但开关点了不生效、重启即丢。现在宿主半边导出 `Config`(八个偏好字段,`volatileField()` 逐字段探测 `.volatile()` 存在才加标记——桌面端内置的 schemastery 3.18.2 没有它,硬加会直接抛错;schemastery 本身用顶层 await 守卫导入,解析不到就让 `Config` 为 `undefined`,皮肤照常加载),新宿主把命名空间取成 `ctx.fiber.entry.id`(读不到回落 patch 里的 `ui-skin-claude-style`)并只调 `settings.configure({ auto: false }, ctx.fiber)` 声明自带页面,旧宿主仍走注册制;客户端有 `ctx.configForms` 就用官方表单(值 + 写队列 + revision 栅栏,一次 `set()` 一个字段串行提交),没有就回落到自建路由。设置界面同样分流:0.1.7 注册成 `plugins.bundle.config`(键 = 包名 `dsh-claude-style`,渲染在「设置 → 插件 → dsh-claude-style」页上),旧宿主注册成 `settings.section` 整页——靠 `slots.inject` 的「槽被声明才触发」天然分流,再用 `configForms` 是否存在否决整页在 0.1.7 上的重复注册。
35
+
36
+ - **统计卡片:内容会缺一块、卡片关不掉、还容易被误触发**:三个毛病同源,都在「统计卡片」这一处。**(1) 内容缺块**——卡片的两节(会话统计 / Token 用量)是**点开宿主那两个药丸、读出它们的弹层**再拼出来的,而给卡片新加的「点击打开」监听挂在统计句的根上:采集时那些**合成点击会冒泡回这个监听**,于是采集在自身内部又触发一次采集,两次读取互相打架——实测同一张卡在「会话统计 + Token 用量」与只剩「Token 用量」之间来回变,看起来就像**两个不同的弹层**(用户报的「一个是会话统计,一个是会话统计+Token 用量」)。现在只认**真实点击**(`event.isTrusted`),合成点击不再回灌。**(2) 关不掉**——同一条回灌路径每次都取消掉待执行的隐藏定时器,所以指针离开后卡片留在原地;回灌修掉后实测每轮都能收起。**(3) 误触发**——统计句横在工具栏行中间,指针从权限控件移向模型触发器时会经过它,于是卡片随机弹出;现在**悬停需停留 300ms** 才打开(快速划过不再触发),并且**点击也能打开**(宿主自己的两个弹层被样式表藏住,点击必须落在某处)。另外补上**热重载遗留卡片的清扫**与**按世代重新绑定**:宿主拥有统计节点并在客户端 HMR 重载后复用,原先的布尔标记让新一代**完全不绑定**,于是上一代的闭包继续供着那张内容陈旧的卡;现在标记改为按世代取值,每轮 pass 顺带清掉不属于本代的 `.dsh-claude-stats-popover`(模型选择器与账户区早就这么做,统计卡是漏掉的那个)。实测(0.1.7-alpha.1 真实 GUI,连续 5 轮开合):每轮都是两节齐全(8 项)且离开即收起;快速划过 100ms 不弹、停留 1.2s 弹、点击弹。
37
+
38
+ - **0.1.7 上设置仍然存不进去:软链安装下 schemastery 解析不到,Config 建不出来**:宿主半边导出 `Config` 需要 schemastery,而本仓库是 `link:D:/Build/dsh-claude-style` 装进 profile 的——Node 按**真实路径**(仓库目录)向上找 `node_modules`,永远走不到 profile 的拦截层,`import('@deepseek-ai/schemastery')` 直接 ERR_MODULE_NOT_FOUND(宿主日志里那条「schemastery did not resolve」就是它);而拦截层里那份指向的是**桌面端的 3.18.2**,本来也没有 `.volatile()`。于是 `Config` 为 undefined、条目没有可编辑字段,`configForms` 那边一写就被拒,设置页显示「设置存储不可用,改动不会被保存」。现在先按 **harness 自己的解析基准**取 schemastery——`createRequire(process.argv[1])`,也就是 `dsh` 的 `lib/bin.js`——那正是设置域自己校验表单用的同一份实例,0.1.7 上就是带 `.volatile()` 的 3.18.3;取不到才回落到普通 import。回归脚本 `.debug/schema-resolution-check.cjs` 用替身 harness 目录跑真实子进程验证:锚点在时 `Config` 八个字段全部 volatile,锚点不在时 `Config` 为 undefined(负对照)。
39
+ - **输入框下面的「+」在 0.1.7 上失去皮肤的样式与位置**:0.1.7 把「添加附件」与「指令」两个按钮合并成一个「+」,它的 `aria-label` 也从 `添加附件` / `Add attachment` 变成 `添加文件或调用指令` / `Add files or run commands` —— 皮肤的规则是按**精确标签**匹配的,于是它整条退回宿主默认:28px 圆、hover 变成 `rgb(46,44,41)` 的近黑板、并按宿主顺序排到权限控件**前面**。现在标签列表补上两个子串匹配(`*="添加文件"` / `*="Add files"`),「+」回到 24px、透明底、浅色 hover 板,并回到权限控件之后(`order: 3`)—— 即 0.1.7 之前的样式与位置。实测:hover 底色从 `rgb(46,44,41)` 变回 `rgba(0,0,0,0.08)`,盒子 28×28 → **24×24**、`order` 3。
40
+ - **侧栏两行改成 Claude 真机的形状:默认无底色、hover 才有底色、「+」套一枚圆形底**:0.1.7 在新会话按钮下新增了一个 panel row(`插件`),宿主给它 36px 高 / 14px / 12px 圆角,而新会话行是 28px / 13px / 6px —— 两行叠在一起却像两份不同的列表;而且两行都常驻一块灰色底板,Claude 真机的侧栏是**平的**,底色只在指针落上去时出现。现在两行取同一套度量(**含 `min-height`**:宿主用 min-height 把面板行顶在 36px,只写 `height` 压不住)、**静止透明 / hover 才铺底板**,并且图标盒统一成 **20px**:新会话那颗「+」不再只是一枚字形,而是一枚 **20px 圆形底 + 12px 加号**的 chip(Claude 用它标记主操作行),插件行的 13px 四角星居中放进同样的 20px 盒。实测两行 computed 逐项一致(`28px / 256px / x=12 / 6px / 13px 500 / padding 0 8px / gap 6px`),**两行文字都从 x=46 起排**(此前 45 / 39,差 6px —— 旧写法给字形又加了 6px 外边距,与行自身的 gap 叠在一起)。chip 用 `border-radius: 50%` + 一张内联 SVG 加号做 `background-image`:mask 做不到——它会把元素裁成字形,圆形底一起被裁掉;代价是加号颜色不能再用 `currentColor`,因此亮/暗各一条规则。
41
+ - **侧栏两行的图标 hover 转 90°**:新会话的「+」与插件行那颗四角星都是四重对称图形,转 90° 落回原形,读起来是一次旋转而不是状态变化;`transition: transform .25s` 让指针离开时自动转回去。实测(0.1.7-alpha.1 真实 GUI):两行 hover 时 `transform: matrix(0, 1, -1, 0, 0, 0)`(即 `rotate(90deg)`),指针离开回到 `none`。
4
42
 
5
43
  ## [0.4.0] - 2026-09-21
6
44
 
package/LICENSE CHANGED
@@ -39,12 +39,4 @@ owners.
39
39
  are JetBrains Mono, distributed under the SIL Open Font License
40
40
  (`fonts/OFL.txt`). They may be redistributed with this plugin.
41
41
 
42
- - `fonts/GoogleSansFlexPicker.woff2` is a Latin-only subset of Google Sans Flex
43
- (standard weight, upright), built by `scripts/slim-google-sans.py` and renamed
44
- to `Google Sans Flex Picker`. Google Sans Flex is distributed under the SIL
45
- Open Font License (`fonts/OFL-GoogleSansFlex.txt`, Copyright 2022 The Google
46
- Sans Flex Project Authors); upstream declares no Reserved Font Name, and this
47
- subset is renamed so it cannot shadow a full system-installed copy. It ships
48
- with the npm package and may be redistributed with this plugin, under no other
49
- licence.
50
42
 
package/README.en.md CHANGED
@@ -23,18 +23,17 @@
23
23
 
24
24
  ## Fonts
25
25
 
26
- > **Important: the JetBrains Mono code font, plus the Google Sans Flex Picker face used only by Gemini rows in the model picker, ship with the plugin and are served to the browser as webfonts by the plugin host — no installation needed. The Anthropic Sans/Serif fonts are NOT shipped with the npm package**; they remain in the repository [`fonts/`](fonts/) for download. You can either install them on the system, or skip installation entirely: drop the two `.ttf` files into the plugin package's `fonts/` directory and the host serves them as webfonts the same way (identical files, identical result). Either way, refresh / restart web afterwards.
26
+ > **Important: the JetBrains Mono code font ships with the plugin and is served to the browser as a webfont by the plugin host — no installation needed. The Anthropic Sans/Serif fonts are NOT shipped with the npm package**; they remain in the repository [`fonts/`](fonts/) for download. You can either install them on the system, or skip installation entirely: drop the two `.ttf` files into the plugin package's `fonts/` directory and the host serves them as webfonts the same way (identical files, identical result). Either way, refresh / restart web afterwards.
27
27
 
28
28
  | Font | Used for | File |
29
29
  |---|---|---|
30
30
  | Anthropic Sans Web Text | UI chrome | [`fonts/AnthropicSansWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/AnthropicSansWebText.ttf) |
31
31
  | Anthropic Serif Web Text | Conversation body / markdown | [`fonts/AnthropicSerifWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/AnthropicSerifWebText.ttf) |
32
32
  | JetBrains Mono Variable | Code / code blocks | [`fonts/JetBrainsMonoVariable.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/JetBrainsMonoVariable.ttf), [`fonts/JetBrainsMonoItalicVariable.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/JetBrainsMonoItalicVariable.ttf) |
33
- | Google Sans Flex Picker | Model picker · Gemini row name | Shipped with the package: [`fonts/GoogleSansFlexPicker.woff2`](fonts/GoogleSansFlexPicker.woff2) |
34
33
 
35
34
  Enabling the Anthropic faces (pick one): ① install on the system — double-click the `.ttf` on Windows → *Install*, or import via *Font Book* on macOS; ② no-install — copy the `.ttf` files into the plugin package's `fonts/` directory (next to `JetBrainsMonoVariable.ttf`). Refresh the page afterwards.
36
35
 
37
- > JetBrains Mono is distributed under the [SIL Open Font License](fonts/OFL.txt). Google Sans Flex Picker is a renamed Latin-only subset of Google Sans Flex, distributed under the same [SIL Open Font License](fonts/OFL-GoogleSansFlex.txt). The Anthropic Sans/Serif typefaces are Anthropic's property, provided for personal use only and not covered by the MIT license above. See [LICENSE](LICENSE) for the font notice.
36
+ > JetBrains Mono is distributed under the [SIL Open Font License](fonts/OFL.txt). The Anthropic Sans/Serif typefaces are Anthropic's property, provided for personal use only and not covered by the MIT license above. See [LICENSE](LICENSE) for the font notice.
38
37
 
39
38
  ## Install
40
39
 
@@ -55,8 +54,8 @@ Only one theme should be active at a time. After installing, **restart `dsh web`
55
54
 
56
55
  1. **Theme** — applies the whole canvas automatically after install; no configuration needed. Light and dark follow the system color scheme.
57
56
  2. **Permission segments** — the composer gets a Read | Edit | Auto segmented control replacing the shipped access-mode menu: read-only, workspace write, and full access (Auto is switched through the shipped risk-confirmation dialog).
58
- 3. **Model picker** (can be switched off in Settings to hand the menu back to the host) — the composer's model seat becomes a two-level Claude-style menu: the first level **always leads with the official service**, followed by the models of the **quick providers** picked in Settings — one rule between providers, the provider's name leading it in small text on the same row so the rule is shortened by it (the official source is not named and is never listed in Settings; a provider removed from the catalog stays in the list marked "Removed" and can be unchecked to clear it) — then a divider, the reasoning-effort row and "More models"; both levels are sized by their content and capped by the viewport, the first level scrolls its model list only while the divider and the two drill rows stay pinned below the scroll area, and the second opens beside it. Model descriptions appear on the first level only; a current seat that none of the listed providers carries gets a row of its own at the end of the list, above the divider, under a provider rule of its own; the "More models" level is every provider's full catalog and lists names alone. Every model row carries its **vendor's lockup** — the mark and the vendor's wordmark composed into one piece of art, drawn in place of the vendor's name inside the label (`[DeepSeek whale + wordmark] V4.1-Flash`); a name that never spells the vendor (Kimi's own catalog calls the model `K3`) leads with the lockup instead. The "More models" group headers carry the provider's name alone, no mark. The artwork is [Lobe Icons](https://lobehub.com/icons) (MIT), composed before the build into one `<svg>` per vendor (no React dependency). The mark's size and the gap after it are **global constants**, not Lobe's per-icon `TEXT_MULTIPLE`/`SPACE_MULTIPLE` — those ratios are relative to each wordmark's own box, which made every vendor's mark a different size and every gap a different width; the brand colours and variant list come from `es/toc.js`. The colour artwork draws on the ivory canvas, with any fill that cannot be read there swapped to `currentColor` (Kimi's white K would vanish, its blue dot stays); the warm black canvas gets the mono layer in the theme's text colour. A few vendors override the lockup in `brands.lockups`: the two halves may come from different icons (Hunyuan's mark with Tencent's wordmark), the word it stands in for may be spelled differently (`Step` for `step-*`, also `MiMo` and `GLM`), and a wordmark may be cropped (GLM-V drops its `-V`, ChatGPT keeps only `GPT`); hand-provided artwork lives in `src/assets/icons/`, beside the generated `combine/` directory. A wordmark has to match the word it stands in for: Claude rows draw Claude's and Grok rows draw Grok's, not their parent companies' — and Claude's remaining text is set in Anthropic Serif, because that lockup is serif lettering. Hidden text keeps the full vendor name in the accessibility tree, so a screen reader still hears "Kimi K3" rather than "K3".
59
- 4. **Brand switch** — Settings (`Ctrl+,`) → **Claude Style**: the sidebar brand flips between the Claude starburst + the official Claude wordmark and Anthropic (`A\` + ANTHROPIC wordmark); the hero mark follows the choice with the clay fill. The choice persists in the browser; `claude` is the default.
57
+ 3. **Model picker** (can be switched off in Settings to hand the menu back to the host) — the composer's model seat becomes a two-level Claude-style menu: the first level **always leads with the official service**, followed by the models of the **quick providers** picked in Settings — one rule between providers, the provider's name leading it in small text on the same row so the rule is shortened by it (the official source is not named and is never listed in Settings; a provider removed from the catalog stays in the list marked "Removed" and can be unchecked to clear it) — then a divider, the reasoning-effort slider and "More models"; the slider travels continuously and settles on the nearest level once the gesture ends (a model with no levels still shows it — the knob moves, the value does not), with `Faster` / `Smarter` at its ends; both levels are sized by their content and capped by the viewport, the first level scrolls its model list only while the divider, the slider and the More-models row stay pinned below the scroll area, and the second opens beside it. Model descriptions appear on the first level only; a current seat that none of the listed providers carries gets a row of its own at the end of the list, above the divider, under a provider rule of its own; the "More models" level is every provider's full catalog and lists names alone. Every model row carries its **vendor's lockup** — the mark and the vendor's wordmark composed into one piece of art, drawn in place of the vendor's name inside the label (`[DeepSeek whale + wordmark] V4.1-Flash`); a name that never spells the vendor (Kimi's own catalog calls the model `K3`) leads with the lockup instead. The "More models" group headers carry the provider's name alone, no mark. The artwork is [Lobe Icons](https://lobehub.com/icons) (MIT), composed before the build into one `<svg>` per vendor (no React dependency). The mark's size and the gap after it are **global constants**, not Lobe's per-icon `TEXT_MULTIPLE`/`SPACE_MULTIPLE` — those ratios are relative to each wordmark's own box, which made every vendor's mark a different size and every gap a different width; the brand colours and variant list come from `es/toc.js`. The colour artwork draws on the ivory canvas, with any fill that cannot be read there swapped to `currentColor` (Kimi's white K would vanish, its blue dot stays); the warm black canvas gets the mono layer in the theme's text colour. A few vendors override the lockup in `brands.lockups`: the two halves may come from different icons (Hunyuan's mark with Tencent's wordmark), the word it stands in for may be spelled differently (`Step` for `step-*`, also `MiMo` and `GLM`), and a wordmark may be cropped (GLM-V drops its `-V`, ChatGPT keeps only `GPT`); hand-provided artwork lives in `src/assets/icons/`, beside the generated `combine/` directory. A wordmark has to match the word it stands in for: Claude rows draw Claude's and Grok rows draw Grok's, not their parent companies' — and Claude's remaining text is set in Anthropic Serif, because that lockup is serif lettering. Hidden text keeps the full vendor name in the accessibility tree, so a screen reader still hears "Kimi K3" rather than "K3".
58
+ 4. **Brand switch** — Settings (`Ctrl+,`) → **Claude Style** (on DSH 0.1.7 it lives under Settings → Plugins → dsh-claude-style): the sidebar brand flips between the Claude starburst + the official Claude wordmark and Anthropic (`A\` + ANTHROPIC wordmark); the hero mark follows the choice with the clay fill. The choice persists in the browser; `claude` is the default.
60
59
  5. **Account drawer** — the sidebar footer button opens a hover popover (the **Open popovers on hover** setting picks Off / Account only / All) with quick access to Settings (`Ctrl+,`) and plugin management; the username row at its top is an easter egg: it opens Claude's own "Your account is on hold" page (a purely local reproduction that touches no real state — click any control on it, or press `Esc`, to leave; it deliberately stays put when the window loses focus, so you can switch away and read it). That page's language is its own setting (**Account-hold easter egg language**, Chinese/English, English by default).
61
60
  6. **Status polish** — thinking turns pick one of Claude Code's 185 spinner verbs per turn; the workspace loading state uses a Fluent-style circular indicator.
62
61
 
package/README.md CHANGED
@@ -24,18 +24,17 @@
24
24
 
25
25
  ## 字体
26
26
 
27
- > **重要:JetBrains Mono 代码字体、以及模型选择器里 Gemini 行专用的 Google Sans Flex Picker,都随插件库分发,由插件宿主以 webfont 形式直接提供给浏览器,无需安装。Anthropic 字体(Sans/Serif)不随 npm 包分发,仅在仓库 [`fonts/`](fonts/) 供下载**——既可以直接安装到系统,也可以免安装:把两个 `.ttf` 放进插件包的 `fonts/` 目录,宿主会以同样的 webfont 方式提供它们(两种方式的字体文件完全一致,效果相同)。生效均需刷新 / 重启 web。
27
+ > **重要:JetBrains Mono 代码字体随插件库分发,由插件宿主以 webfont 形式直接提供给浏览器,无需安装。Anthropic 字体(Sans/Serif)不随 npm 包分发,仅在仓库 [`fonts/`](fonts/) 供下载**——既可以直接安装到系统,也可以免安装:把两个 `.ttf` 放进插件包的 `fonts/` 目录,宿主会以同样的 webfont 方式提供它们(两种方式的字体文件完全一致,效果相同)。生效均需刷新 / 重启 web。
28
28
 
29
29
  | 字体 | 用途 | 文件 |
30
30
  |---|---|---|
31
31
  | Anthropic Sans Web Text | 界面 / UI | [`fonts/AnthropicSansWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/AnthropicSansWebText.ttf) |
32
32
  | Anthropic Serif Web Text | 对话正文 / Markdown | [`fonts/AnthropicSerifWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/AnthropicSerifWebText.ttf) |
33
33
  | JetBrains Mono Variable | 代码 / 代码块 | [`fonts/JetBrainsMonoVariable.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/JetBrainsMonoVariable.ttf)、[`fonts/JetBrainsMonoItalicVariable.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/JetBrainsMonoItalicVariable.ttf) |
34
- | Google Sans Flex Picker | 模型选择器 · Gemini 行名称 | 随包分发:[`fonts/GoogleSansFlexPicker.woff2`](fonts/GoogleSansFlexPicker.woff2) |
35
34
 
36
35
  Anthropic 字体启用(二选一):① 安装到系统——Windows 双击 `.ttf` → 「安装」,macOS 用「字体册」导入;② 免安装——把 `.ttf` 复制到插件包的 `fonts/` 目录(与 `JetBrainsMonoVariable.ttf` 同目录)。完成后刷新页面生效。
37
36
 
38
- > JetBrains Mono 以 [SIL Open Font License](fonts/OFL.txt) 分发;Google Sans Flex Picker 是 Google Sans Flex 的拉丁子集(已改名),同样以 [SIL Open Font License](fonts/OFL-GoogleSansFlex.txt) 分发;Anthropic Sans/Serif 字体版权归 Anthropic 所有,仅供个人使用,不适用 MIT 许可(详见 [LICENSE](LICENSE) 字体声明)。
37
+ > JetBrains Mono 以 [SIL Open Font License](fonts/OFL.txt) 分发;Anthropic Sans/Serif 字体版权归 Anthropic 所有,仅供个人使用,不适用 MIT 许可(详见 [LICENSE](LICENSE) 字体声明)。
39
38
 
40
39
  ## 安装
41
40
 
@@ -56,8 +55,8 @@ dsh plugin --profile web add Nwflower/dsh-claude-style # GitHub 源
56
55
 
57
56
  1. **主题** —— 安装即全局生效,无需配置;亮暗跟随系统颜色模式。
58
57
  2. **权限分段** —— 输入框以 Read | Edit | Auto 三段式控制器替换原生的访问模式菜单:只读、工作区写入与完全权限(Auto 经宿主的安全确认弹窗切换)。
59
- 3. **模型选择器**(可在设置里关闭,交回系统自己的模型菜单)—— 输入框的模型席位换成 Claude 风格的两级弹层:一级**始终以官方服务打头**,后面跟着设置里勾选的**快捷供应商**的模型——不同供应商之间一条横线,供应商名以小字领在横线前面(同一行、把线挤短;官方来源不标名,也不在设置清单里;从目录消失的供应商带「已移除」标记留在清单里,取消勾选即可清除)——再加分隔线、推理等级与「更多模型」;两级弹层高度都随内容自适应(只受视口约束),一级只滚动模型列表,分隔线与两个操作行**钉在滚动区外**始终可见,二级在右侧展开。模型描述只出现在第一级;当前席位不在上面列出的供应商里时,会在列表末尾(分隔线之上)补一行,同样带一条供应商横线;二级「更多模型」是各 provider 的完整目录,只列名称。每个模型行显示**所属厂商的锁定标**——图标与厂商字标合成的一件图形,直接替换名称里的厂商名(`[DeepSeek 鲸鱼+字标] V4.1-Flash`);名称里没有厂商词的(Kimi 官方目录只叫 `K3`)则锁定标领在开头。「更多模型」的分组标题只显示 provider 名称,不带图标。图形取自 [Lobe Icons](https://lobehub.com/icons)(MIT),在构建前的 vendoring 阶段按厂商各合成一个 `<svg>`,不含 React 依赖。图标高度与间距是**全局常数**——不逐家采用 Lobe 的 `TEXT_MULTIPLE`/`SPACE_MULTIPLE`,那套比例是相对各家字标盒子的,会让图标大小与间距逐家漂移;品牌色与变体清单取自 `es/toc.js`。亮色画布用彩色原图,其中在象牙白上读不出来的填充换成 `currentColor`(Kimi 的白 K 会消失、而它的蓝点保留);暖黑画布换成单色、跟随主题文字色。少数厂商在 `brands.lockups` 里覆写:图标与字标可来自不同图标(混元的标 + Tencent 的字标)、匹配词可指定(`step-*` 用 `Step`,另有 `MiMo`、`GLM`)、字标可裁掉多余部分(GLM-V 截掉 `-V`、ChatGPT 只留 `GPT`);手工提供的资产放 `src/assets/icons/`(与生成的 `combine/` 同级)。字标要和它替换的词对得上:Claude 行画 Claude 的、Grok 行画 Grok 的,而不是各自母公司的;Claude 行的其余文字用 Anthropic Serif 排(那枚锁定标本身就是衬线体)。名称里另有视觉隐藏的厂商名,屏读听到的仍是「Kimi K3」而不是「K3」。
60
- 4. **品牌切换** —— 设置(`Ctrl+,`)→ **Claude Style**:侧栏品牌在 Claude(官方星芒 + Claude 字标)与 Anthropic(`A\` + ANTHROPIC 字标)之间切换,新会话页标识随所选品牌保持陶烬橙星芒;选择保存在浏览器本地,默认 Claude。
58
+ 3. **模型选择器**(可在设置里关闭,交回系统自己的模型菜单)—— 输入框的模型席位换成 Claude 风格的两级弹层:一级**始终以官方服务打头**,后面跟着设置里勾选的**快捷供应商**的模型——不同供应商之间一条横线,供应商名以小字领在横线前面(同一行、把线挤短;官方来源不标名,也不在设置清单里;从目录消失的供应商带「已移除」标记留在清单里,取消勾选即可清除)——再加分隔线、推理等级推条与「更多模型」;推条无极滑动,松手或指针离开时对齐到最近的档位(模型没有档位时照样显示,只是拖不动值),两端写 `Faster` / `Smarter`;两级弹层高度都随内容自适应(只受视口约束),一级只滚动模型列表,分隔线、推条与操作行**钉在滚动区外**始终可见,二级在右侧展开。模型描述只出现在第一级;当前席位不在上面列出的供应商里时,会在列表末尾(分隔线之上)补一行,同样带一条供应商横线;二级「更多模型」是各 provider 的完整目录,只列名称。每个模型行显示**所属厂商的锁定标**——图标与厂商字标合成的一件图形,直接替换名称里的厂商名(`[DeepSeek 鲸鱼+字标] V4.1-Flash`);名称里没有厂商词的(Kimi 官方目录只叫 `K3`)则锁定标领在开头。「更多模型」的分组标题只显示 provider 名称,不带图标。图形取自 [Lobe Icons](https://lobehub.com/icons)(MIT),在构建前的 vendoring 阶段按厂商各合成一个 `<svg>`,不含 React 依赖。图标高度与间距是**全局常数**——不逐家采用 Lobe 的 `TEXT_MULTIPLE`/`SPACE_MULTIPLE`,那套比例是相对各家字标盒子的,会让图标大小与间距逐家漂移;品牌色与变体清单取自 `es/toc.js`。亮色画布用彩色原图,其中在象牙白上读不出来的填充换成 `currentColor`(Kimi 的白 K 会消失、而它的蓝点保留);暖黑画布换成单色、跟随主题文字色。少数厂商在 `brands.lockups` 里覆写:图标与字标可来自不同图标(混元的标 + Tencent 的字标)、匹配词可指定(`step-*` 用 `Step`,另有 `MiMo`、`GLM`)、字标可裁掉多余部分(GLM-V 截掉 `-V`、ChatGPT 只留 `GPT`);手工提供的资产放 `src/assets/icons/`(与生成的 `combine/` 同级)。字标要和它替换的词对得上:Claude 行画 Claude 的、Grok 行画 Grok 的,而不是各自母公司的;Claude 行的其余文字用 Anthropic Serif 排(那枚锁定标本身就是衬线体)。名称里另有视觉隐藏的厂商名,屏读听到的仍是「Kimi K3」而不是「K3」。
59
+ 4. **品牌切换** —— 设置(`Ctrl+,`)→ **Claude Style**(DSH 0.1.7 起位于「设置 → 插件 → dsh-claude-style」页):侧栏品牌在 Claude(官方星芒 + Claude 字标)与 Anthropic(`A\` + ANTHROPIC 字标)之间切换,新会话页标识随所选品牌保持陶烬橙星芒;选择保存在浏览器本地,默认 Claude。
61
60
  5. **账户抽屉** —— 侧栏底栏的账户按钮悬停展开弹层(设置里「自动弹出弹层」可选关闭 / 仅账号区 / 全部),一键打开设置(`Ctrl+,`)与管理插件;弹层顶部那条用户名横条是个彩蛋:点开会看到 Claude 官方的「账户已暂停」页(纯本地复刻,不碰任何真实状态,点页面上任意按钮或按 `Esc` 退出;切走窗口**不会**关,方便你切出去照着它看)。这页的语言在设置里单独选(**封号彩蛋语言**,中文/英文,默认英文)。
62
61
  6. **状态细节** —— 思考状态每轮从 Claude Code 的 185 个思考动词随机抽取一个稳定展示;工作区运行中状态使用 Fluent 风格的圆形加载动画。
63
62
 
package/docs/STYLE.md CHANGED
@@ -26,12 +26,7 @@ Rules:
26
26
  - **Serif display** — headings / editorial statements (`--dsw-font-serif`).
27
27
  - **Sans UI** — chrome, body (`--dsw-font-family`).
28
28
  - **Mono** — code, technical labels (`--dsw-font-code`).
29
- - **Vendor face** — one model name in the picker, where the row already wears the
30
- vendor's mark (`--dsw-font-brand-gemini`: a Latin-only Google Sans Flex subset,
31
- standard weight, SIL OFL 1.1). A vendor face is a *material*, not a new UI role:
32
- it never replaces the sans stack, it sits in front of it, so text the subset does
33
- not cover falls through instead of turning to tofu. Rebuilt with
34
- `scripts/slim-google-sans.py`; licence in `fonts/OFL-GoogleSansFlex.txt`.
29
+
35
30
 
36
31
  ## Shapes
37
32
 
@@ -71,3 +71,12 @@
71
71
  - **决定**:把「结构感知」从 CSS 移到 JS:`permissions.js` 的 `syncSegments()` 在 scheduler 每轮 pass 里为 composerStack 祖先写 `data-composer-variant="hero|inline"` 属性(observer 的 attributeFilter 不含 data-*,不会反触发;写前比较旧值防抖动;同轮去重避免多卡命中同一 stack 反复写),CSS 改为读属性。交互敏感的 `:has()`(`:hover`、`:focus-within`)与低频的 dialog `:has()` 保留。
72
72
  - **落地**:已执行完毕。仍未消除的结构感知 `:has()` 只剩 placeholder 那条(`card` 上的 `:not(:has([data-composer-placeholder]))`):`copy.js` 本就管理 placeholder,可在它的 sync 里同步写 `data-has-placeholder` 再改 CSS,属可选的后续优化。
73
73
  - **重审条件**:实测证明 :has() 不再是热点,或宿主提供 hero/inline 的稳定属性契约。
74
+
75
+ ## D10. 设置按宿主世代分流:官方 Config 表单 + 旧版命名空间注册
76
+
77
+ - **背景**:0.1.7 删掉了 `settings.register(ns, schema)`——命名空间不再是插件自取的名字,而是 profile entry id,schema 就是插件导出的 `Config`,只有 `.volatile()` 字段进表单,值写进 profile 的 Cordis patch;客户端服务 `settingsScope` 改名 `configForms`,插件设置席位从 `settings.plugin.item` 变成 `plugins.bundle.config`(键 = 包名)/ `plugins.row.config`。旧宿主(0.1.5-rc.2,桌面端内置)仍是注册制,且其 schemastery 3.18.2 没有 `.volatile()`——两套 API 互斥,`.volatile()` 在旧版上会直接抛错。
78
+ - **决定**:设置层按宿主世代走两条路,探测点各只有一个——宿主半边看 `settings.register` 是否存在,客户端看 `ctx.get('configForms')` 是否可用。
79
+ - 宿主半边导出 `Config`:八个偏好字段,`volatileField()` 逐字段探测 `.volatile()` 存在才加标记;schemastery 用顶层 await 守卫导入,解析不到就让 `Config` 为 `undefined`,皮肤照常加载。新宿主把命名空间取成 `ctx.fiber.entry.id`(读不到回落 patch 里的常量),只调 `settings.configure({ auto: false }, ctx.fiber)` 声明自带页面;旧宿主仍 `register('claude-style', schema)`。两条路共用同一个可变命名空间,`describePrefs/updatePrefs` 与自建 prefs 路由都读它。
80
+ - 客户端:`ctx.configForms.get(entryId)` 可用就用官方表单(值 + 写队列 + revision 栅栏),否则回落到自建路由;设置界面在 0.1.7 注册成 `plugins.bundle.config`(键 = 包名,渲染在插件页上),旧宿主注册成 `settings.section` 整页。分流靠 `slots.inject` 的「槽被声明才触发」语义(旧宿主从不声明前者),再用 `configForms` 是否存在否决后者在 0.1.7 上的重复注册。
81
+ - **代价**:设置层有两套传输与两套席位,回归必须覆盖两种宿主形态(`.debug/settings-bridge-check.cjs` 跑宿主半边两代,`.debug/client-settings-check.cjs` 在无头浏览器里跑客户端两代);`Config` 的顶层 await 让宿主半边模块求值晚一步(loader 本就 await 导入,无实际影响)。
82
+ - **重审条件**:不再支持 0.1.5-rc.2(桌面端内置升级到 0.1.7+)时,删掉旧路径与两处探测,只留 `Config` + `plugins.bundle.config`。
@@ -0,0 +1 @@
1
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 148.09 148.18'><g transform='translate(-75.96,-223.53)'><path fill='#D97757' d='m 105.01,322.07 29.14,-16.35 0.49,-1.42 -0.49,-0.79 h -1.42 l -4.87,-0.3 -16.65,-0.45 -14.44,-0.6 -13.99,-0.75 -3.52,-0.75 -3.3,-4.35 0.34,-2.17 2.96,-1.99 4.24,0.37 9.37,0.64 14.06,0.97 10.2,0.6 15.11,1.57 h 2.4 l 0.34,-0.97 -0.82,-0.6 -0.64,-0.6 -14.55,-9.86 -15.75,-10.42 -8.25,-6 -4.46,-3.04 -2.25,-2.85 -0.97,-6.22 4.05,-4.46 5.44,0.37 1.39,0.37 5.51,4.24 11.77,9.11 15.37,11.32 2.25,1.87 0.9,-0.64 0.11,-0.45 -1.01,-1.69 -8.36,-15.11 -8.92,-15.37 -3.97,-6.37 -1.05,-3.82 c -0.37,-1.57 -0.64,-2.89 -0.64,-4.5 l 4.61,-6.26 2.55,-0.82 6.15,0.82 2.59,2.25 3.82,8.74 6.19,13.76 9.6,18.71 2.81,5.55 1.5,5.14 0.56,1.57 h 0.97 v -0.9 l 0.79,-10.54 1.46,-12.94 1.42,-16.65 0.49,-4.69 2.32,-5.62 4.61,-3.04 3.6,1.72 2.96,4.24 -0.41,2.74 -1.76,11.44 -3.45,17.92 -2.25,12 h 1.31 l 1.5,-1.5 6.07,-8.06 10.2,-12.75 4.5,-5.06 5.25,-5.59 3.37,-2.66 h 6.37 l 4.69,6.97 -2.1,7.2 -6.56,8.32 -5.44,7.05 -7.8,10.5 -4.87,8.4 0.45,0.67 1.16,-0.11 17.62,-3.75 9.52,-1.72 11.36,-1.95 5.14,2.4 0.56,2.44 -2.02,4.99 -12.15,3 -14.25,2.85 -21.22,5.02 -0.26,0.19 0.3,0.37 9.56,0.9 4.09,0.22 h 10.01 l 18.64,1.39 4.87,3.22 2.92,3.94 -0.49,3 -7.5,3.82 -10.12,-2.4 -23.62,-5.62 -8.1,-2.02 h -1.12 v 0.67 l 6.75,6.6 12.37,11.17 15.49,14.4 0.79,3.56 -1.99,2.81 -2.1,-0.3 -13.61,-10.24 -5.25,-4.61 -11.89,-10.01 h -0.79 v 1.05 l 2.74,4.01 14.47,21.75 0.75,6.67 -1.05,2.17 -3.75,1.31 -4.12,-0.75 -8.47,-11.89 -8.74,-13.39 -7.05,-12 -0.86,0.49 -4.16,44.81 -1.95,2.29 -4.5,1.72 -3.75,-2.85 -1.99,-4.61 1.99,-9.11 2.4,-11.89 1.95,-9.45 1.76,-11.74 1.05,-3.9 -0.07,-0.26 -0.86,0.11 -8.85,12.15 -13.46,18.19 -10.65,11.4 -2.55,1.01 -4.42,-2.29 0.41,-4.09 2.47,-3.64 14.74,-18.75 8.89,-11.62 5.74,-6.71 -0.04,-0.97 h -0.34 l -39.15,25.42 -6.97,0.9 -3,-2.81 0.37,-4.61 1.42,-1.5 11.77,-8.1 -0.04,0.04 z'/></g></svg>