dsh-claude-style 0.10.3 → 0.10.4

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
@@ -2,6 +2,38 @@
2
2
 
3
3
  All notable changes to `dsh-claude-style` are documented here, newest first.
4
4
 
5
+ ## [0.10.4] - 2026-10-03
6
+
7
+ [中文](#cn-0.10.4) | [English](#en-0.10.4)
8
+
9
+ <h3 id="cn-0.10.4">体验优化</h3>
10
+
11
+ - **没有 Anthropic 字体时,界面与正文换成外观相近的字体**:没有把 Anthropic Sans / Serif 装进系统或放进插件 `fonts/` 目录时,界面以前落到 Segoe UI、苹方这类系统字体,对话正文落到 Georgia,字形和排版都和原样差得远。现在插件自带 Inter 与 Noto Serif(SIL OFL 1.1)代替两款 Anthropic 字体,它们的字高、字宽与原字体相差不到百分之二,换字体时文字不会重新换行,粗体用的是字体本身的字重。两款字体只含 Anthropic 字体覆盖的拉丁字符,中文照旧使用系统中文字体;npm 包因此大约增加 214 KB。已经启用 Anthropic 字体的环境不受影响。
12
+ - **品牌标识改为大卡片选择**:设置页的品牌选项由分段小按钮改为一排大卡片,每张卡片放对应的品牌标识(Claude 的陶土星芒、DeepSeek 的蓝鲸),当前品牌的卡片带品牌色描边;后续新增品牌只需添一张卡片。
13
+ - **设置页同时出现在设置对话框与插件页**:0.1.7 起这页只显示在插件页内。现在设置对话框恢复「Claude Style」标签页,两处渲染同一份页面、共用同一份偏好存储,一边改动另一边即时跟上;旧版宿主不受影响,仍只有设置对话框一处。
14
+
15
+ ### 问题修复
16
+
17
+ - **简洁用量模式下的统计弹层不再出现空置半行与多余分组**:宿主「性能与用量」设置选为简洁时,上下文弹层以前把四个数字拆在「会话统计」和「Token 用量」两个小标题下,三项指标占两行、一项指标占一行,各自留下一处空白格。现在简洁模式下移除分组小标题,总用时、首 token 平均、输出速度与缓存命中四个指标合并为整齐的 2×2 排版。
18
+ - **其他插件放进输入框的模型菜单恢复上下排列**:同时启用 `dsh-thinking-effort` 这类自带模型菜单的插件时,菜单里的各个供应商分组以前被挤成一行横排。现在分组按原样上下排列。
19
+ - **macOS 桌面端的封号彩蛋页不再重复出现窗口按钮**:macOS 桌面壳的窗口左上角是系统画的三个红黄绿按钮,彩蛋页以前又画了一套最小化 / 还原 / 关闭,页面上于是出现两排按钮。现在这页不画自己那套,星芒标与字标、「退出登录」直接排进系统标题栏那一行,并让开左侧的三个按钮;按住这一行的空白处仍然可以拖动窗口。
20
+ - **其他插件的弹层与标签保持原有的形状**:其他插件的元素只要类名里带 `badge` 或 `tag`,以前都会被改成胶囊圆角。额度插件的弹层因此变成四角极圆的方圆形,标题和底部按钮被切掉一角,弹层里各段的分隔线也弯成弧形。现在胶囊圆角只用在宿主自己的徽标与标签上(轨迹标签、自定义模型行的标签、提问里的「推荐」、快捷键按键等),而且两端是正圆弧,不再略方。
21
+
22
+ <h3 id="en-0.10.4">Improvements</h3>
23
+
24
+ - **Without the Anthropic fonts, the interface and conversation text use close look-alikes**: when Anthropic Sans and Serif were neither installed on the system nor dropped into the plugin's `fonts/` directory, the interface used to fall back to system fonts such as Segoe UI or PingFang and the conversation body to Georgia, far from the intended letterforms and layout. The plugin now ships Inter and Noto Serif (SIL OFL 1.1) in their place; their letter heights and widths are within two percent of the Anthropic fonts, so text does not rewrap when one replaces the other, and bold text uses the fonts' own weights. Both carry only the Latin characters the Anthropic fonts cover, so Chinese text keeps using the system's Chinese fonts; the npm package grows by about 214 KB. Setups that already have the Anthropic fonts are unaffected.
25
+ - **The brand choice is now a row of large cards**: the settings page's brand option changes from small segments to large cards, each carrying the brand's own mark (Claude's clay starburst, DeepSeek's blue whale) with the active card outlined in the brand accent; a new brand is one more card.
26
+ - **The settings page now shows in both the settings dialog and the plugin page**: since 0.1.7 it lived only on the plugin page. The settings dialog keeps its "Claude Style" tab again — both render the same page against the same preference store, so a change in one appears in the other at once. Older hosts are unaffected and keep the dialog tab as the only seat.
27
+
28
+ ### Bug Fixes
29
+
30
+ - **The stats popover under compact usage no longer shows half-empty rows and redundant groups**: when the host's performance and usage setting was set to compact, the context popover previously split the four figures under "Session statistics" and "Token usage" headings, leaving awkward empty slots in two separate grids. The compact mode now removes the section headings and places total time, average TTFT, output speed, and cache hit share together into a clean 2x2 layout.
31
+ - **A model menu another plugin puts in the composer stacks its groups again**: with a plugin that brings its own model menu, such as `dsh-thinking-effort`, the menu's provider groups used to be squeezed into one horizontal row. They stack top to bottom as the plugin draws them now.
32
+ - **The account-hold page on the macOS Desktop no longer draws a second set of window buttons**: the top-left of a macOS Desktop window belongs to the system, which paints the three red-yellow-green buttons; the page used to draw its own minimize, maximize and close as well, so the window showed two rows of buttons. It draws none of its own now — the starburst and wordmark and the Sign out button take their place in the titlebar row, clear of the three buttons on the left, and the window is still dragged by the empty part of that row.
33
+ - **Other plugins' popovers and tags keep their own shape**: any element of another plugin whose class name contained `badge` or `tag` used to be turned into a pill. A quota plugin's popover became a squarish blob with very round corners, its title and bottom buttons clipped at the corners and the dividers between its sections bent into arcs. The pill radius now applies only to the host's own badges and tags (trajectory tags, the custom model row tag, the Recommended mark in a question, shortcut key caps and the like), and their ends are true round arcs instead of slightly squared ones.
34
+
35
+ **Full Changelog**: [v0.10.3...v0.10.4](https://github.com/Nwflower/dsh-claude-style/compare/v0.10.3...v0.10.4)
36
+
5
37
  ## [0.10.3] - 2026-10-01
6
38
 
7
39
  [中文](#cn-0.10.3) | [English](#en-0.10.3)
package/README.en.md CHANGED
@@ -76,6 +76,10 @@ The settings page's Brand mark row switches between the Claude and DeepSeek pale
76
76
  | Anthropic Sans Web Text | Interface / UI | [`fonts/AnthropicSansWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/AnthropicSansWebText.ttf) |
77
77
  | Anthropic Serif Web Text | Conversation body / Markdown | [`fonts/AnthropicSerifWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/AnthropicSerifWebText.ttf) |
78
78
  | 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) |
79
+ | Inter | Interface when Anthropic Sans is absent | [`fonts/InterVariable.woff2`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/InterVariable.woff2) |
80
+ | Noto Serif | Conversation body when Anthropic Serif is absent | [`fonts/NotoSerifVariable.woff2`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/NotoSerifVariable.woff2) |
81
+
82
+ 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.
79
83
 
80
84
  To enable the Anthropic fonts, choose one of the following:
81
85
 
package/README.md CHANGED
@@ -78,6 +78,10 @@
78
78
  | Anthropic Sans Web Text | 界面 / UI | [`fonts/AnthropicSansWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/AnthropicSansWebText.ttf) |
79
79
  | Anthropic Serif Web Text | 对话正文 / Markdown | [`fonts/AnthropicSerifWebText.ttf`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/AnthropicSerifWebText.ttf) |
80
80
  | 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) |
81
+ | Inter | 没有 Anthropic Sans 时的界面字体 | [`fonts/InterVariable.woff2`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/InterVariable.woff2) |
82
+ | Noto Serif | 没有 Anthropic Serif 时的正文字体 | [`fonts/NotoSerifVariable.woff2`](https://github.com/Nwflower/dsh-claude-style/raw/main/fonts/NotoSerifVariable.woff2) |
83
+
84
+ JetBrains Mono、Inter 与 Noto Serif 采用 SIL Open Font License 1.1,随 npm 包分发,无需任何操作。Inter 与 Noto Serif 的字高、字宽与两款 Anthropic 字体几乎一致,没有启用 Anthropic 字体时由它们代替,界面与正文的排版不会因此变样;两者只含 Anthropic 字体覆盖的拉丁字符,中文照旧使用系统中文字体。
81
85
 
82
86
  Anthropic 字体启用(二选一):
83
87
 
@@ -11,7 +11,7 @@
11
11
  按主题:
12
12
 
13
13
  - 构建与源码:D1 拼接构建 · D5 模型文案是数据 · D18 源码布局与共用部件层 · D4 输入框样式门控
14
- - 宿主边界:D2 只做浏览器半边 · D3 宿主选择器纪律 · D19 宿主契约与构建编号 · D10 设置传输 · D11 私有路由与字符串安全
14
+ - 宿主边界:D2 只做浏览器半边 · D3 宿主选择器纪律 · D19 宿主契约与构建编号 · D10 设置传输 · D11 私有路由与字符串安全 · D28 桌面标题带的一处读取
15
15
  - 运行时:D6 单一调度器 · D13 功能契约 · D12 快速失败与功能隔离 · D9 样式性能
16
16
  - 功能:D14 账号表面 · D15 HDSL 契约 · D16 弹层基准 · D17 权限档位 · D20 滑动高亮 · D21 已归档列表 · D22 搜索面板 · D23 轮次状态行 · D24 小鲸鱼 Deepy · D25 封号彩蛋页的桌面顶栏 · D26 动画效果偏好 · D27 会话数字收进上下文弹层
17
17
  - 分发:D7 行为层与皮肤同包 · D8 多主题
@@ -245,11 +245,12 @@
245
245
  ## D25. 封号彩蛋页在桌面端骑在系统标题栏那一行上
246
246
 
247
247
  - **决定**:
248
- - Windows 桌面壳把窗口最上面 `--dsh-windows-titlebar-height` 那一行交给系统层:它用宿主量出来的 `--dsw-specific-sidebar-fill` 涂满这条横带,右端画窗口按钮,页面内容盖不住它,也点不到按钮下面。彩蛋页因此不再画自己那套最小化 / 还原 / 关闭,顶栏改骑在这条横带上:`ban-screen.js` 打开时读 `navigator.windowControlsOverlay` 的标题栏区域,取横带高度与按钮占用的宽度,写成 `--dsh-ban-caption-height` / `--dsh-ban-caption-controls` 并给浮层打 `data-dsh-ban-titlebar`,样式表据此把字标与「退出登录」排进这一行(只有横带存在、且 Window Controls Overlay 可见时才这么排;全屏时 overlay 不可见,回到原来的版面)。
249
- - 横带要跟着页面走:浮层打开期间把页面自己的 `--dsh-ban-canvas` 写到 `<body>` 的 `--dsw-specific-sidebar-fill` 上——桌面壳的探针读的正是这个令牌,`<body>` 的 style 一变它就重画横带——关闭时撤掉。这一行是窗口的拖拽区,浮层盖住了宿主自己的拖拽条(ui-layout 的 `.frame::before`),所以顶栏自己声明 `-webkit-app-region: drag`,里面只有「退出登录」一个控件写 `no-drag`。
248
+ - 两个桌面平台都把窗口最上面一条交给系统层,彩蛋页因此不再画自己那套最小化 / 还原 / 关闭,顶栏改骑在这条横带上:`ban-screen.js` 打开时问一次 `desktopBand()`(D28),把横带高度与窗口控件占用的宽度写成 `--dsh-ban-caption-height` / `--dsh-ban-caption-controls`,把控件所在的一端写成 `data-dsh-ban-controls`,并给浮层打 `data-dsh-ban-titlebar`;样式表据此把字标与「退出登录」排进这一行,并按控件所在的一端让出那一段宽度——Windows 的三个窗口按钮在右端,macOS 的三个红黄绿按钮在左端。
249
+ - Windows 的横带要跟着页面走:浮层打开期间把页面自己的 `--dsh-ban-canvas` 写到 `<body>` 的 `--dsw-specific-sidebar-fill` 上——桌面壳的探针读的正是这个令牌,`<body>` 的 style 一变它就重画横带——关闭时撤掉。这一步只在 Windows 调用:macOS 的横带由系统自己的半透明材质画出,页面写不到它。
250
+ - 这一行是窗口的拖拽区,浮层盖住了宿主自己的拖拽区(Windows 的 `.frame::before` 横条,以及 macOS 上宿主标了 `data-window-drag` 的那些元素),所以顶栏自己声明 `-webkit-app-region: drag`,里面只有「退出登录」一个控件写 `no-drag`。
250
251
  - 这页画的始终是 Claude 自己的星芒加字标,与品牌偏好无关(它复刻的是 Claude 的一页)。
251
252
  - **理由**:桌面端的系统标题栏是原生层,页面既盖不住也画不上;把这一行接过来,整窗浮层才像一整个窗口,而不是窗口里嵌了一页。
252
- - **代价**:依赖 `html[data-windows-titlebar]`、`--dsh-windows-titlebar-height`、`--dsw-specific-sidebar-fill` 探针与 Window Controls Overlay API 的现状;桌面壳不再量这个令牌时横带保持主题色,页面顶端的字标会被横带盖住。
253
+ - **代价**:依赖 `data-windows-titlebar` 与 `data-platform="darwin"`、`--dsh-frame-top-clearance`、Windows 的 `--dsw-specific-sidebar-fill` 探针与 Window Controls Overlay API 的现状;桌面壳不再量这些时横带保持主题色,页面顶端的字标会被横带盖住。
253
254
  - **重审条件**:宿主或桌面壳给出整窗浮层的插槽,或允许浮层隐藏标题栏时,改用那个。
254
255
 
255
256
  ## D26. 动画效果偏好:一处解析,两处读同一个值
@@ -265,12 +266,19 @@
265
266
  ## D27. 会话数字收进上下文弹层
266
267
 
267
268
  - **决定**:
268
- - 宿主画在输入卡片下方的统计行(轮次与步数 · tok/s、总 tokens · 缓存命中)与它的两张明细面板一起隐藏(`features/composer/inline-bar.css`):输入行只剩右端的上下文计量环,数字进它自己的那个弹层。行仍留在文档里,只为让「性能与用量」这一档(详细 / 简洁)的结构还读得出来——详细档给出宿主那两组所有的行,简洁档只给四个数字:总用时(模型用时加工具调用用时,宿主没有这个合计,用皮肤自己的文案)、首 token 平均、输出速度与缓存命中。
269
+ - 宿主画在输入卡片下方的统计行(轮次与步数 · tok/s、总 tokens · 缓存命中)与它的两张明细面板一起隐藏(`features/composer/inline-bar.css`):输入行只剩右端的上下文计量环,数字进它自己的那个弹层。行仍留在文档里,只为让「性能与用量」这一档(详细 / 简洁)的结构还读得出来——详细档给出宿主那两组所有的行,简洁档只给四个数字:总用时(模型用时加工具调用用时,宿主没有这个合计,用皮肤自己的文案)、首 token 平均、输出速度与缓存命中,不带分组小标题,直接排成 2×2 网格。
269
270
  - 数字按**数据**读,不读渲染结果:`sessionStats` 与 `tokenUsage` 是宿主在整份日志上折出来的会话投影,会话面交出它们的按 key 读取面(`session.projections.faceOf`,也就是宿主自己 `useProjection` 走的那个座位;`features/permissions/session-stats.js` 经 `sessions.binding(id)` 取)。面板打开时按当前值填,订阅到新帧就地重写:不再按胶囊、不再等宿主的对话框挂载。
270
271
  - **文字与格式向宿主对齐**:标签、时长模板与分词符都取自宿主的 `chat` 语言命名空间(`locale.bind('chat')`);宿主在原始数值之上做的四条格式规则(`formatDuration`、`formatTokensPerSecond`、`formatExactTokens`、`formatCacheHitPercent`)在皮肤里各写一份,函数名对着来源,输出与宿主自己的显示逐字符相同。
271
272
  - 面板靠结构辨认(非模态、带 `dl`),开合靠按宿主的触发按钮(`aria-expanded` 是权威);悬停进出走共享的停留与宽限,并跟随「悬停打开弹层」偏好,指针停在面板里时不收起。既然面板是宿主挂载的,皮肤给它打 `data-dsh-claude-context-panel` 并让它取卡片那套一次性入场(同一个 4px、同一个 0.15 秒)——同一个动作在哪个控件上都以同样的方式出现。
272
273
  - 面板的左右位置由皮肤重写:宿主从定位点的左缘放下弹层(`useStatDialog` 的 `align: 'start'`),输入行右端按这个位置只够把它挤到窗口右缘,离计量环很远。皮肤量出计量环的右缘与弹层自己的布局宽度(入场动画把盒子缩到 0.98,量矩形会少两个百分点,所以读 `offsetWidth`),写成 `--dsh-claude-context-panel-left` 并同时打上 `data-dsh-claude-context-aligned`,样式表用 `!important` 读取——与 hero 菜单同一套交接。盒子变化时由 ResizeObserver、视口移动时由 `reposition('viewport')` 重新量。
273
- - 追加的块带 `data-dsh-claude-context-stats`;投影还没有应答时,块先按两个真实小标题占住数字要用的高度(占位条按真实行的 37px 排),宿主重绘面板把它抹掉时下一轮刷新重画。占位只保留 `STATS_SKELETON_MS`(2 秒):宿主不提供这两个投影单元时,块退场而不是长期立着占位条。
274
+ - 追加的块带 `data-dsh-claude-context-stats`;投影还没有应答时,详细档按两个真实小标题、简洁档按无标题 2×2 网格占住数字要用的高度(占位条按真实行的 37px 排),宿主重绘面板把它抹掉时下一轮刷新重画。占位只保留 `STATS_SKELETON_MS`(2 秒):宿主不提供这两个投影单元时,块退场而不是长期立着占位条。
274
275
  - **理由**:会话的所有数字只留一个去处,输入行只剩右端那只计量环;面板本体、开合与关合都还是宿主的。读投影让插件画的是数据,不是别人渲染出来的文字——既不重复渲染,也能随会话实时更新。
275
276
  - **代价**:依赖宿主两个投影单元的键名与值形状、`chat` 命名空间的键、会话面上的 `projections.faceOf`,以及统计行的结构(据此判断详细 / 简洁)。宿主不装这两个投影单元时,面板里没有这两组数字(那时宿主的胶囊同样没有数据)。弹层的左右位置另依赖宿主的放置方式与计量环的现状;宿主改从右缘放下弹层、或给面板一个固定宽度时,这段接管要跟着核对。
276
277
  - **重审条件**:宿主把这些数字放进它自己的面板,或给统计行一个可悬挂的插槽时,改回宿主的。
278
+
279
+ ## D28. 桌面标题带只在一处读取
280
+
281
+ - **决定**:`src/core/desktop-band.js` 的 `desktopBand()` 是皮肤读取桌面标题带的唯一入口,一次回答三个问题:这条横带在不在、多高、窗口控件占哪一端,返回值形如 `{ platform, height, controls: { side, size } }`,没有横带时返回 `null`。判据全部取宿主自己的契约:标记用 `data-windows-titlebar` 与 `data-platform="darwin"`,高度用 `--dsh-frame-top-clearance`(宿主在两个平台都写它,Windows 上它是 `--dsh-windows-titlebar-height` 的别名,读取时经计算样式把别名解开),Windows 的控件宽度仍以 Window Controls Overlay API 为准、取不到量值时回落到 140px,macOS 的控件在左端、宽度取宿主自己的 leading seat 起点 88px。macOS 全屏时窗口控件收起,这里答 `null`;Windows 的答案与引入这个模块之前逐字相同,`data-fullscreen` 不参与那个平台的分支。
282
+ - **理由**:封号彩蛋页是第一个读者;判据若写在每个读者里,两个平台的三处分歧就要在每一处重复一遍。集中之后 Windows 的行为不变,macOS 拿到的是同一套答案换成本平台的量值。
283
+ - **代价**:多一个片段,每次调用多一次 `getComputedStyle` 读取;`--dsh-frame-top-clearance` 缺席时回落到模块内的常数(macOS 48px、Windows 40px)。
284
+ - **重审条件**:宿主给出统一的标题带插槽或平台无关的度量接口时,改读宿主的接口。
Binary file
Binary file
@@ -0,0 +1,93 @@
1
+ Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
package/host/index.js CHANGED
@@ -17,7 +17,8 @@
17
17
  * are NOT in the npm package (they remain Anthropic's property), but a
18
18
  * user who drops them into this package's `fonts/` directory gets the
19
19
  * same zero-install treatment; a missing file simply 404s and the stack
20
- * falls back to a system-installed copy.
20
+ * falls back to a system-installed copy, then to the look-alike faces
21
+ * this package does ship (Inter, Noto Serif — SIL OFL).
21
22
  * 3. Resolve the OS user once for the browser half. The username route answers
22
23
  * a single GET and the browser caches it; it runs the host's own request
23
24
  * fence first — see refusalOf() in routes.js.
package/host/routes.js CHANGED
@@ -24,7 +24,8 @@ const COPY_FILE = 'model-descriptions.json'
24
24
  * Webfonts this plugin serves under `${ROUTE_PREFIX}/fonts/`, mapped to their
25
25
  * content type. The table is a whitelist: the filename is the whole request
26
26
  * contract, so nothing below the package's `fonts/` directory is reachable
27
- * and no path traversal is possible. The JetBrains Mono files ship in the
27
+ * and no path traversal is possible. The JetBrains Mono files and the two
28
+ * look-alike faces behind the Anthropic ones (Inter, Noto Serif) ship in the
28
29
  * npm package; the Anthropic faces do not (copyright) — their entries exist so
29
30
  * a user-supplied copy in `fonts/` is served, and readFileSync's ENOENT turns
30
31
  * into a 404 the browser half's font stacks fall back from.
@@ -34,6 +35,8 @@ const FONT_FILES = {
34
35
  'JetBrainsMonoItalicVariable.ttf': 'font/ttf',
35
36
  'AnthropicSansWebText.ttf': 'font/ttf',
36
37
  'AnthropicSerifWebText.ttf': 'font/ttf',
38
+ 'InterVariable.woff2': 'font/woff2',
39
+ 'NotoSerifVariable.woff2': 'font/woff2',
37
40
  }
38
41
  /**
39
42
  * Deepy's animation sheets, served under `${ROUTE_PREFIX}/deepy/` from the
package/lib/client.js CHANGED
@@ -8,6 +8,7 @@
8
8
  * - src/assets/brand/*.svg Brand marks (inlined as CSS url() data URIs at build time)
9
9
  * * - src/constants.js
10
10
  * - src/core/host.js
11
+ * - src/core/desktop-band.js
11
12
  * - src/core/prefs.js
12
13
  * - src/core/model-copy.js
13
14
  * - src/core/i18n.js
@@ -499,15 +500,15 @@ window.__ModuleLoader__.load({
499
500
  /** Wordmark aspect ratio; scripts/build.mjs sizes the sidebar word height from it (geometry lives in src/assets/claude-word.svg). */
500
501
  const CLAUDE_WORD_ASPECT = 512.22 / 121.54
501
502
 
502
- const SANS = "'Anthropic Sans Web Text','Noto Sans SC','Source Han Sans SC',-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Helvetica Neue',Helvetica,Arial,sans-serif"
503
- const SERIF = "'Anthropic Serif Web Text',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif"
503
+ const SANS = "'Anthropic Sans Web Text','Claude Style Inter','Noto Sans SC','Source Han Sans SC',-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Helvetica Neue',Helvetica,Arial,sans-serif"
504
+ const SERIF = "'Anthropic Serif Web Text','Claude Style Noto Serif',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif"
504
505
  /**
505
506
  * Conversation prose: Claude sets Latin text in the serif face and lets
506
507
  * Chinese fall through to a sans CJK — the serif Latin faces carry no CJK
507
508
  * glyphs, so the stack leads with serif and names the sans CJK families
508
509
  * after it. UI chrome keeps SANS; only markdown prose uses this.
509
510
  */
510
- const PROSE = "'Anthropic Serif Web Text',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif"
511
+ const PROSE = "'Anthropic Serif Web Text','Claude Style Noto Serif',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif"
511
512
  const MONO = "'JetBrains Mono','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',ui-monospace,'SF Mono','Fira Code',Consolas,'Liberation Mono',Menlo,Courier,monospace"
512
513
 
513
514
  // ============================================================================
@@ -517,9 +518,9 @@ window.__ModuleLoader__.load({
517
518
  "/* --- 2.1 Design Tokens (Dark Base & Light Overrides) --- */",
518
519
  "/* ---------- typography tokens: theme-independent ---------- */",
519
520
  "body[data-dsh-claude-style] {",
520
- " --dsw-font-family: 'Anthropic Sans Web Text','Noto Sans SC','Source Han Sans SC',-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Helvetica Neue',Helvetica,Arial,sans-serif;",
521
- " --dsw-font-serif: 'Anthropic Serif Web Text',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;",
522
- " --dsw-font-prose: 'Anthropic Serif Web Text',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;",
521
+ " --dsw-font-family: 'Anthropic Sans Web Text','Claude Style Inter','Noto Sans SC','Source Han Sans SC',-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Helvetica Neue',Helvetica,Arial,sans-serif;",
522
+ " --dsw-font-serif: 'Anthropic Serif Web Text','Claude Style Noto Serif',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;",
523
+ " --dsw-font-prose: 'Anthropic Serif Web Text','Claude Style Noto Serif',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;",
523
524
  " --dsw-font-code: 'JetBrains Mono','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',ui-monospace,'SF Mono','Fira Code',Consolas,'Liberation Mono',Menlo,Courier,monospace;",
524
525
  " /* Claude's lockup is drawn in Anthropic's serif, so its rows take the same face:",
525
526
  " the wordmark and the rest of the label then read as one piece of lettering.",
@@ -918,6 +919,33 @@ window.__ModuleLoader__.load({
918
919
  " font-display: swap;",
919
920
  "}",
920
921
  "",
922
+ "/* The look-alikes the stacks name right after the Anthropic faces, for an",
923
+ " install that has neither the files nor a system copy. Both ship in the",
924
+ " package (SIL OFL 1.1), cut to exactly the characters the Anthropic faces",
925
+ " cover, so text the Anthropic faces would not draw falls through to the same",
926
+ " CJK and system fonts as before. Inter matches Anthropic Sans in x-height,",
927
+ " cap height and lowercase width to within 0.01 em; Noto Serif does the same",
928
+ " for Anthropic Serif, so switching between a face and its look-alike does",
929
+ " not rewrap text. Both carry real weights, roman only — the Anthropic faces",
930
+ " have no italic either, so emphasis is slanted the same way in both. The",
931
+ " family names are the skin's own: a plugin that asks for 'Inter' or",
932
+ " 'Noto Serif' still gets its own copy, not these cut-down files. */",
933
+ "@font-face {",
934
+ " font-family: 'Claude Style Inter';",
935
+ " src: url('/dsh-claude-style/fonts/InterVariable.woff2') format('woff2');",
936
+ " font-weight: 100 900;",
937
+ " font-style: normal;",
938
+ " font-display: swap;",
939
+ "}",
940
+ "",
941
+ "@font-face {",
942
+ " font-family: 'Claude Style Noto Serif';",
943
+ " src: url('/dsh-claude-style/fonts/NotoSerifVariable.woff2') format('woff2');",
944
+ " font-weight: 100 900;",
945
+ " font-style: normal;",
946
+ " font-display: swap;",
947
+ "}",
948
+ "",
921
949
  "/* ---------- typography: serif display + sans UI + mono code ---------- */",
922
950
  "body[data-dsh-claude-style] {",
923
951
  " font-family: var(--dsw-font-family);",
@@ -1361,15 +1389,15 @@ window.__ModuleLoader__.load({
1361
1389
  " color: var(--dsh-claude-link-hover);",
1362
1390
  "}",
1363
1391
  "",
1364
- "body[data-dsh-claude-style] button[class*=\"brand\"] {",
1392
+ "body[data-dsh-claude-style] button[class*=\"brand\"]:not([class*=\"dsh-claude\"]) {",
1365
1393
  " color: var(--dsw-alias-brand-primary);",
1366
1394
  "}",
1367
1395
  "",
1368
1396
  "/* The baked-in wordmark \"HARNESS\" badge (a pill rect + its letter paths)",
1369
1397
  " looks crowded next to the whale + wordmark. Hide the badge entirely so",
1370
1398
  " the brand reads as a clean whale + wordmark on the clay pill. */",
1371
- "body[data-dsh-claude-style] button[class*=\"brand\"] rect[fill=\"currentColor\"],",
1372
- "body[data-dsh-claude-style] button[class*=\"brand\"] path[fill*=\"label-primary-inverted\"] {",
1399
+ "body[data-dsh-claude-style] button[class*=\"brand\"]:not([class*=\"dsh-claude\"]) rect[fill=\"currentColor\"],",
1400
+ "body[data-dsh-claude-style] button[class*=\"brand\"]:not([class*=\"dsh-claude\"]) path[fill*=\"label-primary-inverted\"] {",
1373
1401
  " display: none;",
1374
1402
  "}",
1375
1403
  "",
@@ -1393,25 +1421,48 @@ window.__ModuleLoader__.load({
1393
1421
  " the canvas. That pill also carried a 9999px radius, and together with the",
1394
1422
  " button overflow:hidden its arc clipped the leading edge of the mark — so",
1395
1423
  " the radius goes as well. Adaptive logo color: espresso on light, ivory",
1396
- " on dark (--dsh-claude-logo-ink; the DeepSeek brand's own blue under it). */",
1397
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) button[class*=\"brand\"],",
1398
- "body[data-dsh-claude-style][data-ds-dark-theme] button[class*=\"brand\"] {",
1424
+ " on dark (--dsh-claude-logo-ink; the DeepSeek brand's own blue under it).",
1425
+ " Every \"brand\" rule in this file excludes the skin's own controls",
1426
+ " (:not([class*=\"dsh-claude\"])): the settings page's brand cards carry the",
1427
+ " same substring and draw their own border and background. */",
1428
+ "body[data-dsh-claude-style]:not([data-ds-dark-theme]) button[class*=\"brand\"]:not([class*=\"dsh-claude\"]),",
1429
+ "body[data-dsh-claude-style][data-ds-dark-theme] button[class*=\"brand\"]:not([class*=\"dsh-claude\"]) {",
1399
1430
  " background: transparent !important;",
1400
1431
  " border: none;",
1401
1432
  " border-radius: 0;",
1402
1433
  "}",
1403
1434
  "",
1404
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) button[class*=\"brand\"] {",
1435
+ "body[data-dsh-claude-style]:not([data-ds-dark-theme]) button[class*=\"brand\"]:not([class*=\"dsh-claude\"]) {",
1405
1436
  " color: var(--dsh-claude-logo-ink) !important;",
1406
1437
  "}",
1407
1438
  "",
1408
- "body[data-dsh-claude-style][data-ds-dark-theme] button[class*=\"brand\"] {",
1439
+ "body[data-dsh-claude-style][data-ds-dark-theme] button[class*=\"brand\"]:not([class*=\"dsh-claude\"]) {",
1409
1440
  " color: var(--dsh-claude-logo-ink) !important;",
1410
1441
  "}",
1411
1442
  "",
1412
- "/* badges: full pill */",
1413
- "body[data-dsh-claude-style] :is([class*=\"badge\"], [class*=\"Badge\"], [class*=\"_tag\"], [class*=\"Tag\"]) {",
1443
+ "/* Host badges and tags: full pill. Each is named by its own CSS-module class",
1444
+ " (docs/architecture.md D3) — trajectory step tags, the trajectory table's kind",
1445
+ " tag, the custom-model row tag, the recommended-option badge in a question,",
1446
+ " the plugin install attempt badge, the shortcut key caps — so a plugin's own",
1447
+ " badge or tag keeps the shape it draws. The host's Tag primitive is a round",
1448
+ " pill already. corner-shape: round goes with the pill radius: the host's",
1449
+ " corner-shape sheet gives every element a superellipse, which squares off a",
1450
+ " pill's ends. */",
1451
+ "body[data-dsh-claude-style] :is(",
1452
+ " [class*=\"_tagSystem\"],",
1453
+ " [class*=\"_tagUser\"],",
1454
+ " [class*=\"_tagContext\"],",
1455
+ " [class*=\"_tagMessage\"],",
1456
+ " [class*=\"_tagTool\"],",
1457
+ " [class*=\"_tagSubtool\"],",
1458
+ " [class*=\"_kindTag\"]:not([class*=\"_kindTagIcon\"], [class*=\"_kindTagLabel\"]),",
1459
+ " [class*=\"_rowTag\"],",
1460
+ " [class*=\"_optionLine\"] > [class*=\"_badge\"],",
1461
+ " [class*=\"_attemptBadge\"],",
1462
+ " [class*=\"_keyBadge\"]",
1463
+ ") {",
1414
1464
  " border-radius: 9999px;",
1465
+ " corner-shape: round;",
1415
1466
  "}",
1416
1467
  "",
1417
1468
  "/* ---------- chrome details ---------- */",
@@ -1708,7 +1759,10 @@ window.__ModuleLoader__.load({
1708
1759
  " the titlebar, horizontally centred in the window — the same row the host",
1709
1760
  " gives the collapsed sidebar's new-session button. The measured",
1710
1761
  " --dsh-view-tabs-shift is not written in this mode, so the base transform",
1711
- " gives way. */",
1762
+ " gives way.",
1763
+ " macOS has no such block: its shell leaves the frame unpadded, so the",
1764
+ " conversation header draws its first row inside the strip and the measured",
1765
+ " shift seats the tabs on the row beneath it (view-tabs.js). */",
1712
1766
  "body[data-dsh-claude-style][data-dsh-titlebar-tabs] [data-dsh-view-tabs] {",
1713
1767
  " position: fixed !important;",
1714
1768
  " top: calc(var(--dsh-windows-titlebar-height, 40px) / 2) !important;",
@@ -2545,9 +2599,13 @@ window.__ModuleLoader__.load({
2545
2599
  "}",
2546
2600
  "",
2547
2601
  "/* @composer-gate */",
2548
- "/* Model trigger in trailing */",
2549
- "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_trailing\"] > :first-child,",
2550
- "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_trailing\"] :is([class*=\"trigger\" i], [class*=\"model\" i], [class*=\"Model\"]) {",
2602
+ "/* Model trigger in the host's contract slot: the skin's own button, and the",
2603
+ " host's trigger when the picker preference hands the seat back. Both are",
2604
+ " named by their exact place in the seat, so a menu another plugin nests",
2605
+ " inside the seat keeps its own layout, and the seat root the picker hides is",
2606
+ " never matched — a display here would outrank the hide rule below. */",
2607
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [data-slot=\"conversation.input.model\"] > [class*=\"_root\"] > [class*=\"_trigger\"],",
2608
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [data-slot=\"conversation.input.model\"] > .dsh-claude-model-btn {",
2551
2609
  " display: inline-flex !important;",
2552
2610
  " align-items: center !important;",
2553
2611
  " font-size: 13px !important;",
@@ -3554,16 +3612,23 @@ window.__ModuleLoader__.load({
3554
3612
  " visibility: hidden !important;",
3555
3613
  " pointer-events: none !important;",
3556
3614
  "}",
3557
- "/* The desktop shell's sidebar column starts under its 40px titlebar, which",
3558
- " left the brand a titlebar's height lower than on the web; the row rises by",
3559
- " 10px — as far as it goes with the 32px box still wholly below the titlebar,",
3560
- " where the column's top edge would cut it. */",
3615
+ "/* The Windows desktop shell's sidebar column starts under its 40px titlebar,",
3616
+ " which left the brand a titlebar's height lower than on the web; the row rises",
3617
+ " by 10px — as far as it goes with the 32px box still wholly below the titlebar,",
3618
+ " where the column's top edge would cut it. macOS takes no offset: the host pads",
3619
+ " nothing around the traffic lights, and its own `_topStrip + _logoRow` margin",
3620
+ " (-12px) already seats the row's content at the 48px it declares for",
3621
+ " --dsh-frame-top-clearance; -10px would override that margin and move the row",
3622
+ " 2px down. */",
3561
3623
  "html[data-windows-titlebar] body[data-dsh-claude-style] [data-slot=\"sidebar\"] [class*=\"_logoRow\"] {",
3562
3624
  " margin-top: -10px !important;",
3563
3625
  "}",
3564
- "/* There the toggle takes no cell, so the row keeps a single column: a second,",
3565
- " empty one would still cost the brand and the box the column gap. */",
3566
- "html[data-windows-titlebar] body[data-dsh-claude-style] [data-dsh-claude-search-row] {",
3626
+ "/* On both desktop shells the host's toggle takes no cell in the row: macOS",
3627
+ " renders it inside its own 52px `_topStrip`, Windows fixes it into the caption",
3628
+ " overlay. The row keeps a single column there — a second, empty one would",
3629
+ " still cost the brand and the box the host logo row's 8px gap. */",
3630
+ "html[data-windows-titlebar] body[data-dsh-claude-style] [data-dsh-claude-search-row],",
3631
+ "html[data-platform=darwin] body[data-dsh-claude-style] [data-dsh-claude-search-row] {",
3567
3632
  " grid-template-columns: minmax(0, 1fr) !important;",
3568
3633
  "}",
3569
3634
  "/* The host's own sidebar search stays mounted, since its shortcut (Ctrl+K)",
@@ -4741,27 +4806,40 @@ window.__ModuleLoader__.load({
4741
4806
  " color: var(--dsh-ban-text);",
4742
4807
  "}",
4743
4808
  "",
4744
- "/* ---------- Desktop caption band: the bar rides the native titlebar row ---------- */",
4745
- "/* Windows titlebar mode (ban-screen.js stamps data-dsh-ban-titlebar, once per",
4746
- " open) puts the top --dsh-ban-caption-height pixels under a NATIVE layer: the",
4747
- " shell paints that band with the colour it measures off",
4748
- " --dsw-specific-sidebar-fill and draws the window buttons at its right end, and",
4749
- " no page content can cover it. So the bar stops drawing its own three window",
4750
- " buttons — the band already shows a cluster — and moves its content up onto the",
4751
- " band; the overlay lends the band the page's canvas colour while it is open",
4752
- " (holdCaptionFill), which is what lets the lockup and Sign out read as that",
4753
- " row's own content the way Claude's titlebar carries them. The right padding",
4754
- " keeps Sign out clear of the native buttons.",
4755
- " The band has to keep moving the window, so the bar claims the DRAG region for",
4809
+ "/* ---------- Desktop caption strip: the bar rides the native titlebar row ---------- */",
4810
+ "/* Both desktop platforms hand the top of the window to a NATIVE layer, and",
4811
+ " ban-screen.js stamps data-dsh-ban-titlebar on the overlay once per open",
4812
+ " whenever the shared desktopBand() reports a strip, together with the strip's",
4813
+ " height (--dsh-ban-caption-height) and the width its window controls occupy",
4814
+ " (--dsh-ban-caption-controls) and the end those controls take",
4815
+ " (data-dsh-ban-controls). So the bar stops drawing its own three window buttons",
4816
+ " — the strip already shows a cluster — and moves its content up onto the strip,",
4817
+ " clear of that cluster: Windows draws its caption buttons at the RIGHT end and",
4818
+ " macOS draws its traffic lights at the LEFT, which is the one thing the two",
4819
+ " platforms lay out differently. The Windows shell paints its strip with the",
4820
+ " colour it measures off --dsw-specific-sidebar-fill, so the overlay lends it",
4821
+ " the page's canvas colour while it is open (holdCaptionFill), which is what",
4822
+ " lets the lockup and Sign out read as that row's own content the way Claude's",
4823
+ " titlebar carries them; macOS paints the strip as the vibrancy sidebar, which",
4824
+ " no token of ours reaches.",
4825
+ " The strip has to keep moving the window, so the bar claims the DRAG region for",
4756
4826
  " itself and only the one control inside it opts out: the overlay covers the",
4757
- " host's own caption strip (`.frame::before`), and a drag region the pointer",
4758
- " cannot reach is no region at all. */",
4827
+ " host's own drag regions (the Windows frame's `.frame::before` strip, and on",
4828
+ " macOS the elements the host marks `data-window-drag`), and a drag region the",
4829
+ " pointer cannot reach is no region at all. */",
4759
4830
  "body[data-dsh-claude-style] .dsh-claude-ban[data-dsh-ban-titlebar] .dsh-claude-ban-bar {",
4760
- " height: var(--dsh-ban-caption-height, var(--dsh-windows-titlebar-height, 40px)) !important;",
4831
+ " height: var(--dsh-ban-caption-height, 40px) !important;",
4761
4832
  " padding: 0 calc(var(--dsh-ban-caption-controls, 140px) + 10px) 0 44px !important;",
4762
4833
  " -webkit-app-region: drag;",
4763
4834
  "}",
4764
4835
  "",
4836
+ "/* macOS: the traffic lights sit at the LEFT end of the strip, so the bar keeps",
4837
+ " its own leading inset clear of them and gives the trailing end back to the",
4838
+ " row. */",
4839
+ "body[data-dsh-claude-style] .dsh-claude-ban[data-dsh-ban-titlebar][data-dsh-ban-controls=\"left\"] .dsh-claude-ban-bar {",
4840
+ " padding: 0 10px 0 calc(var(--dsh-ban-caption-controls, 88px) + 10px) !important;",
4841
+ "}",
4842
+ "",
4765
4843
  "/* The lockup sits a touch below the row's midline: the wordmark's ink fills its",
4766
4844
  " whole 21px box, so centring it beside the platform's 10px-tall caption glyphs",
4767
4845
  " leaves it reading high. */",
@@ -4779,11 +4857,11 @@ window.__ModuleLoader__.load({
4779
4857
  "}",
4780
4858
  "",
4781
4859
  "body[data-dsh-claude-style] .dsh-claude-ban[data-dsh-ban-titlebar] .dsh-claude-ban-scroll {",
4782
- " inset: calc(var(--dsh-ban-caption-height, var(--dsh-windows-titlebar-height, 40px)) + 6px) 0 0 0;",
4860
+ " inset: calc(var(--dsh-ban-caption-height, 40px) + 6px) 0 0 0;",
4783
4861
  "}",
4784
4862
  "",
4785
4863
  "body[data-dsh-claude-style] .dsh-claude-ban[data-dsh-ban-titlebar] .dsh-claude-ban-toast {",
4786
- " top: calc(var(--dsh-ban-caption-height, var(--dsh-windows-titlebar-height, 40px)) + 16px);",
4864
+ " top: calc(var(--dsh-ban-caption-height, 40px) + 16px);",
4787
4865
  "}",
4788
4866
  "",
4789
4867
  "/* ---------- account_banned toast ---------- */",
@@ -6096,10 +6174,11 @@ window.__ModuleLoader__.load({
6096
6174
  "}",
6097
6175
  "",
6098
6176
  "/* ---------- Claude Code layout: settings page ---------- */",
6099
- "/* The skin's own page in the settings dialog. Its segmented controls reuse the",
6100
- " composer permission picker's classes (`.dsh-claude-segments` /",
6101
- " `.dsh-claude-segment`), so the two are literally one control rather than two",
6102
- " lookalikes; only the row shell and the switch are new here. */",
6177
+ "/* The skin's own page in the settings dialog and on the plugin page. Its",
6178
+ " segmented controls reuse the composer permission picker's classes",
6179
+ " (`.dsh-claude-segments` / `.dsh-claude-segment`), so the two are literally one",
6180
+ " control rather than two lookalikes; the row shell, the switch and the brand",
6181
+ " cards are new here. */",
6103
6182
  "body[data-dsh-claude-style] .dsh-claude-settings {",
6104
6183
  " display: flex;",
6105
6184
  " flex-direction: column;",
@@ -6130,6 +6209,77 @@ window.__ModuleLoader__.load({
6130
6209
  " min-width: 0;",
6131
6210
  "}",
6132
6211
  "",
6212
+ "/* A block row stacks its control under the text across the row's full width —",
6213
+ " the brand picker's cards need more room than an inline control slot gives. */",
6214
+ "body[data-dsh-claude-style] .dsh-claude-settings-row-block {",
6215
+ " flex-direction: column;",
6216
+ " align-items: stretch;",
6217
+ "}",
6218
+ "",
6219
+ "/* The brand picker: one card per brand, the brand's mark above its name. The",
6220
+ " marks are the same assets the sidebar and hero paint — Claude's clay",
6221
+ " starburst, DeepSeek's whale in brand blue — inlined as CSS data URIs, so",
6222
+ " they read in full colour on either theme. A new brand adds one option in",
6223
+ " settings.js and one [data-brand] rule here. */",
6224
+ "body[data-dsh-claude-style] .dsh-claude-brand-picker {",
6225
+ " display: grid;",
6226
+ " grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));",
6227
+ " gap: 8px;",
6228
+ "}",
6229
+ "",
6230
+ "body[data-dsh-claude-style] .dsh-claude-brand-card {",
6231
+ " appearance: none;",
6232
+ " margin: 0;",
6233
+ " display: flex;",
6234
+ " flex-direction: column;",
6235
+ " align-items: center;",
6236
+ " gap: 8px;",
6237
+ " padding: 14px 12px 10px;",
6238
+ " box-sizing: border-box;",
6239
+ " border: 1px solid var(--dsw-alias-border-l1);",
6240
+ " border-radius: 12px;",
6241
+ " background: var(--dsw-alias-bg-layer-1);",
6242
+ " color: var(--dsw-alias-label-primary);",
6243
+ " font: inherit;",
6244
+ " cursor: pointer;",
6245
+ " transition: border-color .12s ease;",
6246
+ "}",
6247
+ "",
6248
+ "body[data-dsh-claude-style] .dsh-claude-brand-card:hover {",
6249
+ " border-color: var(--dsw-alias-border-l2);",
6250
+ "}",
6251
+ "",
6252
+ "body[data-dsh-claude-style] .dsh-claude-brand-card:focus-visible {",
6253
+ " outline: 2px solid var(--dsw-alias-brand-primary, #d97757);",
6254
+ " outline-offset: 1px;",
6255
+ "}",
6256
+ "",
6257
+ "body[data-dsh-claude-style] .dsh-claude-brand-card[data-active] {",
6258
+ " border-color: var(--dsw-alias-brand-primary, #d97757);",
6259
+ "}",
6260
+ "",
6261
+ "body[data-dsh-claude-style] .dsh-claude-brand-card-logo {",
6262
+ " width: 32px;",
6263
+ " height: 32px;",
6264
+ " background-position: center;",
6265
+ " background-repeat: no-repeat;",
6266
+ " background-size: contain;",
6267
+ "}",
6268
+ "",
6269
+ "body[data-dsh-claude-style] .dsh-claude-brand-card-logo[data-brand=\"claude\"] {",
6270
+ " background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%20148.09%20148.18'%3E%3Cg%20transform%3D'translate(-75.96%2C-223.53)'%3E%3Cpath%20fill%3D'%23D97757'%20d%3D'm%20105.01%2C322.07%2029.14%2C-16.35%200.49%2C-1.42%20-0.49%2C-0.79%20h%20-1.42%20l%20-4.87%2C-0.3%20-16.65%2C-0.45%20-14.44%2C-0.6%20-13.99%2C-0.75%20-3.52%2C-0.75%20-3.3%2C-4.35%200.34%2C-2.17%202.96%2C-1.99%204.24%2C0.37%209.37%2C0.64%2014.06%2C0.97%2010.2%2C0.6%2015.11%2C1.57%20h%202.4%20l%200.34%2C-0.97%20-0.82%2C-0.6%20-0.64%2C-0.6%20-14.55%2C-9.86%20-15.75%2C-10.42%20-8.25%2C-6%20-4.46%2C-3.04%20-2.25%2C-2.85%20-0.97%2C-6.22%204.05%2C-4.46%205.44%2C0.37%201.39%2C0.37%205.51%2C4.24%2011.77%2C9.11%2015.37%2C11.32%202.25%2C1.87%200.9%2C-0.64%200.11%2C-0.45%20-1.01%2C-1.69%20-8.36%2C-15.11%20-8.92%2C-15.37%20-3.97%2C-6.37%20-1.05%2C-3.82%20c%20-0.37%2C-1.57%20-0.64%2C-2.89%20-0.64%2C-4.5%20l%204.61%2C-6.26%202.55%2C-0.82%206.15%2C0.82%202.59%2C2.25%203.82%2C8.74%206.19%2C13.76%209.6%2C18.71%202.81%2C5.55%201.5%2C5.14%200.56%2C1.57%20h%200.97%20v%20-0.9%20l%200.79%2C-10.54%201.46%2C-12.94%201.42%2C-16.65%200.49%2C-4.69%202.32%2C-5.62%204.61%2C-3.04%203.6%2C1.72%202.96%2C4.24%20-0.41%2C2.74%20-1.76%2C11.44%20-3.45%2C17.92%20-2.25%2C12%20h%201.31%20l%201.5%2C-1.5%206.07%2C-8.06%2010.2%2C-12.75%204.5%2C-5.06%205.25%2C-5.59%203.37%2C-2.66%20h%206.37%20l%204.69%2C6.97%20-2.1%2C7.2%20-6.56%2C8.32%20-5.44%2C7.05%20-7.8%2C10.5%20-4.87%2C8.4%200.45%2C0.67%201.16%2C-0.11%2017.62%2C-3.75%209.52%2C-1.72%2011.36%2C-1.95%205.14%2C2.4%200.56%2C2.44%20-2.02%2C4.99%20-12.15%2C3%20-14.25%2C2.85%20-21.22%2C5.02%20-0.26%2C0.19%200.3%2C0.37%209.56%2C0.9%204.09%2C0.22%20h%2010.01%20l%2018.64%2C1.39%204.87%2C3.22%202.92%2C3.94%20-0.49%2C3%20-7.5%2C3.82%20-10.12%2C-2.4%20-23.62%2C-5.62%20-8.1%2C-2.02%20h%20-1.12%20v%200.67%20l%206.75%2C6.6%2012.37%2C11.17%2015.49%2C14.4%200.79%2C3.56%20-1.99%2C2.81%20-2.1%2C-0.3%20-13.61%2C-10.24%20-5.25%2C-4.61%20-11.89%2C-10.01%20h%20-0.79%20v%201.05%20l%202.74%2C4.01%2014.47%2C21.75%200.75%2C6.67%20-1.05%2C2.17%20-3.75%2C1.31%20-4.12%2C-0.75%20-8.47%2C-11.89%20-8.74%2C-13.39%20-7.05%2C-12%20-0.86%2C0.49%20-4.16%2C44.81%20-1.95%2C2.29%20-4.5%2C1.72%20-3.75%2C-2.85%20-1.99%2C-4.61%201.99%2C-9.11%202.4%2C-11.89%201.95%2C-9.45%201.76%2C-11.74%201.05%2C-3.9%20-0.07%2C-0.26%20-0.86%2C0.11%20-8.85%2C12.15%20-13.46%2C18.19%20-10.65%2C11.4%20-2.55%2C1.01%20-4.42%2C-2.29%200.41%2C-4.09%202.47%2C-3.64%2014.74%2C-18.75%208.89%2C-11.62%205.74%2C-6.71%20-0.04%2C-0.97%20h%20-0.34%20l%20-39.15%2C25.42%20-6.97%2C0.9%20-3%2C-2.81%200.37%2C-4.61%201.42%2C-1.5%2011.77%2C-8.1%20-0.04%2C0.04%20z'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\");",
6271
+ "}",
6272
+ "",
6273
+ "body[data-dsh-claude-style] .dsh-claude-brand-card-logo[data-brand=\"deepseek\"] {",
6274
+ " background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2023.16%2017.04'%3E%3Cpath%20fill%3D'%234D6BFE'%20d%3D'M22.9168%201.43018C22.6713%201.31018%2022.5658%201.53918%2022.4223%201.65519C22.3733%201.69269%2022.3318%201.74169%2022.2903%201.78669C21.9317%202.1697%2021.5127%202.42121%2020.9657%202.39121C20.1657%202.34621%2019.4827%202.59771%2018.8787%203.20973C18.7502%202.45521%2018.3236%202.0047%2017.6746%201.71569C17.3351%201.56568%2016.9916%201.41518%2016.7536%201.08867C16.5876%200.856163%2016.5421%200.597155%2016.4591%200.341647C16.4061%200.187643%2016.3536%200.0301382%2016.1761%200.00363739C15.9836%20-0.0263635%2015.9081%200.135141%2015.8326%200.270145C15.5306%200.822162%2015.4136%201.43018%2015.4251%202.0462C15.4516%203.43174%2016.0366%204.53527%2017.1991%205.3203C17.3311%205.4103%2017.3651%205.5003%2017.3236%205.63181C17.2441%205.90231%2017.1501%206.16482%2017.0671%206.43533C17.0141%206.60784%2016.9351%206.64584%2016.7501%206.57033C16.1121%206.30383%2015.5611%205.90931%2015.074%205.4328C14.2475%204.63328%2013.5%203.75075%2012.568%203.05973C12.349%202.89822%2012.13%202.74822%2011.9034%202.60522C10.9524%201.68169%2012.028%200.923165%2012.277%200.833162C12.5375%200.739159%2012.3675%200.41615%2011.5259%200.42015C10.6844%200.42365%209.91439%200.705658%208.93286%201.08117C8.78935%201.13767%208.63835%201.17867%208.48384%201.21267C7.59332%201.04367%206.66829%201.00617%205.70226%201.11517C3.88321%201.31768%202.43016%202.1777%201.36213%203.64575C0.0790928%205.4103%20-0.222916%207.41536%200.146595%209.50642C0.535106%2011.7105%201.66014%2013.535%203.38869%2014.9616C5.18125%2016.4406%207.24581%2017.1657%209.60138%2017.0266C11.0319%2016.9441%2012.6245%2016.7526%2014.421%2015.2321C14.874%2015.4576%2015.3496%2015.5476%2016.1381%2015.6151C16.7456%2015.6716%2017.3306%2015.5851%2017.7836%2015.4911C18.4931%2015.3411%2018.4441%2014.6841%2018.1876%2014.5636C16.1081%2013.595%2016.5646%2013.9891%2016.1496%2013.67C17.2061%2012.42%2018.8202%2010.1979%2019.3182%207.17235C19.3672%206.83834%2019.4297%206.36783%2019.4222%206.09732C19.4182%205.93231%2019.4562%205.86831%2019.6447%205.84931C20.1657%205.78931%2020.6712%205.64681%2021.1357%205.3913C22.4833%204.65528%2023.0268%203.44624%2023.1548%201.9972C23.1738%201.77569%2023.1508%201.54668%2022.9168%201.43018ZM11.1749%2014.4736C9.15936%2012.889%208.18184%2012.3675%207.77832%2012.39C7.40081%2012.4125%207.46881%2012.8445%207.55182%2013.126C7.63882%2013.404%207.75182%2013.5955%207.91033%2013.8396C8.01983%2014.0011%208.09533%2014.2411%207.80083%2014.4216C7.15181%2014.8231%206.02327%2014.2866%205.97027%2014.2601C4.65673%2013.4865%203.5587%2012.4655%202.78467%2011.069C2.03715%209.72493%201.60314%208.28289%201.53164%206.74384C1.51264%206.37233%201.62214%206.24082%201.99215%206.17332C2.47916%206.08332%202.98118%206.06432%203.46769%206.13582C5.52476%206.43633%207.27581%207.35586%208.74385%208.8129C9.58188%209.64243%2010.2159%2010.634%2010.8689%2011.6025C11.5634%2012.631%2012.3105%2013.611%2013.262%2014.4146C13.598%2014.6961%2013.866%2014.9101%2014.1225%2015.0681C13.349%2015.1546%2012.058%2015.1731%2011.1749%2014.4746L11.1749%2014.4736ZM12.141%208.25988C12.141%208.09488%2012.273%207.96338%2012.439%207.96338C12.4765%207.96338%2012.5105%207.97088%2012.541%207.98188C12.5825%207.99688%2012.6205%208.01938%2012.6505%208.05338C12.7035%208.10588%2012.7335%208.18088%2012.7335%208.25988C12.7335%208.42489%2012.6015%208.55639%2012.4355%208.55639C12.2695%208.55639%2012.141%208.42489%2012.141%208.25988ZM15.1415%209.79893C14.949%209.87793%2014.7565%209.94544%2014.5715%209.95294C14.2845%209.96794%2013.9715%209.85143%2013.8015%209.70893C13.5375%209.48742%2013.3485%209.36342%2013.2695%208.97691C13.2355%208.8119%2013.2545%208.55639%2013.2845%208.40989C13.3525%208.09438%2013.277%207.89187%2013.0545%207.70787C12.8735%207.55786%2012.643%207.51636%2012.39%207.51636C12.2955%207.51636%2012.209%207.47486%2012.1445%207.44136C12.039%207.38886%2011.9519%207.25735%2012.035%207.09585C12.0615%207.04335%2012.19%206.91584%2012.22%206.89334C12.5635%206.69784%2012.9595%206.76184%2013.326%206.90834C13.6655%207.04735%2013.9225%207.30236%2014.292%207.66287C14.6695%208.09838%2014.7375%208.21838%2014.9525%208.54539C15.1225%208.8009%2015.277%209.06341%2015.3831%209.36392C15.4471%209.55142%2015.3641%209.70493%2015.1415%209.79893Z'%2F%3E%3C%2Fsvg%3E\");",
6275
+ "}",
6276
+ "",
6277
+ "body[data-dsh-claude-style] .dsh-claude-brand-card-name {",
6278
+ " font-size: 13px;",
6279
+ " font-weight: 600;",
6280
+ " line-height: 1.5;",
6281
+ "}",
6282
+ "",
6133
6283
  "body[data-dsh-claude-style] .dsh-claude-settings-row-title {",
6134
6284
  " font-size: 13px;",
6135
6285
  " font-weight: 600;",
@@ -7076,7 +7226,7 @@ window.__ModuleLoader__.load({
7076
7226
  // ============================================================================
7077
7227
  // 构建编号(由 scripts/build.mjs 按产物内容生成) (Build id)
7078
7228
  // ============================================================================
7079
- var BUILD_ID = '7d11fdb1aaea'
7229
+ var BUILD_ID = '9b6e5ae9a898'
7080
7230
 
7081
7231
  // ============================================================================
7082
7232
  // Deepy 帧图内容戳(由 scripts/build.mjs 按帧图字节生成) (Deepy sheet stamps)
@@ -7377,6 +7527,95 @@ window.__ModuleLoader__.load({
7377
7527
  accountAvatar = ''
7378
7528
  }
7379
7529
 
7530
+ /** macOS: the clearance the traffic lights need, read off the host's own leading seat. */
7531
+ const MAC_TRAFFIC_LIGHTS = 88
7532
+ /** macOS: the strip height the host declares, used when the token is absent. */
7533
+ const MAC_BAND_HEIGHT = 48
7534
+ /** Windows: the three caption buttons at their standard size, used when the overlay cannot measure them. */
7535
+ const WIN_CAPTION_CONTROLS = 140
7536
+ /** Windows: the strip height the host declares, used when the token is absent. */
7537
+ const WIN_BAND_HEIGHT = 40
7538
+
7539
+ /**
7540
+ * The host's declared caption-strip height, or `fallback` when the host
7541
+ * declares none.
7542
+ *
7543
+ * `--dsh-frame-top-clearance` is the host's own name for the strip, and the
7544
+ * one token both desktop platforms set: 48px under
7545
+ * `html[data-platform=darwin]`, and `var(--dsh-windows-titlebar-height)`
7546
+ * under `html[data-windows-titlebar]`. It is read through the computed
7547
+ * style, so that alias chain resolves to a length.
7548
+ *
7549
+ * @param fallback - the height to answer with when the token is absent.
7550
+ * @returns the height in CSS pixels.
7551
+ */
7552
+ function bandHeight(fallback) {
7553
+ const declared = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--dsh-frame-top-clearance'))
7554
+ return isFinite(declared) && declared > 0 ? Math.round(declared) : fallback
7555
+ }
7556
+
7557
+ /**
7558
+ * The desktop shell's native caption strip, resolved in one place.
7559
+ *
7560
+ * Both desktop platforms hand the top of the window to a native layer, and
7561
+ * every consumer of that strip asks the same three questions: whether it
7562
+ * exists, how tall it is, and which end the platform's window controls take.
7563
+ * The platforms answer differently, so the answers are resolved here once.
7564
+ *
7565
+ * - Marker. Windows marks `<html>` with `data-windows-titlebar`; macOS
7566
+ * marks it `data-platform="darwin"`. The shell's preload writes both.
7567
+ * - Controls. Windows draws its three caption buttons at the right end and
7568
+ * publishes their box through the Window Controls Overlay API; macOS
7569
+ * draws its traffic lights at the left, at (16, 18) — the shell's own
7570
+ * `trafficLightPosition` — and publishes no measurement. The host's own
7571
+ * leading seat starts at 88px, the clearance those lights need.
7572
+ * - Fullscreen. A fullscreen macOS window hides its traffic lights, and
7573
+ * the host zeroes `--dsh-frame-chrome-top` with them, so that platform
7574
+ * answers null. Windows keeps the answer it gave before this module
7575
+ * existed: its frame holds both the strip and its padding in fullscreen,
7576
+ * and the Window Controls Overlay API's own visibility is the authority
7577
+ * there.
7578
+ *
7579
+ * Windows' answers are the ones the caption-band code computed before this
7580
+ * module existed, the Window Controls Overlay API included, so that
7581
+ * platform's behaviour is unchanged.
7582
+ *
7583
+ * @returns {{ platform: 'win32' | 'darwin', height: number, controls: { side: 'left' | 'right', size: number } } | null}
7584
+ * the strip, or null when the page owns its whole surface.
7585
+ */
7586
+ function desktopBand() {
7587
+ const root = document.documentElement
7588
+ if (root.hasAttribute('data-windows-titlebar')) {
7589
+ const overlay = navigator.windowControlsOverlay
7590
+ if (!overlay || overlay.visible !== true) return null
7591
+ const rect = typeof overlay.getTitlebarAreaRect === 'function' ? overlay.getTitlebarAreaRect() : null
7592
+ if (rect && rect.width > 0 && rect.height > 0) {
7593
+ return {
7594
+ platform: 'win32',
7595
+ height: Math.round(rect.height),
7596
+ controls: { side: 'right', size: Math.max(0, Math.round(window.innerWidth - rect.right)) },
7597
+ }
7598
+ }
7599
+ // The API is present and visible with no measurement yet: the declared
7600
+ // caption height, and Windows' three caption buttons at their standard
7601
+ // size.
7602
+ return {
7603
+ platform: 'win32',
7604
+ height: bandHeight(WIN_BAND_HEIGHT),
7605
+ controls: { side: 'right', size: WIN_CAPTION_CONTROLS },
7606
+ }
7607
+ }
7608
+ if (root.dataset.platform === 'darwin') {
7609
+ if (root.hasAttribute('data-fullscreen')) return null
7610
+ return {
7611
+ platform: 'darwin',
7612
+ height: bandHeight(MAC_BAND_HEIGHT),
7613
+ controls: { side: 'left', size: MAC_TRAFFIC_LIGHTS },
7614
+ }
7615
+ }
7616
+ return null
7617
+ }
7618
+
7380
7619
  /**
7381
7620
  * Skin preferences.
7382
7621
  *
@@ -8974,9 +9213,8 @@ window.__ModuleLoader__.load({
8974
9213
  const STATS_SKELETON_MS = 2000
8975
9214
  /** Rows of each section the placeholder reserves (a session's usual count). */
8976
9215
  const STATS_SKELETON_ROWS = 4
8977
- /** The same, for the compact block: three time rows, one usage row. */
8978
- const COMPACT_SKELETON_ROWS = 3
8979
- const COMPACT_SKELETON_USAGE_ROWS = 1
9216
+ /** The same, for the compact block: four items in a single 2x2 grid. */
9217
+ const COMPACT_SKELETON_ITEMS = 4
8980
9218
 
8981
9219
  /** The custom property the stylesheet reads to line the panel up with the meter. */
8982
9220
  const CONTEXT_PANEL_LEFT = '--dsh-claude-context-panel-left'
@@ -9209,8 +9447,25 @@ window.__ModuleLoader__.load({
9209
9447
  function sessionStatsSections(chat, compact) {
9210
9448
  const sections = []
9211
9449
  const stats = statsValue('sessionStats')
9450
+ if (compact) {
9451
+ const rows = []
9452
+ if (stats !== undefined && stats !== null) {
9453
+ const timeRows = statsTimeRows(stats, chat, true)
9454
+ for (let i = 0; i < timeRows.length; i++) rows.push(timeRows[i])
9455
+ }
9456
+ const usage = statsValue('tokenUsage')
9457
+ if (usage !== undefined && usage !== null) {
9458
+ const billed = usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens
9459
+ if (billed > 0 || usage.outputTokens > 0) {
9460
+ const hit = sessionStatsCacheHit(usage.cacheReadTokens, billed)
9461
+ if (hit !== null) rows.push({ label: chat('message.turnUsage.cacheHit'), value: `${hit}%` })
9462
+ }
9463
+ }
9464
+ if (rows.length > 0) sections.push({ title: '', rows })
9465
+ return sections
9466
+ }
9212
9467
  if (stats !== undefined && stats !== null) {
9213
- const rows = statsTimeRows(stats, chat, compact)
9468
+ const rows = statsTimeRows(stats, chat, false)
9214
9469
  if (rows.length > 0) sections.push({ title: chat('stats.dialog.title'), rows })
9215
9470
  }
9216
9471
  const usage = statsValue('tokenUsage')
@@ -9220,14 +9475,12 @@ window.__ModuleLoader__.load({
9220
9475
  const rows = []
9221
9476
  const hit = sessionStatsCacheHit(usage.cacheReadTokens, billed)
9222
9477
  if (hit !== null) rows.push({ label: chat('message.turnUsage.cacheHit'), value: `${hit}%` })
9223
- if (!compact) {
9224
- rows.push({ label: chat('message.turnUsage.input'), value: sessionStatsTokens(usage.uncachedInputTokens, chat) })
9225
- rows.push({ label: chat('message.turnUsage.cacheRead'), value: sessionStatsTokens(usage.cacheReadTokens, chat) })
9226
- if (usage.cacheWriteTokens !== 0) {
9227
- rows.push({ label: chat('message.turnUsage.cacheWrite'), value: sessionStatsTokens(usage.cacheWriteTokens, chat) })
9228
- }
9229
- rows.push({ label: chat('message.turnUsage.output'), value: sessionStatsTokens(usage.outputTokens, chat) })
9478
+ rows.push({ label: chat('message.turnUsage.input'), value: sessionStatsTokens(usage.uncachedInputTokens, chat) })
9479
+ rows.push({ label: chat('message.turnUsage.cacheRead'), value: sessionStatsTokens(usage.cacheReadTokens, chat) })
9480
+ if (usage.cacheWriteTokens !== 0) {
9481
+ rows.push({ label: chat('message.turnUsage.cacheWrite'), value: sessionStatsTokens(usage.cacheWriteTokens, chat) })
9230
9482
  }
9483
+ rows.push({ label: chat('message.turnUsage.output'), value: sessionStatsTokens(usage.outputTokens, chat) })
9231
9484
  if (rows.length > 0) sections.push({ title: chat('stats.dialog.usageTitle'), rows })
9232
9485
  }
9233
9486
  }
@@ -9331,17 +9584,25 @@ window.__ModuleLoader__.load({
9331
9584
  renderContextStats()
9332
9585
  }, STATS_SKELETON_MS - held)
9333
9586
  }
9334
- const titles = [chat('stats.dialog.title'), chat('stats.dialog.usageTitle')]
9335
- const counts = compact ? [COMPACT_SKELETON_ROWS, COMPACT_SKELETON_USAGE_ROWS] : [STATS_SKELETON_ROWS, STATS_SKELETON_ROWS]
9336
9587
  let signature = compact ? 'skeleton-compact' : 'skeleton'
9337
9588
  let html = ''
9338
- for (let i = 0; i < titles.length; i++) {
9339
- signature += `\u0001${titles[i]}`
9340
- html += `<div class="dsh-claude-context-stats-section">${statsEscape(titles[i])}</div><div class="dsh-claude-context-stats-grid">`
9341
- for (let r = 0; r < counts[i]; r++) {
9589
+ if (compact) {
9590
+ html += '<div class="dsh-claude-context-stats-grid">'
9591
+ for (let r = 0; r < COMPACT_SKELETON_ITEMS; r++) {
9342
9592
  html += '<div class="dsh-claude-context-stats-item"><span class="dsh-claude-context-stats-skeleton-label"></span><span class="dsh-claude-context-stats-skeleton-value"></span></div>'
9343
9593
  }
9344
9594
  html += '</div>'
9595
+ } else {
9596
+ const titles = [chat('stats.dialog.title'), chat('stats.dialog.usageTitle')]
9597
+ const counts = [STATS_SKELETON_ROWS, STATS_SKELETON_ROWS]
9598
+ for (let i = 0; i < titles.length; i++) {
9599
+ signature += `\u0001${titles[i]}`
9600
+ html += `<div class="dsh-claude-context-stats-section">${statsEscape(titles[i])}</div><div class="dsh-claude-context-stats-grid">`
9601
+ for (let r = 0; r < counts[i]; r++) {
9602
+ html += '<div class="dsh-claude-context-stats-item"><span class="dsh-claude-context-stats-skeleton-label"></span><span class="dsh-claude-context-stats-skeleton-value"></span></div>'
9603
+ }
9604
+ html += '</div>'
9605
+ }
9345
9606
  }
9346
9607
  const block = ensureContextBlock(panel)
9347
9608
  if (signature === blockSignature && block.childElementCount > 0) return
@@ -9373,11 +9634,14 @@ window.__ModuleLoader__.load({
9373
9634
  }
9374
9635
  stopSkeleton()
9375
9636
  let html = ''
9376
- let signature = ''
9637
+ let signature = compact ? 'compact' : ''
9377
9638
  for (let sIndex = 0; sIndex < sections.length; sIndex++) {
9378
9639
  const section = sections[sIndex]
9379
- signature += `\u0001${section.title}`
9380
- html += `<div class="dsh-claude-context-stats-section">${statsEscape(section.title)}</div><div class="dsh-claude-context-stats-grid">`
9640
+ if (section.title) {
9641
+ signature += `\u0001${section.title}`
9642
+ html += `<div class="dsh-claude-context-stats-section">${statsEscape(section.title)}</div>`
9643
+ }
9644
+ html += '<div class="dsh-claude-context-stats-grid">'
9381
9645
  for (let r = 0; r < section.rows.length; r++) {
9382
9646
  const row = section.rows[r]
9383
9647
  signature += `\u0001${row.label}\u0002${row.value}`
@@ -14763,14 +15027,8 @@ window.__ModuleLoader__.load({
14763
15027
  */
14764
15028
  function installBanScreen(ctx, ui) {
14765
15029
  let banRoot = null
14766
- /** Whether the open overlay has lent its canvas colour to the caption band. */
15030
+ /** Whether the open overlay has lent its canvas colour to the Windows caption band. */
14767
15031
  let captionFillHeld = false
14768
- /**
14769
- * The width the Desktop's window buttons occupy when the Window Controls
14770
- * Overlay API is there but carries no measurement yet: Windows' three
14771
- * caption buttons at their standard size. A fallback only.
14772
- */
14773
- const CAPTION_CONTROLS_FALLBACK = 140
14774
15032
 
14775
15033
  // A client hot reload drops a generation's disposals without running them,
14776
15034
  // so an overlay left open across one keeps its node — and the caption fill
@@ -14794,54 +15052,17 @@ window.__ModuleLoader__.load({
14794
15052
  }
14795
15053
 
14796
15054
  /**
14797
- * The Desktop caption band's shape, or null when the window has none.
14798
- *
14799
- * Windows titlebar mode (`html[data-windows-titlebar]`) hands the top
14800
- * `--dsh-windows-titlebar-height` pixels to a NATIVE layer: the shell
14801
- * paints it with the colour it measures off `--dsw-specific-sidebar-fill`
14802
- * and draws the three window buttons at its right end, and no page
14803
- * content can cover it. Two things follow for this overlay — the band
14804
- * already shows a window cluster, so the page must not draw its own
14805
- * (that is the duplicate the Desktop showed), and the lockup and Sign out
14806
- * can ride the caption row only while the band carries the page's own
14807
- * canvas colour (holdCaptionFill).
14808
- *
14809
- * The band's height and the width its buttons occupy come from the Window
14810
- * Controls Overlay API, so they follow the platform's own metrics rather
14811
- * than a constant; a window whose overlay is hidden (fullscreen) has no
14812
- * band and keeps the shipped layout.
14813
- *
14814
- * @returns `{ height, controls }` in CSS pixels, or null.
14815
- */
14816
- function captionMetrics() {
14817
- if (!document.documentElement.hasAttribute('data-windows-titlebar')) return null
14818
- const overlay = navigator.windowControlsOverlay
14819
- if (!overlay || overlay.visible !== true) return null
14820
- const rect = typeof overlay.getTitlebarAreaRect === 'function' ? overlay.getTitlebarAreaRect() : null
14821
- if (rect && rect.width > 0 && rect.height > 0) {
14822
- return {
14823
- height: Math.round(rect.height),
14824
- controls: Math.max(0, Math.round(window.innerWidth - rect.right)),
14825
- }
14826
- }
14827
- // The API is there but has no measurement yet: the declared caption
14828
- // height and Windows' three caption buttons at their standard size.
14829
- const declared = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--dsh-windows-titlebar-height'))
14830
- return {
14831
- height: isFinite(declared) && declared > 0 ? Math.round(declared) : 40,
14832
- controls: CAPTION_CONTROLS_FALLBACK,
14833
- }
14834
- }
14835
-
14836
- /**
14837
- * Lend the page's canvas colour to the caption band while the overlay is
14838
- * open.
15055
+ * Lend the page's canvas colour to the Windows caption band while the
15056
+ * overlay is open.
14839
15057
  *
14840
- * The Desktop measures `--dsw-specific-sidebar-fill` off a probe inside
14841
- * `<body>` and repaints the band whenever `<body>`'s style attribute
14842
- * changes, so an inline value here reaches the band and `closeBanScreen`
14843
- * gives the theme's own colour back. Nothing else is visible while the
14844
- * page is up — it covers the sidebar whose fill this token names.
15058
+ * The Windows Desktop measures `--dsw-specific-sidebar-fill` off a probe
15059
+ * inside `<body>` and repaints the band whenever `<body>`'s style
15060
+ * attribute changes, so an inline value here reaches the band and
15061
+ * `closeBanScreen` gives the theme's own colour back. macOS paints its
15062
+ * strip from its own metrics and reads no such token, which is why this
15063
+ * is only ever called for `win32` (openBanScreen). Nothing else is
15064
+ * visible while the page is up — it covers the sidebar whose fill this
15065
+ * token names.
14845
15066
  */
14846
15067
  function holdCaptionFill() {
14847
15068
  if (banRoot === null) return
@@ -15033,17 +15254,27 @@ window.__ModuleLoader__.load({
15033
15254
  banRoot = root
15034
15255
  document.body.appendChild(root)
15035
15256
 
15036
- // On the Desktop the bar rides the native caption band: the page's own
15037
- // window cluster goes (the band draws one) and the band is repainted in
15038
- // the page's canvas so the lockup and Sign out read as that band's own
15039
- // content. Both are decided here, once per open — the band cannot appear
15040
- // or vanish while the page is up.
15041
- const caption = captionMetrics()
15042
- if (caption !== null) {
15257
+ // On the Desktop the bar rides the native caption strip, whose shape
15258
+ // the shared desktopBand() resolves once for every consumer: Windows
15259
+ // marks `<html>` with `data-windows-titlebar` and draws its three
15260
+ // caption buttons at the right end, macOS marks it
15261
+ // `data-platform="darwin"` and draws its traffic lights at the left.
15262
+ // When a strip is reported the page's own window cluster goes (the
15263
+ // strip already draws one) and the lockup and Sign out are laid into
15264
+ // that row — on Windows only after the band has taken the page's canvas
15265
+ // colour, which is what makes them read as the row's own content. A
15266
+ // null band means the platform publishes no strip: Windows answers
15267
+ // through the Window Controls Overlay API, which reports itself hidden
15268
+ // while the window is fullscreen, and on macOS the shell hides the
15269
+ // traffic lights. All of it is decided here, once per open: the strip
15270
+ // cannot appear or vanish while the page is up.
15271
+ const band = desktopBand()
15272
+ if (band !== null) {
15043
15273
  root.setAttribute('data-dsh-ban-titlebar', '')
15044
- root.style.setProperty('--dsh-ban-caption-height', `${caption.height}px`)
15045
- root.style.setProperty('--dsh-ban-caption-controls', `${caption.controls}px`)
15046
- holdCaptionFill()
15274
+ root.setAttribute('data-dsh-ban-controls', band.controls.side)
15275
+ root.style.setProperty('--dsh-ban-caption-height', `${band.height}px`)
15276
+ root.style.setProperty('--dsh-ban-caption-controls', `${band.controls.size}px`)
15277
+ if (band.platform === 'win32') holdCaptionFill()
15047
15278
  }
15048
15279
  }
15049
15280
 
@@ -16851,6 +17082,14 @@ window.__ModuleLoader__.load({
16851
17082
  * marker onto <body> so the stylesheet can switch, and skips the measured
16852
17083
  * shift entirely there.
16853
17084
  *
17085
+ * macOS keeps the measured path. Its shell hands the top of the window to
17086
+ * the traffic lights and pads no content around them: the host sets
17087
+ * `--dsh-frame-top-clearance: 48px` under `html[data-platform=darwin]` and
17088
+ * leaves `--dsh-frame-chrome-top` at 0, so the conversation header keeps its
17089
+ * own top inset and draws its first row inside that strip. A fixed strip
17090
+ * would land on the title and the header actions; the measured shift already
17091
+ * seats it on the row beneath the strip.
17092
+ *
16854
17093
  * The strip itself is stamped `data-dsh-view-tabs`, and every strip rule in
16855
17094
  * view-tabs.css keys on that attribute. Finding the strip is a query this pass
16856
17095
  * runs once; as a stylesheet selector (`header:has(tabs) tabs`) it made the
@@ -19589,24 +19828,27 @@ window.__ModuleLoader__.load({
19589
19828
  }
19590
19829
 
19591
19830
  /**
19592
- * The skin's settings page, mounted by whichever seat this host has.
19831
+ * The skin's settings page, mounted by every seat this host declares.
19593
19832
  *
19594
- * 0.1.7 moved a bundle's own configuration onto its plugin page, so there
19595
- * the rows register as a `plugins.bundle.config` entry keyed by the package
19596
- * name. Older hosts have no such slot and keep the full-page
19597
- * `settings.section` entry. Both render the same component, and neither
19598
- * seat hands it a store: it reads and writes the skin preferences through
19599
- * src/core/prefs.js, which owns the host round trip, and follows changes
19600
- * the same way the rest of the skin does.
19833
+ * Two seats render it: the `plugins.bundle.config` entry on the plugin
19834
+ * page (0.1.7+, keyed by the package name) and the full-page
19835
+ * `settings.section` tab in the settings dialog. An older host declares
19836
+ * only the latter; a newer one gets both, and since both render the same
19837
+ * component against the same prefs store, two live instances stay in
19838
+ * step. Neither seat hands the component a store: it reads and writes the
19839
+ * skin preferences through src/core/prefs.js, which owns the host round
19840
+ * trip, and follows changes the same way the rest of the skin does.
19601
19841
  *
19602
19842
  * Copy comes from the model copy document's `settings` block, so the page
19603
19843
  * follows the shell language like every other string the skin paints. The
19604
19844
  * English literals here are the fallback for a failed fetch.
19605
19845
  *
19606
- * Its segmented controls reuse the shared `.dsh-claude-segments` /
19846
+ * The segmented controls reuse the shared `.dsh-claude-segments` /
19607
19847
  * `.dsh-claude-segment` classes and sliding highlight — the same control the
19608
19848
  * composer's permission picker uses — so the two read as one design instead
19609
- * of two lookalikes.
19849
+ * of two lookalikes. The brand choice is the exception: brands are
19850
+ * presets, not sibling tiers of one setting, so they render as a grid of
19851
+ * large cards, each carrying the brand's own mark.
19610
19852
  */
19611
19853
  /**
19612
19854
  * The quick-provider popover (src/features/settings/quick-providers.js). The settings
@@ -19744,9 +19986,11 @@ window.__ModuleLoader__.load({
19744
19986
  React.createElement('span', { className: 'dsh-claude-settings-switch-knob' }),
19745
19987
  )
19746
19988
 
19747
- const row = (key, title, description, control) => React.createElement(
19989
+ // A block row stacks its control under the text across the row's full
19990
+ // width; the inline form keeps text left and control right.
19991
+ const row = (key, title, description, control, block) => React.createElement(
19748
19992
  'div',
19749
- { className: 'dsh-claude-settings-row', key },
19993
+ { className: block ? 'dsh-claude-settings-row dsh-claude-settings-row-block' : 'dsh-claude-settings-row', key },
19750
19994
  React.createElement(
19751
19995
  'div',
19752
19996
  { className: 'dsh-claude-settings-row-text' },
@@ -19825,7 +20069,28 @@ window.__ModuleLoader__.load({
19825
20069
  'brand',
19826
20070
  settingsCopy('brandTitle', 'Brand mark'),
19827
20071
  settingsCopy('brandDesc', 'The brand mark in the sidebar and on the home page. DeepSeek keeps the host\'s own DeepSeek mark in DeepSeek blue, turns the whole palette blue (a sky-tinted white in light, a blue-black in dark) and puts Deepy the pixel whale on the composer in place of the crab.'),
19828
- segment(brandOptions, prefs.brand, value => { write({ brand: value }) }),
20072
+ // One card per brand: the brand's own mark above its name, the
20073
+ // active card outlined in the brand accent. The stylesheet picks the
20074
+ // mark off the logo's data-brand, so a new brand is one option here
20075
+ // plus one rule in settings.css.
20076
+ React.createElement(
20077
+ 'div',
20078
+ { className: 'dsh-claude-brand-picker', role: 'group' },
20079
+ brandOptions.map(option => React.createElement(
20080
+ 'button',
20081
+ {
20082
+ key: option.value,
20083
+ type: 'button',
20084
+ className: 'dsh-claude-brand-card',
20085
+ 'data-active': option.value === prefs.brand ? '' : undefined,
20086
+ 'aria-pressed': option.value === prefs.brand ? 'true' : 'false',
20087
+ onClick: () => { if (option.value !== prefs.brand) write({ brand: option.value }) },
20088
+ },
20089
+ React.createElement('span', { className: 'dsh-claude-brand-card-logo', 'data-brand': option.value }),
20090
+ React.createElement('span', { className: 'dsh-claude-brand-card-name' }, option.label),
20091
+ )),
20092
+ ),
20093
+ true,
19829
20094
  ),
19830
20095
  row(
19831
20096
  'motion',
@@ -19986,26 +20251,22 @@ window.__ModuleLoader__.load({
19986
20251
  },
19987
20252
  ClaudeStyleBundleConfig,
19988
20253
  )), 'dsh-claude-style: plugin page')
19989
- // The full-page section is the older host's seat. 0.1.7 still declares
19990
- // the slot, but there the skin's settings live on its plugin page — and
19991
- // a host that serves `configForms` is by definition the newer one, so
19992
- // that check tells the two apart without a version probe. (The settings
19993
- // shell cannot have declared this slot before `configForms` mounted: it
19994
- // injects the service itself.)
19995
- scope.effect(() => slots.inject(SETTINGS_SECTION_SLOT, () => {
19996
- if (hostConfigForms(ctx) !== null) return () => {}
19997
- return slots.register(
19998
- {
19999
- name: SETTINGS_SECTION_SLOT,
20000
- id: 'claude-style',
20001
- order: 22,
20002
- // A function, so the navigation entry localizes once the copy
20003
- // document has arrived; the literal is the pre-fetch fallback.
20004
- label() { return settingsCopy('title', 'Claude Style') },
20005
- },
20006
- ClaudeStyleSettingsSection,
20007
- )
20008
- }), 'dsh-claude-style: settings section')
20254
+ // The full-page section registers on every host that declares the
20255
+ // slot. On an older host it is the only seat; on 0.1.7+ the same page
20256
+ // also renders on the plugin page, and this tab keeps a route to it
20257
+ // inside the settings dialog. Two live instances share the prefs
20258
+ // store, so a change in one shows in the other.
20259
+ scope.effect(() => slots.inject(SETTINGS_SECTION_SLOT, () => slots.register(
20260
+ {
20261
+ name: SETTINGS_SECTION_SLOT,
20262
+ id: 'claude-style',
20263
+ order: 22,
20264
+ // A function, so the navigation entry localizes once the copy
20265
+ // document has arrived; the literal is the pre-fetch fallback.
20266
+ label() { return settingsCopy('title', 'Claude Style') },
20267
+ },
20268
+ ClaudeStyleSettingsSection,
20269
+ )), 'dsh-claude-style: settings section')
20009
20270
  })
20010
20271
  return () => {
20011
20272
  if (ui && ui.settingsNav) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-claude-style",
3
3
  "description": "Claude Code Desktop theme for DeepSeek Harness (dsh) web GUI — visual & interaction overhaul",
4
- "version": "0.10.3",
4
+ "version": "0.10.4",
5
5
  "type": "module",
6
6
  "main": "host/index.js",
7
7
  "icon": "lib/claude-mark.svg",
@@ -44,7 +44,11 @@
44
44
  "fonts/JetBrainsMonoVariable.ttf",
45
45
  "fonts/JetBrainsMonoItalicVariable.ttf",
46
46
  "fonts/OFL.txt",
47
- "fonts/AUTHORS.txt"
47
+ "fonts/AUTHORS.txt",
48
+ "fonts/InterVariable.woff2",
49
+ "fonts/OFL-Inter.txt",
50
+ "fonts/NotoSerifVariable.woff2",
51
+ "fonts/OFL-NotoSerif.txt"
48
52
  ],
49
53
  "keywords": [
50
54
  "dsh",