dsh-claude-style 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.5] - 2026-09-20
4
+
5
+ ### Added
6
+ - **模型文案改为运行时读取的数据文件**:文案表迁出 bundle,落到 `src/model-descriptions.json`(构建时校验后随包发布为 `lib/model-descriptions.json`);宿主半边新增 `/dsh-claude-style/model-descriptions.json` 路由按请求读取该文件,浏览器半边在首次绘制模型选择器时 fetch 并按需缓存。此后扩充文案表无需重新构建、也不进 bundle(产物内已无任何模型文案)。解析按「精确条目 → 家族规则 → 档位规则 → 目录自带文本」逐级降级;精确条目按**归一化模型 id** 建键,同一模型被多个 provider 转售(`deepseek-v4-flash` 同时在 deepseek-official 与 opencode-go)折叠为一条;家族规则有序且锚定,且**一律不带最高级**——「最强/旗舰」只写在钉住版本的精确条目里,否则旧版本(如 `gemini-1.5-pro`)会被误称旗舰。取不到文档时静默回退目录自带文本,不影响选择器可用。
7
+ - **补充新模型线文案**:为 Artificial Analysis 榜单上此前未收录的模型线补写家族规则——Meta Muse Spark(Agent 与编码线,与 Llama 开源线区分)、Celeris-1 与 Inception Labs Mercury 2(扩散式 LLM,措辞按延迟而非能力展开)、Apodex(复杂专业工作的 Agent)、Motif 3(韩国 Motif Technologies 全自研开源权重 MoE,314B 总参 / 13.2B 激活)。榜单头部 40 个模型现已 100% 命中;仍未收录的新模型线按设计回退到目录自带文本,不编造文案。
8
+
9
+ ### Fixed
10
+ - **模型文案的两处误判**:其一,参数量档位规则 `\d+b` 未锚定,把 MoE 命名里的激活参数当成总参——`qwen3.8-2.4t-a95b`(2.4T 总参)与 `k2-horizon-375b-a23b` 都被读成「小尺寸稠密模型」;现拆出 `a\d+b` 激活参数规则并让稠密规则要求数字前有分隔符。其二,provider 兜底对多产品线厂商过于宽松,`north-mini-code` 被按 provider 名匹配成 Cohere Command;现补 `north` 家族规则。另补 `gpt-oss` 开源权重系列规则,并去掉家族规则里的最高级表述(见上)。
11
+
12
+ ### Changed
13
+ - **源码按特性级分片重构**:`src/overrides.js` 拆为 `overrides/*.js`(copy / permissions / model-picker / account-footer / scheduler + 共享 popover-utils),`src/context.js` 拆为 `context/*.js`(host / prefs / model-copy / i18n),CSS 按 composer 与 components 拆为独立文件;build.mjs 改为清单驱动并新增碎片守门(禁 import/export、强制 4 空格缩进)。调度器不再读取特性闭包变量,改为通过 `ui` 句柄注册表调用各特性的 sync/close/owns/reposition/invalidateCopy 等接口;`settings.js` 不再重复调用 `loadPrefs()`。构建产物 `lib/client.js` 仍为单文件,不引入新依赖。
14
+ - **模型文案跟随全局语言、单行显示**:描述按 shell 自身的 `locale` 服务取当前语言(`zh` / `en`),每行只渲染一条,不再中英两行叠加;并订阅 locale 变更,切换语言时已渲染的弹层即时重绘。选择器自身的 UI 文案(触发按钮 aria、加载中、空目录、推理等级、More models)走同一路径,bundle 内只保留取不到文档时的中性英文兜底。
15
+
16
+ ### Fixed
17
+ - **塌缩态底栏插件控件压住 Claude 标**:皮肤把 `settingsArea` 压成零尺寸(保留 `overflow: visible` 让浮层可画),展开态下这足以把条目挤成 4px 细条;但设置插件带了自己的 rail 变体(`…_rail`),塌缩时拿到固定 36×36 并逃出零尺寸盒子,正好压在账户控件的 Claude 标上。现于塌缩态隐藏 `settingsArea` 内的按钮与 `triggerRow`;可达性不受影响——弹层镜像项用 `realTrigger.click()` 驱动真实触发器,`display:none` 不阻断。
18
+ - **塌缩态账户弹层被侧栏容器裁掉**:侧栏列宽 56px 且 `overflow: hidden`,弹层原本 `position: absolute` 锚在轨道右侧,越过轨道边缘即被整块裁掉(同时基础规则的百分比 `max-width` 以 59px 页脚为基准把它压成 43px 宽)。现塌缩态改为 `position: fixed`(祖先链无 `transform`/`contain`,故不受该 `overflow` 裁剪),坐标由 `positionAccountPopover()` 解析(内联 `!important`,因为样式表侧同样用 `!important` 锚定);过渡桥同步由「朝下」转为「朝侧」。
19
+ - **修复设置页(含 SubAgent 分页)文字整页消失**:皮肤为隐藏侧栏底栏原按钮,把 `footArea` 下的 `settingsArea`/`footerActions` 容器压成零尺寸并写了 `font-size: 0 !important; line-height: 0 !important`(保留 `overflow: visible` 让浮层可画)。但宿主设置弹窗是**就地渲染**在 `settingsArea` 子树里的(弹窗 overlay 是触发行的兄弟节点,没有 portal),零字号/零行高沿子树继承——官方组件只给文本行设了 `font-size`、没设 `line-height`,继承到 0px 行高的行盒直接塌成 0 高,整页文字"消失"。现把两个容器的折叠拆开:`settingsArea` 只保留几何折叠(零宽高 + `overflow: visible`),字体度量恢复自然继承——它除了被 `display: none` 的触发行就只剩就地弹窗,无需零字号兜底;`footerActions` 仍托管着会被镜像改道的插件原始条目,散落的行内文字仍需零字号压住,维持完整折叠。
20
+
3
21
  ## [0.2.4] - 2026-09-19
