dsh-claude-style 0.6.4 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,116 @@
2
2
 
3
3
  All notable changes to `dsh-claude-style` are documented here, newest first.
4
4
 
5
+ ## [0.7.2] - 2026-09-25
6
+
7
+ [中文](#cn-0.7.2) | [English](#en-0.7.2)
8
+
9
+ <h3 id="cn-0.7.2">新增功能</h3>
10
+
11
+ - **账号位置的头像改为玩家自己的皮肤**:启动器记下了玩家导入的皮肤,宿主半边把那张规范化贴图以只读路由发给浏览器,浏览器按启动器账号列表相同的裁法取头部——脸的 8×8 贴图块按盒子的 1/18 内缩,帽子层铺满整个盒子。头部按**方形**绘制,不用账号头像那条路径的圆形遮罩:它画到盒子边缘,任何圆角都会切掉它的像素。没有头像、头像文件已删、图取不到时显示 Claude 徽标。
12
+ - **权限档位改为跟随宿主目录**:权限控件(输入框分段控件与它的弹层)不再写死四档,段位与行都按宿主的 `permissionPresets` 目录构建——目录里有的档位才画,宿主没提供的整条不出现,第三方插件注册的档位因此成为一等公民:auto mode 插件的 `auto-mode` 会以 **Auto mode** 出现在弹层里,并在分段控件里占用 **Auto** 那一格(部署同时提供宿主内置 Auto review 时,内置档退居弹层、把格子让给部署自己的自动档)。档位名与说明仍由皮肤给(皮肤不认识的档位用宿主自己的名字与说明,机器值不上屏),行保持纯文字——预设声明的 `icon` 也不画,档位列表读起来是一份清单。切换仍走宿主的 `/permission <preset>`。
13
+
14
+ ### 体验优化
15
+
16
+ - **工作台首页在还没选工作区时也显示用量面板与权限分段**:刚打开、尚无会话的首页此前只有问候语和输入框;现在用量面板照常出现在两者之间,输入框下方显示 Read / Edit / Auto / Yolo 分段,停在新会话的默认权限上,点它和点输入框一样打开工作区选择。
17
+ - **工作台首页「先选工作区」的虚线框只框住单行输入框**:虚线框此前连同输入框下方的工具行一起框进去,比输入框高出一截;现在虚线画在单行输入框本身,指针移上去时变色的提示不变。
18
+
19
+ ### 问题修复
20
+
21
+ - 修复 **选中划过行内代码时代码段显示为一块偏浅的独立高亮**:芯片的半透明底色画在选中高亮之上,把芯片内的选中色混浅了一档;现在芯片内的选中色预先抵消这层罩色,整段选区呈现统一的选中色,聚焦与失焦、深色与浅色下一致。
22
+ - 修复 **窗口较矮时工作台首页被用量面板撑出窗口**:面板放不下时整页向下延伸,问候语被顶出窗口上沿;现在面板在问候语与输入框之间自己滚动,底边渐隐,滚到底时渐隐消失,问候语、上下文行与输入框都留在窗口内。
23
+
24
+ <h3 id="en-0.7.2">New Features</h3>
25
+
26
+ - **The account row's mark becomes the player's own skin**: the launcher stores every imported skin as a normalized texture atlas, and the host half serves it over a read-only route. The browser crops the head the way the launcher's own account list does — the face's 8×8 texel block inset by 1/18 of the box, with the hat layer over the whole box. The head is drawn **square** rather than under the round mask the avatar-photo path uses, because it reaches the box's edges and any rounding would shave its pixels off. With no picture, a deleted file, or a picture that cannot be served, the Claude mark shows.
27
+ - **The permission ladder now follows the host catalog**: the permission control (the composer's segmented group and its popover) no longer hardcodes four tiers. Both the segments and the rows are built from the host's `permissionPresets` catalog, so a deployment offers exactly the tiers it configures and a tier no one serves is absent rather than drawn dead. A tier a third-party plugin registers is therefore a first-class entry: the auto mode plugin's `auto-mode` appears as **Auto mode** in the popover and takes the **Auto** slot of the segmented group (when the deployment also serves the host's built-in Auto review, that one stays in the popover and the slot goes to the deployment's own tier). Names and descriptions still come from the skin — a tier it does not know reads with the host's own name and description, never with its machine id — and the rows stay text-only: a preset's declared `icon` is not drawn either, so the ladder reads as one list. Switching still goes through the host's `/permission <preset>`.
28
+
29
+ ### Improvements
30
+
31
+ - **The Studio home shows the usage panel and the permission segments before a workspace is picked**: a fresh home with no session yet showed only the greeting and the input box; the usage panel now sits between them as usual, and the Read / Edit / Auto / Yolo segments show under the input box, resting on the preset a new session starts in — pressing them opens the workspace picker, the same as pressing the input box.
32
+ - **The Studio home's dashed "pick a workspace" ring outlines the single-line input box alone**: the ring used to take in the toolbar row under the input box and stood a row taller than it; it is now drawn on the input box itself, and still changes colour under the pointer.
33
+
34
+ ### Bug Fixes
35
+
36
+ - Fixed **inline code showing a separate, lighter highlight when selected**: the chip's translucent wash painted over the selection and lightened it inside the chip; the in-chip selection paint now pre-compensates for that wash, so a selection crossing inline code is one even colour, focused or blurred, dark or light.
37
+ - Fixed **the usage panel pushing the Studio home past a short window**: when the panel did not fit, the whole page grew downward and the greeting ran off the top; the panel now scrolls between the greeting and the composer with its bottom edge fading out (the fade lifts at the end of the scroll), and the greeting, the context row and the input box stay in the window.
38
+
39
+ **Full Changelog**: [v0.7.1...v0.7.2](https://github.com/Nwflower/dsh-claude-style/compare/v0.7.1...v0.7.2)
40
+
41
+ ## [0.7.1] - 2026-09-25
42
+
43
+ [中文](#cn-0.7.1) | [English](#en-0.7.1)
44
+
45
+ <h3 id="cn-0.7.1">体验优化</h3>
46
+
47
+ - **默认首页版面改为工作台**:没在设置里选过版面时,新会话页用工作台版面——问候在左上、输入卡片贴住窗口底边、中间是用量面板、卡片上沿站着像素螃蟹,也就是 Claude Code 自己的首页。经典版面仍可在设置页随时切回,已经选过版面的不受影响。
48
+
49
+ <h3 id="en-0.7.1">Improvements</h3>
50
+
51
+ - **Studio is the default home layout**: without a layout chosen in the settings, the new-conversation page opens in the Studio layout — the greeting at the top left, the composer on the window's bottom edge, the usage panel in between and the pixel crab on the card, Claude Code's own home. Classic stays one switch away in the settings page, and a layout already chosen is kept.
52
+
53
+ **Full Changelog**: [v0.7.0...v0.7.1](https://github.com/Nwflower/dsh-claude-style/compare/v0.7.0...v0.7.1)
54
+
55
+ ## [0.7.0] - 2026-09-25
56
+
57
+ [中文](#cn-0.7.0) | [English](#en-0.7.0)
58
+
59
+ <h3 id="cn-0.7.0">新增功能</h3>
60
+
61
+ - **两套首页版面,设置页随时切换**:新增「首页版面」设置项。**经典**沿用居中大标题加输入卡片;**工作台**把标题移到左上角并固定为一句「What's up next, 用户名?」(无衬线小字,与品牌标记同一行),输入框改用对话内的单行样式贴住窗口底部、上方一行细边框上下文胶囊,标题与一块 480px 窄栏用量面板贴着输入框左缘排布——概览页签是六个数字格(会话数、消息数、Token 总量、活跃天数、高峰时段、最常用模型)与 26 周按天热力图(等分列方格、蓝色数据色阶);模型页签是每天一根按模型颜色自下而上堆叠的柱子,下面跟一份模型排行(色块、模型名、输入与输出、占比,超过六行折成「再显示 N 个」,展开后末尾是「收起」),柱与色块共用同一套按名次取色的蓝色色阶;右上角的「全部 / 30 天 / 7 天」范围切换过滤数字格(含高峰时段)、趣味行与模型排行,热力图与柱状图保持自身窗口;所选范围内的用量超过一本书时,热力图下方出现倍数趣味行:书从《动物农场》到《追忆似水年华》共十一本,每次回到新会话页随机换一本,范围内用量还不到这本书时退到已经超过的最长那本。面板只在新会话页出现,进入对话后自动消失。
62
+ - **用量数据在本机汇总**:新增只读路由 `GET /dsh-claude-style/usage`(与用户名路由同一同源护栏)。装了 `dsh-cost-meter` 时直接读它的账本缓存(只读),否则读本机会话日志自行汇总,并按日志文件增量缓存——冷启动首次约两秒,之后每次启动约 3 毫秒。两种来源都给出每个模型的四类 token 与每天按模型拆分的 token(`models` 与 `days[].models`),模型页签的柱状图与排行读它们;账本按「提供方:模型」记账,同一模型经不同提供方的用量合成一行。账本不记小时,读账本时其余数字先出,高峰时段随后由本机会话日志按天算出补上。汇总不可用(旧宿主半边或汇总失败)时,面板改用宿主会话列表自带的投影值出数。
63
+ - **面板先画骨架再填数**:数字到达前先画好外框、占位数字、空热力格与模型行的占位条,到达后只替换文字、格子颜色与条长,整个版面不跳动;取不到数据的格子画破折号,不画零。
64
+ - **输入卡片上的像素螃蟹**:工作台版面的新会话页上(经典版面没有),输入卡片上沿靠右站着 Claude Code 的像素螃蟹。点它一下、指针从它身上移开时、以及页面开着时每隔 25–45 秒,它照 Claude Code 的样子钓一次鱼:半转身眨眼,举竿甩到卡片边上,侧身钓一会儿,收竿转回正面,约三秒;系统要求减少动态效果时(例如 Windows 关掉了「在 Windows 中显示动画」),只有点它才播放。只有螃蟹本身接收指针,鱼竿挥动的空白处不挡点击。
65
+ - **热力图的日期提示**:指针停在热力图的某一天上,立刻浮出 Claude Code 样式的深色提示,写着日期和当天的消息数(如「9月9日 — 15,955」);靠两端的几列提示贴着格子外沿,不会伸出面板。会话列表兜底来源没有按天的消息数,此时提示写当天的 Token。概览的数字格同时改用 Claude Code 的叫法:消息数、Token 总量、最常用模型(模型名不加粗)。
66
+ - **经典首页的问候更多了**:经典版面的大标题不再每个时段只有一句,早上、午间、下午、晚上、深夜各有一组问候,另有几句不分时段;每次回到新会话页随机换一句,同一次停留里不会跳动。
67
+ - **接入 HDSL 启动器的账号信息**:由 HDSL 启动的实例,昵称与头像会优先取启动器里的账号。昵称按「自定义昵称 → 官方账号昵称 → HDSL 昵称 → 上次探测到的系统用户名 → 系统用户名 → `User`」回退,头像按「官方账号头像 → HDSL 头像 → Claude 徽标」回退。宿主半边新增两条只读私有路由,与用户名路由同级过宿主请求栅栏:`GET /dsh-claude-style/hdsl` 回账号元数据(不含头像文件的绝对路径),`GET /dsh-claude-style/hdsl-skin.png` 回头像图片。契约版本读不到或不认识时整组忽略;头像文件被删除时回落到徽标。
68
+
69
+ <h3 id="en-0.7.0">New Features</h3>
70
+
71
+ - **Two home layouts, switchable in the settings page**: a new Home layout preference. **Classic** keeps the centered headline over the composer card; **Studio** pins the greeting to the top left as one fixed line ("What's up next, <user>?", in the sans UI face on the brand mark's line), docks the composer in the conversation's single-line form at the window's bottom edge with a row of hairline context chips above it, and sets the greeting and a 480px usage panel against the composer's left edge — an Overview tab with six stat cells (sessions, messages, total tokens, active days, peak hour, favorite model) and a twenty-six-week per-day heat grid of square cells in equal columns, and a Models tab stacking each day's per-model tokens into one bar over a ranked model list (swatch, model name, input and output, share; past six rows it folds behind a "show more" row, and the open list ends in a "show less" row), both reading the same rank-ordered blue ramp. The All / 30d / 7d range pills filter the tiles (the peak hour included), the multiplier line and the model list while the grid and the chart keep their own windows, and once the picked range's total passes one book a multiplier line appears under the grid — eleven books from Animal Farm to In Search of Lost Time, a fresh one drawn each time the new-conversation page comes back, stepping down to the longest book the range has passed when it has not reached the drawn one. The panel appears on the new-conversation page only and steps away once a session is open.
72
+ - **The usage numbers are folded locally**: a new read-only route, `GET /dsh-claude-style/usage`, behind the same same-origin fence as the username route. With `dsh-cost-meter` installed it reads that plugin's ledger cache (read-only); otherwise the host half folds the local session logs itself and caches the result incrementally per log file — about two seconds on the first cold pass, about 3 ms per start afterwards. Both sources answer each model's four token buckets and each day's per-model tokens (`models` and `days[].models`), which the Models tab's chart and ranking read; the ledger books usage per provider and model, and one model served by several providers is one row. The ledger keeps no hours, so with it the other figures land first and the peak hour follows a moment later, folded per day from the local session logs. When the fold cannot answer (an older host half or a failed pass), the panel falls back to the projection block each host session row carries.
73
+ - **The panel draws its skeleton before its numbers**: the frame, number placeholders, an empty heat grid and stand-in share bars come first; arriving values replace only the text, the cell colours and the bar lengths, so nothing shifts. A figure no source can answer is a dash, never a zero.
74
+ - **A pixel crab on the composer card**: on the new-conversation page of the Studio layout (the Classic layout has none), Claude Code's pixel crab stands on the composer card's top edge near its right end. When it is clicked, when the pointer leaves it, and every 25–45 seconds while the page is in view, it plays Claude Code's fishing routine — a half turn and a wink, the rod cast down onto the card's edge, a spell of fishing side-on, and the rod put away as it turns back, about three seconds; with reduced motion requested (Windows' "Show animations in Windows" turned off, for one) only a click plays it. Only the crab itself takes the pointer, so the room the rod swings through never blocks a click.
75
+ - **Day tips on the heat grid**: resting the pointer on a day of the heat grid shows Claude Code's dark tip at once, with the date and that day's messages ("Sep 9 — 15,955"); over the columns at either end the tip lines up with the cell's outer edge and stays inside the panel. The session list's fallback has no per-day message count, and its tips name the day's tokens instead. The Overview tiles take Claude Code's names as well: Messages, Total tokens and Favorite model (the model name at regular weight).
76
+ - **More greetings on the classic home page**: the classic headline no longer has one line per time of day — morning, midday, afternoon, evening and night each have a pool, plus a few lines for any hour; a fresh line is drawn each time the new-conversation page comes back and holds still while you stay.
77
+ - **HDSL launcher accounts are picked up**: an instance started by HDSL prefers the launcher's account for both the nickname and the picture. The nickname falls back through the custom nickname → the signed-in account's name → the HDSL account name → the last probed system user → the system user → `User`; the picture through the account's avatar → the HDSL avatar → the Claude mark. The host half gains two read-only private routes behind the same request fence as the username route: `GET /dsh-claude-style/hdsl` answers the account metadata (never the avatar file's absolute path) and `GET /dsh-claude-style/hdsl-skin.png` answers the image. A contract version that is missing or unknown voids the whole group, and a deleted avatar file falls back to the mark.
78
+
79
+ <h3 id="cn-0.7.0">体验优化</h3>
80
+
81
+ - **同时只开一张弹层卡片**:悬停或点击打开模型选择器、推理强度、权限、账户抽屉、会话统计卡片、工作台新会话页的工作区菜单时,之前打开的那张卡片立即收起,两张卡不再叠在同一角;宿主自带的工作区菜单与账户菜单一并参与。
82
+ - **悬停停留由 50ms 延长到 100ms**:指针以平常速度扫过触发器不再展开卡片——工作台版面把上下文行贴在输入卡片正上方,此前指针移向输入框时几乎每次都会展开工作区菜单;停在触发器上仍然立即展开。模型选择器的收起宽限与会话统计卡片的展开停留保持各自的例外值。
83
+
84
+ <h3 id="en-0.7.0">Improvements</h3>
85
+
86
+ - **One popover card at a time**: opening the model picker, the reasoning-effort card, the permission menu, the account drawer, the session-stats card or the studio new-conversation page's workspace menu now folds whatever card was up before it, so two panels no longer stack over one corner; the host's own workspace and account menus join on the same terms.
87
+ - **The hover dwell goes from 50 ms to 100 ms**: a pointer crossing a trigger at an ordinary pace no longer unfolds a card — the studio layout sets the context row directly above the composer card, where a pointer on its way to the input used to unfold the workspace menu almost every time — while a pointer parked on the trigger still opens at once. The model picker's close grace and the stats card's open dwell keep their own values.
88
+
89
+ <h3 id="cn-0.7.0">问题修复</h3>
90
+
91
+ - 修复 **浅色模式下聊天记录顶部「加载更早」按钮几乎看不见**:浅色下的实色悬停底改为浅灰,按钮文字对比度从 2.6:1 回到 4.7:1,深色不变;同族令牌下的工作区重命名输入框在浅色里也不再是深色方块。
92
+ - 修复 **设置页「账号与余额」的「充值」按钮文字与底色几乎同色**:统一的链接色不再覆盖这类实心按钮链接,按钮恢复自身配色(浅色 3.1:1,深色 6.1:1),旁边的「查询用量」回到描边按钮样式。
93
+ - 修复 **实心主按钮悬停时底色跳出陶土色系**:悬停色与底色现在同族,悬停不再跳到冷灰或近白。
94
+ - 修复 **工作区选择器弹层打开时先跳到触发器下方再弹回**:弹层从打开的第一帧起就停在触发器旁边的正确位置,不再有可见的跳动。
95
+ - 修复 **切换到 Yolo 或 Auto 后权限停留在原档位**:皮肤此前通过模拟点击被隐藏的宿主访问按钮、再按文字寻找弹层里的档位行来完成这两个档位的切换,输入栏被锁定或按钮处于禁用状态时这条路径点不动,点确认后预设也不变化。现在任何档位都直接请求宿主的 `/permission` 命令完成切换(与官方弹窗确认后走的是同一条写入路径),点击后权限立即生效,皮肤控件与宿主按钮同步显示新档位;切换请求失败时控件按各功能失败隔离规则交还宿主按钮,不再无声无息。
96
+ - 修复 **权限弹层随重建在页面里残留**:宿主重渲染换掉控件容器后,下一次重建会往页面里追加一颗新弹层,旧弹层失去引用后永远留在文档里(长时间使用的页面上数到过 38 颗)。现在每次安装弹层先清扫文档里已有的弹层,功能卸载时一并扫除,页面上始终只有当前这一颗。
97
+ - 修复 **已归档行的时间被操作按钮顶离右缘**:取消归档与删除按钮此前以透明常驻占位,时间只能停在按钮预留区的左边,悬停时三样挤在一行。现在与官方会话行同一机制:静止时按钮不占位,时间与行右缘对齐;悬停(或键盘聚焦行)时时间让位隐藏,两颗按钮出现在原位置。按钮隐身期间也不再响应落在那片区域上的点击。
98
+ - 修复 **页面加载后第一次修改设置提示「设置存储不可用,改动不会被保存」**:设置表单现在会等宿主的命名空间登记完成后再绑定,登记晚到时也会在到达后立即绑定并读回取值,第一次修改即可保存。
99
+ - 修复 **新会话页把鼠标从预设模式快速移到工作文件夹时两个弹层同时打开并闪烁**:一行里的两个选择器(工作文件夹与预设模式)现在只会有一个打开,移到另一个触发器时先收起前一个,两个弹窗不再同时出现、也不再闪动;悬停离开收起的是悬停打开的那一个。
100
+
101
+ <h3 id="en-0.7.0">Bug Fixes</h3>
102
+
103
+ - Fix **the "load earlier" button at the top of the transcript being nearly invisible in light mode**: the light-mode solid hover fill is now a light gray, returning the button's contrast from 2.6:1 to 4.7:1 (dark unchanged); the workspace rename field on the same token family is no longer a dark block in light mode either.
104
+ - Fix **the "Top up" button in Settings → Account & balance showing its label in the same colour as its fill**: the shared link ink no longer overrides filled-button anchors, so the button keeps its own ink (3.1:1 light, 6.1:1 dark) and the neighbouring "View usage" returns to the outlined treatment.
105
+ - Fix **a filled primary button's hover fill jumping out of the accent family**: the hover now stays in the fill's family instead of jumping to a cold gray or near-white.
106
+ - Fix **the workspace picker card jumping below its trigger and back as it opens**: the card holds its correct position beside the trigger from the first frame it appears, with no visible jump.
107
+ - Fix **the preset staying on its old tier after switching to Yolo or Auto**: the skin drove these two tiers by synthetically clicking the hidden host access button and then locating the tier row by label in the opened menu; with the input bar locked or the trigger disabled that path pressed nothing, and the preset never changed after confirming. Every tier is now requested directly through the host's `/permission` command — the same write path the official dialog's confirmation ends in — so the switch takes effect immediately and the skin control and the host button agree on the new tier; a failed request retires the control through the per-feature failure isolation rule instead of passing silently.
108
+ - Fix **the permission popover stranding in the document across rebuilds**: when a host re-render replaced the control's container, the next rebuild appended a fresh popover while the previous one lost its only reference and stayed in the document forever (38 were counted on a long-lived page). Each install now sweeps every popover already in the document before appending its own, teardown sweeps the rest, and exactly one popover remains.
109
+ - Fix **the archived rows' time being pushed off the row's right edge by the action buttons**: the unarchive and delete buttons reserved their place while transparent, so the time stopped where their reserved space began and hover crowded all three onto one line. The rows now follow the host's own session rows' mechanism: at rest the buttons occupy nothing and the time right-aligns with the row; on hover (or while the row holds keyboard focus) the time steps aside and the two buttons take its place. The invisible buttons also stop answering clicks aimed at that area.
110
+ - Fix **the first settings change after a page load reporting "The settings store is unavailable, so changes will not be saved."**: the settings form now waits for the host's namespace registration before binding, binds as soon as a late registration arrives and reads the value back, so the first change saves.
111
+ - Fix **both popovers on the new-conversation page opening at once, and flickering, when the pointer moved quickly from the preset mode to the workspace folder**: only one of the row's two pickers (the workspace folder and the preset mode) is open at a time — moving to the other trigger folds the first, so the two cards no longer appear together or flicker, and a hover-leave folds the picker the hover opened.
112
+
113
+ **Full Changelog**: [v0.6.4...v0.7.0](https://github.com/Nwflower/dsh-claude-style/compare/v0.6.4...v0.7.0)
114
+
5
115
  ## [0.6.4] - 2026-09-24
6
116
 
7
117
  [中文](#cn-0.6.4) | [English](#en-0.6.4)
package/README.en.md CHANGED
@@ -63,12 +63,14 @@ Keep only one theme enabled at a time. After installation, **restart `dsh web`**
63
63
  ## Features
64
64
 
65
65
  1. **Theme** — applies globally the moment it is installed, with nothing to configure. Light mode uses ivory `#FCFCFB`, dark mode warm black `#141413`, and both share the same ember orange `#D97757` action accent; light/dark follows the system color mode.
66
- 2. **Composer** — the input box is rebuilt from the ground up: a permission segmented control (Read / Edit / Auto / Yolo — the Auto tier appears only when the host has the built-in auto review enabled), a model trigger carrying the vendor lockup, and the toolbar and status stats laid out on a single line, with the send and stop buttons unified into 7px rounded rectangles. The stats sentence and the model trigger share the same font size and color.
67
- 3. **Model picker** — a new two-level popover. The first level lists the official service plus the quick providers selected in settings; "More models" opens a second level grouped by provider. Every row carries its vendor lockup and a description, and the footer holds the reasoning-effort slider (stepless — it snaps to the nearest level on release) together with the "More models" entry. The second level aligns its bottom edge with the first, providers already shown on the first level are not repeated, and models without thinking support get no slider. Hovering opens the popover after a 50 ms dwell and closes it after 150 ms; the gap between the two cards does not count as leaving.
66
+ 2. **Composer** — the input box is rebuilt from the ground up: a permission segmented control (Read / Edit / Auto / Yolo) with its tier list under it, a model trigger carrying the vendor lockup, and the toolbar and status stats laid out on a single line, with the send and stop buttons unified into 7px rounded rectangles. The stats sentence and the model trigger share the same font size and color. The permission tiers come from the host's own catalog: a deployment draws exactly the tiers it configures, and a tier a third-party plugin registers (the auto mode plugin's `auto-mode`, say) appears in the list and takes the Auto slot; the list stays text-only, so a tier's declared glyph is not drawn.
67
+ 3. **Model picker** — a new two-level popover. The first level lists the official service plus the quick providers selected in settings; "More models" opens a second level grouped by provider. Every row carries its vendor lockup and a description, and the footer holds the reasoning-effort slider (stepless — it snaps to the nearest level on release) together with the "More models" entry. The second level aligns its bottom edge with the first, providers already shown on the first level are not repeated, and models without thinking support get no slider. Hovering opens the popover after a 100 ms dwell and closes it after 150 ms; the gap between the two cards does not count as leaving.
68
68
  4. **Workspace** — the sidebar's "Workspace" heading becomes an **Active / Archived** segmented control. Active keeps the host's session tree, while Archived is the skin's own flat list (title, time, and per-row unarchive and delete icon buttons). Rows match the host's session rows measurement for measurement (row x=12 / width 251 / height 28 / title x=36).
69
69
  5. **Sidebar** — the New session and Plugins rows take Claude's real shape: no background at rest, a background on hover, the "+" of New session set inside a circular chip, and both icons rotating 90° clockwise on hover (four-fold symmetric glyphs, so they land back on themselves). The account drawer and the ban-screen easter egg remain as they were.
70
- 6. **Account area** — after signing in on desktop, the row shows your **real avatar and nickname** (fetched from the server only at startup, on hot reload, and on sign-in and sign-out — never polled; a hand-drawn starburst is the fallback). The account popover remains the host's own: the host's account row is the entry point, and the popover lists the account header, the other plugins' footer entries, and the host's own Settings / Feedback / Sign out, each keeping its original copy, order, and click behavior. On hosts without an account area (Web), the plugin builds a popover with the same look. The card uses a pure white background and spans the sidebar's width.
70
+ 6. **Account area** — the nickname and the picture each follow one fixed order. Nickname: the custom nickname → the signed-in account's name → the HDSL launcher's account name → the last probed system user → the freshly probed system user → `User`. Picture: the signed-in account's avatar → the HDSL launcher's avatar → the Claude mark. The account profile is fetched from the server only at startup, on hot reload, and on sign-in and sign-out — never polled. The HDSL contract is read once by the host half and forwarded over two read-only routes (`GET /dsh-claude-style/hdsl` for the metadata, `GET /dsh-claude-style/hdsl-skin.png` for the avatar image; neither carries the avatar file's absolute path, and both sit behind the host's request fence). A contract version that is missing or unknown voids the whole group, and a deleted avatar file falls back to the mark. What the launcher publishes is a skin sheet rather than a finished avatar, so that picture is cropped into the head first — the face's 8×8 texel block inset by 1/18 of the box with the hat layer over the whole box, drawn square so no rounding shaves its pixels. The account popover remains the host's own: the host's account row is the entry point, and the popover lists the account header, the other plugins' footer entries, and the host's own Settings / Feedback / Sign out, each keeping its original copy, order, and click behavior. On hosts without an account area (Web), the plugin builds a popover with the same look. The card uses a pure white background and spans the sidebar's width.
71
71
  7. **Conversation views** — in the desktop Windows titlebar mode, the Conversation / Trajectory control sits centered and always visible on the titlebar row (the same row as the New session button). On wide windows with a short title it stays on the title's row; otherwise it keeps its own row below the title.
72
+ 8. **Home layouts** — the new-conversation page comes in two layouts, Studio by default and switchable in the settings page at any time: **Classic** is the centered headline over the composer card, the headline drawn from a pool of greetings for the time of day (a few each for morning, midday, afternoon, evening and night, plus a few for any hour) and redrawn each time the new-conversation page comes back; **Studio** pins the greeting to the top left as one fixed line ("What's up next, <user>?"), docks the composer in the conversation's single-line form at the window's bottom edge with a row of hairline context chips above it, and sets the greeting and a 480px usage panel against the composer's left edge: an Overview tab with six stat cells (sessions, messages, total tokens, active days, peak hour, favorite model) and a twenty-six-week per-day heat grid of square cells in equal columns, a Models tab stacking each day's per-model tokens into one bar over a ranked model list (swatch, name, input and output, share; past six rows it folds behind a "show more" row, and the open list folds back with "show less"), and All / 30d / 7d range pills at the top right, which the peak hour and the multiplier line follow too; once the picked range's total passes one book, a multiplier line appears under the grid, naming one of eleven books (Animal Farm to In Search of Lost Time) drawn afresh each time the new-conversation page comes back. The numbers are rolled up by the plugin's host half: with `dsh-cost-meter` installed it reads that plugin's ledger cache directly (read-only — their file is never written), and without it the host half folds the local session logs itself, caching per log by size and modification time. Both sources answer each model's four buckets and each day's per-model tokens, with one model served by several providers counted as one row; the ledger keeps no hours, so the peak hour is always folded per day from the local session logs; when the roll-up cannot answer, the panel falls back to the projection block each host session row carries. The panel draws its skeleton first and does not shift when the numbers land. Before a workspace is picked (a fresh start, no session yet), the usage panel and the Read / Edit / Auto / Yolo permission segments still show; the segments rest on the preset a new session starts in, and pressing them opens the workspace picker, the same as pressing the input box, whose dashed "pick a workspace" ring now outlines the single-line input box alone. When the window is too short for the whole panel, the panel scrolls between the greeting and the composer with its bottom edge fading out, and the page no longer runs past the window.
73
+ 9. **The pixel crab** — in the Studio layout, Claude Code's pixel crab stands on the new-conversation composer card's top edge, near its right end (the Classic layout has none). When it is clicked or the pointer leaves it (and on its own every half minute or so while the page is open), it plays Claude Code's fishing routine: a half turn and a wink, the rod raised and cast down onto the card's edge, a spell of fishing side-on, and the rod put away as it turns back — about three seconds. With reduced motion requested (Windows' "Show animations in Windows" turned off, for one), it moves only when clicked.
72
74
 
73
75
  ## Disabling and uninstalling
74
76
 
package/README.md CHANGED
@@ -63,12 +63,14 @@ dsh plugin --profile web add Nwflower/dsh-claude-style # GitHub 源
63
63
  ## 特点
64
64
 
65
65
  1. **主题** —— 安装即全局生效,无需配置。亮色象牙白 `#FCFCFB`、暗色暖黑 `#141413`,两套画布共用同一个操作强调色陶烬橙 `#D97757`;亮暗跟随系统颜色模式切换。
66
- 2. **输入框** —— 输入框整块重做:权限分段控件(只读 / 工作区 / 自动审查 / Yolo,自动审查档位仅在宿主启用内置自动审查时出现)、带品牌锁定标的模型触发器、工具栏与状态统计同一行排版,发送键与停止键统一成 7px 圆角。统计句与模型触发器共用一套字号与颜色。
67
- 3. **模型选择器** —— 全新的两级弹层:一级列官方服务与设置页勾选的快捷供应商,「更多模型」二级按供应商分组;每行带厂商锁定标与说明文案,底部是推理等级推条(无极滑动,松手对齐最近档位)与「更多模型」入口。二级底边与一级对齐,一级已显示的供应商不再重复;模型不支持思考时不画推条。悬停停留 50ms 打开、离开 150ms 关闭,两卡之间的空隙不算离开。
66
+ 2. **输入框** —— 输入框整块重做:权限分段控件(只读 / 编辑 / 自动 / Yolo)与它下方的档位列表、带品牌锁定标的模型触发器、工具栏与状态统计同一行排版,发送键与停止键统一成 7px 圆角。统计句与模型触发器共用一套字号与颜色。权限档位取自宿主的权限目录:宿主配置了哪几档就画哪几档,第三方插件注册的档位(例如 auto mode 插件的 `auto-mode`)会自动出现在列表里并占用「自动」那一格;列表保持纯文字,档位自己声明的图标不画。
67
+ 3. **模型选择器** —— 全新的两级弹层:一级列官方服务与设置页勾选的快捷供应商,「更多模型」二级按供应商分组;每行带厂商锁定标与说明文案,底部是推理等级推条(无极滑动,松手对齐最近档位)与「更多模型」入口。二级底边与一级对齐,一级已显示的供应商不再重复;模型不支持思考时不画推条。悬停停留 100ms 打开、离开 150ms 关闭,两卡之间的空隙不算离开。
68
68
  4. **工作区** —— 侧栏「工作区」标题改成 **进行中 / 已归档** 分段控件:进行中沿用宿主的会话树,已归档是皮肤自己的平铺列表(标题、时间、每行「取消归档」与「删除」两个图标按钮)。行排版与宿主的会话行逐项一致(行 x=12 / 宽 251 / 高 28 / 标题起点 x=36)。
69
69
  5. **侧栏** —— 新会话与插件两行取 Claude 真机形状:默认无底色、hover 才有底色,新会话的「+」套一枚圆形底,两行图标 hover 顺时针转 90°(四重对称图形,转回原位);底部账户抽屉、封号彩蛋一并保留。
70
- 6. **账号区** —— 桌面端登录后显示**真实头像与昵称**(只在启动、热重载、登录与登出时向服务器读取,不轮询;失败回退手绘星芒);账号弹层沿用宿主自带的那一个:宿主的账号行就是入口,弹层里依次是账号头部、其它插件的页脚条目与宿主自己的「设置 / 意见反馈 / 退出登录」,各行的文案、顺序与点击行为保持宿主原样;宿主没有账号区的环境(Web)由插件自建同样外观的弹层。弹层卡片为纯白背景、宽度与侧栏一致。
70
+ 6. **账号区** —— 昵称与头像各走一条固定顺序。昵称:自定义昵称 → 官方账号昵称 → HDSL 启动器昵称 → 上次探测到的系统用户名 → 本次探测到的系统用户名 → `User`;头像:官方账号头像 → HDSL 启动器头像 → Claude 徽标。官方账号资料只在启动、热重载、登录与登出时向服务器读取,不轮询;HDSL 契约由宿主半边读一次后经两条只读路由转发(`GET /dsh-claude-style/hdsl` 回账号元数据、`GET /dsh-claude-style/hdsl-skin.png` 回头像图片,都不含头像文件的绝对路径,并过宿主的请求栅栏),契约版本读不到或不认识时整组忽略,头像文件被删除时回落到徽标。启动器给的是皮肤贴图而不是成品头像,因此账号位置的头像由贴图裁出:脸部 8×8 贴图块按盒子 1/18 内缩、帽子层铺满整盒,方形绘制不被圆角裁切。账号弹层沿用宿主自带的那一个:宿主的账号行就是入口,弹层里依次是账号头部、其它插件的页脚条目与宿主自己的「设置 / 意见反馈 / 退出登录」,各行的文案、顺序与点击行为保持宿主原样;宿主没有账号区的环境(Web)由插件自建同样外观的弹层。弹层卡片为纯白背景、宽度与侧栏一致。
71
71
  7. **对话视图** —— 对话 / 轨迹控件在桌面 Windows 标题栏模式下居中常显在标题栏那一行(与新会话按钮同一行);宽窗口且标题较短时它停在标题那一行,否则留在标题下方自己那一行。
72
+ 8. **首页版面** —— 新会话页有两套版面,默认是工作台,在设置页随时切换:**经典**是居中大标题加输入卡片,标题按时段从一组问候里抽一句(早上、午间、下午、晚上、深夜各有几句,另有几句不分时段),每次回到新会话页换一句;**工作台**把标题移到左上并固定为一句「What's up next, 用户名?」,输入框改用对话内的单行样式贴住窗口底部、上方一行细边框上下文胶囊,标题与一块 480px 窄栏用量面板贴着输入框左缘排布:概览页签是六个数字格(会话数、消息数、Token 总量、活跃天数、高峰时段、最常用模型)与 26 周按天热力图(等分列方格),模型页签是每天一根按模型颜色堆叠的柱子加一份模型排行(色块、模型名、输入与输出、占比,超过六行折成「再显示 N 个」,展开后可「收起」),右上角的「全部 / 30 天 / 7 天」切换统计范围(高峰时段与趣味行一并跟随);所选范围内的用量超过一本书时,热力图下方出现倍数趣味行,书目共十一本(《动物农场》到《追忆似水年华》),每次回到新会话页随机换一本。数据由插件的宿主半边汇总:装了 `dsh-cost-meter` 时直接读它的账本缓存(只读,不写对方文件),没有时读本机会话日志自己算,并按日志的文件大小与修改时间增量缓存;两种来源都按模型给出四类 token、按天给出各模型的 token,同一模型经不同提供方的用量合成一行;账本不记小时,高峰时段始终由本机会话日志按天算出;汇总不可用时面板改用宿主会话列表每行自带的投影值。数字没到之前面板先画骨架,位置在填充前后不变。还没选工作区时(刚打开、尚无会话),用量面板与 Read / Edit / Auto / Yolo 权限分段照常显示,分段停在新会话的默认权限上,点它和点输入框一样打开工作区选择;表示「先选工作区」的虚线框只框住单行输入框本身。窗口高度放不下整块面板时,面板在问候语与输入框之间自己滚动、底边渐隐,页面不会被撑出窗口。
73
+ 9. **像素螃蟹** —— 工作台版面的新会话页上,输入卡片上沿、靠右的位置站着 Claude Code 的像素螃蟹(经典版面没有)。点它一下、或指针从它身上移开时(页面开着时也会每隔二三十秒自己来一次),它会照 Claude Code 的样子钓一次鱼:半转身眨眼,举起鱼竿甩到卡片边上,侧身钓一会儿,再收竿转回正面,前后约三秒。系统要求减少动态效果时(例如 Windows 关掉了「在 Windows 中显示动画」),它只在被点击时才动。
72
74
 
73
75
  ## 停用与卸载
74
76
 
package/docs/STYLE.md CHANGED
@@ -21,6 +21,18 @@ Rules:
21
21
  - Never pure white, never pure black, never cool grays.
22
22
  - Accent usage stays under ~10% of visible elements.
23
23
 
24
+ **Host token bindings.** The skin supplies its palette through the host's own
25
+ alias tokens, so a host control that reads more than one token for one surface
26
+ has to be given all of them. A filled primary button is that case: the host takes
27
+ the fill from `--dsw-alias-brand-primary`, but the hover from a monochrome step
28
+ of its own scale (light `#43454a`, dark `#ebeef2`) and the label from a
29
+ foreground token the skin leaves alone. `--dsw-alias-button-primary-fill` and
30
+ `--dsw-alias-button-primary-hover` therefore state both steps in each palette.
31
+ Two fills are palette-specific rather than carried over from the dark base:
32
+ `--dsw-alias-interactive-bg-hover-solid` (a solid chip, lighter than the canvas
33
+ in light) and `--dsw-alias-button-elevated-fill` (a surface above the canvas in
34
+ light, a raised gray in dark).
35
+
24
36
  ## Typography
25
37
 
26
38
  - **Serif display** — headings / editorial statements (`--dsw-font-serif`).
@@ -105,6 +117,18 @@ the account drawer, the session-stats card, and the host's own menu primitive
105
117
  under the hero row's workspace and preset pickers — is meant to start from one
106
118
  recipe. New popovers take it rather than inventing a card.
107
119
 
120
+ Two rules hold across all of them, both owned by `overrides/popover-utils.js`. A
121
+ pointer opens a card only after a **100 ms dwell** — long enough that crossing a
122
+ 28px trigger on the way somewhere else unfolds nothing — and the card closes
123
+ 100 ms after the pointer leaves. Two cards keep their own numbers with the reason
124
+ written where they are used: the model picker's levels close after 150 ms (the
125
+ pointer has to cross level 1 to reach level 2) and the session-stats card waits
126
+ 300 ms to open (its sentence sits mid-row, where a passing pointer would trip
127
+ it). And **only one card is on screen at a time**: each popover registers its
128
+ close path with `registerPopover(name, close)` and calls
129
+ `closeOtherPopovers(name)` on the way open, so opening the model picker folds the
130
+ permission menu, the account drawer, the stats card and the host's hero menu.
131
+
108
132
  **Card**
109
133
 
110
134
  | Property | Value |
@@ -164,7 +188,135 @@ attribute, so it takes the same fade/scale as a one-shot `0.15s` animation; and
164
188
  the host places it *below* its trigger, which is where the composer sits — so
165
189
  `src/overrides/hero-menu.js` re-places it beside the trigger (bottom-aligned,
166
190
  growing upward into the empty hero space, flipping left when the viewport is
167
- tight) with an `important` inline write that outranks the host's own.
191
+ tight). The host re-runs its own placement from its anchor geometry on every
192
+ frame while the card is open, so the position is handed over in two custom
193
+ properties on the card (`--dsh-claude-hero-menu-x` / `--dsh-claude-hero-menu-y`,
194
+ written by the same pass that stamps it) which `components/hero-menu.css` reads
195
+ with `!important`; that declaration outranks the host's plain inline value.
196
+
197
+ ## Home layouts · 首页版面
198
+
199
+ The new-conversation page has two arrangements. `homeLayout` (settings: Home
200
+ layout) writes `data-dsh-claude-home-layout` onto `<body>` and
201
+ `src/styles/components/home-panel.css` branches on it. Both are the host's own
202
+ hero markup — the greeting, the workspace row, the dock and the composer card
203
+ inside `…_composerStack …_composerHero` — so only the arrangement differs.
204
+
205
+ | | Classic | Studio |
206
+ |---|---|---|
207
+ | greeting | centred, 44.2px serif, brand mark on its line | top left, one fixed line naming the user ("What's up next, …?") in the 20px sans UI face, a 21px brand mark on its line |
208
+ | composer card | vertically centred in the scroll body | the conversation's single-line inline form, resting on the window's bottom edge (16px foot) |
209
+ | between them | — | the usage panel |
210
+ | workspace / preset row | the card's footer tray | hairline chips directly above the card |
211
+ | column width | the hero's centred box | a 720px composer column; the greeting and the panel form a 480px block against its left edge |
212
+
213
+ The studio composer is stamped `data-composer-variant="inline"` (composer.js's
214
+ pass reads the same preference), so the home card is drawn by the conversation's
215
+ single-line stylesheet. The classic tray rules in `composer/card.css` are scoped
216
+ away from studio with `:not([data-dsh-claude-home-layout="studio"])`. That is
217
+ deliberate: they set `order: 1` / `order: 2` on the same boxes, and a competing
218
+ rule would have to be out-specified rather than merely reordered — scoping them
219
+ removes the contest.
220
+
221
+ **The usage panel.** It is a `conversation.input.dock` entry (the host's list
222
+ seat between the greeting and the card; a list seat keys its entries by `id`),
223
+ and it renders only in the hero phase — the host mounts that same seat inside a
224
+ conversation, where the todo, queue and goal bars hang on it. The seat is scoped
225
+ to a session, so the cold start screen (no session yet, the card waiting for a
226
+ workspace) has none; there the skin mounts the same component on a
227
+ `display: contents` element of its own (`.dsh-claude-home-seat`) and drops it
228
+ once the host's dock arrives. A window too short for the whole panel shrinks the
229
+ panel, never the page: the greeting keeps the top edge, the chips and the card
230
+ keep the bottom, and the panel scrolls between them with a 32px fade over its
231
+ bottom edge that lifts over the last 32px of travel.
232
+
233
+ The cold start card waits for a workspace: the host marks it with a dashed ring
234
+ and makes every control on it inert, so a press anywhere opens the workspace
235
+ picker. In the studio form the ring sits on the single-line input box itself
236
+ (the host's dashed stroke and colours, flat, no drop shadow), and the permission
237
+ segments fill the card's empty mode strip on the preset a new session starts in
238
+ — disabled, like the rest of the card's controls.
239
+
240
+ The panel is Claude Code's dashboard shape: a flat warm-gray wash (a
241
+ `color-mix` of the label tone at 4% over the card tone, which flips with the
242
+ theme on its own), an Overview/Models tab pair on the left of its head, and the
243
+ All/30d/7d range pills on the right. The active tab and range pill are a gray
244
+ chip one step below that wash — half the radius the 20px control would round to,
245
+ so it reads as a rounded rectangle rather than a full pill. Overview carries six
246
+ stat cells in a 3×2 grid under Claude Code's names (Sessions, Messages, Total
247
+ tokens, Active days, Peak hour, Favorite model) whose tiles sit one clear step
248
+ deeper than the panel (15% of the label tone) and set a 12px label over a 13px
249
+ bold figure — the figure stays barely above its own label, which is also what
250
+ lets a long model id such as deepseek-v4.1-flash sit on one line; the favourite
251
+ model is a name, not a figure, and keeps the regular weight. Messages are the
252
+ settled calls. Once the picked range's total passes one
253
+ book, the yardstick line appears under the grid: eleven books from Animal Farm
254
+ (39k tokens) to In Search of Lost Time (1.6M), each sized at 1.3 tokens a word.
255
+ The book is drawn afresh each time the page comes back to the new-conversation
256
+ hero, and a range that has not reached it steps down to the longest book it has
257
+ passed, so the range pills keep the same book whenever the totals allow. The heat
258
+ grid takes one
259
+ equal column per week (twenty-six weeks), square cells from a 3px gutter, in
260
+ Claude Code's blue data ramp (`#3b6ecf` at 20/40/65/100 over the neutral empty
261
+ cell); because the columns are fractions of the panel's own width, the newest
262
+ week can never fall past the edge. Hovering a cell shows Claude Code's day tip at
263
+ once: a solid pill in the label ink with the canvas tone for text (so it inverts
264
+ with the theme), 13px medium, reading the day in the shell's language and its
265
+ messages ("Sep 9 — 15,955"); over the three columns at either end the pill lines
266
+ up with the cell's outer edge so it stays inside the panel. The session list's
267
+ fallback has no per-day message count, and its tip names the day's tokens.
268
+ Models is Claude Code's own shape: one column
269
+ per day of the chart's thirty-day window, stacked from the axis up with each
270
+ model's slice in its rank's colour (ranks past the ramp share its last, grey
271
+ step), four gridlines with their token labels in a 34px left gutter and every
272
+ third column's date under it in the shell's language ("Aug 26"), and beneath it
273
+ the ranked list — swatch, model name, the input/output split, and the share of
274
+ the models shown — folding past six rows behind one "show more" row, which turns
275
+ into "show less" once the list is open. The chart
276
+ and the list write counts Claude Code's way: one decimal at most, no trailing
277
+ ".0", a lowercase k ("109M", "963.6k"). The list reads the roll-up's per-model
278
+ buckets; the chart reads its per-day per-model map, so a day with nothing
279
+ attributed to a model draws no stack. Each column is sized to its day's total
280
+ against the axis top and each slice to its share of the day, so the column's
281
+ rounding sits on the top of the stack. A range window filters the
282
+ tiles (the peak hour included), the yardstick line and the model list; the heat
283
+ grid and the chart keep their own windows.
284
+
285
+ Two sources, in this order: the host half's usage route, then the session list's
286
+ own projection block (`tokenUsage`, `modelSelection`, `sessionListMetadata`).
287
+ The second answers in a few milliseconds and carries per-model totals without the
288
+ four buckets, which is what the list falls back to when the first cannot answer.
289
+ The first reads the cost-meter ledger when one covers the newest activity — its
290
+ `<provider>:<model>` split becomes the per-model cells, one model across providers
291
+ merged into one — and otherwise the accurate per-event fold; both carry each day's
292
+ session ids, which a range window unions into one distinct session count. Only
293
+ the fold knows the settlement hours: it keeps one hour histogram per day, which a
294
+ range window sums into its own peak hour, and behind a cost-meter answer it still
295
+ runs for the histograms alone — the ledger's figures land first, and the peak
296
+ hour of the sessions whose logs remain lands a moment later. The panel names
297
+ which one it drew from, and a figure neither can answer is a dash.
298
+
299
+ The skeleton keeps the frame's geometry — six fixed-size stat cells, a heat grid
300
+ of a fixed cell count, and on the models tab the chart's frame with three
301
+ stand-in rows — and the heat grid's empty cell **is** the zero step, so "no data"
302
+ and "a day with no usage" stay different things: a missing value draws a
303
+ placeholder, a zero day draws the grid's own base tone.
304
+
305
+ ## The composer crab · 输入卡片上的螃蟹
306
+
307
+ On the studio layout's new-conversation page, Claude Code's pixel crab stands on
308
+ the composer card's top edge: 4px cells, a nine-cell shell with two-cell arms
309
+ (52×32px), feet on the card's edge and the right arm 8px inside the single-line
310
+ card's right edge, so the feet stand where its 18px corner starts to round. The
311
+ classic layout keeps its centred hero without the crab. The shell is the clay accent `#d97757` in both
312
+ themes, the side-on back is `#b9603f`, the eyes `#141413`, and the rod takes the
313
+ tertiary label ink. When the crab is clicked, when the pointer leaves it, and on its own every 25–45
314
+ seconds while the page is in view, it plays Claude Code's fishing routine
315
+ (about three seconds): a half turn and a wink, the rod raised overhead and cast
316
+ down onto the card's edge, a hop, a spell of fishing side-on, and the rod put
317
+ away as it turns back to face front. With reduced motion requested, only a click
318
+ plays it: the pointer passing by and the idle timer leave it still. Only the crab
319
+ takes the pointer; the room the rod swings through does not.
168
320
 
169
321
  ## Implementation notes
170
322
 
@@ -175,7 +327,9 @@ tight) with an `important` inline write that outranks the host's own.
175
327
 
176
328
  The shipped bundle `lib/client.js` is **generated** — never edit it directly.
177
329
  Source lives in `src/` and `node scripts/build.mjs` (or `npm run build`)
178
- assembles the bundle:
330
+ assembles the bundle. The map below annotates what each source is for; the
331
+ assembly order itself is authoritative in `scripts/build.mjs`
332
+ (`FRAGMENTS` / `STYLE_FILES`), which the build enforces:
179
333
 
180
334
  | Source | Content |
181
335
  |---|---|
@@ -191,6 +345,7 @@ assembles the bundle:
191
345
  | `src/styles/composer/inline.css` | in-conversation single-line composer (gated by composer preference) |
192
346
  | `src/styles/composer/inline-bar.css` | the inline composer's trailing bar: model trigger and merged time/usage stats line (gated by composer preference) |
193
347
  | `src/styles/sidebar.css` | sidebar brand, new-session row, workspace tree |
348
+ | `src/styles/components/workspace.css` | the workspace section's 进行中 / 已归档 segment control and the skin's own archived list |
194
349
  | `src/styles/components/permissions.css` | permission segments and popover |
195
350
  | `src/styles/components/account-footer.css` | account row and floating popover |
196
351
  | `src/styles/components/ban-screen.css` | the account-hold easter egg (full-window overlay) |
@@ -201,23 +356,45 @@ assembles the bundle:
201
356
  | `src/styles/components/footer-takeover.css` | host footer takeover rules |
202
357
  | `src/styles/components/third-party.css` | agy-link repair rules |
203
358
  | `src/styles/components/settings.css` | settings page section |
359
+ | `src/styles/components/home-panel.css` | the studio home layout and the usage panel's shell (keyed on the attribute `home-layout.js` writes) |
360
+ | `src/styles/components/home-overview.css` | the usage panel's Overview tab: stat cells, heat grid, yardstick line |
361
+ | `src/styles/components/home-models.css` | the usage panel's Models tab: the stacked chart and the ranked list |
362
+ | `src/styles/components/mascot.css` | the pixel crab: its seat on the card's top edge and its inks |
363
+ | `src/styles/components/theme-flip.css` | theme-flip transition suppression |
204
364
  | `src/context/host.js` | host accessors and helpers |
205
365
  | `src/context/prefs.js` | preference store |
206
366
  | `src/context/model-copy.js` | model copy document store |
207
367
  | `src/context/i18n.js` | localized copy lookups |
208
368
  | `src/overrides/popover-utils.js` | shared anchor positioning and hover intent |
369
+ | `src/overrides/composer.js` | composer layout feature: each pass writes the variant, gate, attachment and context-ring attributes the composer stylesheets read |
209
370
  | `src/overrides/copy.js` | composer/copy rewrites installer |
210
371
  | `src/overrides/session-stats.js` | session-stats card factory (`createSessionStats`), the permissions feature's split-out half |
211
372
  | `src/overrides/permissions.js` | permission segments/popover installer (the stats card lives in `session-stats.js`) |
373
+ | `src/overrides/model-brand.js` | brand lockup lookup: maps a catalog model to its vendored lockup through the `brands` bindings |
374
+ | `src/overrides/model-copy-lookup.js` | model copy lookup: exact entry → family rule → tier rule → the catalog's own text |
212
375
  | `src/overrides/model/catalog.js` | model catalog factory (`createModelCatalog`): the per-session ModelDirectory store |
213
376
  | `src/overrides/model/rows.js` | model row factory (`createModelRows`): option cells, provider rules, level-1 sections |
214
377
  | `src/overrides/model-picker.js` | model picker installer, wiring the `model/` factories (thin orchestration) |
378
+ | `src/overrides/effort/matrix.js` | effort matrix factory (`createEffortMatrix`): the tier data the slider snaps to |
379
+ | `src/overrides/effort/control.js` | effort control factory (`createEffortControl`): the reasoning-effort slider and its card |
380
+ | `src/overrides/effort-picker.js` | effort picker installer, wiring the `effort/` factories (thin orchestration) |
215
381
  | `src/overrides/hero-menu.js` | stamps the host menu card the hero row's pickers open |
382
+ | `src/overrides/quick-providers.js` | the settings page's quick-providers multi-select popover |
216
383
  | `src/overrides/account/profile.js` | account profile factory (`createAccountProfile`): signed-in name/avatar reads and retries |
217
384
  | `src/overrides/account/host-menu.js` | host account menu factory (`createHostAccountMenu`): reads and drives the host's own menu |
385
+ | `src/overrides/account/rows.js` | account rows factory (`createAccountRows`): the account popover's row builders |
218
386
  | `src/overrides/account/footer-mirror.js` | footer mirror factory (`createFooterMirror`): redirects other plugins' footer entries into the drawer |
387
+ | `src/overrides/account/surface.js` | account surface factory (`createAccountSurface`): one row model, picks the mount point each pass (the host's account menu or the self-built popover) |
219
388
  | `src/overrides/account-footer.js` | account drawer installer, wiring the `account/` factories (thin orchestration) |
220
389
  | `src/overrides/ban-screen.js` | account-hold easter egg installer |
390
+ | `src/overrides/theme-flip.js` | suppresses transitions during a theme flip, so colours and shapes land together |
391
+ | `src/overrides/workspace-view.js` | workspace section feature: the 进行中 / 已归档 segments and the archived list (row delete goes through the plugin's own route) |
392
+ | `src/overrides/view-tabs.js` | conversation view tabs: moves the tab bar onto the title row when it fits |
393
+ | `src/overrides/home-layout.js` | home layout feature shell: writes the layout attribute, registers the usage panel into the host's dock seat, and draws the panel's head |
394
+ | `src/overrides/home/data.js` | the usage panel's data: the roll-up route, the session list's fallback, and the figures both tabs read |
395
+ | `src/overrides/home/overview.js` | the usage panel's Overview tab: stat cells, heat grid, yardstick line |
396
+ | `src/overrides/home/models.js` | the usage panel's Models tab: the stacked chart and the ranked list |
397
+ | `src/overrides/mascot.js` | the pixel crab on the new-conversation card: its poses as one inline SVG, and the fishing routine |
221
398
  | `src/overrides/scheduler.js` | scheduler, observers, subscriptions, teardown |
222
399
  | `src/overrides/selection.js` | mirrors the window's focus state onto the document for the two text-selection paints |
223
400
  | `src/settings.js` | settings section (brand switch) |
@@ -247,6 +424,10 @@ rule should be checked for accidental hits:
247
424
  - Never override the host's active-phase layout contract on
248
425
  `[class*="viewArea"]` (`flex: 1 0 auto; min-height: auto`) — it is what
249
426
  keeps the sticky composer seat pinned to the scrollport bottom.
427
+ - An anchor that carries a host button class (`_linkButton`, e.g. the settings
428
+ page's "充值") is a filled or outlined control, not a text link: it states its
429
+ own ink. The blanket anchor colour excludes that class — painting it leaves a
430
+ filled button's label the same colour as its fill.
250
431
 
251
432
  `node scripts/probe.cjs --token <launch-token>` drives a headless Chrome over
252
433
  CDP and asserts the invariants (composer pinned at bottom, single-line start,
@@ -84,13 +84,13 @@
84
84
  ## D11. 插件自有路由借宿主的请求栅栏;宿主/用户来源的字符串只以文本上屏
85
85
 
86
86
  - **背景**:`webServer.register()` 交给插件的是裸请求。宿主自己的 `/api` 挂在 Host/Origin 栅栏与浏览器会话 cookie 之后(`connection.requestRejection()`),插件路由不在其内。本插件的 `/username`(读系统用户名)因此曾经完全无鉴权:`dsh web` 绑定 `0.0.0.0` 时局域网里任何人都能直接读。读到的用户名又被客户端拼进 `innerHTML`——实测在 GUI 页面里执行了脚本,而这个页面能驱动执行 shell 命令的智能体。
87
- - **决定**:宿主半边 `/username` 处理前先调 `ctx.get('connection').requestRejection(req)`,拒绝即回 401/403。宿主没有该服务时,插件自带的替身只服务回环(回环 Host、无跨站标记、Origin 与 Host 一致)。模型文案与字体等静态资产保持公开。删除会话的私有路由 `POST /dsh-claude-style/session-delete` 同样先过栅栏,另加四道:只接受 POST,id 必须匹配宿主自身的会话 id 形状,正在打开的会话拒绝,目录解析后必须留在会话根目录内(删除发生在宿主半边,浏览器半边只提交一个 id)。客户端:一切来自设置、账号服务、系统或第三方插件的字符串只用 `textContent` / 元素属性写入,图标复制节点而非重新解析 markup。
87
+ - **决定**:宿主半边 `/username` 处理前先调 `ctx.get('connection').requestRejection(req)`,拒绝即回 401/403。宿主没有该服务时,插件自带的替身只服务回环(回环 Host、无跨站标记、Origin 与 Host 一致)。模型文案与字体等静态资产保持公开。删除会话的私有路由 `POST /dsh-claude-style/session-delete` 同样先过栅栏,另加四道:只接受 POST,id 必须匹配宿主自身的会话 id 形状,正在打开的会话拒绝,目录解析后必须留在会话根目录内(删除发生在宿主半边,浏览器半边只提交一个 id)。首页用量汇总路由 `GET /dsh-claude-style/usage` 与用户名路由同级:它回的是本机会话历史的聚合结果(每日 token 用量、调用次数、活跃天数),属于用户数据,因此同样先过栅栏,并且只读——第三方插件的账本(`dsh-cost-meter` 的 `ledger.json`)只读不写,自己的汇总落在 `$DSH_HOME/cache/dsh-claude-style/`。客户端:一切来自设置、账号服务、系统或第三方插件的字符串只用 `textContent` / 元素属性写入,图标复制节点而非重新解析 markup。
88
88
  - **代价**:路由依赖宿主的 connection 服务。已核对两种壳都能通过:浏览器同源请求带会话 cookie;0.1.7 桌面壳经 `forwardWebRequest` 转发到回环 Host、剥掉页面 Origin 并自带 cookie(宿主真实的 `isTrustedApiRequest` 对这两种请求放行,对跨站与 DNS 重绑定请求拒绝)。
89
89
  - **重审条件**:宿主为插件提供自带鉴权的路由注册(如 `connection.fetch.register`)时,迁移过去并删掉本地替身。
90
90
 
91
91
  ## D12. 特性级失败隔离:一个特性出错只关掉它自己
92
92
 
93
- - **背景**:`apply()` 依次装 14 个特性,teardown 却在最后才交给 `ctx.effect`;每轮 pass 里各 `sync()` 也没有逐个兜底——一个抛错,排在它后面的全部跳过,而且每轮如此。实测:宿主 `remote.account.getProfile()` 返回非 Promise 时 `apply()` 中途抛错,样式表与 body 属性留在页面上、调度器没装上、teardown 没注册(关掉插件也清不掉),前面装好的特性的监听器一并泄漏。effort-picker 那次「Loading plugins…」卡死是同一类问题。
93
+ - **背景**:`apply()` 依次安装全部特性,teardown 却在最后才交给 `ctx.effect`;每轮 pass 里各 `sync()` 也没有逐个兜底——一个抛错,排在它后面的全部跳过,而且每轮如此。实测:宿主 `remote.account.getProfile()` 返回非 Promise 时 `apply()` 中途抛错,样式表与 body 属性留在页面上、调度器没装上、teardown 没注册(关掉插件也清不掉),前面装好的特性的监听器一并泄漏。effort-picker 那次「Loading plugins…」卡死是同一类问题。
94
94
  - **决定**:
95
95
  - teardown 最先经 `ctx.effect` 注册且幂等;每个特性单独 try/catch 安装,装不上的报一次 `console.error` 并退役。调度器本身装不上时整体回滚到宿主原样——没有调度器,其余特性都不会同步,留着只是一张半套的皮。
96
96
  - 每轮 pass 里每个特性的 `sync()` 单独 try/catch,连续失败 3 轮即报一次并退役(`ui.retire`)。
@@ -116,3 +116,35 @@
116
116
  - **理由**:宿主的菜单自带定位、动画、键盘遍历、Esc 与外部点击关闭,追加进去的行会加入同一套键盘遍历;删除驱动与镜像之后,账号区不再依赖宿主菜单的渲染时序与标签文案。
117
117
  - **代价**:宿主菜单关闭即卸载,容器每轮重新确认并追加;宿主的列表是 React 管理的子树,追加位置固定在列表首位,宿主重渲染后由每轮同步自愈。
118
118
  - **重审条件**:宿主为账号菜单提供追加行的插槽时,改用插槽注册,去掉 DOM 追加。
119
+
120
+ ## D15. HDSL 账号契约:宿主半边转发,浏览器半边只认一条回退顺序
121
+
122
+ - **背景**:HDSL 启动器用 `HDSL_` 前缀的环境变量向实例公布「这是谁」:账号显示名、供应商、账号种类、头像来源,以及玩家头像贴图的绝对路径;`HDSL_ACCOUNT_CONTRACT` 是唯一必需键,读不到它整组作废。浏览器半边读不到进程环境,贴图又只有绝对路径,两者都只能由宿主半边转发。启动器的皮肤是一张 64×64(或整数倍)的贴图集,不是成品头像:脸只是其中 (8,8) 起的 8×8 贴图块,帽子层在 (40,8)。
123
+ - **决定**:
124
+ - 宿主半边读 harness 启动时填好的启动环境快照(`ctx.launchEnvironment`,也就是 `launchEnvironmentOf(ctx)` 内部读的那个槽位),只信 `process` 与 `user-env` 两层(项目目录的 `.env` 会随仓库被克隆,没有资格声明玩家是谁),契约版本不等于 `1` 时回 `{ contract: false }`,其余字段一律不看。**不 import** harness 自带的启动环境包:`link:` 安装的插件解析不到它(与 `resolveSchemaFactory` 同一类问题),而那个包要读的正是这个槽位;宿主没填槽位就是没有契约。
125
+ - 新增两条私有路由,与 `/username` 同级过 `refusalOf` 栅栏:`GET /dsh-claude-style/hdsl` 回账号元数据,**不含**头像文件的绝对路径;`GET /dsh-claude-style/hdsl-skin.png` 回贴图字节——路径只来自环境,永远不来自请求,读不到就回 404 让浏览器回退。契约在进程生命周期内不变,宿主半边读一次并记住。
126
+ - 浏览器半边把昵称与头像各收敛成一条回退顺序:昵称 = 自定义昵称 → 官方账号昵称 → HDSL 昵称 → 探测昵称缓存 → 探测昵称 → `User`;头像 = 官方账号头像 → HDSL 头像 → Claude 徽标(样式表里的底色标记)。官方账号资料与 HDSL 契约各自推给 `src/context/host.js` 的身份存储,两个消费点(欢迎语、账号行)只读这一处。
127
+ - 头像不是直接贴上去的:启动器给的是贴图集,浏览器按启动器账号列表同样的裁法取头部(脸的 8×8 贴图块按盒子 1/18 内缩、帽子层铺满整盒)画进 64×64 画布再缩到账号位的尺寸,方形绘制不被圆角裁切;贴图只以裁出的头部出现,加载完成前、图取不到、契约没带图、文件被删时都显示徽标。画完主动叫一次 pass:贴图加载不改 DOM,没有 pass 会自己来。
128
+ - **理由**:契约读一次就够,所以路由只转发、不轮询;把两条链收在一处,欢迎语、账号行、封号彩蛋不会各自漂移;缓存探测到的系统用户名,是为了刷新页面时欢迎语不再从 `User` 跳一次;裁脸几何与启动器账号列表一致,同一个玩家在启动器与皮肤里长得一样。
129
+ - **代价**:桌面端宿主的账号行由宿主自己渲染,插件只在自建账号行上画头像,所以 HDSL 头像在桌面端只作用于插件自己的表面;只有玩家自选的本地图片能画,启动器内置的默认形象(steve/alex 等)没有像素可用,回退到徽标;契约在进程生命周期内不变,玩家中途换皮肤要下次启动才生效。
130
+ - **重审条件**:HDSL 契约递增版本(其 v2 计划里有按指纹取图的媒体通道与账号 UUID)时,取图改为优先走该通道、以指纹作缓存键,按玩家存储的数据改用 UUID 作键;宿主提供「显示名 / 头像」的统一服务时,改接那一处,删掉本插件的转发路由。
131
+
132
+ ## D16. 弹层共用两条基准:一个停留时长,一次只开一张卡
133
+
134
+ - **背景**:皮肤的弹层从六处各自长出来(模型选择器、推理强度、权限、账户、会话统计、hero 行的宿主菜单,另加设置页的快捷供应商),每处自己写停留时长与收起宽限。停留原本是 50ms,指针以平常速度扫过就已经超过它,于是位于指针必经之路上的触发器(工作台版面把上下文行贴在输入卡片正上方)看上去没有停留;同时「同时只开一张卡」此前只在模型与推理这一对上硬编码(两侧各自调用对方的 close),换一对弹层就会两张卡叠在一起。
135
+ - **决定**:
136
+ - 停留与宽限收到 `overrides/popover-utils.js` 的 `POPOVER_OPEN_DELAY`(100ms)与 `POPOVER_CLOSE_DELAY`(100ms)。100ms 是指针以平常速度扫过 28px 触发器所需的时间量级,路过不再展开,停在上面照常立即展开。两处例外连同理由写在使用处:模型选择器的两级卡片收起宽限 150ms(指针要横穿一级卡片才能到二级),会话统计卡片展开停留 300ms(统计句位于输入行中部,路过容易误触)。
137
+ - 同一文件里维护一张弹层登记表:`registerPopover(name, close)` 登记每张弹层的关闭路径,`closeOtherPopovers(name)` 在打开前关掉其余弹层。一个弹层的多层卡片按一项登记(模型选择器的一级与二级同名 `model`),宿主的菜单由驱动它的特性登记(hero 菜单、账户菜单),关闭动作各自复用既有路径——皮肤自己的卡片直接收,宿主的菜单再次点击其触发位或派发 Escape。
138
+ - hero 行的一项登记对应两个宿主菜单(工作文件夹与预设模式),所以 `hero-menu.js` 自己再分一次:打开前先收起行内另一个已展开的触发位,悬停离开时只收悬停打开的那一个,每轮同步里若发现行内两个触发位同时展开也收掉多余的一个。判定按「已展开的触发位」而不是「body 下的菜单卡片」——后者数得到宿主别处的菜单,前者才是这一行的选择器。
139
+ - 登记表只存关闭函数,不记「谁开着」:每个关闭函数在弹层未开时是空操作,所以用户用 Escape 或外部按下关掉的卡片不会留下过期状态。按名字覆盖登记,使客户端热重载后残留的旧一代闭包被新一代覆盖。
140
+ - **理由**:停留时长是同一个交互问题(路过还是停留),分处写必然漂移;互斥此前成对硬编码,新增弹层时要记得改 N 处,登记表把它变成一次登记。
141
+ - **代价**:登记表是模块级状态,特性卸载必须 `unregisterPopover`,否则旧一代的关闭函数留在表里。
142
+ - **重审条件**:宿主提供统一的弹层管理(全局互斥或焦点陷阱)时,改接宿主的机制,删掉本插件的登记表。
143
+
144
+ ## D17. 权限档位以宿主目录为准,皮肤只做表现层
145
+
146
+ - **背景**:权限控件(输入框分段控件与它的弹层)此前把四档写死在两张常量表里,只对内置 `auto`(Auto review)做一次目录判定。于是第三方插件注册的权限档位看不见也点不到——例如 auto mode 插件的 `auto-mode`(宿主 `permissionPresets` 目录里的一等条目:确定性规则 + 两阶段分类器),而且会话已经处于该档位时,触发键上会显示机器值 `auto-mode`。宿主的目录是「当前可选档位」的权威:`PermissionCatalog.options` 由宿主按贡献顺序给出,`PresetOption` 只有 `value` / `name` / `description`(部署打上 automode 提供的补丁或宿主将来原生支持时另有 `icon`)。
147
+ - **决定**:段位与弹层行都按宿主目录构建——目录里有的档位才画,宿主没提供的整条不出现(不再有「画出来再隐藏」);`PERMISSION_SEGMENTS` 只描述**格子**,每格列出可绑定的档位、按先后取第一个宿主提供的(部署自己的自动档优先于宿主内置的 Auto review),一格都绑不上就不画。档位名与说明来自皮肤的 `PERMISSION_PRESETS`,皮肤不认识的档位用目录自带的 `name`/`description`,永不把机器值上屏(宿主的 `custom` 用宿主自己的词)。行的内容是纯文字:档位声明的 `icon`(部署方写在预设上的 SVG 路径,宿主原生会忽略、由第三方补丁开启)**不画**——同一条列表里只有第三方档位带图标会读成两种东西,档位之间的区别交给名字与说明。切换仍走宿主的 `/permission <preset>`(与 automode 的 `/auto` 是同一条写入路径)。
148
+ - **理由**:目录变化时皮肤不再漏档位;第三方档位成为一等公民而皮肤不需要认识它;名字由皮肤给、档位由宿主提供,两件事各自独立,接入新档位不用改皮肤代码,也不必让第三方为图标去打宿主 `node_modules` 的补丁。
149
+ - **代价**:档位集合变化时要重建行与段位(此前只改显隐);皮肤的表现表是一张白名单,不认识的档位会以宿主给的名字出现(可能不够 Claude 风味)但不会消失。
150
+ - **重审条件**:宿主原生支持预设 `icon`、且界面上有了统一的档位图形语言时,再考虑把图标画回来。