4
22
 
5
23
  ### Changed
package/README.en.md CHANGED
@@ -84,6 +84,7 @@ Then restart `dsh web`; remove hand-added rows for this theme from `cordis.patch
84
84
  | [Design Tokens](docs/STYLE.md) | Palette, typography, shapes, plus the bundle source layout and host-selector discipline |
85
85
  | [Changelog](CHANGELOG.md) | Version history |
86
86
  | [Contributing](CONTRIBUTING.md) | How to build from `src/`, commit rules, and the screenshot/regression tooling |
87
+ | [AGENTS.md](AGENTS.md) | Development guide for AI assistants: build commands, fragment rules, host-selector discipline, and the release flow (Chinese) |
87
88
 
88
89
  ## Related Links
89
90
 
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # DSH Claude Style
2
2
 
3
- **为 DeepSeek Harness (DSH) Web 客户端复刻 Claude Code Desktop 视觉与交互体验的主题插件 —— 暖调象牙画布、陶烬橙强调色,以及在 DSH 上重筑的 Claude Code 交互模型。**
3
+ **为 DeepSeek Harness Web 复刻 Claude Code Desktop 风格与交互体验的主题插件。**
4
4
 
5
5
  <div align="center">
6
6
 
7
- > **咖啡与 Claude 的下午茶时光。**
7
+ > **Coffee and Claude time?**
8
8
 
9
9
  [![English](https://img.shields.io/badge/lang-English-blue.svg)](README.en.md) [![简体中文](https://img.shields.io/badge/lang-%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-red.svg)](README.md)
10
10
 
@@ -95,6 +95,7 @@ dsh plugin --profile web remove dsh-claude-style # 卸载
95
95
  | [设计令牌](docs/STYLE.md) | 调色板、字体、形状,源码结构与宿主选择器纪律(英文) |
96
96
  | [更新日志](CHANGELOG.md) | 版本历史 |
97
97
  | [贡献指南](CONTRIBUTING.md) | 如何从 `src/` 构建、提交规范与截图/回归工具(英文) |
98
+ | [AGENTS.md](AGENTS.md) | AI 助手的开发指南:构建命令、碎片规则、宿主选择器纪律与发布流程 |
98
99
 
99
100
  ## 友链
100
101
 
@@ -0,0 +1,294 @@
1
+ # dsh-claude-style 架构重构规划
2
+
3
+ > 交付给开发 Agent 的执行文档。目标:在不改变任何运行时行为的前提下,把
4
+ > 现有"Zone 级"分片细化为"特性级"分片,消除单文件冗长和闭包隐式耦合。
5
+ >
6
+ > **总原则:阶段 1–3 必须做到构建产物 lib/client.js 逐字节不变;阶段 4
7
+ > 只允许结构变化,不允许行为变化。**
8
+
9
+ ## 1. 背景与硬约束(先读)
10
+
11
+ - 运行时约束:DSH 模块加载器**没有相对 require、没有资产 URL**,所以
12
+ `scripts/build.mjs` 把 `src/` 碎片**按固定顺序逐字拼接**进一个工厂函数
13
+ 作用域,生成单文件 `lib/client.js`。**这个架构保留**,不引入
14
+ esbuild/rollup 等打包器,不引入任何新依赖。
15
+ - 碎片规则(现状即如此,重构后继续遵守,并在 build 里加守门):
16
+ - 所有碎片共享一个工厂作用域,**禁止使用 import/export**;
17
+ - 每个碎片保持 **4 空格基础缩进**;
18
+ - 风格保持 ES5(var、function 声明),与现状一致;
19
+ - 跨碎片引用靠共享作用域:function 声明会提升,且运行时调用都发生在
20
+ apply() 之后,因此顺序安全;但清单顺序仍须按 §6 固定。
21
+ - 宿主端 `lib/index.js`、文案数据 `src/model-descriptions.json`、
22
+ `skin.json`、`cordis.patch.yml` **不在本次重构范围内**。
23
+
24
+ ## 2. 现状问题(重构动机,仅供理解)
25
+
26
+ | 文件 | 行数 | 问题 |
27
+ |---|---|---|
28
+ | `src/overrides.js` | 1928 | 一个 `installOverrides(ctx)` 函数塞了 5 个特性;Zone 5 调度器直接读各特性的闭包变量(`modelBtn`/`modelPop`/`accountPopover`/`modelBodySig` 等);`var MARGIN = 8` 在同一作用域重复声明(986、1223 行);章节编号已乱(4.4 排在 4.3 前) |
29
+ | `src/context.js` | 357 | 混合 4 种职责:宿主访问器、prefs store、model copy store、i18n |
30
+ | `src/styles/components.css` | 742 | 权限段、账户弹层、模型选择器、页脚接管、第三方修复、设置页 6 块混排 |
31
+ | `src/styles/composer.css` | 710 | hero / 输入卡片 / 会话内单行 composer 3 块混排 |
32
+
33
+ ## 3. 目标目录结构
34
+
35
+ ```
36
+ src/
37
+ constants.js # 不变(原 Zone 1)
38
+ context/
39
+ host.js # 宿主访问器
40
+ prefs.js # 偏好 store
41
+ model-copy.js # 文案文档 store
42
+ i18n.js # 本地化取值
43
+ overrides/
44
+ popover-utils.js # 锚定定位 + 悬停开闭(新提取的共享帮助器)
45
+ copy.js # 原 4.1 文案改写
46
+ permissions.js # 原 4.2 权限段控件 + 会话内弹层
47
+ model-picker.js # 原 4.4 模型选择器
48
+ account-footer.js # 原 4.3 账户页脚 + 弹层
49
+ scheduler.js # 原 Zone 5 调度与生命周期
50
+ settings.js # 不变(设置页)
51
+ entry.js # 重写为编排器(见 §5)
52
+ styles/
53
+ tokens.css # 不变
54
+ typography.css # 不变
55
+ chrome.css # 不变
56
+ composer/
57
+ hero.css # composer.css 第 1–45 行(hero 品牌区,gate 标记之前)
58
+ card.css # composer.css 第 46–246 行(含 @composer-gate 标记)
59
+ inline.css # composer.css 第 247 行至末尾
60
+ sidebar.css # 不变
61
+ components/
62
+ permissions.css # components.css 第 1–136 行
63
+ account-footer.css # components.css 第 137–261 行
64
+ model-picker.css # components.css 第 262–501 行
65
+ footer-takeover.css # components.css 第 502–648 行
66
+ third-party.css # components.css 第 649–683 行(agy-link 修复)
67
+ settings.css # components.css 第 684 行至末尾
68
+ ```
69
+
70
+ **CSS 切分必须严格按上表边界整块移动**,保证拼接后的最终 CSS 与现状
71
+ 逐字节一致(仅文件衔接处的空行可差)。行号以切分当时的文件为准,动手前
72
+ 先以各 `/* ---------- ... ---------- */` 区块注释复核边界。
73
+
74
+ ## 4. 逐文件映射(JS)
75
+
76
+ ### context/(来自 src/context.js,纯移动、不改逻辑)
77
+
78
+ | 新文件 | 移入的函数/变量 |
79
+ |---|---|
80
+ | `context/host.js` | `findAccessTrigger`、`currentSessionId`、`currentSession`、`currentPreset`、`getUsername`、`hostCtx`、`setHostContext` |
81
+ | `context/prefs.js` | `prefs`、`prefsRevision`、`prefsAvailable`、`prefsListeners`、`readPrefs`、`subscribePrefs`、`adoptPrefs`、`loadPrefs`、`normalizePrefs`、`savePrefs`、`applyBrand` |
82
+ | `context/model-copy.js` | `modelCopy`/`modelCopyRequested`/`modelCopyListeners`、`onModelCopyLoaded`、`loadModelCopy`、`indexModelCopy`、`normalizeModelId` |
83
+ | `context/i18n.js` | `activeLocale`、`localized`、`copyLabel`、`settingsCopy` |
84
+
85
+ 依赖方向:i18n → model-copy → constants;prefs → constants。清单顺序即此序。
86
+
87
+ ### overrides/(来自 src/overrides.js,阶段 4,含结构重构)
88
+
89
+ | 新文件 | 移入内容 |
90
+ |---|---|
91
+ | `popover-utils.js` | 新提取:`POPOVER_MARGIN = 8`(替换两处重复的 `var MARGIN`);`positionAnchoredPopover(trigger, pop, opts)` 统一 `positionModelPopovers`/`positionAccountPopover` 的锚定几何;`createHoverIntent(open, close, delay)` 统一 `scheduleCloseModel`/`cancelCloseModel` 与 `scheduleClosePopover`/`cancelClosePopover` 两套定时 |
92
+ | `copy.js` | `HINT_SOURCES`、`rewriteHeadline`、`isHeroView`、`isComposerActive`、`rewriteHint`、`pickRandomSpinnerVerb`、`rewriteTurnStatus` |
93
+ | `permissions.js` | `buildSegments`、`closePermMenu`、`buildPermTriggerAndPopover`、`openPerm`、`updatePermState`、`submitPreset`、`openShippedGate`、`seek`、`pick`、`syncAttachmentState`、`mergeStatsIntoRow`、`syncSegments`、`syncChatTabComposer` |
94
+ | `model-picker.js` | `exactModelCopy`、`familyModelCopy`、`tierModelCopy`、`cancelCloseModel`、`scheduleCloseModel`、`closeModelPopovers`、`openModelPopover`、`openModelSub`、`currentModelSessionId`、`dropModelSubscription`、`modelDirectory`、`modelSnapshot`、`modelCurrent`、`modelEffort`、`modelDescription`、`modelEl`、`MODEL_CHECK_SVG`/`MODEL_CHEVRON_SVG`/`MODEL_CHEVRON_DOWN_SVG`、`buildModelOption`、`buildModelCell`、`pickModel`、`pickEffort`、`renderModelBody`、`renderModelSub`、`positionModelPopovers`、`ensureModelChrome`、`syncModelControl`,以及原 565–579 行的特性状态变量 |
95
+ | `account-footer.js` | `openPopover`、`closePopover`、`togglePopover`、`scheduleClosePopover`、`cancelClosePopover`、`positionAccountPopover`、`syncPopoverItems`、`syncFooterActionVisibility`、`footerEntriesOf`、`markFooterHiddenBranches`、`removeActionMirror`、`removeEmbedMirror`、`entryIsActionLike`、`textExcludingOverlays`、`syncEmbedMirror`、`INTERACTIVE_SELECTOR`、`resolveEmbedActivator`、`hasOverlayAncestor`、`findFooterTrigger`、`dropAccountFooter`、`syncAccountFooter`,以及原 1138–1139 行的特性状态变量 |
96
+ | `scheduler.js` | `onGlobalPointerDown`、`onGlobalKeyDown`、`onCardPointerDown`、`onFixedPopoverViewportChange`、`onLocaleChange`、`schedule`、MutationObserver/ResizeObserver 装配、问候语定时器、全部退订清理 |
97
+
98
+ ## 5. 核心设计变更:ui 句柄注册表(阶段 4 的灵魂)
99
+
100
+ 现状 Zone 5 直接读各特性的闭包变量。重构后每个特性是一个顶层安装函数,
101
+ **入参 (ctx, ui),返回 teardown 函数**,并把自己的对外句柄挂到 ui 上。
102
+ 调度器只认识句柄,不认识任何特性内部变量。
103
+
104
+ ### 句柄契约(签名固定,不得增减)
105
+
106
+ ```js
107
+ ui.copy = {
108
+ sync: ..., // rewriteHeadline + rewriteHint + rewriteTurnStatus
109
+ syncGreeting: ..., // 仅 rewriteHeadline(给每分钟滚动的问候语定时器)
110
+ }
111
+ ui.permissions = {
112
+ sync: ..., // syncAttachmentState + mergeStatsIntoRow + syncSegments + syncChatTabComposer
113
+ closeMenu: ..., // closePermMenu
114
+ }
115
+ ui.model = {
116
+ sync: ..., // syncModelControl
117
+ close: ..., // closeModelPopovers
118
+ owns: ..., // (target) => target 是否落在 modelBtn/modelPop/modelSubPop 任一之内
119
+ reposition: ..., // positionModelPopovers
120
+ invalidateCopy: ..., // modelBodySig = '' ; modelSubSig = '' (locale/prefs/copy 变化时)
121
+ teardown: ..., // cancelCloseModel + dropModelSubscription + 状态归零(现 teardown 中模型相关段)
122
+ }
123
+ ui.footer = {
124
+ sync: ..., // syncAccountFooter
125
+ close: ..., // closePopover
126
+ owns: ..., // (target) => target 是否落在 accountBtn/accountPopover 内
127
+ isOpen: ..., // () => accountPopover 存在且 data-open === 'true'
128
+ reposition: ..., // positionAccountPopover
129
+ }
130
+ ```
131
+
132
+ ### entry.js 重写为编排器
133
+
134
+ `apply(ctx)` 内的装配顺序固定为:
135
+
136
+ ```js
137
+ var ui = {}
138
+ var teardowns = []
139
+ teardowns.push(installCopy(ctx, ui))
140
+ teardowns.push(installPermissions(ctx, ui))
141
+ teardowns.push(installModelPicker(ctx, ui))
142
+ teardowns.push(installAccountFooter(ctx, ui))
143
+ teardowns.push(installScheduler(ctx, ui)) // 最后装,回调中惰性读 ui 句柄
144
+ var stopSettings = installSettingsSection(ctx)
145
+ // ctx.effect 清理:倒序跑 teardowns + stopSettings + 现有 body 属性清理
146
+ ```
147
+
148
+ `installOverrides` 这个名字随 Zone 概念一起废弃。
149
+
150
+ ### scheduler.js 的职责边界
151
+
152
+ - 拥有:RAF 合并的 `schedule()`(体内改为调用 `ui.copy.sync()`、
153
+ `ui.permissions.sync()`、`ui.model.sync()`、`ui.footer.sync()`,以及
154
+ `ui.footer.isOpen() && ui.footer.reposition()`)、MutationObserver、
155
+ composer 卡片的 ResizeObserver、问候语 setInterval(调
156
+ `ui.copy.syncGreeting()`)、全局 pointerdown/keydown/resize/scroll
157
+ 监听、locale/prefs/modelCopy 三个订阅(回调统一为:先
158
+ `ui.model.invalidateCopy()` 再 `schedule()`)。
159
+ - 全局事件中的特性判断一律走句柄:`onGlobalPointerDown` 用
160
+ `ui.model.owns(target)` / `ui.footer.owns(target)`;Escape 用三个
161
+ close 句柄。
162
+ - Enter 发送、Ctrl+, 打开设置、卡片点击聚焦这三个全局键鼠逻辑**留在
163
+ scheduler**(它们是全局行为,不属于任何单一特性)。
164
+ - 注意现状 `loadPrefs()` 被 Zone 5 和 `installSettingsSection` 各调一次:
165
+ 重构后由 scheduler 统一调用,`installSettingsSection` 里的那次删除。
166
+ (`loadModelCopy` 有 `modelCopyRequested` 幂等保护,两处保留无妨;
167
+ `loadPrefs` 没有幂等保护,必须只留一处。)
168
+
169
+ ## 6. build.mjs 改造(阶段 1)
170
+
171
+ 1. 顶部新增两个有序清单,替换 `main()` 里硬编码的 `fragment(...)` 调用
172
+ 和现有 `STYLE_FILES`:
173
+
174
+ ```js
175
+ const FRAGMENTS = [
176
+ 'constants.js',
177
+ 'context/host.js',
178
+ 'context/prefs.js',
179
+ 'context/model-copy.js',
180
+ 'context/i18n.js',
181
+ 'overrides/popover-utils.js',
182
+ 'overrides/copy.js',
183
+ 'overrides/permissions.js',
184
+ 'overrides/model-picker.js',
185
+ 'overrides/account-footer.js',
186
+ 'overrides/scheduler.js',
187
+ 'settings.js',
188
+ 'entry.js',
189
+ ]
190
+
191
+ // gate: true 表示该样式表内含 /* @composer-gate */ 标记,走 gateComposerScope
192
+ const STYLE_FILES = [
193
+ { file: 'tokens.css' },
194
+ { file: 'typography.css' },
195
+ { file: 'chrome.css' },
196
+ { file: 'composer/hero.css' },
197
+ { file: 'composer/card.css', gate: true },
198
+ { file: 'composer/inline.css', gate: true },
199
+ { file: 'sidebar.css' },
200
+ { file: 'components/permissions.css' },
201
+ { file: 'components/account-footer.css' },
202
+ { file: 'components/model-picker.css' },
203
+ { file: 'components/footer-takeover.css' },
204
+ { file: 'components/third-party.css' },
205
+ { file: 'components/settings.css' },
206
+ ]
207
+ ```
208
+
209
+ 注意:`@composer-gate` 标记**只保留在 composer/card.css**(它在原文件
210
+ 第 47 行,正好落在 card 段首);hero 段在标记之前、不需要门;inline 段
211
+ 原本全在标记之后——切分时要为 inline.css 重新处理:把整段视为已盖门
212
+ 内容,做法是**给 inline.css 文件首行补一个 @composer-gate 标记并设
213
+ gate: true**(这样 hero/card/inline 三个文件全部 gate: true,清单更
214
+ 一致,守门逻辑不变)。拼接顺序必须严格等于上表——这是产物逐字节不变
215
+ 的前提。
216
+
217
+ 2. `HEADER` 里的目录注释改为从两个清单自动生成,杜绝文档漂移。
218
+ 3. 新增碎片守门(任一违规即构建失败):
219
+ - 碎片匹配 `/^[ \t]*(import|export)[ \t]/m` → 报错并指出文件;
220
+ - 碎片的非空行若不以前 4 个空格开头(注释续行 ` * ` 也满足,因为它
221
+ 以空格开头)→ 报错并指出文件与行号。
222
+ 4. 构建日志改为报告碎片数与样式表数(从清单取长度)。
223
+
224
+ ## 7. 分阶段执行与验收
225
+
226
+ ### 阶段 0:基线
227
+ - 跑 `npm run build`,把 `lib/client.js` 复制为 `.debug/baseline-client.js`。
228
+
229
+ ### 阶段 1:build.mjs 清单化 + 守门(不改任何 src 内容)
230
+ - 清单先填**现有**文件名(constants.js、context.js、overrides.js、
231
+ settings.js、entry.js 与现有 6 个 css)。
232
+ - 验收:`npm run build` 后 `lib/client.js` 与基线**逐字节相同**
233
+ (`git diff --no-index` 为空)。
234
+
235
+ ### 阶段 2:context.js 拆分(纯移动)
236
+ - 按 §4 表移动,不改任何函数体。
237
+ - 验收:构建产物与基线逐字节相同。
238
+
239
+ ### 阶段 3:CSS 拆分(纯移动)
240
+ - 按 §3 边界整块移动,更新 STYLE_FILES。
241
+ - 验收:构建产物与基线逐字节相同;`gateComposerScope` 的"未盖门即报错"
242
+ 检查仍然生效(可故意删一条规则的门验证报错,再还原)。
243
+
244
+ ### 阶段 4:overrides.js 拆分 + ui 注册表(唯一动结构的阶段)
245
+ - 先做纯移动把 5 个特性切成文件(此时调度器仍在原位置、仍读闭包变量,
246
+ 产物可保持逐字节一致),再做 ui 句柄化与 entry 编排器化。
247
+ - `popover-utils.js` 的 `positionAnchoredPopover`/`createHoverIntent`
248
+ 提取放本阶段;提取时必须保持两处弹层现有定位结果像素级一致
249
+ (先抽象、逐参数对照,再替换调用点)。
250
+ - 验收:
251
+ 1. `npm run build` 通过(vm 语法门);
252
+ 2. 无 `MARGIN` 重复声明;scheduler.js 中 grep 不到
253
+ `modelBtn`/`modelPop`/`accountPopover`/`modelBodySig` 等特性内部
254
+ 变量;
255
+ 3. 人工冒烟清单(§8)全过。
256
+
257
+ ### 阶段 5(可选,单独提交):文案数据外移
258
+ - `SPINNER_VERBS`、`PERMISSION_SEGMENTS`/`PERMISSION_OPTIONS`、
259
+ `HINT_SOURCES` 迁入 `src/copy.json`,由 build.mjs 读取、校验后以内联
260
+ var 声明注入 bundle(复用现有 JSON 校验思路)。**不做也不影响验收。**
261
+
262
+ ## 8. 人工冒烟清单(阶段 4 验收用)
263
+
264
+ 在 DSH web GUI 中逐项验证(与重构前行为完全一致才算过):
265
+
266
+ 1. 新会话页:时段问候语出现且每分钟翻转不错位;composer hint 被改写;
267
+ 2. 发送一条消息:思考中显示 Claude spinner 动词且同一轮内稳定;
268
+ 3. 权限段控件:Read/Edit/Auto 三态切换;会话内弹层可开可关;
269
+ Auto 触发宿主的风险确认;
270
+ 4. 模型选择器:两级弹层开/关、悬停桥、选模型、选推理档位、描述行跟随
271
+ 界面语言;Escape 与点击外部均关闭;
272
+ 5. 账户页脚:悬停/点击开弹层(受 autoPopover 偏好控制)、定位不漂移、
273
+ 折叠 rail 模式正常;
274
+ 6. Ctrl+, 打开设置;设置页 4 个控件(品牌/折叠页脚/悬停弹层/输入区
275
+ 范围)切换后刷新仍保持;
276
+ 7. Enter 发送、Shift+Enter 换行、IME 组合输入不被截获;
277
+ 8. 切换界面语言后模型选择器文案即时跟随;
278
+ 9. 重载插件(或刷新页面)后无残留 DOM/监听,控制台无报错。
279
+
280
+ ## 9. 收尾
281
+
282
+ - grep 全仓 "Zone" 字样的注释与文档(含 `docs/STYLE.md`、README、
283
+ build.mjs 头注释),按新结构改写;
284
+ - 变更写入 `CHANGELOG.md`;
285
+ - 完成后每个 src 文件应 ≤ 约 650 行,最大的应是
286
+ `overrides/model-picker.js` 与 `overrides/account-footer.js`(各约
287
+ 550–600 行);若超过,再评估二次拆分,不在本次范围。
288
+
289
+ ## 10. 非目标(明确不做)
290
+
291
+ - 不改任何视觉样式与交互行为;
292
+ - 不引入打包器/依赖/TypeScript;
293
+ - 不动 `lib/index.js`(宿主端)、`src/model-descriptions.json`;
294
+ - 不动 `src/settings.js` 的组件实现(仅它在清单中的位置不变)。
package/docs/STYLE.md CHANGED
@@ -51,26 +51,54 @@ The shipped bundle `lib/client.js` is **generated** — never edit it directly.
51
51
  Source lives in `src/` and `node scripts/build.mjs` (or `npm run build`)
52
52
  assembles the bundle:
53
53
 
54
- | Source | Zone | Content |
55
- |---|---|---|
56
- | `src/constants.js` | 1 | constants & spinner verbs (evaluated at build time to fill `%%TOKEN%%` placeholders) |
57
- | `src/assets/*.svg` | — | brand marks, inlined as CSS `url()` data URIs at build time (the loader exposes no asset URLs) |
58
- | `src/styles/tokens.css` | 2.1 | design tokens (dark base + ivory light) |
59
- | `src/styles/typography.css` | 2.2 | serif display / sans UI / mono code, editorial markdown |
60
- | `src/styles/chrome.css` | 2.3 | canvas, hairlines, clay accent, chrome details |
61
- | `src/styles/composer.css` | 2.3 | hero + in-conversation composer |
62
- | `src/styles/sidebar.css` | 2.3 | sidebar brand, new-session row, workspace tree |
63
- | `src/styles/components.css` | 2.4 | segments, permission popover, account drawer, settings section |
64
- | `src/context.js` | 3 | host context & helpers |
65
- | `src/overrides.js` | 4+5 | DOM overrides, MutationObserver scheduler, teardown |
66
- | `src/settings.js` | 4.4 | settings section (brand switch) |
67
- | `src/entry.js` | 6 | `apply()` + exports |
54
+ | Source | Content |
55
+ |---|---|
56
+ | `src/constants.js` | constants, spinner verbs, shared token values (evaluated at build time to fill `%%TOKEN%%` placeholders) |
57
+ | `src/assets/*.svg` | brand marks, inlined as CSS `url()` data URIs at build time (the loader exposes no asset URLs) |
58
+ | `src/styles/tokens.css` | design tokens (dark base + ivory light) |
59
+ | `src/styles/typography.css` | serif display / sans UI / mono code, editorial markdown |
60
+ | `src/styles/chrome.css` | canvas, hairlines, clay accent, chrome details |
61
+ | `src/styles/composer/hero.css` | hero brand mark and headline |
62
+ | `src/styles/composer/card.css` | composer input card and footer tray (gated by composer preference) |
63
+ | `src/styles/composer/inline.css` | in-conversation single-line composer (gated by composer preference) |
64
+ | `src/styles/sidebar.css` | sidebar brand, new-session row, workspace tree |
65
+ | `src/styles/components/permissions.css` | permission segments and popover |
66
+ | `src/styles/components/account-footer.css` | account row and floating popover |
67
+ | `src/styles/components/model-picker.css` | model picker popovers |
68
+ | `src/styles/components/footer-takeover.css` | host footer takeover rules |
69
+ | `src/styles/components/third-party.css` | agy-link repair rules |
70
+ | `src/styles/components/settings.css` | settings page section |
71
+ | `src/context/host.js` | host accessors and helpers |
72
+ | `src/context/prefs.js` | preference store |
73
+ | `src/context/model-copy.js` | model copy document store |
74
+ | `src/context/i18n.js` | localized copy lookups |
75
+ | `src/overrides/popover-utils.js` | shared anchor positioning and hover intent |
76
+ | `src/overrides/copy.js` | composer/copy rewrites installer |
77
+ | `src/overrides/permissions.js` | permission segments/popover installer |
78
+ | `src/overrides/model-picker.js` | model picker installer |
79
+ | `src/overrides/account-footer.js` | account footer/popover installer |
80
+ | `src/overrides/scheduler.js` | scheduler, observers, subscriptions, teardown |
81
+ | `src/settings.js` | settings section (brand switch) |
82
+ | `src/entry.js` | `apply()` orchestrator + exports |
83
+ | `src/model-descriptions.json` | model copy (picker labels + per-model descriptions); validated at build time and **copied** to `lib/`, not inlined |
68
84
 
69
85
  Fragments share one factory scope at runtime: keep the 4-space base
70
86
  indentation and do not use `import`/`export` inside fragments. The build
71
87
  script rejects unsubstituted `%%TOKENS%%` and refuses to emit a bundle that
72
88
  fails to parse.
73
89
 
90
+ `src/model-descriptions.json` is the one thing that does **not** go into the
91
+ bundle. Model copy is data: the build validates it and copies it to
92
+ `lib/model-descriptions.json`, and the host half (`lib/index.js`) serves it at
93
+ `/dsh-claude-style/model-descriptions.json` for the browser half to fetch on
94
+ first use. So the table grows without a rebuild, and no model text ships inside
95
+ `lib/client.js`. Every entry is a `{ locale: text }` pair — the picker renders
96
+ one line in the language the shell's own `locale` service reports, falling back
97
+ to the document's `fallback` locale. Lookup descends exact → family → tier →
98
+ the catalog's own text; family rules are ordered and must stay anchored (the
99
+ `flash` rule is scoped to `deepseek`, or another vendor's flash tier inherits
100
+ DeepSeek's copy).
101
+
74
102
  ### Host selector discipline · 宿主选择器纪律
75
103
 
76
104
  The host uses hashed CSS-module classes (`p_FcLG_row`, `_0cyzDW_viewArea`, …).